From 617904b649ffd8ee1bf3740bc0b1031c8d0cb14f Mon Sep 17 00:00:00 2001 From: Alex Cheung Date: Wed, 25 Feb 2026 03:42:25 +0800 Subject: [PATCH] WCAG 1.3.1 and add back 1.1.1 --- src/components/FiDataGrid.js | 29 +- .../Announcement/Search_Public/SearchForm.js | 4 +- src/pages/DemandNote/Create/SearchForm.js | 4 + src/pages/DemandNote/Export/SearchForm.js | 4 + src/pages/DemandNote/Search/SearchForm.js | 12 + .../DemandNote/Search_Public/SearchForm.js | 12 +- src/pages/Message/Search/SearchForm.js | 4 +- .../DetailPage/OrganizationCard.js | 2 +- .../OrganizationCard_loadFromUser.js | 2 +- src/pages/Payment/Search_Public/SearchForm.js | 8 +- src/pages/Proof/Search_Public/SearchForm.js | 12 +- .../ListPanel/PendingPaymentTab.js | 4 + .../ListPanel/SearchPublicNoticeForm.js | 8 +- src/pages/User/ChangePasswordPage/index.js | 8 +- .../User/DetailPage/UserInformationCard.js | 4 +- .../User/DetailsPage_Individual/index.js | 7 +- .../User/DetailsPage_Organization/index.js | 2 +- .../ForgotPassword/AuthCallback/index.js | 8 +- .../ForgotUsername/AuthCallback/index.js | 8 +- .../authentication/auth-forms/AuthLogin.js | 4 +- .../auth-forms/AuthLoginCustom.js | 4 +- .../authentication/auth-forms/AuthRegister.js | 4 +- .../auth-forms/BusCustomFormWizard.js | 58 +++- .../auth-forms/CustomFormWizard.js | 62 ++++- .../auth-forms/IAmSmartFormWizard.js | 63 ++++- .../PrivacyPolicyPage/PrivacyPolicy_cn.js | 90 +++---- .../PrivacyPolicyPage/PrivacyPolicy_en.js | 247 +++++++++--------- .../PrivacyPolicyPage/PrivacyPolicy_zh.js | 209 +++++++-------- src/translations/en.json | 18 +- src/translations/zh-CN.json | 18 +- src/translations/zh-HK.json | 18 +- src/utils/Combo.js | 6 + src/utils/DateUtils.js | 50 ++++ src/utils/SelectBase.js | 8 +- 34 files changed, 651 insertions(+), 350 deletions(-) diff --git a/src/components/FiDataGrid.js b/src/components/FiDataGrid.js index 67c389c..6b28f0f 100644 --- a/src/components/FiDataGrid.js +++ b/src/components/FiDataGrid.js @@ -239,16 +239,25 @@ export function FiDataGrid({ rows, columns, sx, autoHeight = true, ? { Pagination: () => ( - `${(_rows?.length ? page * pageSize + 1 : 0)}-${page * pageSize + (_rows?.length ?? 0)} ${intl.formatMessage({ id: "of" })} ${rowCount}` - } - labelRowsPerPage={intl.formatMessage({ id: "rowsPerPage" }) + ":"} - onPageChange={handleChangePage} - onRowsPerPageChange={handleChangePageSize} + count={rowCount || 0} + page={page} + rowsPerPage={pageSize} + rowsPerPageOptions={_pageSizeOptions} + labelDisplayedRows={() => + `${(_rows?.length ? page * pageSize + 1 : 0)}-${page * pageSize + (_rows?.length ?? 0)} ${intl.formatMessage({ id: "of" })} ${rowCount}` + } + labelRowsPerPage={intl.formatMessage({ id: "rowsPerPage" }) + ":"} + getItemAriaLabel={(type) => { + if (type === 'previous') { + return intl.formatMessage({ id: 'paginationPrev' }); + } + if (type === 'next') { + return intl.formatMessage({ id: 'paginationNext' }); + } + return ''; + }} + onPageChange={handleChangePage} + onRowsPerPageChange={handleChangePageSize} /> ), } diff --git a/src/pages/Announcement/Search_Public/SearchForm.js b/src/pages/Announcement/Search_Public/SearchForm.js index 7d8f618..156b538 100644 --- a/src/pages/Announcement/Search_Public/SearchForm.js +++ b/src/pages/Announcement/Search_Public/SearchForm.js @@ -116,7 +116,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady }) => - + - + )} + clearText={intl.formatMessage({ id: "muiClear" })} + closeText={intl.formatMessage({ id: "muiClose" })} + openText={intl.formatMessage({ id: "muiOpen" })} + noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} /> diff --git a/src/pages/DemandNote/Export/SearchForm.js b/src/pages/DemandNote/Export/SearchForm.js index 7596bb7..c33d201 100644 --- a/src/pages/DemandNote/Export/SearchForm.js +++ b/src/pages/DemandNote/Export/SearchForm.js @@ -187,6 +187,10 @@ const SearchPublicNoticeForm = ({ applySearch, issueComboData }) => { }} /> )} + clearText={intl.formatMessage({ id: "muiClear" })} + closeText={intl.formatMessage({ id: "muiClose" })} + openText={intl.formatMessage({ id: "muiOpen" })} + noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} /> diff --git a/src/pages/DemandNote/Search/SearchForm.js b/src/pages/DemandNote/Search/SearchForm.js index debc2dd..3b2d891 100644 --- a/src/pages/DemandNote/Search/SearchForm.js +++ b/src/pages/DemandNote/Search/SearchForm.js @@ -224,6 +224,10 @@ const SearchDemandNoteForm = ({ applySearch, orgComboData, searchCriteria, issue }} /> )} + clearText={intl.formatMessage({ id: "muiClear" })} + closeText={intl.formatMessage({ id: "muiClose" })} + openText={intl.formatMessage({ id: "muiOpen" })} + noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} /> @@ -282,6 +286,10 @@ const SearchDemandNoteForm = ({ applySearch, orgComboData, searchCriteria, issue {params.children} )} + clearText={intl.formatMessage({ id: "muiClear" })} + closeText={intl.formatMessage({ id: "muiClose" })} + openText={intl.formatMessage({ id: "muiOpen" })} + noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} /> : <> @@ -445,6 +453,10 @@ const SearchDemandNoteForm = ({ applySearch, orgComboData, searchCriteria, issue }} /> )} + clearText={intl.formatMessage({ id: "muiClear" })} + closeText={intl.formatMessage({ id: "muiClose" })} + openText={intl.formatMessage({ id: "muiOpen" })} + noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} /> diff --git a/src/pages/DemandNote/Search_Public/SearchForm.js b/src/pages/DemandNote/Search_Public/SearchForm.js index bbed57c..d4402be 100644 --- a/src/pages/DemandNote/Search_Public/SearchForm.js +++ b/src/pages/DemandNote/Search_Public/SearchForm.js @@ -180,6 +180,10 @@ const SearchDemandNoteForm = ({ applySearch, searchCriteria, issueComboData, onG }} /> )} + clearText={intl.formatMessage({ id: "muiClear" })} + closeText={intl.formatMessage({ id: "muiClose" })} + openText={intl.formatMessage({ id: "muiOpen" })} + noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} /> @@ -210,7 +214,7 @@ const SearchDemandNoteForm = ({ applySearch, searchCriteria, issueComboData, onG - + - + diff --git a/src/pages/Message/Search/SearchForm.js b/src/pages/Message/Search/SearchForm.js index 78bdc4d..e76cd7b 100644 --- a/src/pages/Message/Search/SearchForm.js +++ b/src/pages/Message/Search/SearchForm.js @@ -119,7 +119,7 @@ const SearchForm = ({ applySearch, searchCriteria, onGridReady }) => { - + { - + { value={fromDate != null ? DateUtils.dateStr(fromDate) : DateUtils.dateStr(currentFromDate)} disabled={true} /> : - + { {FieldUtils.notNullFieldLabel("Expiry Date:")} - + - + - + InputLabelProps={{ shrink: true }} + clearText={intl.formatMessage({ id: "muiClear" })} + closeText={intl.formatMessage({ id: "muiClose" })} + openText={intl.formatMessage({ id: "muiOpen" })} + noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} /> diff --git a/src/pages/Proof/Search_Public/SearchForm.js b/src/pages/Proof/Search_Public/SearchForm.js index d2c39ad..0a40d5c 100644 --- a/src/pages/Proof/Search_Public/SearchForm.js +++ b/src/pages/Proof/Search_Public/SearchForm.js @@ -223,6 +223,10 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, issueComboData, o }} /> )} + clearText={intl.formatMessage({ id: "muiClear" })} + closeText={intl.formatMessage({ id: "muiClose" })} + openText={intl.formatMessage({ id: "muiOpen" })} + noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} /> @@ -252,7 +256,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, issueComboData, o - + - + diff --git a/src/pages/PublicNotice/ListPanel/PendingPaymentTab.js b/src/pages/PublicNotice/ListPanel/PendingPaymentTab.js index 01b1242..ed97bf6 100644 --- a/src/pages/PublicNotice/ListPanel/PendingPaymentTab.js +++ b/src/pages/PublicNotice/ListPanel/PendingPaymentTab.js @@ -401,6 +401,10 @@ export default function SubmittedTab({ setCount, url }) { '& .MuiOutlinedInput-root': { height: 40 } }} renderInput={(params) => } + clearText={intl.formatMessage({ id: "muiClear" })} + closeText={intl.formatMessage({ id: "muiClose" })} + openText={intl.formatMessage({ id: "muiOpen" })} + noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} /> : null diff --git a/src/pages/PublicNotice/ListPanel/SearchPublicNoticeForm.js b/src/pages/PublicNotice/ListPanel/SearchPublicNoticeForm.js index 6721000..446f865 100644 --- a/src/pages/PublicNotice/ListPanel/SearchPublicNoticeForm.js +++ b/src/pages/PublicNotice/ListPanel/SearchPublicNoticeForm.js @@ -138,7 +138,7 @@ const SearchPublicNoticeForm = ({ applySearch, searchCriteria, onGridReady }) => - + - + }} /> )} + clearText={intl.formatMessage({ id: "muiClear" })} + closeText={intl.formatMessage({ id: "muiClose" })} + openText={intl.formatMessage({ id: "muiOpen" })} + noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} // InputLabelProps={{ // shrink: true // }} diff --git a/src/pages/User/ChangePasswordPage/index.js b/src/pages/User/ChangePasswordPage/index.js index 24f4484..4e8cea4 100644 --- a/src/pages/User/ChangePasswordPage/index.js +++ b/src/pages/User/ChangePasswordPage/index.js @@ -241,7 +241,9 @@ const Index = () => { endAdornment:( { endAdornment:( { + const intl = useIntl(); + const params = useParams(); const navigate = useNavigate(); const [formData, setFormData] = React.useState({}) @@ -148,7 +150,8 @@ const UserMaintainPage_Individual = () => { - diff --git a/src/pages/authentication/ForgotPassword/AuthCallback/index.js b/src/pages/authentication/ForgotPassword/AuthCallback/index.js index 1b378e0..43a7d26 100644 --- a/src/pages/authentication/ForgotPassword/AuthCallback/index.js +++ b/src/pages/authentication/ForgotPassword/AuthCallback/index.js @@ -281,7 +281,9 @@ const Index = () => { endAdornment:( { endAdornment:( { endAdornment:( { endAdornment:( { endAdornment={ { endAdornment={ { endAdornment={ { @@ -118,6 +120,39 @@ const BusCustomFormWizard = (props) => { changePassword(''); }, []); + const playCaptchaAudio = async () => { + try { + 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); + + const audio = new Audio(url); + + await audio.play(); + + } catch (error) { + + let message = intl.formatMessage({ id: "captchaAudioError" }); + + if (error.response) { + if (error.response.status === 404) { + message = intl.formatMessage({ id: "captchaExpired" }); + } else if (error.response.status === 500) { + message = intl.formatMessage({ id: "captchaAudioServerError" }); + } + } else if (error.request) { + message = intl.formatMessage({ id: "connectionError" }); + } + + notifyActionError(message); + } + }; + const handleCheckUsername = async () => { if (values?.username) { const response = await axios.post(`${POST_USERNAME}`, { @@ -712,7 +747,9 @@ const BusCustomFormWizard = (props) => { endAdornment={ { endAdornment={ { }} renderInput={(params) => } + clearText={intl.formatMessage({ id: "muiClear" })} + closeText={intl.formatMessage({ id: "muiClose" })} + openText={intl.formatMessage({ id: "muiOpen" })} + noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} /> { '& .MuiOutlinedInput-root': { height: 40 } }} renderInput={(params) => } + clearText={intl.formatMessage({ id: "muiClear" })} + closeText={intl.formatMessage({ id: "muiClose" })} + openText={intl.formatMessage({ id: "muiOpen" })} + noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} /> {formik.touched.address1 && formik.errors.address1 && ( @@ -1514,6 +1561,11 @@ const BusCustomFormWizard = (props) => { {formik.errors.captchaField} )} + + + diff --git a/src/pages/authentication/auth-forms/CustomFormWizard.js b/src/pages/authentication/auth-forms/CustomFormWizard.js index 4c36244..f84767c 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 } from "utils/ApiPathConst"; +import { POST_USERNAME, POST_USER_EMAIL, POST_CAPTCHA, POST_PUBLIC_USER_REGISTER, POST_IDNO, POST_CAPTCHA_AUDIO } from "utils/ApiPathConst"; // import * as HttpUtils from 'utils/HttpUtils'; import * as ComboData from "utils/ComboData"; @@ -49,7 +49,7 @@ import { FormattedMessage, useIntl } from "react-intl"; import { Link } from 'react-router-dom'; import { PNSPS_LONG_BUTTON_THEME } from "../../../themes/buttonConst"; import * as HttpUtils from "../../../utils/HttpUtils"; - +import { notifyActionError } from 'utils/CommonFunction'; // ============================|| FIREBASE - REGISTER ||============================ // const CustomFormWizard = (props) => { @@ -137,6 +137,39 @@ const CustomFormWizard = (props) => { onCaptchaChange(); } }, []); + + const playCaptchaAudio = async () => { + try { + 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); + + const audio = new Audio(url); + + await audio.play(); + + } catch (error) { + + let message = intl.formatMessage({ id: "captchaAudioError" }); + + if (error.response) { + if (error.response.status === 404) { + message = intl.formatMessage({ id: "captchaExpired" }); + } else if (error.response.status === 500) { + message = intl.formatMessage({ id: "captchaAudioServerError" }); + } + } else if (error.request) { + message = intl.formatMessage({ id: "connectionError" }); + } + + notifyActionError(message); + } + }; useEffect(() => { if (selectedIdDocType.type === "HKID"){ @@ -945,7 +978,9 @@ const CustomFormWizard = (props) => { endAdornment={ { endAdornment={ { error={formik.touched.idDocType && (selectedIdDocType === null || selectedIdDocType?.type == null)} placeholder={intl.formatMessage({ id: 'idDocType' })} />} + clearText={intl.formatMessage({ id: "muiClear" })} + closeText={intl.formatMessage({ id: "muiClose" })} + openText={intl.formatMessage({ id: "muiOpen" })} + noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} /> {formik.touched.idDocType && ( selectedIdDocType === null || selectedIdDocType?.type == null ? @@ -1412,6 +1453,10 @@ const CustomFormWizard = (props) => { }} renderInput={(params) => } + clearText={intl.formatMessage({ id: "muiClear" })} + closeText={intl.formatMessage({ id: "muiClose" })} + openText={intl.formatMessage({ id: "muiOpen" })} + noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} /> { '& .MuiOutlinedInput-root': { height: 40 } }} renderInput={(params) => } + clearText={intl.formatMessage({ id: "muiClear" })} + closeText={intl.formatMessage({ id: "muiClose" })} + openText={intl.formatMessage({ id: "muiOpen" })} + noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} /> {formik.touched.address1 && formik.errors.address1 && ( @@ -1854,6 +1903,11 @@ const CustomFormWizard = (props) => { {formik.errors.captchaField} )} + + + diff --git a/src/pages/authentication/auth-forms/IAmSmartFormWizard.js b/src/pages/authentication/auth-forms/IAmSmartFormWizard.js index 7c4d734..105c644 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 } from "utils/ApiPathConst"; +import { POST_IAMSMART_USER_REGISTER, POST_CAPTCHA, POST_USER_EMAIL, POST_CAPTCHA_AUDIO} from "utils/ApiPathConst"; import * as ComboData from "utils/ComboData"; @@ -40,6 +40,7 @@ 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"; +import { notifyActionError } from 'utils/CommonFunction'; // ============================|| FIREBASE - REGISTER ||============================ // @@ -86,6 +87,39 @@ const CustomFormWizard = (props) => { responseToData(); }, []); + const playCaptchaAudio = async () => { + try { + 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); + + const audio = new Audio(url); + + await audio.play(); + + } catch (error) { + + let message = intl.formatMessage({ id: "captchaAudioError" }); + + if (error.response) { + if (error.response.status === 404) { + message = intl.formatMessage({ id: "captchaExpired" }); + } else if (error.response.status === 500) { + message = intl.formatMessage({ id: "captchaAudioServerError" }); + } + } else if (error.request) { + message = intl.formatMessage({ id: "connectionError" }); + } + + notifyActionError(message); + } + }; + const handleClickShowId = () => { setshowId(!showId); }; @@ -620,8 +654,12 @@ const CustomFormWizard = (props) => { '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, '& .MuiOutlinedInput-root': { height: 40 } }} - renderInput={(params) => } + clearText={intl.formatMessage({ id: "muiClear" })} + closeText={intl.formatMessage({ id: "muiClose" })} + openText={intl.formatMessage({ id: "muiOpen" })} + noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} /> { setCheckCountry(true) } }} - sx={{ - '& .MuiInputBase-root': { alignItems: 'center' }, - '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, - '& .MuiOutlinedInput-root': { height: 40 } - }} - renderInput={(params) => } + sx={{ + '& .MuiInputBase-root': { alignItems: 'center' }, + '& .MuiAutocomplete-endAdornment': { top: '50%', transform: 'translateY(-50%)' }, + '& .MuiOutlinedInput-root': { height: 40 } + }} + renderInput={(params) => } + clearText={intl.formatMessage({ id: "muiClear" })} + closeText={intl.formatMessage({ id: "muiClose" })} + openText={intl.formatMessage({ id: "muiOpen" })} + noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} /> {formik.touched.address1 && formik.errors.address1 && ( @@ -1014,6 +1056,11 @@ const CustomFormWizard = (props) => { {formik.errors.captchaField} )} + + + diff --git a/src/pages/extra-pages/PrivacyPolicyPage/PrivacyPolicy_cn.js b/src/pages/extra-pages/PrivacyPolicyPage/PrivacyPolicy_cn.js index 0f554b3..a994845 100644 --- a/src/pages/extra-pages/PrivacyPolicyPage/PrivacyPolicy_cn.js +++ b/src/pages/extra-pages/PrivacyPolicyPage/PrivacyPolicy_cn.js @@ -1,8 +1,8 @@ -const AboutUs = () => { +const Page = () => { - const content =` - + .privacy-policy p { margin: 0 0 1rem; } + .privacy-policy ol { margin: 0.5rem 0 1.6rem; padding-left: 1.4rem; } + .privacy-policy ol li { margin: 0.6rem 0; } + .privacy-policy a { text-decoration: underline; } + .privacy-policy address { font-style: normal; line-height: 1.6; margin-bottom: 1rem; } + .privacy-policy .note { font-size: 0.95rem; font-style: italic; } + -
+
-

