You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

258 lines
8.1 KiB

  1. import PropTypes from 'prop-types';
  2. import React, { useContext, useEffect, useMemo, useState } from 'react';
  3. import { Box, Grid, Typography, Dialog, DialogContent, IconButton } from '@mui/material';
  4. import CloseIcon from '@mui/icons-material/Close';
  5. import Loadable from 'components/Loadable';
  6. import { lazy } from 'react';
  7. import { FormattedMessage, useIntl } from 'react-intl';
  8. import { checkSysEnv } from "utils/Utils";
  9. import backbroundImg from 'assets/images/bg_ml.jpg';
  10. import lgceImg from 'assets/images/2025_lgce.jpg'; // <-- your popup image
  11. import 'assets/style/loginStyles.css';
  12. import { SysContext } from 'components/SysSettingProvider';
  13. const AuthCard = Loadable(lazy(() => import('./AuthCardCustom')));
  14. const BackgroundHead = {
  15. backgroundImage: `url(${backbroundImg})`,
  16. width: '100%',
  17. height: '100%',
  18. backgroundSize: 'cover'
  19. };
  20. const parseToDate = (v) => {
  21. if (v == null || v === "") return null;
  22. // timestamp: 10 digits (sec) or 13 digits (ms)
  23. const s = String(v).trim();
  24. if (/^\d{10,13}$/.test(s)) {
  25. const n = Number(s);
  26. return new Date(s.length === 10 ? n * 1000 : n);
  27. }
  28. // date-only: YYYY-MM-DD -> treat as LOCAL midnight
  29. const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s);
  30. if (m) {
  31. const y = Number(m[1]);
  32. const mo = Number(m[2]) - 1; // 0-based
  33. const d = Number(m[3]);
  34. return new Date(y, mo, d, 0, 0, 0, 0);
  35. }
  36. // otherwise: ISO datetime or other formats
  37. const d = new Date(s);
  38. return Number.isNaN(d.getTime()) ? null : d;
  39. };
  40. const AuthWrapper = ({ children }) => {
  41. const intl = useIntl();
  42. const { sysSetting } = useContext(SysContext);
  43. // ===== Popup #1 (image popup) =====
  44. const today = new Date();
  45. const showUntil = new Date('2025-11-27T00:00:00');
  46. const [openImagePopup, setOpenImagePopup] = useState(today < showUntil);
  47. const handleCloseImagePopup = () => setOpenImagePopup(false);
  48. // ===== Popup #2 (system popup) =====
  49. const [openNoticePopup, setOpenNoticePopup] = useState(false);
  50. const popupStart = useMemo(() => parseToDate(sysSetting?.loginPopupStart), [sysSetting?.loginPopupStart]);
  51. const popupEnd = useMemo(() => parseToDate(sysSetting?.loginPopupEnd), [sysSetting?.loginPopupEnd]);
  52. const popupHtml = useMemo(() => {
  53. return intl.formatMessage({ id: 'loginPopupHtml', defaultMessage: '' }) || '';
  54. }, [intl]);
  55. useEffect(() => {
  56. const now = new Date();
  57. const inRange =
  58. (popupStart ? now >= popupStart : true) &&
  59. (popupEnd ? now <= popupEnd : true);
  60. const hasMessage = popupHtml && popupHtml.trim().length > 0;
  61. setOpenNoticePopup(Boolean(hasMessage && inRange));
  62. }, [popupHtml, popupStart, popupEnd]);
  63. const handleCloseNoticePopup = () => {
  64. setOpenNoticePopup(false);
  65. };
  66. return (
  67. <Box sx={{ minHeight: '87vh' }}>
  68. <Dialog
  69. open={openImagePopup}
  70. onClose={handleCloseImagePopup}
  71. aria-labelledby="election-promo-title"
  72. maxWidth="md"
  73. PaperProps={{
  74. sx: {
  75. borderRadius: 2,
  76. overflow: 'hidden',
  77. boxShadow: 6
  78. }
  79. }}
  80. >
  81. <Box sx={{ position: 'relative' }}>
  82. <IconButton
  83. aria-label="Close"
  84. onClick={handleCloseImagePopup}
  85. sx={{
  86. position: 'absolute',
  87. top: 6,
  88. right: 6,
  89. zIndex: 1,
  90. bgcolor: 'rgba(255,255,255,0.8)',
  91. '&:hover': { bgcolor: 'rgba(255,255,255,1)' }
  92. }}
  93. >
  94. <CloseIcon />
  95. </IconButton>
  96. <DialogContent sx={{ p: 0 }}>
  97. <Box
  98. component="img"
  99. src={lgceImg}
  100. alt={intl.formatMessage({ id: 'lgce_alt', defaultMessage: '2025 Legislative Council General Election' })}
  101. title={intl.formatMessage({ id: 'lgce_title', defaultMessage: '2025 Legislative Council General Election' })}
  102. sx={{ display: 'block', width: '100%', maxWidth: '720px', height: 'auto' }}
  103. />
  104. </DialogContent>
  105. </Box>
  106. </Dialog>
  107. {/* =========================
  108. Popup #2: system popup
  109. ========================= */}
  110. <Dialog
  111. open={openNoticePopup}
  112. onClose={handleCloseNoticePopup}
  113. maxWidth="md"
  114. PaperProps={{
  115. sx: {
  116. borderRadius: 1,
  117. overflow: 'hidden',
  118. width: { xs: '95vw', md: 900 },
  119. maxWidth: '95vw',
  120. boxShadow: 10,
  121. border: '1px solid #0c489e'
  122. }
  123. }}
  124. >
  125. <IconButton
  126. aria-label="Close"
  127. onClick={handleCloseNoticePopup}
  128. sx={{
  129. position: 'absolute',
  130. top: 8,
  131. right: 8,
  132. zIndex: 10,
  133. width: 34,
  134. height: 34,
  135. border: '1px solid rgba(0,0,0,0.5)',
  136. borderRadius: 0,
  137. bgcolor: '#fff'
  138. }}
  139. >
  140. <CloseIcon />
  141. </IconButton>
  142. <DialogContent
  143. sx={{
  144. bgcolor: '#d9ecff',
  145. p: 3,
  146. maxHeight: { xs: '70vh', md: '60vh' },
  147. overflowY: 'auto',
  148. '& h1, & h2, & h3': { marginTop: 0, marginBottom: '12px' },
  149. '& p': { marginBottom: '12px', lineHeight: 1.7 }
  150. }}
  151. >
  152. <Typography component="div" sx={{ fontSize: 18, lineHeight: 1.8 }}>
  153. <div dangerouslySetInnerHTML={{ __html: popupHtml }} />
  154. </Typography>
  155. </DialogContent>
  156. </Dialog>
  157. {/* =========================
  158. Page content
  159. ========================= */}
  160. <div style={BackgroundHead}>
  161. <Grid
  162. container
  163. direction="row"
  164. justifyContent="space-between"
  165. alignItems="center"
  166. sx={{ minHeight: '87vh' }}
  167. >
  168. <Grid item xs={12} md={8} lg={8} xl={8}>
  169. <Grid
  170. container
  171. direction="column"
  172. justifyContent="flex-start"
  173. alignItems="flex-start"
  174. sx={{ minHeight: { md: 'calc(87vh)' } }}
  175. >
  176. <Grid item xs={12} sx={{ ml: 4 }}>
  177. <Typography style={{ textAlign: "flex-start" }}>
  178. <div
  179. style={{ padding: 12 }}
  180. dangerouslySetInnerHTML={{ __html: intl.formatMessage({ id: "homePageHeaderMessage" }) }}
  181. />
  182. </Typography>
  183. </Grid>
  184. <Grid
  185. container
  186. direction="column"
  187. justifyContent="flex-start"
  188. alignItems="center"
  189. spacing={2}
  190. sx={{ minHeight: { md: 'calc(50vh)' } }}
  191. >
  192. <Grid item xs={12} sx={{ ml: 4, mt: 12, display: { xs: 'none', sm: 'block' } }}>
  193. <Typography style={{ textAlign: 'center', fontSize: '1.8rem' }}>
  194. <FormattedMessage id="HKSARGOV" />
  195. </Typography>
  196. <Typography style={{ textAlign: 'center', fontSize: '1.8rem' }}>
  197. <FormattedMessage id="Gazette" />
  198. </Typography>
  199. <Typography style={{ textAlign: 'center', fontSize: '1.8rem' }}>
  200. <FormattedMessage id="PNSPS_fullname" />
  201. </Typography>
  202. {checkSysEnv() !== '' ? (
  203. <Typography style={{ color: 'red', textAlign: 'center', fontSize: '1.8rem' }}>
  204. User Acceptance Test Environment
  205. </Typography>
  206. ) : (
  207. ''
  208. )}
  209. </Grid>
  210. </Grid>
  211. </Grid>
  212. </Grid>
  213. <Grid item xs={12} md={4} lg={4} xl={4}>
  214. <Grid
  215. container
  216. justifyContent="center"
  217. alignItems="center"
  218. sx={{ minHeight: { xs: 'calc(90vh - 134px)', md: 'calc(90vh - 112px)' } }}
  219. >
  220. <Grid item xs={12} md={11} lg={11} xl={11}>
  221. <AuthCard>{children}</AuthCard>
  222. </Grid>
  223. </Grid>
  224. </Grid>
  225. </Grid>
  226. </div>
  227. </Box>
  228. );
  229. };
  230. AuthWrapper.propTypes = {
  231. children: PropTypes.node
  232. };
  233. export default AuthWrapper;