diff --git a/src/pages/GFMIS/DataGrid.js b/src/pages/GFMIS/DataGrid.js
index a9c2bfd..76618c4 100644
--- a/src/pages/GFMIS/DataGrid.js
+++ b/src/pages/GFMIS/DataGrid.js
@@ -7,8 +7,8 @@ import { FiDataGrid } from "components/FiDataGrid";
// ==============================|| EVENT TABLE ||============================== //
-export default function SearchTable({ previewSearchCriteria, onPreviewGridOnReady,selectedIds = []}) {
- const [_searchCriteria, set_searchCriteria] = React.useState(previewSearchCriteria);
+export default function SearchTable({ previewSearchCriteria, onPreviewGridOnReady,selectedIds = [], previewToken }) {
+ // const [_searchCriteria, set_searchCriteria] = React.useState(previewSearchCriteria);
const navigate = useNavigate()
// const [rows, setRows] = React.useState([]);
@@ -28,9 +28,21 @@ export default function SearchTable({ previewSearchCriteria, onPreviewGridOnRead
}
}
- React.useEffect(() => {
- set_searchCriteria(previewSearchCriteria);
- }, [previewSearchCriteria]);
+ const doLoad = React.useMemo(() => {
+ if (!selectedIds?.length) return undefined;
+ return {
+ url: GFIMIS_LIST,
+ params: {
+ ...previewSearchCriteria,
+ paymentId: selectedIds.join(',')
+ }
+ };
+ }, [previewSearchCriteria, selectedIds, previewToken]);
+
+
+ // React.useEffect(() => {
+ // set_searchCriteria(previewSearchCriteria);
+ // }, [previewSearchCriteria]);
const handleEditClick = (params) => () => {
navigate('/paymentPage/details/' + params.row.id);
@@ -61,23 +73,15 @@ export default function SearchTable({ previewSearchCriteria, onPreviewGridOnRead
return (
-
{
- if (!selectedIds?.length) {
- return undefined; // ⬅️ will keep grid empty
- }
- return {
- url: GFIMIS_LIST,
- params: { ..._searchCriteria, paymentId: selectedIds.join(',') },
- };
- }, [_searchCriteria, selectedIds])}
+ doLoad={doLoad}
/>
);
diff --git a/src/pages/GFMIS/TransactionDataGrid.js b/src/pages/GFMIS/TransactionDataGrid.js
index 8c527d7..489cc7d 100644
--- a/src/pages/GFMIS/TransactionDataGrid.js
+++ b/src/pages/GFMIS/TransactionDataGrid.js
@@ -117,10 +117,10 @@ export default function SearchPaymentTable({ searchCriteria, applyGridOnReady, a
callback: (responseData) => {
const newIds = responseData.records.map(r => r.id);
if (selectedIds.length === 0) {
- onSelectionChange(newIds);
+ onSelectionChange?.(newIds);
} else {
const stillValid = selectedIds.filter(id => newIds.includes(id));
- onSelectionChange(stillValid);
+ onSelectionChange?.(stillValid);
}
}
}), [_searchCriteria])}
diff --git a/src/pages/GFMIS/index.js b/src/pages/GFMIS/index.js
index 11fe8d8..6380305 100644
--- a/src/pages/GFMIS/index.js
+++ b/src/pages/GFMIS/index.js
@@ -57,6 +57,7 @@ const Index = () => {
const [inputDate, setInputDate] = React.useState(searchCriteria.dateTo);
const [inputDateValue, setInputDateValue] = React.useState("dd / mm / yyyy");
+ const [previewToken, setPreviewToken] = React.useState(0);
React.useEffect(() => {
setInputDateValue(inputDate);
@@ -145,11 +146,14 @@ const Index = () => {
}
function previewSearch() {
- // trigger reload even if criteria object is identical
- const withToken = { ...searchCriteria, __ts: Date.now() };
+ if (selectedIds.length === 0) return;
setIsPopUp(false);
setIsPreviewLoading(true);
+
+ const withToken = { ...searchCriteria, __ts: Date.now() };
setPreviewSearchCriteria(withToken);
+
+ setPreviewToken(t => t + 1);
}
function onPreviewGridOnReady(isLoading) {
@@ -228,6 +232,7 @@ const Index = () => {
previewSearchCriteria={previewSearchCriteria}
onPreviewGridOnReady={onPreviewGridOnReady}
selectedIds={selectedIds}
+ previewToken={previewToken}
/>
diff --git a/src/pages/authentication/auth-forms/IAmSmartFormWizard.js b/src/pages/authentication/auth-forms/IAmSmartFormWizard.js
index 20ff7e9..4306516 100644
--- a/src/pages/authentication/auth-forms/IAmSmartFormWizard.js
+++ b/src/pages/authentication/auth-forms/IAmSmartFormWizard.js
@@ -1,19 +1,19 @@
-import { useEffect, useState, } from 'react';
+import { useEffect, useState, lazy } from 'react';
// material-ui
import {
- Button,
- FormHelperText,
- Grid, IconButton,
- InputLabel, OutlinedInput,
- Stack,
- Typography,
- FormGroup,
- TextField,
- Checkbox
- // MenuItem
+ Button,
+ FormHelperText,
+ Grid,
+ IconButton,
+ InputLabel,
+ OutlinedInput,
+ Stack,
+ Typography,
+ FormGroup,
+ TextField,
+ Checkbox
} from '@mui/material';
-import { useForm, } from 'react-hook-form'
import Autocomplete from "@mui/material/Autocomplete";
// third party
@@ -26,7 +26,6 @@ import { POST_IAMSMART_USER_REGISTER, POST_CAPTCHA, POST_USER_EMAIL } from "util
import * as ComboData from "utils/ComboData";
import Loadable from 'components/Loadable';
-import { lazy } from 'react';
const LoadingComponent = Loadable(lazy(() => import('../../extra-pages/LoadingComponent')));
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
@@ -34,1184 +33,1131 @@ import CancelOutlinedIcon from '@mui/icons-material/CancelOutlined';
import iAmSmartICon from 'assets/images/icons/icon_iAmSmart.png';
import { EyeInvisibleOutlined, EyeOutlined } from '@ant-design/icons';
-import { Link } from 'react-router-dom';
+import { Link, useLocation } from 'react-router-dom';
import * as HttpUtils from "../../../utils/HttpUtils";
import LoopIcon from '@mui/icons-material/Loop';
import { useTheme } from '@mui/material/styles';
-import { useLocation } from "react-router-dom";
import { FormattedMessage, useIntl } from "react-intl";
-// ============================|| FIREBASE - REGISTER ||============================ //
-
-const CustomFormWizard = (props) => {
- const location = useLocation();
- const theme = useTheme();
- const intl = useIntl();
- const { locale } = intl;
-
- const [iAmSmartData, setIAmSmartData] = useState({});
-
- const [checkUpload, setCheckUpload] = useState(false);
- const [isLoading, setLoding] = useState(true);
- const [isLoadingData, setLodingData] = useState(true);
-
- const [captchaImg, setCaptchaImage] = useState("");
-
- const [selectedAddress4, setSelectedAddress4] = useState(null);
- const [selectedAddress5, setSelectedAddress5] = useState(ComboData.country[0]);
- const [termsAndConAccept, setTermsAndConAccept] = useState(false);
- const [termsAndConNotAccept, setTermsAndConNotAccept] = useState(false);
- const [isValid, setisValid] = useState(false);
- const [checkCountry, setCheckCountry] = useState(false);
- const email = document.getElementById("email-login")
- const [checkEmail, setCheckEmail] = useState(false)
- const [checkEmailBlur, setCheckEmailBlur] = useState(false)
- const district = document.getElementById("address4-combo")
- const [checkDistrict, setCheckDistrict] = useState(false)
- const [checkDistrictBlur, setCheckDistrictBlur] = useState(false)
- const [districtErrStr, setDistrictErrStr] = useState("")
-
- const address4ComboList = ComboData.district;
- const address5ComboList = ComboData.country;
- const [showId, setshowId] = useState(false);
- const [showComId, setshowComId] = useState(false);
-
- const [base64Url, setBase64Url] = useState("")
- const [checkCode, setCheckCode] = useState("")
-
- useEffect(() => {
- location.state?.responseData ?? window.location.assign("/login");
- if (captchaImg == "")
- onCaptchaChange();
- responseToData();
- }, []);
-
- const handleClickShowId = () => {
- setshowId(!showId);
- };
+const IAmSmartFormWizard = (props) => {
+ const location = useLocation();
+ const theme = useTheme();
+ const intl = useIntl();
+ const { locale } = intl;
- const handleMouseDownId = (event) => {
- event.preventDefault();
- };
+ const [iAmSmartData, setIAmSmartData] = useState({});
- const handleClickShowComId = () => {
- setshowComId(!showId);
- };
+ const [checkUpload, setCheckUpload] = useState(false);
+ const [isLoading, setLoding] = useState(true);
+ const [isLoadingData, setLodingData] = useState(true);
- const handleMouseDownComId = (event) => {
- event.preventDefault();
- };
+ const [captchaImg, setCaptchaImage] = useState("");
+ const [base64Url, setBase64Url] = useState("");
+ const [checkCode, setCheckCode] = useState("");
- useEffect(() => {
- if (district) {
- district.addEventListener("blur", function () {
- setCheckDistrictBlur(true)
- })
- }
- }, [district])
-
- useEffect(() => {
- if (checkDistrictBlur) {
- handleCheckDistrict()
- setCheckDistrictBlur(false)
- }
- }, [checkDistrictBlur])
-
- const handleCheckDistrict = async () => {
- setDistrictErrStr("");
- if (selectedAddress5?.type === "hongKong") {
- if (selectedAddress4 == null || selectedAddress4 == "" || selectedAddress4 == {}){
- setCheckDistrict(true)
- setDistrictErrStr(getRequiredErrStr("district"))
- }else {
- setCheckDistrict(false)
- }
- }
- }
+ const [selectedAddress4, setSelectedAddress4] = useState(null);
+ const [selectedAddress5, setSelectedAddress5] = useState(ComboData.country[0]); // usually hongKong
+ const [termsAndConAccept, setTermsAndConAccept] = useState(false);
+ const [termsAndConNotAccept, setTermsAndConNotAccept] = useState(false);
- function getRequiredErrStr(fieldname) {
- return displayErrorMsg(intl.formatMessage({ id: 'require' }, { fieldname: fieldname ? intl.formatMessage({ id: fieldname }) : "" }));
- }
+ const [isValid, setisValid] = useState(false);
+
+ const [checkCountry, setCheckCountry] = useState(false);
+
+ // email duplication
+ const [checkEmail, setCheckEmail] = useState(false);
+
+ // district validation UI
+ const [checkDistrict, setCheckDistrict] = useState(false);
+ const [districtErrStr, setDistrictErrStr] = useState("");
+
+ const [showId, setshowId] = useState(false);
+ const [showComId, setshowComId] = useState(false);
+
+ const address4ComboList = ComboData.district;
+ const address5ComboList = ComboData.country;
+
+ const handleClickShowId = () => setshowId(!showId);
+ const handleMouseDownId = (event) => event.preventDefault();
+
+ const handleClickShowComId = () => setshowComId(!showComId);
+ const handleMouseDownComId = (event) => event.preventDefault();
+
+ // ===== Helpers =====
+ function displayErrorMsg(errorMsg) {
+ return {errorMsg};
+ }
- function getMaxErrStr(num, fieldname) {
- return displayErrorMsg(intl.formatMessage({ id: 'noMoreThenNWords' }, { num: num, fieldname: fieldname ? intl.formatMessage({ id: fieldname }) + ": " : "" }));
+ function getRequiredErrStr(fieldname) {
+ return displayErrorMsg(
+ intl.formatMessage(
+ { id: 'require' },
+ { fieldname: fieldname ? intl.formatMessage({ id: fieldname }) : "" }
+ )
+ );
+ }
+
+ function getMaxErrStr(num, fieldname) {
+ return displayErrorMsg(
+ intl.formatMessage(
+ { id: 'noMoreThenNWords' },
+ { num: num, fieldname: fieldname ? intl.formatMessage({ id: fieldname }) + ": " : "" }
+ )
+ );
+ }
+
+ function handlePhone(phone) {
+ return (phone || "").length >= 8;
+ }
+
+ function handleCaptcha(captchaField) {
+ return (captchaField || "").length === 5;
+ }
+
+ function handleEmail(email) {
+ const validRegex =
+ /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
+ return !!email?.match(validRegex);
+ }
+
+ // ===== District validation (FIX #1) =====
+ const isDistrictRequired = () => selectedAddress5?.type === "hongKong";
+
+ const validateDistrict = () => {
+ setDistrictErrStr("");
+
+ if (!isDistrictRequired()) {
+ setCheckDistrict(false);
+ return true;
}
+ const ok = !!selectedAddress4?.type;
+ setCheckDistrict(!ok);
+ setDistrictErrStr(ok ? "" : getRequiredErrStr("district"));
+ return ok;
+ };
+
+ // Run district validation whenever district/country changes
+ useEffect(() => {
+ validateDistrict();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [selectedAddress4, selectedAddress5]);
- const responseToData = () => {
- //let rd = JSON.parse("{\"emailAddress\":\"test@gmail.com\",\"postalAddress\":{\"EngPremisesAddress\":{\"EngDistrict\":{\"DcDistrict\":\"KC\",\"Sub-district\":\"TSING YI\"},\"EngEstate\":{\"EstateName\":\"Cheung Hang Estate\",\"EngPhase\":{\"PhaseName\":\"N/A\"}},\"BuildingName\":\"Hang Lai House\",\"EngBlock\":{\"BlockDescriptor\":\"Block\",\"BlockNo\":\"2\"},\"Region\":\"NT\",\"EngStreet\":{\"StreetName\":\"Liu To Road\",\"BuildingNoFrom\":\"6\"},\"Eng3dAddress\":{\"EngFloor\":{\"FloorNum\":\"33\"},\"EngUnit\":{\"UnitDescriptor\":\"Room\",\"UnitNo\":\"3301\"}}}},\"mobileNumber\":{\"CountryCode\":\"852\",\"SubscriberNumber\":\"99999999\"},\"residentialAddress\":{\"ChiPremisesAddress\":{\"Chi3dAddress\":{\"ChiUnit\":{\"UnitDescriptor\":\"室\",\"UnitNo\":\"1010\"},\"ChiFloor\":{\"FloorNum\":\"10\"}},\"ChiBlock\":{\"BlockDescriptor\":\"座\",\"BlockNo\":\"2\"},\"BuildingName\":\"亨麗樓(第2座)\",\"ChiDistrict\":{\"DcDistrict\":\"KC\",\"Sub-district\":\"青衣\"},\"Region\":\"新界\",\"ChiEstate\":{\"EstateName\":\"長亨邨\"},\"ChiStreet\":{\"StreetName\":\"寮肚路\",\"BuildingNoFrom\":\"6\"}}},\"enName\":{\"UnstructuredName\":\"Testing Co One\"},\"idNo\":{\"Identification\":\"G561107\",\"CheckDigit\":\"4\"},\"chName\":{\"ChineseName\":\"測試商一\"}}");
- let rd = JSON.parse(location.state?.responseData.data);
- let data = {
- "enName": rd?.enName?.UnstructuredName ?? "",
- "chName": rd?.chName?.ChineseName ?? "",
- "idNo": rd?.idNo?.Identification ?? "",
- "checkDigit": rd?.idNo?.CheckDigit ?? "",
- "email": rd?.emailAddress ?? "",
- "phone": rd?.mobileNumber?.SubscriberNumber ?? "",
- "phoneCountryCode": rd?.mobileNumber?.CountryCode ?? "",
- };
-
- if (rd?.postalAddress) {
- if (rd?.postalAddress?.EngPremisesAddress) {
- data["address1"] = getAddressEng(rd?.postalAddress?.EngPremisesAddress);
- } else if (rd.postalAddress.ChiPremisesAddress) {
- data["address1"] = getAddressChi(rd?.postalAddress?.ChiPremisesAddress);
- }
- } else if (rd?.residentialAddress) {
- if (rd?.residentialAddress?.EngPremisesAddress) {
- data["address1"] = getAddressEng(rd?.residentialAddress?.EngPremisesAddress);
- } else if (rd?.residentialAddress?.ChiPremisesAddress) {
- data["address1"] = getAddressChi(rd?.residentialAddress?.ChiPremisesAddress);
- }
- }
-
- setIAmSmartData(data);
+ // ===== Email duplication check (FIX #2) =====
+ const checkEmailDuplicate = async (email) => {
+ const value = (email || "").trim();
+ if (!value) return false;
+ if (!handleEmail(value)) return false;
+ try {
+ const response = await axios.post(`${POST_USER_EMAIL}`, { e1: value });
+ return Number(response.data?.[0]) === 1;
+ } catch (e) {
+ // choose: do not block if API fails (keeps old behavior)
+ return false;
}
+ };
+
+ // ===== Captcha =====
+ const onCaptchaChange = () => {
+ HttpUtils.post({
+ url: POST_CAPTCHA,
+ params: { width: 130, height: 40, captcha: captchaImg },
+ onSuccess: (responseData) => {
+ props.setBase64Url(responseData.base64Url);
+ setBase64Url(responseData.base64Url);
+ localStorage.setItem("base64Url", responseData.base64Url);
+ setCaptchaImage(localStorage.getItem('base64Url'));
+ }
+ });
+ };
+
+ // ===== iAM Smart data mapping =====
+ const getAddressStr = (strs) => {
+ let add = "";
+ strs.forEach(str => {
+ add += str?.trim() ? str.trim() + ", " : "";
+ });
+ add = add.trim();
+ if (add?.slice(-1) === ",") add = add.substring(0, add.length - 1);
+ return add;
+ };
+
+ const getAddressEng = (pAdd) => {
+ let unit = (pAdd.Eng3dAddress?.EngUnit?.UnitDescriptor ?? "") + " " + (pAdd.Eng3dAddress?.EngUnit?.UnitNo ?? "");
+ let block = (pAdd.EngBlock?.BlockDescriptor ?? "") + " " + (pAdd.EngBlock?.BlockNo ?? "");
+ let floor = pAdd.Eng3dAddress?.EngFloor?.FloorNum ? pAdd.Eng3dAddress?.EngFloor?.FloorNum + "/F " : "";
+ let street = (pAdd.EngStreet?.StreetName ?? "") + " " + (pAdd.EngStreet?.BuildingNoFrom ?? "");
+ let buildingName = pAdd.BuildingName ?? "";
+ let estate = pAdd.EngEstate?.EstateName ?? "";
+ // NOTE: your original key had weird spacing; keep safe access
+ let district = pAdd.EngDistrict?.["Sub - district"] ?? pAdd.EngDistrict?.["Sub-district"] ?? "";
+ return getAddressStr([unit, block, floor, buildingName, estate, street, district]);
+ };
+ const getAddressChi = (pAdd) => {
+ let unit = (pAdd.Chi3dAddress?.ChiUnit?.UnitDescriptor ?? "") + " " + (pAdd.Chi3dAddress?.ChiUnit?.UnitNo ?? "");
+ let block = (pAdd.ChiBlock?.BlockDescriptor ?? "") + " " + (pAdd.ChiBlock?.BlockNo ?? "");
+ let floor = pAdd.Chi3dAddress?.ChiFloor?.FloorNum ? pAdd.Chi3dAddress?.ChiFloor?.FloorNum + "樓 " : "";
+ let street = (pAdd.ChiStreet?.StreetName ?? "") + " " + (pAdd.ChiStreet?.BuildingNoFrom ?? "");
+ let buildingName = pAdd.BuildingName ?? "";
+ let estate = pAdd.ChiEstate?.EstateName ?? "";
+ let district = pAdd.ChiDistrict?.["Sub - district"] ?? pAdd.ChiDistrict?.["Sub-district"] ?? "";
+ return getAddressStr([district, street, estate, buildingName, floor, block, unit]);
+ };
- const getAddressEng = (pAdd) => {
- let unit = (pAdd.Eng3dAddress?.EngUnit?.UnitDescriptor ?? "") + " " + (pAdd.Eng3dAddress?.EngUnit?.UnitNo ?? "");
- let block = (pAdd.EngBlock?.BlockDescriptor ?? "") + " " + (pAdd.EngBlock?.BlockNo ?? "");
- let floor = pAdd.Eng3dAddress?.EngFloor?.FloorNum ? pAdd.Eng3dAddress?.EngFloor?.FloorNum + "/F " : "";
- let street = (pAdd.EngStreet?.EngUnit?.UnitDescriptor ?? "") + " " + (pAdd.Eng3dAddress?.EngUnit?.UnitNo ?? "");
- //let region = pAdd.Region ?? "";
- let buildingName = pAdd.BuildingName ?? "";
- let estate = pAdd.EngEstate?.EstateName ?? "";
- let district = pAdd.EngDistrict["Sub - district"] ?? "";
- return getAddressStr([unit, block, floor, buildingName, estate, street, district]);
+ const responseToData = () => {
+ const raw = location.state?.responseData?.data;
+ if (!raw) return;
+
+ let rd;
+ try {
+ rd = JSON.parse(raw);
+ } catch (e) {
+ return;
}
- const getAddressChi = (pAdd) => {
- let unit = (pAdd.Chi3dAddress?.ChiUnit?.UnitDescriptor ?? "") + " " + (pAdd.Chi3dAddress?.ChiUnit?.UnitNo ?? "");
- let block = (pAdd.ChiBlock?.BlockDescriptor ?? "") + " " + (pAdd.ChiBlock?.BlockNo ?? "");
- let floor = pAdd.Chi3dAddress?.ChiFloor?.FloorNum ? pAdd.Chi3dAddress?.ChiFloor?.FloorNum + "樓 " : "";
- let street = (pAdd.ChiStreet?.ChiUnit?.UnitDescriptor ?? "") + " " + (pAdd.Chi3dAddress?.ChiUnit?.UnitNo ?? "");
- //let region = pAdd.Region ?? "";
- let buildingName = pAdd.BuildingName ?? "";
- let estate = pAdd.ChiEstate?.EstateName ?? "";
- let district = pAdd.ChiDistrict["Sub - district"] ?? "";
- return getAddressStr([district, street, estate, buildingName, street, floor, block, unit]);
+ let data = {
+ enName: rd?.enName?.UnstructuredName ?? "",
+ chName: rd?.chName?.ChineseName ?? "",
+ idNo: rd?.idNo?.Identification ?? "",
+ checkDigit: rd?.idNo?.CheckDigit ?? "",
+ email: rd?.emailAddress ?? "",
+ phone: rd?.mobileNumber?.SubscriberNumber ?? "",
+ phoneCountryCode: rd?.mobileNumber?.CountryCode ?? "",
+ address1: ""
+ };
+
+ if (rd?.postalAddress) {
+ if (rd?.postalAddress?.EngPremisesAddress) {
+ data.address1 = getAddressEng(rd?.postalAddress?.EngPremisesAddress);
+ } else if (rd?.postalAddress?.ChiPremisesAddress) {
+ data.address1 = getAddressChi(rd?.postalAddress?.ChiPremisesAddress);
+ }
+ } else if (rd?.residentialAddress) {
+ if (rd?.residentialAddress?.EngPremisesAddress) {
+ data.address1 = getAddressEng(rd?.residentialAddress?.EngPremisesAddress);
+ } else if (rd?.residentialAddress?.ChiPremisesAddress) {
+ data.address1 = getAddressChi(rd?.residentialAddress?.ChiPremisesAddress);
+ }
}
- const getAddressStr = (strs) => {
- let add = ""
- strs.forEach(str => {
- add += str.trim() ? str.trim() + ", " : "";
- });
- add = add.trim();
- if (add?.slice(- 1) == ",") {
- add = add.substring(0, add.length - 1);
- }
- return add;
+ setIAmSmartData(data);
+ };
+
+ useEffect(() => {
+ // redirect if no iAM Smart response
+ location.state?.responseData ?? window.location.assign("/login");
+
+ if (!captchaImg) onCaptchaChange();
+ responseToData();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ // ===== Formik =====
+ const formik = useFormik({
+ initialValues: {
+ email: iAmSmartData.email ?? "",
+ emailConfirm: iAmSmartData.email ?? "",
+ address1: iAmSmartData.address1 ?? "",
+ address2: '',
+ address3: '',
+ phone: iAmSmartData.phone ?? "",
+ phoneCountryCode: iAmSmartData.phoneCountryCode ?? "852",
+ submit: null,
+ fax: '',
+ faxCountryCode: '852',
+ captchaField: ''
+ },
+ validationSchema: yup.object().shape({
+ address1: yup.string().max(40, getMaxErrStr(40)).required(displayErrorMsg(intl.formatMessage({ id: 'validateAddressLine1' }))),
+ address2: yup.string().max(40),
+ address3: yup.string().max(40),
+ email: yup.string().email(displayErrorMsg(intl.formatMessage({ id: 'validEmailFormat' }))).max(128, getMaxErrStr(128)).required(displayErrorMsg(intl.formatMessage({ id: 'requireEmail' }))),
+ emailConfirm: yup.string().email(displayErrorMsg(intl.formatMessage({ id: 'validEmailFormat' }))).max(128, getMaxErrStr(128))
+ .required(displayErrorMsg(intl.formatMessage({ id: 'requireEmail' })))
+ .oneOf([yup.ref('email'), null], displayErrorMsg(intl.formatMessage({ id: 'validSameEmail' }))),
+ phoneCountryCode: yup.string().min(2, displayErrorMsg(intl.formatMessage({ id: 'requireAtLeast2Number' }))).required(displayErrorMsg(intl.formatMessage({ id: 'requireDialingCode' }))),
+ phone: yup.string().min(8, displayErrorMsg(intl.formatMessage({ id: 'requireAtLeast8Number' }))).required(displayErrorMsg(intl.formatMessage({ id: 'requireContactNumber' }))),
+ captchaField: yup.string().max(5, getMaxErrStr(5)).required(displayErrorMsg(intl.formatMessage({ id: 'requireVerify' }))).min(5, displayErrorMsg(intl.formatMessage({ id: 'requireVerify' })))
+ })
+ });
+
+ const { values } = formik;
+
+ // Apply iAM Smart data to form
+ useEffect(() => {
+ if (iAmSmartData) {
+ formik.setFieldValue("enName", iAmSmartData.enName ?? "");
+ formik.setFieldValue("chName", iAmSmartData.chName ?? "");
+ formik.setFieldValue("idNo", iAmSmartData.idNo ?? "");
+ formik.setFieldValue("checkDigit", iAmSmartData.checkDigit ?? "");
+ formik.setFieldValue("email", iAmSmartData.email ?? "");
+ formik.setFieldValue("emailConfirm", iAmSmartData.email ?? "");
+ formik.setFieldValue("phone", iAmSmartData.phone ?? "");
+ formik.setFieldValue("phoneCountryCode", iAmSmartData.phoneCountryCode ?? "");
+ formik.setFieldValue("address1", iAmSmartData.address1 ?? "");
+
+ props.setIdNo(iAmSmartData.idNo ?? "");
+ setLodingData(false);
}
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [iAmSmartData]);
+
+ // ===== Validity check (FIX #3: no stale return) =====
+ const checkDataField = (data) => {
+ const ok =
+ data.address1 !== "" &&
+ data.email !== "" &&
+ data.emailConfirm !== "" &&
+ data.email === data.emailConfirm &&
+ data.phone !== "" &&
+ data.phoneCountryCode !== "" &&
+ termsAndConAccept === true &&
+ handleEmail(data.email) &&
+ handlePhone(data.phone) &&
+ handleCaptcha(data.captchaField) &&
+ validateDistrict() &&
+ !checkEmail &&
+ !checkDistrict;
+
+ setisValid(ok);
+ return ok;
+ };
+
+ // keep parent updated
+ useEffect(() => {
+ props.setUpdateValid(isValid);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [isValid]);
- const handleCheckEmail = async () => {
- if (values?.email) {
- const response = await axios.post(`${POST_USER_EMAIL}`, {
- e1: values.email,
- })
- setCheckEmail((Number(response.data[0]) === 1))
- return Number(response.data[0]) === 1
- }
+ // re-check on relevant changes
+ useEffect(() => {
+ checkDataField(values);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [values, selectedAddress4, selectedAddress5, termsAndConAccept, termsAndConNotAccept, checkEmail, checkDistrict]);
+
+ const handleCheckBoxChange = (event) => {
+ if (event.target.name === 'termsAndConAccept') {
+ setTermsAndConAccept(event.target.checked);
+ setTermsAndConNotAccept(!event.target.checked);
+ }
+ if (event.target.name === 'termsAndConNotAccept') {
+ setTermsAndConNotAccept(event.target.checked);
+ setTermsAndConAccept(!event.target.checked);
}
+ };
+
+ // ===== Submit (FIX #4: force email + district validation before posting) =====
+ const _onSubmit = async () => {
+ setLoding(true);
+
+ // force sync validations
+ const districtOk = validateDistrict();
- useEffect(() => {
- if (email) {
- email.addEventListener("blur", function () {
- setCheckEmailBlur(true)
- })
- }
- }, [email])
-
- useEffect(() => {
- if (checkEmailBlur) {
- handleCheckEmail()
- setCheckEmailBlur(false)
- }
- }, [checkEmailBlur])
-
- useEffect(() => {
- if (iAmSmartData) {
- formik.setFieldValue("enName", iAmSmartData.enName ?? "");
- formik.setFieldValue("chName", iAmSmartData.chName ?? "");
- formik.setFieldValue("idNo", iAmSmartData.idNo ?? "");
- formik.setFieldValue("checkDigit", iAmSmartData.checkDigit ?? "");
- formik.setFieldValue("email", iAmSmartData.email ?? "");
- formik.setFieldValue("emailConfirm", iAmSmartData.email ?? "");
- formik.setFieldValue("phone", iAmSmartData.phone ?? "");
- formik.setFieldValue("phoneCountryCode", iAmSmartData.phoneCountryCode ?? "");
- formik.setFieldValue("address1", iAmSmartData.address1 ?? "");
- props.setIdNo(iAmSmartData.idNo ?? "");
- setLodingData(false)
- }
- }, [iAmSmartData])
-
- const onCaptchaChange = () => {
- HttpUtils.post({
- url: POST_CAPTCHA,
- params: { width: 130, height: 40, captcha: captchaImg },
- onSuccess: (responseData) => {
- props.setBase64Url(responseData.base64Url)
- setBase64Url(responseData.base64Url)
- localStorage.setItem("base64Url", responseData.base64Url);
- setCaptchaImage(localStorage.getItem('base64Url'));
- }
- });
+ // force email dup check even if user never blurred
+ const duplicated = await checkEmailDuplicate(values.email);
+ setCheckEmail(duplicated);
+
+ // recompute validity using latest flags
+ const okNow = checkDataField(values) && districtOk && !duplicated;
+
+ if (!okNow) {
+ setLoding(false);
+ return;
}
+ const userAddress = {
+ addressLine1: values.address1,
+ addressLine2: values.address2,
+ addressLine3: values.address3,
+ district: selectedAddress4?.type ? selectedAddress4.type : "",
+ country: selectedAddress5?.type ? selectedAddress5.type : ""
+ };
- const checkDataField = (data) => {
- if (data.address1 !== "" &&
- data.email !== "" &&
- data.emailConfirm !== "" &&
- data.email == data.emailConfirm &&
- data.phone !== "" &&
- data.phoneCountryCode !== "" &&
- termsAndConAccept == true &&
- data.captchaField &&
- handleEmail(data.email) &&
- handlePhone(data.phone) &&
- handleCaptcha(data.captchaField) &&
- handleCheckDistrict()&&
- !checkEmail&&
- !checkDistrict
- ) {
- setisValid(true)
- return isValid
- } else {
- setisValid(false)
- return isValid
- }
+ const userFaxNo = {
+ countryCode: values.faxCountryCode,
+ faxNumber: values.fax
};
- const handleCheckBoxChange = (event) => {
- if (event.target.name == 'termsAndConAccept') {
- setTermsAndConAccept(event.target.checked)
- setTermsAndConNotAccept(!event.target.checked)
- }
- if (event.target.name == 'termsAndConNotAccept') {
- setTermsAndConNotAccept(event.target.checked)
- setTermsAndConAccept(!event.target.checked)
- }
+ const userMobileNumber = {
+ countryCode: values.phoneCountryCode,
+ phoneNumber: values.phone
};
- useEffect(() => {
- props.setUpdateValid(isValid)
- }, [isValid])
-
- useEffect(() => {
- checkDataField(values)
- }, [
- selectedAddress4, selectedAddress5,
- termsAndConAccept, termsAndConNotAccept])
-
- useEffect(() => {
- props.step == 2 ? _onSubmit() : null;
- if (captchaImg == "")
- onCaptchaChange();
- checkDataField(values)
- }, [props.step])
-
- const { handleSubmit } = useForm({})
- const _onSubmit = () => {
- setLoding(true);
-
- const userAddress = {
- "addressLine1": "",
- "addressLine2": "",
- "addressLine3": "",
- "district": "",
- "country": ""
- };
- userAddress.addressLine1 = values.address1
- userAddress.addressLine2 = values.address2
- userAddress.addressLine3 = values.address3
- userAddress.district = selectedAddress4 == null ? "" : selectedAddress4.type
- userAddress.country = selectedAddress5.type
-
- const userFaxNo = {
- "countryCode": values.faxCountryCode,
- "faxNumber": values.fax,
- };
- const userMobileNumber = {
- "countryCode": values.phoneCountryCode,
- "phoneNumber": values.phone,
- };
- let tncFlag = false;
- if (termsAndConAccept) {
- tncFlag = true
- }
- if (termsAndConNotAccept) {
- tncFlag = false
- }
-
- const preferLocale = locale === 'en' ? 'en' : locale === 'zh-HK' ? 'zh_HK' : 'zh-CN'
- const formData = {
- enName: iAmSmartData.enName,
- chName: iAmSmartData.chName,
- emailAddress: values.email,
- idDocType: "HKID",
- identification: iAmSmartData.idNo,
- checkDigit: iAmSmartData.checkDigit,
- tncFlag: tncFlag,
- type: "IND",
- userFaxNo: JSON.stringify(userFaxNo),
- userMobileNumber: JSON.stringify(userMobileNumber),
- userAddress: JSON.stringify(userAddress),
- captcha: base64Url,
- checkCode: checkCode,
- preferLocale: preferLocale
- };
-
- if (isValid) {
- axios.post(POST_IAMSMART_USER_REGISTER, formData, {
- headers: {
- "Content-Type": "multipart/form-data"
- }
- })
- .then((response) => {
- console.log(response)
- setCheckUpload(true)
- setLoding(false);
- })
- .catch(error => {
- console.error(error);
- setLoding(false);
- });
- } else {
- setLoding(false);
- }
- }
+ const tncFlag = termsAndConAccept ? true : false;
- function handlePhone(phone) {
- if (phone.length < 8) {
- return false;
- } else {
- return true;
- }
- }
+ const preferLocale = locale === 'en' ? 'en' : locale === 'zh-HK' ? 'zh_HK' : 'zh-CN';
- function handleCaptcha(captchaField) {
- if (captchaField.length == 5) {
- return true
- } else {
- return false
- }
- }
+ const formData = {
+ enName: iAmSmartData.enName,
+ chName: iAmSmartData.chName,
+ emailAddress: values.email,
+ idDocType: "HKID",
+ identification: iAmSmartData.idNo,
+ checkDigit: iAmSmartData.checkDigit,
+ tncFlag: tncFlag,
+ type: "IND",
+ userFaxNo: JSON.stringify(userFaxNo),
+ userMobileNumber: JSON.stringify(userMobileNumber),
+ userAddress: JSON.stringify(userAddress),
+ captcha: base64Url,
+ checkCode: checkCode,
+ preferLocale: preferLocale
+ };
- function handleEmail(email) {
- var validRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
- if (!email.match(validRegex)) {
- return false;
- } else {
- return true;
- }
+ try {
+ await axios.post(POST_IAMSMART_USER_REGISTER, formData, {
+ headers: { "Content-Type": "multipart/form-data" }
+ });
+ setCheckUpload(true);
+ } catch (error) {
+ console.error(error);
+ setCheckUpload(false);
+ } finally {
+ setLoding(false);
}
+ };
- function displayErrorMsg(errorMsg) {
- return {errorMsg}
+ // Auto-submit when step reaches 2 (existing behavior)
+ useEffect(() => {
+ if (props.step === 2) {
+ _onSubmit();
}
+ if (!captchaImg) onCaptchaChange();
+ checkDataField(values);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [props.step]);
- const formik = useFormik({
- initialValues: ({
- email: iAmSmartData.email ?? "",
- emailConfirm: iAmSmartData.email ?? "",
- address1: iAmSmartData.address1 ?? "",
- address2: '',
- address3: '',
- phone: iAmSmartData.phone ?? "",
- phoneCountryCode: iAmSmartData.phoneCountryCode ?? "852",
- submit: null,
- fax: '',
- faxCountryCode: '852',
- captchaField: ''
- }),
- validationSchema: yup.object().shape({
- address1: yup.string().max(40, getMaxErrStr(40)).required(displayErrorMsg(intl.formatMessage({ id: 'validateAddressLine1' }))),
- address2: yup.string().max(40),
- address3: yup.string().max(40),
- email: yup.string().email(displayErrorMsg(intl.formatMessage({ id: 'validEmailFormat' }))).max(128, getMaxErrStr(128)).required(displayErrorMsg(intl.formatMessage({ id: 'requireEmail' }))),
- emailConfirm: yup.string().email(displayErrorMsg(intl.formatMessage({ id: 'validEmailFormat' }))).max(128, getMaxErrStr(128)).required(displayErrorMsg(intl.formatMessage({ id: 'requireEmail' }))).oneOf([yup.ref('email'), null], displayErrorMsg(intl.formatMessage({ id: 'validSameEmail' }))),
- phoneCountryCode: yup.string().min(2, displayErrorMsg(intl.formatMessage({ id: 'requireAtLeast2Number' }))).required(displayErrorMsg(intl.formatMessage({ id: 'requireDialingCode' }))),
- phone: yup.string().min(8, displayErrorMsg(intl.formatMessage({ id: 'requireAtLeast8Number' }))).required(displayErrorMsg(intl.formatMessage({ id: 'requireContactNumber' }))),
- captchaField: yup.string().max(5, getMaxErrStr(5)).required(displayErrorMsg(intl.formatMessage({ id: 'requireVerify' }))).min(5, displayErrorMsg(intl.formatMessage({ id: 'requireVerify' }))),//.oneOf([captcha], displayErrorMsg('請輸入有效驗證')),
- }),
- });
+ const handleCCPChange = (e) => e.preventDefault();
- const handleCCPChange = (e) => {
- e.preventDefault();
- };
+ return (
+ isLoadingData ? (
+
+ ) : (
+
+
-
- );
-}
+
+
+
+
+ {/* Captcha */}
+
+
+
+
+
+ *
+
+
+
+
+
+
+
+ { onCaptchaChange(); }}>
+
+
+
+
+ {
+ const value = event.target.value;
+ props.setCheckCode(value);
+ setCheckCode(value);
+ formik.setFieldValue("captchaField", value);
+ }}
+ sx={{ width: '75%' }}
+ />
+
+
+
+ {formik.touched.captchaField && formik.errors.captchaField && (
+
+ {formik.errors.captchaField}
+
+ )}
+
+
+
+
+
+
+
+
+ {/* Preview Form */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {iAmSmartData?.idNo?.slice(0, 4)}
+
+
+ {showComId ? iAmSmartData?.idNo?.slice(4) : "****"}
+ {showComId ? '(' + iAmSmartData.checkDigit + ')' : null}
+
+
+
+ {showComId ? : }
+
+
+
+
+
+
+
+ :
+
+
+ {iAmSmartData.enName}
+
+ {iAmSmartData.enName ?
: null}
+
+
+
+
+
+
+ :
+
+
+ {iAmSmartData.chName}
+
+
+
+
+
+
+
+ :
+
+
+ {formik.values.address1}
+ {formik.values.address2 ? {formik.values.address2} : null}
+ {formik.values.address3 ? {formik.values.address3} : null}
+
+ {selectedAddress5.type === "hongKong" ? (
+
+ {!selectedAddress4 ? "" : intl.formatMessage({ id: selectedAddress4.type })}
+
+ ) : null}
+
+
+ {intl.formatMessage({ id: selectedAddress5.type })}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ :
+
+ {formik.values.email}
+
+
+
+
+
+
+ :
+
+
+ +{formik.values.phoneCountryCode} {formik.values.phone}
+
+
+
+
+ {formik.values.faxCountryCode && formik.values.fax ? (
+
+
+
+ :
+
+
+ +{formik.values.faxCountryCode} {formik.values.fax}
+
+
+
+ ) : null}
+
+
+
+
+
+ {/* Submit page */}
+
+
+ {isLoading ? (
+
+ ) : (
+
+ {checkUpload ? (
+
+
+
+
+
+
+
+
+
+
+ ) : (
+
+
+
+
+
+
+
+ )}
+
+ )}
+
+
+
+
+ )
+ );
+};
-export default CustomFormWizard;
+export default IAmSmartFormWizard;
diff --git a/src/utils/HttpUtils.js b/src/utils/HttpUtils.js
index 6680b2e..9b89f3f 100644
--- a/src/utils/HttpUtils.js
+++ b/src/utils/HttpUtils.js
@@ -106,19 +106,19 @@ export const fileDownload = ({ url, fileId, skey, params, method, onResponse, on
};
const fileDownloadResponse = (response, onResponse) => {
- const fn = response.headers.get("content-disposition")?.split("filename=")[1]?.split('"')[1]?.trim() ?? filename;
+ const cd = response.headers?.['content-disposition'];
+ const fn = cd?.split('filename=')[1]?.replaceAll('"','')?.trim() || 'export.xlsx';
+
const url = URL.createObjectURL(response.data);
const a = document.createElement('a');
a.href = url;
- a.setAttribute("download", fn);
+ a.download = fn;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
- if (onResponse) {
- onResponse();
- }
-}
+ onResponse?.();
+};
export const fileUpload = ({ refType, refId, files, refCode, onSuccess, onFail, onError }) => {
postWithFiles({