| @@ -1,13 +1,15 @@ | |||
| import PropTypes from 'prop-types'; | |||
| import React, { useContext, useEffect, useMemo, useState } from 'react'; | |||
| import { Box, Grid, Typography, Dialog, DialogContent, IconButton } from '@mui/material'; | |||
| import CloseIcon from '@mui/icons-material/Close'; | |||
| import Loadable from 'components/Loadable'; | |||
| import { lazy, useState } from 'react'; | |||
| import { FormattedMessage, useIntl } from "react-intl"; | |||
| import { lazy } from 'react'; | |||
| import { FormattedMessage, useIntl } from 'react-intl'; | |||
| import { checkSysEnv } from "utils/Utils"; | |||
| import backbroundImg from 'assets/images/bg_ml.jpg'; | |||
| import lgceImg from 'assets/images/2025_lgce.jpg'; // <-- your popup image | |||
| import 'assets/style/loginStyles.css'; | |||
| import { SysContext } from 'components/SysSettingProvider'; | |||
| const AuthCard = Loadable(lazy(() => import('./AuthCardCustom'))); | |||
| @@ -18,23 +20,72 @@ const BackgroundHead = { | |||
| backgroundSize: 'cover' | |||
| }; | |||
| const parseToDate = (v) => { | |||
| if (v == null || v === "") return null; | |||
| // timestamp: 10 digits (sec) or 13 digits (ms) | |||
| const s = String(v).trim(); | |||
| if (/^\d{10,13}$/.test(s)) { | |||
| const n = Number(s); | |||
| return new Date(s.length === 10 ? n * 1000 : n); | |||
| } | |||
| // date-only: YYYY-MM-DD -> treat as LOCAL midnight | |||
| const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s); | |||
| if (m) { | |||
| const y = Number(m[1]); | |||
| const mo = Number(m[2]) - 1; // 0-based | |||
| const d = Number(m[3]); | |||
| return new Date(y, mo, d, 0, 0, 0, 0); | |||
| } | |||
| // otherwise: ISO datetime or other formats | |||
| const d = new Date(s); | |||
| return Number.isNaN(d.getTime()) ? null : d; | |||
| }; | |||
| const AuthWrapper = ({ children }) => { | |||
| const intl = useIntl(); | |||
| const { sysSetting } = useContext(SysContext); | |||
| // --- Date control --- | |||
| // ===== Popup #1 (image popup) ===== | |||
| const today = new Date(); | |||
| const showUntil = new Date("2025-11-27T00:00:00"); // 8 Dec 2025 and onwards = hide popup | |||
| const [openPopup, setOpenPopup] = useState(today < showUntil); | |||
| const showUntil = new Date('2025-11-27T00:00:00'); | |||
| const [openImagePopup, setOpenImagePopup] = useState(today < showUntil); | |||
| const handleCloseImagePopup = () => setOpenImagePopup(false); | |||
| // ===== Popup #2 (system popup) ===== | |||
| const [openNoticePopup, setOpenNoticePopup] = useState(false); | |||
| const handleClosePopup = () => { | |||
| setOpenPopup(false); | |||
| const popupStart = useMemo(() => parseToDate(sysSetting?.loginPopupStart), [sysSetting?.loginPopupStart]); | |||
| const popupEnd = useMemo(() => parseToDate(sysSetting?.loginPopupEnd), [sysSetting?.loginPopupEnd]); | |||
| const popupHtml = useMemo(() => { | |||
| return intl.formatMessage({ id: 'loginPopupHtml', defaultMessage: '' }) || ''; | |||
| }, [intl]); | |||
| useEffect(() => { | |||
| const now = new Date(); | |||
| const inRange = | |||
| (popupStart ? now >= popupStart : true) && | |||
| (popupEnd ? now <= popupEnd : true); | |||
| const hasMessage = popupHtml && popupHtml.trim().length > 0; | |||
| setOpenNoticePopup(Boolean(hasMessage && inRange)); | |||
| }, [popupHtml, popupStart, popupEnd]); | |||
| const handleCloseNoticePopup = () => { | |||
| setOpenNoticePopup(false); | |||
| }; | |||
| return ( | |||
| <Box sx={{ minHeight: '87vh' }}> | |||
| <Dialog | |||
| open={openPopup} | |||
| onClose={handleClosePopup} | |||
| open={openImagePopup} | |||
| onClose={handleCloseImagePopup} | |||
| aria-labelledby="election-promo-title" | |||
| maxWidth="md" | |||
| PaperProps={{ | |||
| @@ -48,7 +99,7 @@ const AuthWrapper = ({ children }) => { | |||
| <Box sx={{ position: 'relative' }}> | |||
| <IconButton | |||
| aria-label="Close" | |||
| onClick={handleClosePopup} | |||
| onClick={handleCloseImagePopup} | |||
| sx={{ | |||
| position: 'absolute', | |||
| top: 6, | |||
| @@ -60,24 +111,74 @@ const AuthWrapper = ({ children }) => { | |||
| > | |||
| <CloseIcon /> | |||
| </IconButton> | |||
| <DialogContent sx={{ p: 0 }}> | |||
| <Box | |||
| component="img" | |||
| src={lgceImg} | |||
| alt={intl.formatMessage({ id: 'lgce_alt', defaultMessage: '2025 Legislative Council General Election' })} | |||
| title={intl.formatMessage({ id: 'lgce_title', defaultMessage: '2025 Legislative Council General Election' })} | |||
| sx={{ | |||
| display: 'block', | |||
| width: '100%', | |||
| maxWidth: '720px', | |||
| height: 'auto' | |||
| }} | |||
| sx={{ display: 'block', width: '100%', maxWidth: '720px', height: 'auto' }} | |||
| /> | |||
| </DialogContent> | |||
| </Box> | |||
| </Dialog> | |||
| {/* Page content */} | |||
| {/* ========================= | |||
| Popup #2: system popup | |||
| ========================= */} | |||
| <Dialog | |||
| open={openNoticePopup} | |||
| onClose={handleCloseNoticePopup} | |||
| maxWidth="md" | |||
| PaperProps={{ | |||
| sx: { | |||
| borderRadius: 1, | |||
| overflow: 'hidden', | |||
| width: { xs: '95vw', md: 900 }, | |||
| maxWidth: '95vw', | |||
| boxShadow: 10, | |||
| border: '1px solid #0c489e' | |||
| } | |||
| }} | |||
| > | |||
| <IconButton | |||
| aria-label="Close" | |||
| onClick={handleCloseNoticePopup} | |||
| sx={{ | |||
| position: 'absolute', | |||
| top: 8, | |||
| right: 8, | |||
| zIndex: 10, | |||
| width: 34, | |||
| height: 34, | |||
| border: '1px solid rgba(0,0,0,0.5)', | |||
| borderRadius: 0, | |||
| bgcolor: '#fff' | |||
| }} | |||
| > | |||
| <CloseIcon /> | |||
| </IconButton> | |||
| <DialogContent | |||
| sx={{ | |||
| bgcolor: '#d9ecff', | |||
| p: 3, | |||
| maxHeight: { xs: '70vh', md: '60vh' }, | |||
| overflowY: 'auto', | |||
| '& h1, & h2, & h3': { marginTop: 0, marginBottom: '12px' }, | |||
| '& p': { marginBottom: '12px', lineHeight: 1.7 } | |||
| }} | |||
| > | |||
| <Typography component="div" sx={{ fontSize: 18, lineHeight: 1.8 }}> | |||
| <div dangerouslySetInnerHTML={{ __html: popupHtml }} /> | |||
| </Typography> | |||
| </DialogContent> | |||
| </Dialog> | |||
| {/* ========================= | |||
| Page content | |||
| ========================= */} | |||
| <div style={BackgroundHead}> | |||
| <Grid | |||
| container | |||
| @@ -111,21 +212,21 @@ const AuthWrapper = ({ children }) => { | |||
| sx={{ minHeight: { md: 'calc(50vh)' } }} | |||
| > | |||
| <Grid item xs={12} sx={{ ml: 4, mt: 12, display: { xs: 'none', sm: 'block' } }}> | |||
| <Typography style={{ textAlign: "center", fontSize: "1.8rem" }}> | |||
| <Typography style={{ textAlign: 'center', fontSize: '1.8rem' }}> | |||
| <FormattedMessage id="HKSARGOV" /> | |||
| </Typography> | |||
| <Typography style={{ textAlign: "center", fontSize: "1.8rem" }}> | |||
| <Typography style={{ textAlign: 'center', fontSize: '1.8rem' }}> | |||
| <FormattedMessage id="Gazette" /> | |||
| </Typography> | |||
| <Typography style={{ textAlign: "center", fontSize: "1.8rem" }}> | |||
| <Typography style={{ textAlign: 'center', fontSize: '1.8rem' }}> | |||
| <FormattedMessage id="PNSPS_fullname" /> | |||
| </Typography> | |||
| {checkSysEnv() !== '' ? ( | |||
| <Typography style={{ color: 'red', textAlign: "center", fontSize: "1.8rem" }}> | |||
| <Typography style={{ color: 'red', textAlign: 'center', fontSize: '1.8rem' }}> | |||
| User Acceptance Test Environment | |||
| </Typography> | |||
| ) : ( | |||
| "" | |||
| '' | |||
| )} | |||
| </Grid> | |||
| </Grid> | |||