私隐政策

+

私隐政策

保障个人资料私隐是政府物流服务署每位成员所关注的事宜。我们尊重个人资料私隐,并承诺全面执行及遵从保障资料原则及《个人资料(私隐)条例》的所有相关条文。政府物流服务署建立及实施系统监控,以确保遵从以下六项保障资料原则: @@ -75,22 +47,22 @@ const AboutUs = () => {

  • 所收集的个人资料只可用作收集资料时资料会被使用于的目的或直接有关的目的,除非有关个人已就更改使用明示同意或法律准许有关使用;
  • 采取所有切实可行的步骤,以确保个人资料受保障而不受未获准许或意外的查阅、处理、删除或使用所影响;
  • 采取所有切实可行的步骤,以确保任何人能获政府物流服务署告知所持有的个人资料的种类及资料将会为什么目的而使用的;以及
  • -
  • 准许资料当事人查阅/改正其个人资料及以法律准许或规定的方式处理任何查阅/改正个人资料的要求。
  • +
  • 准许资料当事人查阅/改正其个人资料及以法律准许或规定的方式处理任何查阅/改正个人资料的要求。
  • -

    收集个人资料声明

    +

    收集个人资料声明

    -

    收集资料的目的

    +

    收集资料的目的

    申请人在本网站内所提供的个人资料会供申请刊登公共启事及为承担法律责任用途。在本网站提供的个人资料是自愿的。如果申请人未有提供足够资料,我们可能无法处理相关公共启事的刊登事宜。

    -

    披露个人资料

    +

    披露个人资料

    政府物流服务署可能会向其他政策局/部门披露你在本网站提供的个人资料。

    -

    查阅个人资料

    +

    查阅个人资料

    如要查阅或改正本署所持有的个人资料,请向以下人士提出:

    @@ -103,14 +75,18 @@ const AboutUs = () => {

    - (注:请以个人资料私隐专员指定的表格提出查阅资料申请。) + (注:请以个人资料私隐专员指定的 + + 表格 + + 提出查阅资料申请。)

    -
    - ` - ; +
    + `; - return (
    ); + return (
    ); } - export default AboutUs; \ No newline at end of file +export default Page; \ No newline at end of file diff --git a/src/pages/extra-pages/PrivacyPolicyPage/PrivacyPolicy_en.js b/src/pages/extra-pages/PrivacyPolicyPage/PrivacyPolicy_en.js index 9e5b4f7..b6854b6 100644 --- a/src/pages/extra-pages/PrivacyPolicyPage/PrivacyPolicy_en.js +++ b/src/pages/extra-pages/PrivacyPolicyPage/PrivacyPolicy_en.js @@ -1,148 +1,137 @@ - const Page = () => { - - const content = ` - - -
    -

    Privacy Policy

    - -

    - The protection of personal data privacy is the concern of every member of Government Logistics - Department. We respect personal data privacy and are committed to fully implementing and complying - with the data protection principles and all relevant provisions of the Personal Data (Privacy) - Ordinance. We develop and implement programme controls that give effect to the six data protection - principles below: -

    - -
      -
    1. + const content = ` + + +
      +

      Privacy Policy

      + +

      + The protection of personal data privacy is the concern of every member of Government Logistics + Department. We respect personal data privacy and are committed to fully implementing and complying + with the data protection principles and all relevant provisions of the Personal Data (Privacy) + Ordinance. We develop and implement programme controls that give effect to the six data protection + principles below: +

      + +
        +
      1. collect adequate, but not excessive, personal data by lawful and fair means only for lawful purposes related to our functions or activities; -
      2. -
      3. +
      4. +
      5. take all reasonably practicable steps to ensure that the personal data collected or retained are accurate, having regard to the purposes for which they are to be used. Erase personal data no longer than necessary for the purposes for which they are to be used; -
      6. -
      7. +
      8. +
      9. use the personal data collected only for purposes or directly related purposes for which the data were to be used at the time of collection, unless the individual concerned has given express consent for a change of use or such use is permitted by law; -
      10. -
      11. +
      12. +
      13. take all reasonably practicable steps to ensure that personal data are protected against unauthorized or accidental access, processing, erasure or other use; -
      14. -
      15. +
      16. +
      17. take all reasonably practicable steps to ensure that a person can be informed of the kinds of personal data that GLD holds and the purposes for which the data are to be used; and -
      18. -
      19. +
      20. +
      21. permit persons to access and correct personal data of which they are the data subject and process any access/correction requests in a manner permitted or required by law. -
      22. -
      - -

      Personal Information Collection Statement

      - -

      Purpose of Collection

      -

      - The personal data provided by the applicant in this website would be used for application of public - notice publication and for assumption of liability. The provision of the personal data is voluntary. - If the applicant does not provide sufficient information, we may not be able to process publication - of the relevant public notice. -

      - -

      Disclosure of Personal Data

      -

      - The personal data provided in this website may be disclosed to other bureaux / departments. -

      - -

      Access to Personal Data

      -

      - Requests for access to or correction of personal data held by us should be addressed to - -

      - -
      - Data Protection Officer
      - Government Logistics Department
      - 10/F, North Point Government Offices
      - 333 Java Road North Point
      - Hong Kong -
      - -

      - (Note: Data access requests should be made on a - +

    2. +
    + +

    Personal Information Collection Statement

    + +

    Purpose of Collection

    +

    + The personal data provided by the applicant in this website would be used for application of public + notice publication and for assumption of liability. The provision of the personal data is voluntary. + If the applicant does not provide sufficient information, we may not be able to process publication + of the relevant public notice. +

    + +

    Disclosure of Personal Data

    +

    + The personal data provided in this website may be disclosed to other bureaux / departments. +

    + +

    Access to Personal Data

    +

    + Requests for access to or correction of personal data held by us should be addressed to - +

    + +
    + Data Protection Officer
    + Government Logistics Department
    + 10/F, North Point Government Offices
    + 333 Java Road North Point
    + Hong Kong +
    + +

    + (Note: Data access requests should be made on a + form - - specified by the Privacy Commissioner for Personal Data.) -

    -
    - ` - ; - - return (
    ); - -} + + specified by the Privacy Commissioner for Personal Data.) +

    +
    + `; + + return
    ; +}; export default Page; \ No newline at end of file diff --git a/src/pages/extra-pages/PrivacyPolicyPage/PrivacyPolicy_zh.js b/src/pages/extra-pages/PrivacyPolicyPage/PrivacyPolicy_zh.js index 202a132..e77b6b0 100644 --- a/src/pages/extra-pages/PrivacyPolicyPage/PrivacyPolicy_zh.js +++ b/src/pages/extra-pages/PrivacyPolicyPage/PrivacyPolicy_zh.js @@ -1,116 +1,109 @@ - -const AboutUs = () => { - - const content =` +const Page = () => { + const content = `
    -

    私隱政策

    - -

    - 保障個人資料私隱是政府物流服務署每位成員所關注的事宜。我們尊重個人資料私隱,並承諾全面執行及遵從保障資料原則及《個人資料(私隱)條例》的所有相關條文。政府物流服務署建立及實施系統監控,以確保遵從以下六項保障資料原則 - -

    - -
      -
    1. 以合法和公平的方法收集足夠但不超乎適度的個人資料,只供用於與政府物流服務署的職能或活動有關的合法目的;
    2. -
    3. 採取所有切實可行的步驟,以確保在顧及有關的個人資料會被使用於的目的下,所收集或保留的個人資料是準確的。刪除就使用目的而言不再屬有需要的個人資料;
    4. -
    5. 所收集的個人資料只可用作收集資料時資料會被使用於的目的或直接有關的目的,除非有關個人已就更改使用明示同意或法律准許有關使用;
    6. -
    7. 採取所有切實可行的步驟,以確保個人資料受保障而不受未獲准許或意外的查閱、處理、刪除或使用所影響;
    8. -
    9. 採取所有切實可行的步驟,以確保任何人能獲政府物流服務署告知所持有的個人資料的種類及資料將會為甚麼目的而使用的;以及
    10. -
    11. 准許資料當事人查閱/改正其個人資料及以法律准許或規定的方式處理任何查閱/改正個人資料的要求。
    12. -
    - -

    收集個人資料聲明

    - -

    收集資料的目的

    -

    - 申請人在本網站內所提供的個人資料會供申請刊登公共啟事及為承擔法律責任用途。在本網站提供的個人資料是自願的。如果申請人未有提供足夠資料,我們可能無法處理相關公共啟事的刊登事宜。 -

    - -

    披露個人資料

    -

    - 政府物流服務署可能會向其他政策局/部門披露你在本網站提供的個人資料。 -

    - -

    查閱個人資料

    -

    - 如要查閱或改正本署所持有的個人資料,請向以下人士提出: -

    - -
    - 香港北角渣華道333號
    - 北角政府合署10樓
    - 政府物流服務署
    - 保障資料主任 -
    - -

    - (註:請以個人資料私隱專員指定的表格提出查閱資料要求。) -

    +

    私隱政策

    + +

    + 保障個人資料私隱是政府物流服務署每位成員所關注的事宜。我們尊重個人資料私隱,並承諾全面執行及遵從保障資料原則及《個人資料(私隱)條例》的所有相關條文。政府物流服務署建立及實施系統監控,以確保遵從以下六項保障資料原則 - +

    + +
      +
    1. 以合法和公平的方法收集足夠但不超乎適度的個人資料,只供用於與政府物流服務署的職能或活動有關的合法目的;
    2. +
    3. 採取所有切實可行的步驟,以確保在顧及有關的個人資料會被使用於的目的下,所收集或保留的個人資料是準確的。刪除就使用目的而言不再屬有需要的個人資料;
    4. +
    5. 所收集的個人資料只可用作收集資料時資料會被使用於的目的或直接有關的目的,除非有關個人已就更改使用明示同意或法律准許有關使用;
    6. +
    7. 採取所有切實可行的步驟,以確保個人資料受保障而不受未獲准許或意外的查閱、處理、刪除或使用所影響;
    8. +
    9. 採取所有切實可行的步驟,以確保任何人能獲政府物流服務署告知所持有的個人資料的種類及資料將會為甚麼目的而使用的;以及
    10. +
    11. 准許資料當事人查閱/改正其個人資料及以法律准許或規定的方式處理任何查閱/改正個人資料的要求。
    12. +
    + +

    收集個人資料聲明

    + +

    收集資料的目的

    +

    + 申請人在本網站內所提供的個人資料會供申請刊登公共啟事及為承擔法律責任用途。在本網站提供的個人資料是自願的。如果申請人未有提供足夠資料,我們可能無法處理相關公共啟事的刊登事宜。 +

    + +

    披露個人資料

    +

    + 政府物流服務署可能會向其他政策局/部門披露你在本網站提供的個人資料。 +

    + +

    查閱個人資料

    +

    + 如要查閱或改正本署所持有的個人資料,請向以下人士提出: +

    + +
    + 香港北角渣華道333號
    + 北角政府合署10樓
    + 政府物流服務署
    + 保障資料主任 +
    + +

    + (註:請以個人資料私隱專員指定的 + + 表格 + + 提出查閱資料要求。) +

    - ` - ; - - return (
    ); +`; -} + return
    ; +}; - export default AboutUs; \ No newline at end of file +export default Page; \ No newline at end of file diff --git a/src/translations/en.json b/src/translations/en.json index 745e9c5..be58bcd 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -610,5 +610,21 @@ "Dashboard": "Dashboard", "event": "Event", "lgce_alt": "2025 Legislative Council General Election", - "lgce_title": "2025 Legislative Council General Election" + "lgce_title": "2025 Legislative Council General Election", + + "ariaShowPassword": "Show password", + "ariaHidePassword": "Hide password", + + "captchaAudioError": "Unable to play audio CAPTCHA.", + "captchaAudioServerError": "Audio CAPTCHA is temporarily unavailable. Please refresh the CAPTCHA.", + "captchaExpired": "CAPTCHA expired. Please refresh and try again.", + "connectionError": "Connection error. Please try again.", + + "muiClear": "Clear", + "muiClose": "Close", + "muiOpen": "Open", + "muiNoOptions": "No options", + + "paginationPrev": "Go to previous page", + "paginationNext": "Go to next page" } \ No newline at end of file diff --git a/src/translations/zh-CN.json b/src/translations/zh-CN.json index 76513b0..7f19d12 100644 --- a/src/translations/zh-CN.json +++ b/src/translations/zh-CN.json @@ -606,5 +606,21 @@ "Dashboard": "仪表板", "event": "活动", "lgce_alt": "2025年立法会换届选举", - "lgce_title": "2025年立法会换届选举" + "lgce_title": "2025年立法会换届选举", + + "ariaShowPassword": "显示密码", + "ariaHidePassword": "隐藏密码", + + "captchaAudioError": "无法播放语音验证码。", + "captchaAudioServerError": "语音验证码暂时不可用,请刷新验证码。", + "captchaExpired": "验证码已过期,请刷新后再试。", + "connectionError": "连接错误,请稍后再试。", + + "muiClear": "清除", + "muiClose": "关闭", + "muiOpen": "打开", + "muiNoOptions": "没有选项", + + "paginationPrev": "上一页", + "paginationNext": "下一页" } \ No newline at end of file diff --git a/src/translations/zh-HK.json b/src/translations/zh-HK.json index 341a52e..195eb60 100644 --- a/src/translations/zh-HK.json +++ b/src/translations/zh-HK.json @@ -607,5 +607,21 @@ "Dashboard": "儀表板", "event": "活動", "lgce_alt": "2025年立法會換屆選舉", - "lgce_title": "2025年立法會換屆選舉" + "lgce_title": "2025年立法會換屆選舉", + + "ariaShowPassword": "顯示密碼", + "ariaHidePassword": "隱藏密碼", + + "captchaAudioError": "無法播放語音驗證碼。", + "captchaAudioServerError": "語音驗證碼暫時無法使用,請重新整理驗證碼。", + "captchaExpired": "驗證碼已過期,請重新整理並再試。", + "connectionError": "連線錯誤,請稍後再試。", + + "muiClear": "清除", + "muiClose": "關閉", + "muiOpen": "開啟", + "muiNoOptions": "沒有選項", + + "paginationPrev": "上一頁", + "paginationNext": "下一頁" } \ No newline at end of file diff --git a/src/utils/Combo.js b/src/utils/Combo.js index 484d01a..6a04200 100644 --- a/src/utils/Combo.js +++ b/src/utils/Combo.js @@ -1,5 +1,6 @@ import { Autocomplete, TextField } from '@mui/material'; import { useEffect, useState } from 'react'; +import {useIntl } from "react-intl"; export default function Combo({ valueName, @@ -14,6 +15,7 @@ export default function Combo({ ...props }) { const formValue = form.values[valueName] ?? null; + const intl = useIntl(); const [inputValue, setInputValue] = useState(""); @@ -84,6 +86,10 @@ export default function Combo({ }} /> )} + clearText={intl.formatMessage({ id: "muiClear" })} + closeText={intl.formatMessage({ id: "muiClose" })} + openText={intl.formatMessage({ id: "muiOpen" })} + noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} /> ); } diff --git a/src/utils/DateUtils.js b/src/utils/DateUtils.js index 5877593..c8af554 100644 --- a/src/utils/DateUtils.js +++ b/src/utils/DateUtils.js @@ -1,4 +1,6 @@ import dayjs from 'dayjs'; +import "dayjs/locale/zh-hk"; +import "dayjs/locale/zh-cn"; var days_ZH = ['星期日','星期一','星期二','星期三','星期四','星期五','星期六']; var days_CN = ['星期日','星期一','星期二','星期三','星期四','星期五','星期六']; @@ -156,3 +158,51 @@ export const getWeekdayStr_CN = (date) =>{ if(date) return days_CN[date.getDay()] return ""; }; + +const formatPickerAriaDate = (dateStr, locale) => { + if (!dateStr) return ""; + + // MUI may pass a full Date string; parse via native Date first + const dNative = new Date(dateStr); + const d = isNaN(dNative.getTime()) ? null : dNative; + + if (!d) return dateStr; // fallback if parsing fails + + // Format only the date part (ignore time/GMT) + const dj = dayjs(d); + + if (locale === "zh-HK" || locale === "zh") { + return dj.locale("zh-hk").format("YYYY年MM月DD日"); + } + if (locale === "zh-CN") { + return dj.locale("zh-cn").format("YYYY年MM月DD日"); + } + return dj.locale("en").format("DD MMM YYYY"); +}; + +export const getPickerLocaleText = (locale) => { + if (locale === "zh-HK" || locale === "zh") { + return { + openDatePickerDialogue: (dateStr) => { + const s = formatPickerAriaDate(dateStr, locale); + return s ? `選擇日期,已選日期為 ${s}` : "選擇日期"; + } + }; + } + + if (locale === "zh-CN") { + return { + openDatePickerDialogue: (dateStr) => { + const s = formatPickerAriaDate(dateStr, locale); + return s ? `选择日期,已选日期为 ${s}` : "选择日期"; + } + }; + } + + return { + openDatePickerDialogue: (dateStr) => { + const s = formatPickerAriaDate(dateStr, locale); + return s ? `Choose date, selected date is ${s}` : "Choose date"; + } + }; +}; diff --git a/src/utils/SelectBase.js b/src/utils/SelectBase.js index 806ff0d..ef47b2c 100644 --- a/src/utils/SelectBase.js +++ b/src/utils/SelectBase.js @@ -3,10 +3,12 @@ import { } from '@mui/material'; import * as React from 'react'; import Select, { SelectChangeEvent } from '@mui/material/Select'; +import {useIntl } from "react-intl"; export default function Combo ({valueName, disabled, form, dataList, filterOptions, getOptionLabel, isOptionEqualToValue, onInputChange, onChange, ...props}){ const [value, setValue] = React.useState(form.values[valueName]); const [inputValue, setInputValue] = React.useState(""); + const intl = useIntl(); return ( } + }}/>} + clearText={intl.formatMessage({ id: "muiClear" })} + closeText={intl.formatMessage({ id: "muiClose" })} + openText={intl.formatMessage({ id: "muiOpen" })} + noOptionsText={intl.formatMessage({ id: "muiNoOptions" })} /> ); } \ No newline at end of file