From 686ca8aeeb25dc689be26202c57453499381c56b Mon Sep 17 00:00:00 2001 From: Jason Chuang Date: Mon, 23 Feb 2026 01:42:05 +0800 Subject: [PATCH] WCAG 2.0 AA 1.4.3 Contrast (Minimum) --- src/assets/style/styles.css | 91 ++++++----- src/layout/MainLayout/Header/index.js | 2 +- src/pages/authentication/BusRegister.js | 39 +++-- src/pages/authentication/IAmSmartRegister.js | 51 +++--- src/pages/authentication/Register.js | 39 +++-- src/pages/authentication/RegisterCustom.js | 39 ++++- .../auth-forms/BusCustomFormWizard.js | 115 +++++-------- .../auth-forms/CustomFormWizard.js | 136 ++++++---------- .../auth-forms/IAmSmartFormWizard.js | 154 +++++------------- src/pages/dashboard/Public/index.js | 63 +++++-- src/pages/pnspsUserGroupDetailPage/index.js | 2 +- src/themes/buttonConst.js | 99 ++++++++--- src/themes/themeConst.js | 73 ++++++--- src/utils/statusUtils/DnStatus.js | 6 +- src/utils/statusUtils/PaymentStatus.js | 10 +- src/utils/statusUtils/ProofStatus.js | 10 +- .../statusUtils/PublicNoteStatusUtils.js | 108 ++++++------ 17 files changed, 541 insertions(+), 496 deletions(-) diff --git a/src/assets/style/styles.css b/src/assets/style/styles.css index f438c04..3dc79aa 100644 --- a/src/assets/style/styles.css +++ b/src/assets/style/styles.css @@ -52,31 +52,18 @@ img img:hover{-webkit-filter:none;} - -a:link { - color: #1890ff; - background-color: transparent; - text-decoration: none; -} -a:visited { - color: #1890ff; - background-color: transparent; - text-decoration: none; -} -a:hover { - color: #1890ff; - background-color: transparent; - text-decoration: none; -} +a:link, +a:visited, +a:hover, a:active { - color: #1890ff; + color: #005FCC; /* WCAG AA compliant */ background-color: transparent; - text-decoration: none; + text-decoration: underline; } /* iframe#webpack-dev-server-client-overlay{display:none!important} */ -/* ===== WCAG 2.4.7 Focus Visible (Global) ===== */ +/* ===== WCAG 2.4.7 Focus Visible (Keyboard only) ===== */ :where( a, button, @@ -93,7 +80,7 @@ a:active { border-radius: 4px; } -/* Fallback for browsers that don't support :focus-visible */ +/* Suppress mouse focus */ :where( a, button, @@ -104,30 +91,60 @@ a:active { [role="button"], [role="link"], [tabindex]:not([tabindex="-1"]) -):focus { - outline: 3px solid #0C489E; - outline-offset: 2px; - border-radius: 4px; +):focus:not(:focus-visible) { + outline: none; } -/* ===== MUI DataGrid focus visible (WCAG 2.4.7) ===== */ - -/* Column headers */ -.MuiDataGrid-columnHeader:focus, -.MuiDataGrid-columnHeader:focus-within { +/* ===== MUI DataGrid keyboard focus (WCAG 2.4.7 / 2.4.11) ===== */ +.MuiDataGrid-columnHeader:focus-visible, +.MuiDataGrid-cell:focus-visible { outline: 3px solid #0C489E; outline-offset: -2px; + box-shadow: 0 0 0 3px rgba(12, 72, 158, 0.25); +} + +/* Contained buttons only */ +.MuiButton-contained { + border: 2px solid #0C489E; + box-shadow: none; +} + +.MuiButton-contained:hover { + border: 2px solid #0C489E; +} + +/* iAM Smart button — keep green border */ +.MuiButton-outlinedIAmSmart { + color: #2E7D32; /* green text */ + border: 1px solid #2E7D32; /* keep your green */ +} + +.MuiButton-outlinedIAmSmart:hover { + color: #2E7D32; /* green text */ + border: 1px solid #2E7D32; + background-color: rgba(46, 125, 50, 0.08); /* optional */ } -/* Cells */ -.MuiDataGrid-cell:focus, -.MuiDataGrid-cell:focus-within { +/* ===== Outlined button focus ===== */ +.MuiButton-outlined:focus-visible { outline: 3px solid #0C489E; - outline-offset: -2px; + outline-offset: 2px; } -/* If outline is clipped, add halo */ -.MuiDataGrid-columnHeader:focus-within, -.MuiDataGrid-cell:focus-within { - box-shadow: 0 0 0 3px rgba(12, 72, 158, 0.25); +/* Step icon number color (text inside the circle) */ +.MuiStepIcon-text { + fill: #1A1A1A; /* dark text, high contrast */ +} +/* Placeholder text contrast */ +.MuiInputBase-input::placeholder { + color: #6B6B6B; /* passes 4.5:1 on white */ + opacity: 1; /* IMPORTANT */ +} + +/* WCAG-safe error text color */ +.Mui-error, +.MuiFormHelperText-root.Mui-error, +.MuiTypography-errorMessage1 { + color: #B00020; /* dark red, AA compliant */ + opacity: 1; /* remove faded appearance */ } \ No newline at end of file diff --git a/src/layout/MainLayout/Header/index.js b/src/layout/MainLayout/Header/index.js index 7182086..c1dd25e 100644 --- a/src/layout/MainLayout/Header/index.js +++ b/src/layout/MainLayout/Header/index.js @@ -746,7 +746,7 @@ function Header(props) { {/*公共啟事提交*/} {/*及繳費系統*/} - + diff --git a/src/pages/authentication/BusRegister.js b/src/pages/authentication/BusRegister.js index 1648237..179ef5b 100644 --- a/src/pages/authentication/BusRegister.js +++ b/src/pages/authentication/BusRegister.js @@ -42,29 +42,28 @@ const BusRegister = () => { ]; const stepStyle = { - width: {lg:"40%", md:"70%", xs:"100%"}, + width: { lg: "40%", md: "70%", xs: "100%" }, boxShadow: 1, backgroundColor: "#FFFFFF", padding: 2, + + /* Inactive (default) */ + "& .MuiStepIcon-root": { color: "#757575", fontSize: "2rem" }, // darker grey + "& .MuiStepLabel-label": { color: "#424242" }, // label darker + "& .MuiStepConnector-line": { borderColor: "#9E9E9E" }, // connector darker + + /* Active */ "& .Mui-active": { - "&.MuiStepIcon-root": { - color: "warning.main", - fontSize: "2rem", - }, - "& .MuiStepConnector-line": { - borderColor: "warning.main" - } + "&.MuiStepIcon-root": { color: "warning.main", fontSize: "2rem" }, + "& .MuiStepConnector-line": { borderColor: "warning.main" } }, + + /* Completed */ "& .Mui-completed": { - "&.MuiStepIcon-root": { - color: "secondary.main", - fontSize: "2rem", - }, - "& .MuiStepConnector-line": { - borderColor: "secondary.main" - } + "&.MuiStepIcon-root": { color: "#0C489E", fontSize: "2rem" }, // use your strong blue + "& .MuiStepConnector-line": { borderColor: "#0C489E" } } - } + }; const totalSteps = () => { return steps.length; @@ -153,7 +152,13 @@ const BusRegister = () => { ) : (} // onClick={handleStep(index)} > diff --git a/src/pages/authentication/IAmSmartRegister.js b/src/pages/authentication/IAmSmartRegister.js index 322eecc..9a63104 100644 --- a/src/pages/authentication/IAmSmartRegister.js +++ b/src/pages/authentication/IAmSmartRegister.js @@ -26,30 +26,29 @@ import {FormattedMessage, useIntl} from "react-intl"; const CustomFormWizard = Loadable(lazy(() => import('./auth-forms/IAmSmartFormWizard'))); const AuthWrapper = Loadable(lazy(() => import('./AuthWrapperCustom'))); // ================================|| REGISTER ||================================ // -const stepStyle = { - width: {lg:"40%", md:"70%", xs:"100%"}, - boxShadow: 1, - backgroundColor: "#FFFFFF", - padding: 2, - "& .Mui-active": { - "&.MuiStepIcon-root": { - color: "warning.main", - fontSize: "2rem", - }, - "& .MuiStepConnector-line": { - borderColor: "warning.main" - } - }, - "& .Mui-completed": { - "&.MuiStepIcon-root": { - color: "secondary.main", - fontSize: "2rem", + const stepStyle = { + width: { lg: "40%", md: "70%", xs: "100%" }, + boxShadow: 1, + backgroundColor: "#FFFFFF", + padding: 2, + + /* Inactive (default) */ + "& .MuiStepIcon-root": { color: "#757575", fontSize: "2rem" }, // darker grey + "& .MuiStepLabel-label": { color: "#424242" }, // label darker + "& .MuiStepConnector-line": { borderColor: "#9E9E9E" }, // connector darker + + /* Active */ + "& .Mui-active": { + "&.MuiStepIcon-root": { color: "warning.main", fontSize: "2rem" }, + "& .MuiStepConnector-line": { borderColor: "warning.main" } }, - "& .MuiStepConnector-line": { - borderColor: "secondary.main" + + /* Completed */ + "& .Mui-completed": { + "&.MuiStepIcon-root": { color: "#0C489E", fontSize: "2rem" }, // use your strong blue + "& .MuiStepConnector-line": { borderColor: "#0C489E" } } - } -} + }; const Register = () => { const [activeStep, setActiveStep] = useState(0); @@ -151,7 +150,13 @@ const Register = () => { {label} ) : (} // onClick={handleStep(index)} > diff --git a/src/pages/authentication/Register.js b/src/pages/authentication/Register.js index e36e96b..9d542d1 100644 --- a/src/pages/authentication/Register.js +++ b/src/pages/authentication/Register.js @@ -42,29 +42,28 @@ const Register = () => { ]; const stepStyle = { - width: {lg:"40%", md:"70%", xs:"100%"}, + width: { lg: "40%", md: "70%", xs: "100%" }, boxShadow: 1, backgroundColor: "#FFFFFF", padding: 2, + + /* Inactive (default) */ + "& .MuiStepIcon-root": { color: "#757575", fontSize: "2rem" }, // darker grey + "& .MuiStepLabel-label": { color: "#424242" }, // label darker + "& .MuiStepConnector-line": { borderColor: "#9E9E9E" }, // connector darker + + /* Active */ "& .Mui-active": { - "&.MuiStepIcon-root": { - color: "warning.main", - fontSize: "2rem", - }, - "& .MuiStepConnector-line": { - borderColor: "warning.main" - } + "&.MuiStepIcon-root": { color: "warning.main", fontSize: "2rem" }, + "& .MuiStepConnector-line": { borderColor: "warning.main" } }, + + /* Completed */ "& .Mui-completed": { - "&.MuiStepIcon-root": { - color: "secondary.main", - fontSize: "2rem", - }, - "& .MuiStepConnector-line": { - borderColor: "secondary.main" - } + "&.MuiStepIcon-root": { color: "#0C489E", fontSize: "2rem" }, // use your strong blue + "& .MuiStepConnector-line": { borderColor: "#0C489E" } } - } + }; const totalSteps = () => { return steps.length; @@ -155,7 +154,13 @@ const Register = () => { ) : (} // onClick={handleStep(index)} > diff --git a/src/pages/authentication/RegisterCustom.js b/src/pages/authentication/RegisterCustom.js index 50cccd0..38eb1bb 100644 --- a/src/pages/authentication/RegisterCustom.js +++ b/src/pages/authentication/RegisterCustom.js @@ -134,9 +134,22 @@ const RegisterCustom = () => { - +
@@ -146,9 +159,23 @@ const RegisterCustom = () => { - +
diff --git a/src/pages/authentication/auth-forms/BusCustomFormWizard.js b/src/pages/authentication/auth-forms/BusCustomFormWizard.js index 81401ff..51c45cd 100644 --- a/src/pages/authentication/auth-forms/BusCustomFormWizard.js +++ b/src/pages/authentication/auth-forms/BusCustomFormWizard.js @@ -30,7 +30,7 @@ import * as yup from 'yup'; import { strengthColorChi, strengthIndicator } from 'utils/password-strength'; // import {apiPath} from "auth/utils"; import axios from "axios"; -import { POST_PUBLIC_USER_REGISTER, POST_CAPTCHA, POST_USERNAME, POST_USER_EMAIL, POST_CAPTCHA_AUDIO } from "utils/ApiPathConst"; +import { POST_PUBLIC_USER_REGISTER, POST_CAPTCHA, POST_USERNAME, POST_USER_EMAIL } from "utils/ApiPathConst"; // import * as HttpUtils from 'utils/HttpUtils'; import * as ComboData from "utils/ComboData"; @@ -118,16 +118,6 @@ const BusCustomFormWizard = (props) => { changePassword(''); }, []); - const playCaptchaAudio = async () => { - const resp = await axios.post(`${POST_CAPTCHA_AUDIO}`, - { base64Url, lang: intl.locale }, - { responseType: "arraybuffer" } - ); - const blob = new Blob([resp.data], { type: "audio/wav" }); - const url = URL.createObjectURL(blob); - new Audio(url).play(); - }; - const handleCheckUsername = async () => { if (values?.username) { const response = await axios.post(`${POST_USERNAME}`, { @@ -193,21 +183,17 @@ const BusCustomFormWizard = (props) => { } }, [checkDistrictBlur]) - const handleCheckDistrict = () => { + const handleCheckDistrict = async () => { setDistrictErrStr(""); - if (selectedAddress5?.type === "hongKong") { - if (!selectedAddress4 || Object.keys(selectedAddress4).length === 0) { - setCheckDistrict(true); - setDistrictErrStr(getRequiredErrStr("district")); - return false; + if (selectedAddress4 == null || selectedAddress4 == "" || selectedAddress4 == {}){ + setCheckDistrict(true) + setDistrictErrStr(getRequiredErrStr("district")) + }else { + setCheckDistrict(false) } } - - setCheckDistrict(false); - return true; - }; - + } function getRequiredErrStr(fieldname){ return displayErrorMsg(intl.formatMessage({ id: 'require'},{fieldname:fieldname?intl.formatMessage({ id: fieldname}):""})); @@ -231,12 +217,8 @@ const BusCustomFormWizard = (props) => { } const checkDataField = (data) => { - const districtValid = - selectedAddress5?.type !== "hongKong" || - (selectedAddress4 && Object.keys(selectedAddress4).length > 0); - - - const valid = + // console.log(data.brExpiryDate) + if ( handleCaptcha(data.captchaField) && data.username !== "" && data.password !== "" && @@ -260,15 +242,19 @@ const BusCustomFormWizard = (props) => { handlePhone(data.phone) && handleUserName(data.username) && handleBrNo(data.brNo) && - districtValid && - !checkUsername && - !checkEmail; - - setisValid(valid); + handleCheckDistrict()&& + !checkUsername&& + !checkEmail&& + !checkDistrict + ) { + setisValid(true) + return isValid + } else { + setisValid(false) + return isValid + } }; - - const handleCheckBoxChange = (event) => { // console.log(event.target) if (event.target.name == 'termsAndConAccept') { @@ -372,18 +358,7 @@ const BusCustomFormWizard = (props) => { const { handleSubmit } = useForm({}) const _onSubmit = () => { - if (!handleCheckDistrict()) { - setLoding(false); - return; - } - - if (!isValid) { - setLoding(false); - return; - } - setLoding(true); - values.address4 = selectedAddress4==null?"":selectedAddress4.type values.address5 = selectedAddress5.type // console.log(values) @@ -633,15 +608,6 @@ const BusCustomFormWizard = (props) => { checkDataField(values) }, [values]) - useEffect(() => { - if ( - selectedAddress5?.type === "hongKong" && - values?.captchaField?.length === 5 - ) { - handleCheckDistrict(); - } - }, [values?.captchaField, selectedAddress5, selectedAddress4]); - return (
@@ -658,7 +624,7 @@ const BusCustomFormWizard = (props) => {
- + @@ -676,7 +642,7 @@ const BusCustomFormWizard = (props) => { - * + * {/* - @@ -1569,7 +1530,7 @@ const BusCustomFormWizard = (props) => {
- {/* 註有*的項目必須輸入資料 */} + {/* 註有*的項目必須輸入資料 */} @@ -1798,4 +1759,4 @@ const BusCustomFormWizard = (props) => { ); } -export default BusCustomFormWizard; +export default BusCustomFormWizard; \ No newline at end of file diff --git a/src/pages/authentication/auth-forms/CustomFormWizard.js b/src/pages/authentication/auth-forms/CustomFormWizard.js index 14c9127..4c36244 100644 --- a/src/pages/authentication/auth-forms/CustomFormWizard.js +++ b/src/pages/authentication/auth-forms/CustomFormWizard.js @@ -25,7 +25,7 @@ import * as yup from 'yup'; import { strengthColorChi, strengthIndicator } from 'utils/password-strength'; // import {apiPath} from "auth/utils"; import axios from "axios"; -import { POST_USERNAME, POST_USER_EMAIL, POST_CAPTCHA, POST_PUBLIC_USER_REGISTER, POST_IDNO, POST_CAPTCHA_AUDIO } from "utils/ApiPathConst"; +import { POST_USERNAME, POST_USER_EMAIL, POST_CAPTCHA, POST_PUBLIC_USER_REGISTER, POST_IDNO } from "utils/ApiPathConst"; // import * as HttpUtils from 'utils/HttpUtils'; import * as ComboData from "utils/ComboData"; @@ -137,16 +137,6 @@ const CustomFormWizard = (props) => { onCaptchaChange(); } }, []); - - const playCaptchaAudio = async () => { - const resp = await axios.post(`${POST_CAPTCHA_AUDIO}`, - { base64Url, lang: intl.locale }, - { responseType: "arraybuffer" } - ); - const blob = new Blob([resp.data], { type: "audio/wav" }); - const url = URL.createObjectURL(blob); - new Audio(url).play(); - }; useEffect(() => { if (selectedIdDocType.type === "HKID"){ @@ -207,21 +197,17 @@ const CustomFormWizard = (props) => { } } - const handleCheckDistrict = () => { + const handleCheckDistrict = async () => { setDistrictErrStr(""); - if (selectedAddress5?.type === "hongKong") { - if (!selectedAddress4 || Object.keys(selectedAddress4).length === 0) { - setCheckDistrict(true); - setDistrictErrStr(getRequiredErrStr("district")); - return false; + if (selectedAddress4 == null || selectedAddress4 == "" || selectedAddress4 == {}){ + setCheckDistrict(true) + setDistrictErrStr(getRequiredErrStr("district")) + }else { + setCheckDistrict(false) } } - - setCheckDistrict(false); - return true; - }; - + } useEffect(() => { if (username) { @@ -327,43 +313,46 @@ const CustomFormWizard = (props) => { } const checkDataField = (data) => { - const districtValid = - selectedAddress5?.type !== "hongKong" || - (selectedAddress4 && Object.keys(selectedAddress4).length > 0); - - - const valid = + // console.log(data) + if ( handleCaptcha(data.captchaField) && data.username !== "" && data.password !== "" && data.confirmPassword !== "" && - data.password === data.confirmPassword && + data.password == data.confirmPassword && selectedIdDocType.type !== "" && data.idNo !== "" && + // (data.enName !== "" || selectedIdDocType.type === "CNID") && + // data.chName !== "" && handleName(data.enName, data.chName) && data.address1 !== "" && data.email !== "" && data.emailConfirm !== "" && - data.email === data.emailConfirm && + data.email == data.emailConfirm && data.phone !== "" && data.phoneCountryCode !== "" && - termsAndConAccept === true && + termsAndConAccept == true && fileList.length !== 0 && + // data.captchaField && handlePassword(data.password) && handleEmail(data.email) && handleIdNo(data.idNo, selectedIdDocType.type, data.checkDigit) && handlePhone(data.phone) && handleUsername(data.username) && - districtValid && + handleCheckDistrict()&& !checkUsername && !checkEmail && - !checkIdDocNumber; - - setisValid(valid); + !checkIdDocNumber&& + !checkDistrict + ) { + setisValid(true) + return isValid + } else { + setisValid(false) + return isValid + } }; - - const handleCheckBoxChange = (event) => { if (event.target.name == 'termsAndConAccept') { setTermsAndConAccept(event.target.checked) @@ -460,28 +449,14 @@ const CustomFormWizard = (props) => { // }, [selectedAddress4, selectedAddress5]) useEffect(() => { - if (props.step == 2) { - handleCheckDistrict(); - _onSubmit(); - } - if (captchaImg == "") onCaptchaChange(); - checkDataField(values); - }, [props.step]); - + props.step == 2 ? _onSubmit() : null; + if (captchaImg == "") + onCaptchaChange(); + checkDataField(values) + }, [props.step]) const { handleSubmit } = useForm({}) const _onSubmit = () => { - // hard stop - if (!handleCheckDistrict()) { - setLoding(false); - return; - } - - if (!isValid) { - setLoding(false); - return; - } - setLoding(true); values.idDocType = selectedIdDocType.type values.address4 = selectedAddress4 == null ? "" : selectedAddress4.type @@ -863,15 +838,6 @@ const CustomFormWizard = (props) => { const { values } = formik - useEffect(() => { - if ( - selectedAddress5?.type === "hongKong" && - values?.captchaField?.length === 5 - ) { - handleCheckDistrict(); - } - }, [values?.captchaField, selectedAddress5, selectedAddress4]); - useEffect(() => { checkDataField(values) }, [values]) @@ -894,7 +860,7 @@ const CustomFormWizard = (props) => {
- + @@ -909,7 +875,7 @@ const CustomFormWizard = (props) => { - * + * {/* { - * + * @@ -1023,7 +989,7 @@ const CustomFormWizard = (props) => { - * + * { - * + * {/* {formik.touched.enName && formik.errors.enName && ( @@ -1306,7 +1272,7 @@ const CustomFormWizard = (props) => { - {selectedIdDocType.type === "CNID" ? "" : } + {selectedIdDocType.type === "CNID" ? "" : } { - + { - * + * { getOptionLabel={(option) => option.type ? intl.formatMessage({ id: option.type }) : ""} onChange={(event, newValue) => { setSelectedAddress4(newValue); - - setCheckDistrict(false); - setDistrictErrStr(""); }} sx={{ '& .MuiInputBase-root': { alignItems: 'center' }, @@ -1536,7 +1499,7 @@ const CustomFormWizard = (props) => { - * + * { - * + * { - * + * @@ -1761,7 +1724,7 @@ const CustomFormWizard = (props) => { - * + * @@ -1801,7 +1764,7 @@ const CustomFormWizard = (props) => { - * + * @@ -1856,7 +1819,7 @@ const CustomFormWizard = (props) => { - * + * @@ -1891,11 +1854,6 @@ const CustomFormWizard = (props) => { {formik.errors.captchaField} )} - - - @@ -1912,7 +1870,7 @@ const CustomFormWizard = (props) => { - {/* 註有*的項目必須輸入資料 */} + {/* 註有*的項目必須輸入資料 */} @@ -2147,4 +2105,4 @@ const CustomFormWizard = (props) => { ); } -export default CustomFormWizard; +export default CustomFormWizard; \ No newline at end of file diff --git a/src/pages/authentication/auth-forms/IAmSmartFormWizard.js b/src/pages/authentication/auth-forms/IAmSmartFormWizard.js index 04132d1..7c4d734 100644 --- a/src/pages/authentication/auth-forms/IAmSmartFormWizard.js +++ b/src/pages/authentication/auth-forms/IAmSmartFormWizard.js @@ -21,7 +21,7 @@ import { useFormik, FormikProvider } from 'formik'; import * as yup from 'yup'; import axios from "axios"; -import { POST_IAMSMART_USER_REGISTER, POST_CAPTCHA, POST_USER_EMAIL, POST_CAPTCHA_AUDIO } from "utils/ApiPathConst"; +import { POST_IAMSMART_USER_REGISTER, POST_CAPTCHA, POST_USER_EMAIL } from "utils/ApiPathConst"; import * as ComboData from "utils/ComboData"; @@ -79,16 +79,6 @@ const CustomFormWizard = (props) => { const [base64Url, setBase64Url] = useState("") const [checkCode, setCheckCode] = useState("") - const playCaptchaAudio = async () => { - const resp = await axios.post(`${POST_CAPTCHA_AUDIO}`, - { base64Url, lang: intl.locale }, - { responseType: "arraybuffer" } - ); - const blob = new Blob([resp.data], { type: "audio/wav" }); - const url = URL.createObjectURL(blob); - new Audio(url).play(); - }; - useEffect(() => { location.state?.responseData ?? window.location.assign("/login"); if (captchaImg == "") @@ -96,15 +86,6 @@ const CustomFormWizard = (props) => { responseToData(); }, []); - useEffect(() => { - if ( - selectedAddress5?.type === "hongKong" && - values?.captchaField?.length === 5 - ) { - handleCheckDistrict(); - } - }, [values?.captchaField, selectedAddress5, selectedAddress4]); - const handleClickShowId = () => { setshowId(!showId); }; @@ -136,22 +117,17 @@ const CustomFormWizard = (props) => { } }, [checkDistrictBlur]) - const handleCheckDistrict = () => { + const handleCheckDistrict = async () => { setDistrictErrStr(""); - if (selectedAddress5?.type === "hongKong") { - if (!selectedAddress4 || Object.keys(selectedAddress4).length === 0) { - setCheckDistrict(true); - setDistrictErrStr(getRequiredErrStr("district")); - return false; + if (selectedAddress4 == null || selectedAddress4 == "" || selectedAddress4 == {}){ + setCheckDistrict(true) + setDistrictErrStr(getRequiredErrStr("district")) + }else { + setCheckDistrict(false) } } - - setCheckDistrict(false); - return true; - }; - - + } function getRequiredErrStr(fieldname) { return displayErrorMsg(intl.formatMessage({ id: 'require' }, { fieldname: fieldname ? intl.formatMessage({ id: fieldname }) : "" })); @@ -232,16 +208,13 @@ const CustomFormWizard = (props) => { const handleCheckEmail = async () => { if (values?.email) { - if (handleEmail(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; - } + const response = await axios.post(`${POST_USER_EMAIL}`, { + e1: values.email, + }) + setCheckEmail((Number(response.data[0]) === 1)) + return Number(response.data[0]) === 1 } - }; - + } useEffect(() => { if (email) { @@ -271,10 +244,6 @@ const CustomFormWizard = (props) => { formik.setFieldValue("address1", iAmSmartData.address1 ?? ""); props.setIdNo(iAmSmartData.idNo ?? ""); setLodingData(false) - - if (iAmSmartData.email) { - handleCheckEmail(); - } } }, [iAmSmartData]) @@ -293,30 +262,29 @@ const CustomFormWizard = (props) => { const checkDataField = (data) => { - const districtValid = - selectedAddress5?.type !== "hongKong" || - (selectedAddress4 && Object.keys(selectedAddress4).length > 0); - - - const valid = - data.address1 !== "" && + if (data.address1 !== "" && data.email !== "" && data.emailConfirm !== "" && - data.email === data.emailConfirm && + data.email == data.emailConfirm && data.phone !== "" && data.phoneCountryCode !== "" && - termsAndConAccept === true && + termsAndConAccept == true && data.captchaField && handleEmail(data.email) && handlePhone(data.phone) && handleCaptcha(data.captchaField) && - districtValid && - !checkEmail; - - setisValid(valid); + handleCheckDistrict()&& + !checkEmail&& + !checkDistrict + ) { + setisValid(true) + return isValid + } else { + setisValid(false) + return isValid + } }; - const handleCheckBoxChange = (event) => { if (event.target.name == 'termsAndConAccept') { setTermsAndConAccept(event.target.checked) @@ -339,30 +307,14 @@ const CustomFormWizard = (props) => { termsAndConAccept, termsAndConNotAccept]) useEffect(() => { - if (props.step == 2) { - handleCheckDistrict(); - _onSubmit(); - } - if (captchaImg == "") onCaptchaChange(); - checkDataField(values); - }, [props.step]); + props.step == 2 ? _onSubmit() : null; + if (captchaImg == "") + onCaptchaChange(); + checkDataField(values) + }, [props.step]) const { handleSubmit } = useForm({}) - const _onSubmit = async () => { - // hard stop: district - if (!handleCheckDistrict()) { - setLoding(false); - return; - } - - await handleCheckEmail(); - - // hard stop: overall validation (now includes fresh email result) - if (!isValid) { - setLoding(false); - return; - } - + const _onSubmit = () => { setLoding(true); const userAddress = { @@ -496,15 +448,6 @@ const CustomFormWizard = (props) => { checkDataField(values) }, [values]) - useEffect(() => { - if ( - selectedAddress5?.type === "hongKong" && - values?.captchaField?.length === 5 - ) { - handleCheckDistrict(); - } - }, [values?.captchaField, selectedAddress5, selectedAddress4]); - return ( isLoadingData ? : @@ -523,7 +466,7 @@ const CustomFormWizard = (props) => { - + iAM Smart: @@ -589,7 +532,7 @@ const CustomFormWizard = (props) => { - * + * @@ -671,16 +614,13 @@ const CustomFormWizard = (props) => { getOptionLabel={(option) => option.type ? intl.formatMessage({ id: option.type }) : ""} onChange={(event, newValue) => { setSelectedAddress4(newValue); - - setCheckDistrict(false); - setDistrictErrStr(""); }} sx={{ '& .MuiInputBase-root': { alignItems: 'center' }, '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, '& .MuiOutlinedInput-root': { height: 40 } }} - renderInput={(params) => } /> { - * + * @@ -760,10 +700,7 @@ const CustomFormWizard = (props) => { type="email" value={formik.values.email.trim()} name="email" - onChange={(e) => { - setCheckEmail(false); - formik.handleChange(e); - }} + onChange={formik.handleChange} placeholder={intl.formatMessage({ id: 'userContactEmail' })} onBlur={formik.handleBlur} inputProps={{ @@ -794,7 +731,7 @@ const CustomFormWizard = (props) => { - * + * @@ -841,7 +778,7 @@ const CustomFormWizard = (props) => { - * + * @@ -985,7 +922,7 @@ const CustomFormWizard = (props) => { - * + * @@ -1042,7 +979,7 @@ const CustomFormWizard = (props) => { - * + * @@ -1077,11 +1014,6 @@ const CustomFormWizard = (props) => { {formik.errors.captchaField} )} - - - @@ -1282,4 +1214,4 @@ const CustomFormWizard = (props) => { ); } -export default CustomFormWizard; +export default CustomFormWizard; \ No newline at end of file diff --git a/src/pages/dashboard/Public/index.js b/src/pages/dashboard/Public/index.js index 75f88ac..9af059a 100644 --- a/src/pages/dashboard/Public/index.js +++ b/src/pages/dashboard/Public/index.js @@ -93,7 +93,25 @@ const DashboardDefault = () => { let list = [] response.map((item) => { list.push( - @@ -206,9 +241,17 @@ const DashboardDefault = () => { diff --git a/src/pages/pnspsUserGroupDetailPage/index.js b/src/pages/pnspsUserGroupDetailPage/index.js index 8c938fd..fd60e30 100644 --- a/src/pages/pnspsUserGroupDetailPage/index.js +++ b/src/pages/pnspsUserGroupDetailPage/index.js @@ -203,7 +203,7 @@ const UserMaintainPage = () => { alignItems: 'end' }} onClick={() => { location.reload() }} - color="gray" + color="secondary" > Reset & Back diff --git a/src/themes/buttonConst.js b/src/themes/buttonConst.js index 025c764..23635c5 100644 --- a/src/themes/buttonConst.js +++ b/src/themes/buttonConst.js @@ -70,31 +70,84 @@ export const PNSPS_BUTTON_THEME = createTheme({ }, MuiButton: { styleOverrides: { - startIcon:{ - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - '& > *:nth-of-type(1)': { - fontSize: '28px', - }, + /* ----------------------------- + Icon alignment + ------------------------------ */ + startIcon: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + '& > *:nth-of-type(1)': { fontSize: '28px' }, }, + + /* ----------------------------- + Base button styles + ------------------------------ */ root: { - fontSize: '1.2rem', - fontWeight: '600', - height: '45px', - minWidth: '35' + - 'vw', // Default width for xs screen sizes - '@media (min-width: 600px)': { // sm breakpoint - minWidth: '20vw', - }, - '@media (min-width: 960px)': { // md breakpoint - minWidth: '15vw', - }, - '@media (min-width: 1280px)': { // lg breakpoint - minWidth: '9vw', - }, - textTransform: "none", - alignItems: 'normal', + fontSize: '1.2rem', + fontWeight: '600', + height: '45px', + minWidth: '35vw', + '@media (min-width: 600px)': { minWidth: '20vw' }, + '@media (min-width: 960px)': { minWidth: '15vw' }, + '@media (min-width: 1280px)': { minWidth: '9vw' }, + textTransform: 'none', + alignItems: 'normal', + }, + + /* ----------------------------- + Remove default contained elevation / blue focus + ------------------------------ */ + contained: { + boxShadow: 'none', + '&:hover': { boxShadow: 'none' }, + '&:active': { boxShadow: 'none' }, + '&.Mui-focusVisible': { boxShadow: 'none' }, + }, + + /* ----------------------------- + RESET / CANCEL (contained + cancel) + WCAG 2.1 AA compliant + ------------------------------ */ + containedCancel: { + backgroundColor: '#616161', // dark grey (passes with white text) + color: '#FFFFFF', + + border: '0 !important', + outline: 'none', + boxShadow: 'none !important', + + '&:hover': { + backgroundColor: '#545454', + boxShadow: 'none !important', + }, + + '&:active': { + backgroundColor: '#4A4A4A', + boxShadow: 'none !important', + }, + + /* ✅ Keyboard TAB focus (visible & accessible) */ + '&.Mui-focusVisible': { + boxShadow: '0 0 0 3px rgba(255,255,255,0.6) !important', + }, + }, + + /* ----------------------------- + Optional: outlined cancel variant + ------------------------------ */ + outlinedCancel: { + borderColor: '#616161', + color: '#424242', + + '&:hover': { + borderColor: '#616161', + backgroundColor: 'transparent', + }, + + '&.Mui-focusVisible': { + boxShadow: '0 0 0 3px rgba(66,66,66,0.4)', + }, }, }, }, diff --git a/src/themes/themeConst.js b/src/themes/themeConst.js index 931ac3a..82583a4 100644 --- a/src/themes/themeConst.js +++ b/src/themes/themeConst.js @@ -17,6 +17,10 @@ const themeTypography = Typography( export const PNSPS_THEME = createTheme({ palette: theme.palette, typography: themeTypography, + cancel: { + main: '#424242', // dark gray, WCAG safe + contrastText: '#FFFFFF' + }, components: { MuiImage: { root: { @@ -41,19 +45,17 @@ export const PNSPS_THEME = createTheme({ }, MuiButton: { styleOverrides: { - outlined: { - borderColor: "#0050B3", - color: "#0050B3", - - "&:hover": { - borderColor: "#0050B3", - backgroundColor: "transparent", - }, - - "& .MuiSvgIcon-root": { - color: "inherit", - }, - }, + root: ({ ownerState }) => ({ + ...(ownerState.color === "cancel" && { + borderColor: "#9E9E9E", + }), + }), + contained: ({ ownerState }) => ({ + ...(ownerState.color === "cancel" && { + border: "2px solid #9E9E9E", + boxShadow: "none", + }), + }), }, }, MuiInputAdornment: { @@ -416,18 +418,45 @@ export const PNSPS_THEME = createTheme({ MuiTab: { styleOverrides: { root: { - fontSize: TABLE_FONT_SIZE, - minHeight: 46, - color: theme.palette.text.primary - } - } - }, + fontSize: TABLE_FONT_SIZE, // ✅ unchanged + minHeight: 46, + textTransform: 'none', + + // Unselected tab text on white – AA compliant + color: '#424242', + + // Selected tab text – AA compliant blue + '&.Mui-selected': { + color: '#1565C0', + }, + + // Hover (optional but consistent) + '&:hover': { + color: '#1565C0', + }, + + // ✅ Keyboard TAB focus (visible & accessible) + '&.Mui-focusVisible': { + outline: '3px solid rgba(21, 101, 192, 0.55)', + outlineOffset: '3px', + borderRadius: '6px', + }, + }, + }, + }, + MuiTabs: { styleOverrides: { vertical: { - overflow: 'visible' - } - } + overflow: 'visible', + }, + + // ✅ Selected tab indicator – AA compliant blue + indicator: { + backgroundColor: '#1565C0', + height: '3px', + }, + }, }, MuiFormLabel: { styleOverrides: { diff --git a/src/utils/statusUtils/DnStatus.js b/src/utils/statusUtils/DnStatus.js index 9c7e96e..527ab36 100644 --- a/src/utils/statusUtils/DnStatus.js +++ b/src/utils/statusUtils/DnStatus.js @@ -1,8 +1,8 @@ import {getStatusTag} from "utils/statusUtils/Base"; -const pending = {color:"#f5a83d", eng:"Pending", cht:"待辦", cn: "待办"} -const toBePaid = {color:"#f5a83d", eng:"To be Paid", cht:"待付款", cn: "待付款"} -const paid = {color:"#22a13f", eng:"Paid", cht:"已付款", cn: "已付款"} +const pending = { color: "#8F7200", eng: "Pending", cht: "待辦", cn: "待办" } // was #f5a83d +const toBePaid = { color: "#8F7200", eng: "To be Paid", cht: "待付款", cn: "待付款" } // was #f5a83d +const paid = { color: "#1D8735", eng: "Paid", cht: "已付款", cn: "已付款" } // was #22a13f export function getStatus_Cht(params) { let status = getStatus(params); diff --git a/src/utils/statusUtils/PaymentStatus.js b/src/utils/statusUtils/PaymentStatus.js index 2e7d5fd..f24f643 100644 --- a/src/utils/statusUtils/PaymentStatus.js +++ b/src/utils/statusUtils/PaymentStatus.js @@ -1,10 +1,10 @@ import {getStatusTag} from "utils/statusUtils/Base"; -const APPR = {color:"#22a13f", eng:"Success", cht:"成功"} -const REJT = {color:"#d9372b", eng:"Reject", cht:"拒絕"} -const CANC = {color:"#8a8784", eng:"Cancelled", cht:"取消"} -const INPR = {color:"#f5a83d", eng:"In Progress", cht:"進行中"} -const DUPA = {color:"#8a8784", eng:"", cht:""} +const APPR = { color: "#1D8735", eng: "Success", cht: "成功" } // was #22a13f +const REJT = { color: "#d9372b", eng: "Reject", cht: "拒絕" } // unchanged +const CANC = { color: "#797674", eng: "Cancelled", cht: "取消" } // was #8a8784 +const INPR = { color: "#8F7200", eng: "In Progress", cht: "進行中" } // was #f5a83d +const DUPA = { color: "#797674", eng: "", cht: "" } // was #8a8784 export function getStatus_Cht(params) { let status = getStatus(params); diff --git a/src/utils/statusUtils/ProofStatus.js b/src/utils/statusUtils/ProofStatus.js index e0fd6db..f374e18 100644 --- a/src/utils/statusUtils/ProofStatus.js +++ b/src/utils/statusUtils/ProofStatus.js @@ -1,11 +1,11 @@ import {getStatusTag} from "utils/statusUtils/Base"; import * as DateUtils from "utils/DateUtils"; -const confirm = {color:"#22a13f", eng:"Pass for printing", cht:"可以付印", cn:"可以付印"} -const unable = {color:"#d9372b", eng:"Re-proofing", cht:"未能付印", cn:"未能付印"} -const timeOut = {color:"#8a8784", eng:"Proofing timed out. Please apply again", cht:"校對回覆逾時,請重新申請", cn:"校对回复逾时,请重新申请"} -const pendingReply = {color:"#f5a83d", eng:"Proofing reply pending", cht:"校對待覆", cn:"校对待复"} -const cancel = {color:"#000", textColor:"#fff", eng:"Cancelled", cht:"已取消", cn:"已取消"} +const confirm = { color: "#1D8735", eng: "Pass for printing", cht: "可以付印", cn: "可以付印" } // was #22a13f +const unable = { color: "#d9372b", eng: "Re-proofing", cht: "未能付印", cn: "未能付印" } // unchanged +const timeOut = { color: "#797674", eng: "Proofing timed out. Please apply again", cht: "校對回覆逾時,請重新申請", cn: "校对回复逾时,请重新申请" } // was #8a8784 +const pendingReply = { color: "#9D6C27", eng: "Proofing reply pending", cht: "校對待覆", cn: "校对待复" } // was #f5a83d +const cancel = { color: "#000", textColor: "#fff", eng: "Cancelled", cht: "已取消", cn: "已取消" } // unchanged export function getStatus_Cht(params) { let status = getStatus(params); diff --git a/src/utils/statusUtils/PublicNoteStatusUtils.js b/src/utils/statusUtils/PublicNoteStatusUtils.js index 03978eb..fab1794 100644 --- a/src/utils/statusUtils/PublicNoteStatusUtils.js +++ b/src/utils/statusUtils/PublicNoteStatusUtils.js @@ -7,33 +7,43 @@ export function getStatus(params) { } export function getStatusByText(status, creditor) { - switch (status) { - case "submitted": - return getStatusTag({ color: "#f5a83d", text: "處理中" }) - case "reviewed": - return getStatusTag({ color: "#f5a83d", text: "處理中" }) - case "confirmed": - if (creditor) - return getStatusTag({ color: "#22a13f", text: "待發佈" }) - else - return getStatusTag({ color: "#22a13f", text: "待付款" }) - case "published": - return getStatusTag({ color: "#22a13f", text: "待付款" }) - case "paid": - return getStatusTag({ color: "#22a13f", text: "待發佈" }) - case "completed": - return getStatusTag({ color: "#4D4D4D", text: "已完成" }) - case "notAccepted": - return getStatusTag({ color: "#d9372b", text: "不接受" }) - case "resubmit": - return getStatusTag({ color: "#757373", text: "需重新提交" }) - case "cancelled": - return getStatusTag({ color: "#909497", text: "已取消" }) - case "withdrawn": - return getStatusTag({ color: "#6E14CC", text: "已撤銷" }) - default: - return getStatusTag({ text: status }) - } + switch (status) { + case "submitted": + return getStatusTag({ color: "#8F7200", text: "處理中" }) // was #f5a83d + + case "reviewed": + return getStatusTag({ color: "#8F7200", text: "處理中" }) // was #f5a83d + + case "confirmed": + if (creditor) + return getStatusTag({ color: "#1D8735", text: "待發佈" }) // was #22a13f + else + return getStatusTag({ color: "#1D8735", text: "待付款" }) // was #22a13f + + case "published": + return getStatusTag({ color: "#1D8735", text: "待付款" }) // was #22a13f + + case "paid": + return getStatusTag({ color: "#1D8735", text: "待發佈" }) // was #22a13f + + case "completed": + return getStatusTag({ color: "#4D4D4D", text: "已完成" }) // unchanged (already AA) + + case "notAccepted": + return getStatusTag({ color: "#d9372b", text: "不接受" }) // unchanged (already AA) + + case "resubmit": + return getStatusTag({ color: "#757373", text: "需重新提交" }) // unchanged (already AA) + + case "cancelled": + return getStatusTag({ color: "#737679", text: "已取消" }) // was #909497 + + case "withdrawn": + return getStatusTag({ color: "#6E14CC", text: "已撤銷" }) // unchanged (already AA) + + default: + return getStatusTag({ text: status }) + } } export function getStatusEng(params) { @@ -42,28 +52,28 @@ export function getStatusEng(params) { export function getStatusByTextEng(status, creditor) { switch (status) { case "submitted": - return getStatusTag({ color: "#F1C40F", text: "Submitted" }) + return getStatusTag({ color: "#8F7200", text: "Submitted" }) // was #F1C40F case "reviewed": - return getStatusTag({ color: "#0C489E", text: "Reviewed" }) + return getStatusTag({ color: "#0C489E", text: "Reviewed" }) // unchanged case "confirmed": if (creditor) - return getStatusTag({ color: "#3498DB", text: "Pending Publish" }) + return getStatusTag({ color: "#217CBA", text: "Pending Publish" }) // was #3498DB else - return getStatusTag({ color: "#F39C12", text: "Pending Payment" }) + return getStatusTag({ color: "#B45309", text: "Pending Payment" }) // was #F39C12 case "published": - return getStatusTag({ color: "#F39C12", text: "Pending Payment" }) + return getStatusTag({ color: "#B45309", text: "Pending Payment" }) // was #F39C12 case "paid": - return getStatusTag({ color: "#3498DB", text: "Pending Publish" }) + return getStatusTag({ color: "#217CBA", text: "Pending Publish" }) // was #3498DB case "completed": - return getStatusTag({ color: "#4D4D4D", text: "Completed" }) + return getStatusTag({ color: "#4D4D4D", text: "Completed" }) // unchanged case "notAccepted": - return getStatusTag({ color: "#d9372b", text: "Not Accepted" }) + return getStatusTag({ color: "#d9372b", text: "Not Accepted" }) // unchanged case "resubmit": - return getStatusTag({ color: "#757373", text: "Re-Submit Required" }) + return getStatusTag({ color: "#757373", text: "Re-Submit Required" }) // unchanged case "cancelled": - return getStatusTag({ color: "#8a8784", text: "Cancelled" }) + return getStatusTag({ color: "#797674", text: "Cancelled" }) // was #8a8784 case "withdrawn": - return getStatusTag({ color: "#6E14CC", text: "Withdrawn" }) + return getStatusTag({ color: "#6E14CC", text: "Withdrawn" }) // unchanged default: return getStatusTag({ text: status }) } @@ -76,28 +86,28 @@ export function getStatusIntl(params, intl) { export function getStatusByTextIntl(status, creditor, intl) { switch (status) { case "submitted": - return getStatusTag({ color: "#F1C40F", text: intl.formatMessage({id: 'processing'}) }) + return getStatusTag({ color: "#8F7200", text: intl.formatMessage({id: 'processing'}) }) // was #F1C40F case "reviewed": - return getStatusTag({ color: "#F1C40F", text: intl.formatMessage({id: 'processing'}) }) + return getStatusTag({ color: "#8F7200", text: intl.formatMessage({id: 'processing'}) }) // was #F1C40F case "confirmed": if (creditor) - return getStatusTag({ color: "#3498DB", text: intl.formatMessage({id: 'pendingPublish'}) }) + return getStatusTag({ color: "#217CBA", text: intl.formatMessage({id: 'pendingPublish'}) }) // was #3498DB else - return getStatusTag({ color: "#F39C12", text: intl.formatMessage({id: 'pendingPayment'}) }) + return getStatusTag({ color: "#B45309", text: intl.formatMessage({id: 'pendingPayment'}) }) // was #F39C12 case "published": - return getStatusTag({ color: "#F39C12", text: intl.formatMessage({id: 'pendingPayment'}) }) + return getStatusTag({ color: "#B45309", text: intl.formatMessage({id: 'pendingPayment'}) }) // was #F39C12 case "paid": - return getStatusTag({ color: "#3498DB", text: intl.formatMessage({id: 'pendingPublish'}) }) + return getStatusTag({ color: "#217CBA", text: intl.formatMessage({id: 'pendingPublish'}) }) // was #3498DB case "completed": - return getStatusTag({ color: "#4D4D4D", text: intl.formatMessage({id: 'completed'}) }) + return getStatusTag({ color: "#4D4D4D", text: intl.formatMessage({id: 'completed'}) }) // unchanged case "notAccepted": - return getStatusTag({ color: "#d9372b", text: intl.formatMessage({id: 'notAccepted'}) }) + return getStatusTag({ color: "#d9372b", text: intl.formatMessage({id: 'notAccepted'}) }) // unchanged case "resubmit": - return getStatusTag({ color: "#757373", text: intl.formatMessage({id: 'resubmit'}) }) + return getStatusTag({ color: "#757373", text: intl.formatMessage({id: 'resubmit'}) }) // unchanged case "cancelled": - return getStatusTag({ color: "#8a8784", text: intl.formatMessage({id: 'cancelled'}) }) + return getStatusTag({ color: "#797674", text: intl.formatMessage({id: 'cancelled'}) }) // was #8a8784 case "withdrawn": - return getStatusTag({ color: "#6E14CC", text: intl.formatMessage({id: 'withdrawn'}) }) + return getStatusTag({ color: "#6E14CC", text: intl.formatMessage({id: 'withdrawn'}) }) // unchanged default: return getStatusTag({ text: status }) }