| @@ -32,6 +32,7 @@ | |||||
| "axios": "^1.12.2", | "axios": "^1.12.2", | ||||
| "date-fns": "^3.0.6", | "date-fns": "^3.0.6", | ||||
| "dayjs": "^1.11.9", | "dayjs": "^1.11.9", | ||||
| "dompurify": "^3.4.15", | |||||
| "formik": "^2.2.9", | "formik": "^2.2.9", | ||||
| "framer-motion": "^7.3.6", | "framer-motion": "^7.3.6", | ||||
| "history": "^5.3.0", | "history": "^5.3.0", | ||||
| @@ -8419,6 +8420,15 @@ | |||||
| } | } | ||||
| ] | ] | ||||
| }, | }, | ||||
| "node_modules/dompurify": { | |||||
| "version": "3.4.15", | |||||
| "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.15.tgz", | |||||
| "integrity": "sha512-EUBjM+B+lkDE41iE82DDSCfkoPGfXx8IxFxPMjNzm/Uk4xDet77rTN9wqlxlVg71kK7XGuUMv6wUxJUwwv+Xyw==", | |||||
| "license": "(MPL-2.0 OR Apache-2.0)", | |||||
| "optionalDependencies": { | |||||
| "@types/trusted-types": "^2.0.7" | |||||
| } | |||||
| }, | |||||
| "node_modules/domutils": { | "node_modules/domutils": { | ||||
| "version": "1.7.0", | "version": "1.7.0", | ||||
| "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", | "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", | ||||
| @@ -28,6 +28,7 @@ | |||||
| "axios": "^1.12.2", | "axios": "^1.12.2", | ||||
| "date-fns": "^3.0.6", | "date-fns": "^3.0.6", | ||||
| "dayjs": "^1.11.9", | "dayjs": "^1.11.9", | ||||
| "dompurify": "^3.4.15", | |||||
| "formik": "^2.2.9", | "formik": "^2.2.9", | ||||
| "framer-motion": "^7.3.6", | "framer-motion": "^7.3.6", | ||||
| "history": "^5.3.0", | "history": "^5.3.0", | ||||
| @@ -0,0 +1,17 @@ | |||||
| import PropTypes from 'prop-types'; | |||||
| import { sanitizeHtml } from 'utils/sanitizeHtml'; | |||||
| /** | |||||
| * Renders HTML after {@link sanitizeHtml}. Use instead of raw | |||||
| * dangerouslySetInnerHTML for messages, settings, CMS, and i18n HTML. | |||||
| */ | |||||
| const SafeHtml = ({ html, component: Component = 'div', ...rest }) => { | |||||
| return <Component {...rest} dangerouslySetInnerHTML={{ __html: sanitizeHtml(html) }} />; | |||||
| }; | |||||
| SafeHtml.propTypes = { | |||||
| html: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), | |||||
| component: PropTypes.elementType | |||||
| }; | |||||
| export default SafeHtml; | |||||
| @@ -19,6 +19,7 @@ import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | |||||
| import { FormattedMessage } from "react-intl"; | import { FormattedMessage } from "react-intl"; | ||||
| import usePageTitle from 'components/usePageTitle'; | import usePageTitle from 'components/usePageTitle'; | ||||
| import { PRIMARY_CONTAINED_BUTTON_SX } from 'themes/colorConst'; | import { PRIMARY_CONTAINED_BUTTON_SX } from 'themes/colorConst'; | ||||
| import SafeHtml from 'components/SafeHtml'; | |||||
| const BackgroundHead = { | const BackgroundHead = { | ||||
| backgroundImage: `url(${titleBackgroundImg})`, | backgroundImage: `url(${titleBackgroundImg})`, | ||||
| @@ -93,7 +94,7 @@ const Index = () => { | |||||
| </Typography> | </Typography> | ||||
| <Typography sx={{p:1}} align="justify">{DateUtils.datetimeStr(record?.sentDate)}</Typography> | <Typography sx={{p:1}} align="justify">{DateUtils.datetimeStr(record?.sentDate)}</Typography> | ||||
| <Typography component="div" variant="body1" sx={{ p: 4, textAlign: "left", bgcolor: "#f8f8f8" }} align="justify"> | <Typography component="div" variant="body1" sx={{ p: 4, textAlign: "left", bgcolor: "#f8f8f8" }} align="justify"> | ||||
| <div dangerouslySetInnerHTML={{ __html: record?.content }} /> | |||||
| <SafeHtml html={record?.content} /> | |||||
| </Typography> | </Typography> | ||||
| <Typography component="h3" variant="h4" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "center" }}> | <Typography component="h3" variant="h4" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "center" }}> | ||||
| @@ -25,6 +25,7 @@ import Loadable from 'components/Loadable'; | |||||
| const LoadingComponent = Loadable(React.lazy(() => import('pages/extra-pages/LoadingComponent'))); | const LoadingComponent = Loadable(React.lazy(() => import('pages/extra-pages/LoadingComponent'))); | ||||
| import * as FormatUtils from "utils/FormatUtils"; | import * as FormatUtils from "utils/FormatUtils"; | ||||
| import SafeHtml from 'components/SafeHtml'; | |||||
| import VisaIcon from "assets/images/icons/visacard.svg"; | import VisaIcon from "assets/images/icons/visacard.svg"; | ||||
| import MasterIcon from "assets/images/icons/mastercard.svg"; | import MasterIcon from "assets/images/icons/mastercard.svg"; | ||||
| import JcbIcon from "assets/images/icons/jcb.svg"; | import JcbIcon from "assets/images/icons/jcb.svg"; | ||||
| @@ -500,7 +501,7 @@ const MultiPaymentWindow = (props) => { | |||||
| <Typography variant="h4" style={{ paddingLeft: '24px' }}><FormattedMessage id="MSG.actionFail" /></Typography> | <Typography variant="h4" style={{ paddingLeft: '24px' }}><FormattedMessage id="MSG.actionFail" /></Typography> | ||||
| <DialogContent style={{ display: 'flex', }}> | <DialogContent style={{ display: 'flex', }}> | ||||
| <Stack direction="column" justifyContent="space-between"> | <Stack direction="column" justifyContent="space-between"> | ||||
| <div dangerouslySetInnerHTML={{ __html: intl.formatMessage({ id: 'MSG.paymentHolded' }, { appNo: paymentHoldedErrText }) }} /> | |||||
| <SafeHtml html={intl.formatMessage({ id: 'MSG.paymentHolded' }, { appNo: paymentHoldedErrText })} /> | |||||
| </Stack> | </Stack> | ||||
| </DialogContent> | </DialogContent> | ||||
| <DialogActions> | <DialogActions> | ||||
| @@ -28,6 +28,7 @@ const BackgroundHead = { | |||||
| import { PNSPS_BUTTON_THEME, PNSPS_LONG_BUTTON_THEME } from "../../../themes/buttonConst"; | import { PNSPS_BUTTON_THEME, PNSPS_LONG_BUTTON_THEME } from "../../../themes/buttonConst"; | ||||
| import { ThemeProvider } from "@emotion/react"; | import { ThemeProvider } from "@emotion/react"; | ||||
| import { FormattedMessage, useIntl } from "react-intl"; | import { FormattedMessage, useIntl } from "react-intl"; | ||||
| import SafeHtml from 'components/SafeHtml'; | |||||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | // ==============================|| DASHBOARD - DEFAULT ||============================== // | ||||
| const Index = ({ record }) => { | const Index = ({ record }) => { | ||||
| @@ -107,7 +108,7 @@ const Index = ({ record }) => { | |||||
| </Typography> | </Typography> | ||||
| <Typography variant="h4" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "left" }}> | <Typography variant="h4" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "left" }}> | ||||
| <div dangerouslySetInnerHTML={{ __html: intl.formatMessage({ id: 'MSG.payMsg1' }, { appNo: record.appNo }) }} /> | |||||
| <SafeHtml html={intl.formatMessage({ id: 'MSG.payMsg1' }, { appNo: record.appNo })} /> | |||||
| <br /> | <br /> | ||||
| <FormattedMessage id="MSG.payMsg2_1" /> | <FormattedMessage id="MSG.payMsg2_1" /> | ||||
| <span style={{ color: "red" }}> | <span style={{ color: "red" }}> | ||||
| @@ -116,14 +117,12 @@ const Index = ({ record }) => { | |||||
| <FormattedMessage id="MSG.payMsg2_2" /> | <FormattedMessage id="MSG.payMsg2_2" /> | ||||
| <br /> | <br /> | ||||
| <br /> | <br /> | ||||
| <div dangerouslySetInnerHTML={{ | |||||
| __html: intl.formatMessage({ id: 'MSG.payMsg3' }, | |||||
| <SafeHtml html={intl.formatMessage({ id: 'MSG.payMsg3' }, | |||||
| { | { | ||||
| issueYear: record?.issueYear, | issueYear: record?.issueYear, | ||||
| issueVolume: record?.issueVolume, | issueVolume: record?.issueVolume, | ||||
| issueNo: record?.issueNo, | issueNo: record?.issueNo, | ||||
| }) | |||||
| }} /> | |||||
| })} /> | |||||
| </Typography> | </Typography> | ||||
| <Typography variant="h4" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "left" }}> | <Typography variant="h4" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "left" }}> | ||||
| @@ -213,7 +212,7 @@ const Index = ({ record }) => { | |||||
| <Typography variant="h4" style={{ paddingLeft: '24px' }}><FormattedMessage id="MSG.actionFail" /></Typography> | <Typography variant="h4" style={{ paddingLeft: '24px' }}><FormattedMessage id="MSG.actionFail" /></Typography> | ||||
| <DialogContent style={{ display: 'flex', }}> | <DialogContent style={{ display: 'flex', }}> | ||||
| <Stack direction="column" justifyContent="space-between"> | <Stack direction="column" justifyContent="space-between"> | ||||
| <div dangerouslySetInnerHTML={{ __html: intl.formatMessage({ id: 'MSG.paymentHolded' }, { appNo: paymentHoldedErrText }) }} /> | |||||
| <SafeHtml html={intl.formatMessage({ id: 'MSG.paymentHolded' }, { appNo: paymentHoldedErrText })} /> | |||||
| </Stack> | </Stack> | ||||
| </DialogContent> | </DialogContent> | ||||
| <DialogActions> | <DialogActions> | ||||
| @@ -14,6 +14,7 @@ import * as DateUtils from "utils/DateUtils"; | |||||
| import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | ||||
| import { FormattedMessage, useIntl } from "react-intl"; | import { FormattedMessage, useIntl } from "react-intl"; | ||||
| import SafeHtml from 'components/SafeHtml'; | |||||
| const BackgroundHead = { | const BackgroundHead = { | ||||
| backgroundImage: `url(${titleBackgroundImg})`, | backgroundImage: `url(${titleBackgroundImg})`, | ||||
| width: '100%', | width: '100%', | ||||
| @@ -76,19 +77,16 @@ const isAfter = checkIsOnlyOnlinePayment(); | |||||
| )} | )} | ||||
| <Typography variant="h5" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "left" }}> | <Typography variant="h5" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "left" }}> | ||||
| <div dangerouslySetInnerHTML={{ | |||||
| __html: intl.formatMessage( | |||||
| <SafeHtml html={intl.formatMessage( | |||||
| { | { | ||||
| id: 'proofPaymentBody_demandNote' | id: 'proofPaymentBody_demandNote' | ||||
| }, | }, | ||||
| { | { | ||||
| appNo: record?.appNo, | appNo: record?.appNo, | ||||
| } | } | ||||
| ) | |||||
| }} /> | |||||
| )} /> | |||||
| <br /> | <br /> | ||||
| <div dangerouslySetInnerHTML={{ | |||||
| __html: intl.formatMessage( | |||||
| <SafeHtml html={intl.formatMessage( | |||||
| { | { | ||||
| id: 'proofPaymentBody_demandNote2' | id: 'proofPaymentBody_demandNote2' | ||||
| }, | }, | ||||
| @@ -96,27 +94,22 @@ const isAfter = checkIsOnlyOnlinePayment(); | |||||
| closingDateOff: DateUtils.dateFormat(record?.closingDateOff, intl.formatMessage({id: "dateStrFormat"})), | closingDateOff: DateUtils.dateFormat(record?.closingDateOff, intl.formatMessage({id: "dateStrFormat"})), | ||||
| email: record?.mail, | email: record?.mail, | ||||
| } | } | ||||
| ) | |||||
| }} /> | |||||
| )} /> | |||||
| <br /> | <br /> | ||||
| <div dangerouslySetInnerHTML={{ | |||||
| __html: intl.formatMessage( | |||||
| <SafeHtml html={intl.formatMessage( | |||||
| { | { | ||||
| id: 'proofPaymentBody_demandNote3' | id: 'proofPaymentBody_demandNote3' | ||||
| }, | }, | ||||
| { | { | ||||
| paymentDeadline: DateUtils.dateFormat(record?.closingDate, intl.formatMessage({id: "dateStrFormat"})), | paymentDeadline: DateUtils.dateFormat(record?.closingDate, intl.formatMessage({id: "dateStrFormat"})), | ||||
| } | } | ||||
| ) | |||||
| }} /> | |||||
| )} /> | |||||
| <br /> | <br /> | ||||
| <div dangerouslySetInnerHTML={{ | |||||
| __html: intl.formatMessage( | |||||
| <SafeHtml html={intl.formatMessage( | |||||
| { | { | ||||
| id: 'proofPaymentBody_demandNote4' | id: 'proofPaymentBody_demandNote4' | ||||
| } | } | ||||
| ) | |||||
| }} /> | |||||
| )} /> | |||||
| </Typography> | </Typography> | ||||
| @@ -14,6 +14,7 @@ import * as DateUtils from "utils/DateUtils"; | |||||
| import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | ||||
| import { FormattedMessage, useIntl } from "react-intl"; | import { FormattedMessage, useIntl } from "react-intl"; | ||||
| import SafeHtml from 'components/SafeHtml'; | |||||
| const BackgroundHead = { | const BackgroundHead = { | ||||
| backgroundImage: `url(${titleBackgroundImg})`, | backgroundImage: `url(${titleBackgroundImg})`, | ||||
| width: '100%', | width: '100%', | ||||
| @@ -78,59 +79,47 @@ const isAfter = checkIsOnlyOnlinePayment(); | |||||
| <Typography variant="h5" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "left" }}> | <Typography variant="h5" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "left" }}> | ||||
| <div dangerouslySetInnerHTML={{ | |||||
| __html: intl.formatMessage( | |||||
| <SafeHtml html={intl.formatMessage( | |||||
| { | { | ||||
| id: 'proofPaymentBody_office' | id: 'proofPaymentBody_office' | ||||
| }, | }, | ||||
| { | { | ||||
| appNo: record?.appNo, | appNo: record?.appNo, | ||||
| } | } | ||||
| ) | |||||
| }} /> | |||||
| )} /> | |||||
| <br /> | <br /> | ||||
| <div dangerouslySetInnerHTML={{ | |||||
| __html: intl.formatMessage( | |||||
| <SafeHtml html={intl.formatMessage( | |||||
| { | { | ||||
| id: 'proofPaymentBody_office2' | id: 'proofPaymentBody_office2' | ||||
| }, | }, | ||||
| ) | |||||
| }} /> | |||||
| )} /> | |||||
| <br /> | <br /> | ||||
| <div dangerouslySetInnerHTML={{ | |||||
| __html: intl.formatMessage( | |||||
| <SafeHtml html={intl.formatMessage( | |||||
| { | { | ||||
| id: 'proofPaymentBody_office3' | id: 'proofPaymentBody_office3' | ||||
| } | } | ||||
| ) | |||||
| }} /> | |||||
| )} /> | |||||
| <br /> | <br /> | ||||
| <div dangerouslySetInnerHTML={{ | |||||
| __html: intl.formatMessage( | |||||
| <SafeHtml html={intl.formatMessage( | |||||
| { | { | ||||
| id: 'proofPaymentBody_office4' | id: 'proofPaymentBody_office4' | ||||
| } | } | ||||
| ) | |||||
| }} /> | |||||
| )} /> | |||||
| <br /> | <br /> | ||||
| <div dangerouslySetInnerHTML={{ | |||||
| __html: intl.formatMessage( | |||||
| <SafeHtml html={intl.formatMessage( | |||||
| { | { | ||||
| id: 'proofPaymentBody_office5' | id: 'proofPaymentBody_office5' | ||||
| }, | }, | ||||
| { | { | ||||
| paymentDeadline: DateUtils.dateFormat(record?.closingDate, intl.formatMessage({id: "dateStrFormat"})), | paymentDeadline: DateUtils.dateFormat(record?.closingDate, intl.formatMessage({id: "dateStrFormat"})), | ||||
| } | } | ||||
| ) | |||||
| }} /> | |||||
| )} /> | |||||
| <br /> | <br /> | ||||
| <div dangerouslySetInnerHTML={{ | |||||
| __html: intl.formatMessage( | |||||
| <SafeHtml html={intl.formatMessage( | |||||
| { | { | ||||
| id: 'proofPaymentBody_office6' | id: 'proofPaymentBody_office6' | ||||
| } | } | ||||
| ) | |||||
| }} /> | |||||
| )} /> | |||||
| </Typography> | </Typography> | ||||
| @@ -22,6 +22,7 @@ import Loadable from 'components/Loadable'; | |||||
| const LoadingComponent = Loadable(React.lazy(() => import('pages/extra-pages/LoadingComponent'))); | const LoadingComponent = Loadable(React.lazy(() => import('pages/extra-pages/LoadingComponent'))); | ||||
| import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | ||||
| import SafeHtml from 'components/SafeHtml'; | |||||
| const BackgroundHead = { | const BackgroundHead = { | ||||
| backgroundImage: `url(${titleBackgroundImg})`, | backgroundImage: `url(${titleBackgroundImg})`, | ||||
| width: '100%', | width: '100%', | ||||
| @@ -195,32 +196,28 @@ const Index = () => { | |||||
| )} | )} | ||||
| <Typography variant="h5" sx={bodySx}> | <Typography variant="h5" sx={bodySx}> | ||||
| <div dangerouslySetInnerHTML={{ | |||||
| __html: intl.formatMessage( | |||||
| <SafeHtml html={intl.formatMessage( | |||||
| { | { | ||||
| id: 'proofPaymentBody_online' | id: 'proofPaymentBody_online' | ||||
| }, | }, | ||||
| { | { | ||||
| appNo: record?.appNo, | appNo: record?.appNo, | ||||
| } | } | ||||
| ) | |||||
| }} /> | |||||
| )} /> | |||||
| <br /> | <br /> | ||||
| <div dangerouslySetInnerHTML={{ | |||||
| __html: intl.formatMessage( | |||||
| <SafeHtml html={intl.formatMessage( | |||||
| { | { | ||||
| id: 'proofPaymentBody_online2' | id: 'proofPaymentBody_online2' | ||||
| }, | }, | ||||
| { | { | ||||
| paymentDeadline: DateUtils.formatDateForLocale(record?.expiryDate, intl, locale), | paymentDeadline: DateUtils.formatDateForLocale(record?.expiryDate, intl, locale), | ||||
| } | } | ||||
| ) | |||||
| }} /> | |||||
| )} /> | |||||
| <br /> | <br /> | ||||
| {checkPaymentSuspension()? | {checkPaymentSuspension()? | ||||
| <div> | <div> | ||||
| <Typography style={{ textAlign: "flex-start", color: "red" }}> | <Typography style={{ textAlign: "flex-start", color: "red" }}> | ||||
| <div dangerouslySetInnerHTML={{ __html: intl.formatMessage({ id: "suspensionMessageText" }) }} /> | |||||
| <SafeHtml html={intl.formatMessage({ id: "suspensionMessageText" })} /> | |||||
| </Typography> | </Typography> | ||||
| <br /> | <br /> | ||||
| </div>:null | </div>:null | ||||
| @@ -272,13 +269,11 @@ const Index = () => { | |||||
| } | } | ||||
| <Typography variant="h4" sx={bodySx}> | <Typography variant="h4" sx={bodySx}> | ||||
| <div dangerouslySetInnerHTML={{ | |||||
| __html: intl.formatMessage( | |||||
| <SafeHtml html={intl.formatMessage( | |||||
| { | { | ||||
| id: 'proofPaymentBody_online3' | id: 'proofPaymentBody_online3' | ||||
| } | } | ||||
| ) | |||||
| }} /> | |||||
| )} /> | |||||
| <br /> | <br /> | ||||
| </Typography> | </Typography> | ||||
| @@ -338,7 +333,7 @@ const Index = () => { | |||||
| <Typography variant="h4" style={{ paddingLeft: '24px' }}><FormattedMessage id="MSG.actionFail" /></Typography> | <Typography variant="h4" style={{ paddingLeft: '24px' }}><FormattedMessage id="MSG.actionFail" /></Typography> | ||||
| <DialogContent style={{ display: 'flex', }}> | <DialogContent style={{ display: 'flex', }}> | ||||
| <Stack direction="column" justifyContent="space-between"> | <Stack direction="column" justifyContent="space-between"> | ||||
| <div dangerouslySetInnerHTML={{ __html: intl.formatMessage({ id: 'MSG.paymentHolded' }, { appNo: paymentHoldedErrText }) }} /> | |||||
| <SafeHtml html={intl.formatMessage({ id: 'MSG.paymentHolded' }, { appNo: paymentHoldedErrText })} /> | |||||
| </Stack> | </Stack> | ||||
| </DialogContent> | </DialogContent> | ||||
| <DialogActions> | <DialogActions> | ||||
| @@ -23,6 +23,7 @@ import { useNavigate } from "react-router-dom"; | |||||
| import * as DateUtils from "utils/DateUtils" | import * as DateUtils from "utils/DateUtils" | ||||
| import Loadable from 'components/Loadable'; | import Loadable from 'components/Loadable'; | ||||
| import { notifyActionSuccess } from 'utils/CommonFunction'; | import { notifyActionSuccess } from 'utils/CommonFunction'; | ||||
| import SafeHtml from 'components/SafeHtml'; | |||||
| import { PNSPS_BUTTON_THEME } from "themes/buttonConst"; | import { PNSPS_BUTTON_THEME } from "themes/buttonConst"; | ||||
| import { PRIMARY_CONTAINED_BUTTON_SX } from "themes/colorConst"; | import { PRIMARY_CONTAINED_BUTTON_SX } from "themes/colorConst"; | ||||
| import { ThemeProvider } from "@emotion/react"; | import { ThemeProvider } from "@emotion/react"; | ||||
| @@ -382,7 +383,7 @@ const FormPanel = ({ formData }) => { | |||||
| <li><FormattedMessage id="post" /></li> | <li><FormattedMessage id="post" /></li> | ||||
| </ul> | </ul> | ||||
| <Typography variant="h6" component="span"> | <Typography variant="h6" component="span"> | ||||
| <div style={{ padding: 12 }} dangerouslySetInnerHTML={{ __html: intl.formatMessage({ id: "proofNote" }) }} /> | |||||
| <SafeHtml html={intl.formatMessage({ id: "proofNote" })} style={{ padding: 12 }} /> | |||||
| </Typography> | </Typography> | ||||
| </> | </> | ||||
| ); | ); | ||||
| @@ -468,7 +469,7 @@ const FormPanel = ({ formData }) => { | |||||
| <FormattedMessage id="payOnline" /> | <FormattedMessage id="payOnline" /> | ||||
| {checkPaymentSuspension()? | {checkPaymentSuspension()? | ||||
| <Typography style={{ padding: '16px', color: "red" }}> | <Typography style={{ padding: '16px', color: "red" }}> | ||||
| <div dangerouslySetInnerHTML={{ __html: intl.formatMessage({ id: "suspensionMessageText" }) }} /> | |||||
| <SafeHtml html={intl.formatMessage({ id: "suspensionMessageText" })} /> | |||||
| </Typography>:null | </Typography>:null | ||||
| } | } | ||||
| <br /><a href="#payOnlineDetails" color='#fff' onClick={() => { | <br /><a href="#payOnlineDetails" color='#fff' onClick={() => { | ||||
| @@ -536,7 +537,7 @@ const FormPanel = ({ formData }) => { | |||||
| <li><FormattedMessage id="post" /></li> | <li><FormattedMessage id="post" /></li> | ||||
| </ul> | </ul> | ||||
| <Typography variant="h6" component="span"> | <Typography variant="h6" component="span"> | ||||
| <div style={{ padding: 12 }} dangerouslySetInnerHTML={{ __html: intl.formatMessage({ id: "proofNote" }) }} /> | |||||
| <SafeHtml html={intl.formatMessage({ id: "proofNote" })} style={{ padding: 12 }} /> | |||||
| </Typography> | </Typography> | ||||
| </> | </> | ||||
| ); | ); | ||||
| @@ -599,13 +600,13 @@ const FormPanel = ({ formData }) => { | |||||
| } | } | ||||
| {/* <Grid item xs={12}> | {/* <Grid item xs={12}> | ||||
| <Typography variant="h6" component="span" height="100%" > | <Typography variant="h6" component="span" height="100%" > | ||||
| <div style={{ padding: 12 }} dangerouslySetInnerHTML={{ __html: intl.formatMessage({ id: "proofNote" }) }} /> | |||||
| <SafeHtml html={intl.formatMessage({ id: "proofNote" })} style={{ padding: 12 }} /> | |||||
| </Typography> | </Typography> | ||||
| </Grid> */} | </Grid> */} | ||||
| {!isOnlyOnlinePayment? | {!isOnlyOnlinePayment? | ||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| <Typography variant="h6" component="span" height="100%" > | <Typography variant="h6" component="span" height="100%" > | ||||
| <div style={{ padding: 12 }} dangerouslySetInnerHTML={{ __html: intl.formatMessage({ id: "proofImportant" }) }} /> | |||||
| <SafeHtml html={intl.formatMessage({ id: "proofImportant" })} style={{ padding: 12 }} /> | |||||
| </Typography> | </Typography> | ||||
| </Grid>:null | </Grid>:null | ||||
| } | } | ||||
| @@ -34,6 +34,7 @@ import { FormattedMessage, useIntl } from "react-intl"; | |||||
| import { useState, useEffect, useRef } from 'react'; | import { useState, useEffect, useRef } from 'react'; | ||||
| import { checkPaymentSuspension } from "utils/Utils"; | import { checkPaymentSuspension } from "utils/Utils"; | ||||
| import { PRIMARY_CONTAINED_BUTTON_SX } from "themes/colorConst"; | import { PRIMARY_CONTAINED_BUTTON_SX } from "themes/colorConst"; | ||||
| import SafeHtml from 'components/SafeHtml'; | |||||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | // ==============================|| DASHBOARD - DEFAULT ||============================== // | ||||
| @@ -409,13 +410,12 @@ const PublicNoticeApplyForm = ({ loadedData, _selections, gazetteIssueList }) => | |||||
| ml:2 | ml:2 | ||||
| }} | }} | ||||
| > | > | ||||
| <span | |||||
| dangerouslySetInnerHTML={{ | |||||
| __html: intl.formatMessage({ | |||||
| id: "suspensionMessageText", | |||||
| defaultMessage: "" | |||||
| }) | |||||
| }} | |||||
| <SafeHtml | |||||
| component="span" | |||||
| html={intl.formatMessage({ | |||||
| id: "suspensionMessageText", | |||||
| defaultMessage: "" | |||||
| })} | |||||
| /> | /> | ||||
| </Typography> | </Typography> | ||||
| )} | )} | ||||
| @@ -459,7 +459,7 @@ const PublicNoticeApplyForm = ({ loadedData, _selections, gazetteIssueList }) => | |||||
| <li><FormattedMessage id="post" /></li> | <li><FormattedMessage id="post" /></li> | ||||
| </ul> | </ul> | ||||
| <Typography variant="h6" component="span"> | <Typography variant="h6" component="span"> | ||||
| <div style={{ padding: 12 }} dangerouslySetInnerHTML={{ __html: intl.formatMessage({ id: "proofNote" }) }} /> | |||||
| <SafeHtml html={intl.formatMessage({ id: "proofNote" })} style={{ padding: 12 }} /> | |||||
| </Typography> | </Typography> | ||||
| </> | </> | ||||
| ); | ); | ||||
| @@ -603,14 +603,14 @@ const PublicNoticeApplyForm = ({ loadedData, _selections, gazetteIssueList }) => | |||||
| <Grid item xs={12} mr={1} mb={2}> | <Grid item xs={12} mr={1} mb={2}> | ||||
| <Typography variant="pnspsFormParagraphBold"> | <Typography variant="pnspsFormParagraphBold"> | ||||
| <span style={{ textAlign: 'justify', }} dangerouslySetInnerHTML={{ __html: intl.formatMessage({ id: "applyTickUnderStr0" }) }} /> | |||||
| <SafeHtml component="span" style={{ textAlign: 'justify', }} html={intl.formatMessage({ id: "applyTickUnderStr0" })} /> | |||||
| </Typography> | </Typography> | ||||
| <Typography display="inline" variant="subtitle1" component="span" sx={{ color: 'primary.primary' }} > | <Typography display="inline" variant="subtitle1" component="span" sx={{ color: 'primary.primary' }} > | ||||
| <ol style={{ textAlign: 'justify', }}> | <ol style={{ textAlign: 'justify', }}> | ||||
| <li dangerouslySetInnerHTML={{ __html: intl.formatMessage({ id: "applyTickUnderStr1" }) }} /> | |||||
| <li dangerouslySetInnerHTML={{ __html: intl.formatMessage({ id: "applyTickUnderStr2" }) }} /> | |||||
| <li dangerouslySetInnerHTML={{ __html: intl.formatMessage({ id: "applyTickUnderStr3" }) }} /> | |||||
| <li dangerouslySetInnerHTML={{ __html: intl.formatMessage({ id: "tradeMarkFootnote" }) }} /> | |||||
| <SafeHtml component="li" html={intl.formatMessage({ id: "applyTickUnderStr1" })} /> | |||||
| <SafeHtml component="li" html={intl.formatMessage({ id: "applyTickUnderStr2" })} /> | |||||
| <SafeHtml component="li" html={intl.formatMessage({ id: "applyTickUnderStr3" })} /> | |||||
| <SafeHtml component="li" html={intl.formatMessage({ id: "tradeMarkFootnote" })} /> | |||||
| </ol> | </ol> | ||||
| </Typography> | </Typography> | ||||
| </Grid> | </Grid> | ||||
| @@ -630,7 +630,7 @@ const PublicNoticeApplyForm = ({ loadedData, _selections, gazetteIssueList }) => | |||||
| }} | }} | ||||
| /> | /> | ||||
| <Typography variant="h6" component="span" height="100%" > | <Typography variant="h6" component="span" height="100%" > | ||||
| <div style={{ padding: 12, textAlign: 'justify' }} dangerouslySetInnerHTML={{ __html: intl.formatMessage({ id: "applyTickStr" }) }} /> | |||||
| <SafeHtml html={intl.formatMessage({ id: "applyTickStr" })} style={{ padding: 12, textAlign: 'justify' }} /> | |||||
| </Typography> | </Typography> | ||||
| </Stack> | </Stack> | ||||
| </Grid> | </Grid> | ||||
| @@ -653,7 +653,7 @@ const PublicNoticeApplyForm = ({ loadedData, _selections, gazetteIssueList }) => | |||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| <Typography variant="h6" component="span" height="100%" > | <Typography variant="h6" component="span" height="100%" > | ||||
| <div style={{ padding: 12 }} dangerouslySetInnerHTML={{ __html: intl.formatMessage({ id: "applyPublicNoticeText" }) }} /> | |||||
| <SafeHtml html={intl.formatMessage({ id: "applyPublicNoticeText" })} style={{ padding: 12 }} /> | |||||
| </Typography> | </Typography> | ||||
| </Grid> | </Grid> | ||||
| @@ -41,6 +41,7 @@ import EditNoteIcon from '@mui/icons-material/EditNote'; | |||||
| import DownloadIcon from '@mui/icons-material/Download'; | import DownloadIcon from '@mui/icons-material/Download'; | ||||
| import { PNSPS_BUTTON_THEME } from "../../../themes/buttonConst"; | import { PNSPS_BUTTON_THEME } from "../../../themes/buttonConst"; | ||||
| import { PRIMARY_CONTAINED_BUTTON_SX } from "themes/colorConst"; | import { PRIMARY_CONTAINED_BUTTON_SX } from "themes/colorConst"; | ||||
| import SafeHtml from 'components/SafeHtml'; | |||||
| import { ThemeProvider } from "@emotion/react"; | import { ThemeProvider } from "@emotion/react"; | ||||
| import { FormattedMessage, useIntl } from "react-intl"; | import { FormattedMessage, useIntl } from "react-intl"; | ||||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | // ==============================|| DASHBOARD - DEFAULT ||============================== // | ||||
| @@ -834,7 +835,7 @@ const ApplicationDetailCard = ( | |||||
| <Typography variant="h4" component="span" style={{ paddingLeft: '24px' }}><FormattedMessage id="MSG.actionFail" /></Typography> | <Typography variant="h4" component="span" style={{ paddingLeft: '24px' }}><FormattedMessage id="MSG.actionFail" /></Typography> | ||||
| <DialogContent style={{ display: 'flex', }}> | <DialogContent style={{ display: 'flex', }}> | ||||
| <Stack direction="column" justifyContent="space-between"> | <Stack direction="column" justifyContent="space-between"> | ||||
| <div dangerouslySetInnerHTML={{ __html: intl.formatMessage({ id: 'MSG.paymentHolded' }, { appNo: paymentHoldedErrText }) }} /> | |||||
| <SafeHtml html={intl.formatMessage({ id: 'MSG.paymentHolded' }, { appNo: paymentHoldedErrText })} /> | |||||
| </Stack> | </Stack> | ||||
| </DialogContent> | </DialogContent> | ||||
| <DialogActions> | <DialogActions> | ||||
| @@ -28,6 +28,7 @@ import { | |||||
| // import { dateStr } from "utils/DateUtils"; | // import { dateStr } from "utils/DateUtils"; | ||||
| import { ThemeProvider, useTheme } from "@emotion/react"; | import { ThemeProvider, useTheme } from "@emotion/react"; | ||||
| import { PNSPS_BUTTON_THEME } from "../../../themes/buttonConst"; | import { PNSPS_BUTTON_THEME } from "../../../themes/buttonConst"; | ||||
| import SafeHtml from 'components/SafeHtml'; | |||||
| import { FormattedMessage, useIntl } from "react-intl"; | import { FormattedMessage, useIntl } from "react-intl"; | ||||
| import { PRIMARY_TEXT_BUTTON_SX, PRIMARY_CONTAINED_BUTTON_SX } from "themes/colorConst"; | import { PRIMARY_TEXT_BUTTON_SX, PRIMARY_CONTAINED_BUTTON_SX } from "themes/colorConst"; | ||||
| // ==============================|| EVENT TABLE ||============================== // | // ==============================|| EVENT TABLE ||============================== // | ||||
| @@ -580,7 +581,7 @@ export default function SubmittedTab({ setCount, url }) { | |||||
| <Typography variant="h4" style={{ paddingLeft: '24px' }}><FormattedMessage id="MSG.actionFail" /></Typography> | <Typography variant="h4" style={{ paddingLeft: '24px' }}><FormattedMessage id="MSG.actionFail" /></Typography> | ||||
| <DialogContent style={{ display: 'flex', }}> | <DialogContent style={{ display: 'flex', }}> | ||||
| <Stack direction="column" justifyContent="space-between"> | <Stack direction="column" justifyContent="space-between"> | ||||
| <div dangerouslySetInnerHTML={{ __html: intl.formatMessage({ id: 'MSG.paymentHolded' }, { appNo: paymentHoldedErrText }) }} /> | |||||
| <SafeHtml html={intl.formatMessage({ id: 'MSG.paymentHolded' }, { appNo: paymentHoldedErrText })} /> | |||||
| </Stack> | </Stack> | ||||
| </DialogContent> | </DialogContent> | ||||
| <DialogActions> | <DialogActions> | ||||
| @@ -7,6 +7,7 @@ import MainCard from "components/MainCard"; | |||||
| import * as React from "react"; | import * as React from "react"; | ||||
| import { FiDataGrid } from "components/FiDataGrid"; | import { FiDataGrid } from "components/FiDataGrid"; | ||||
| import { GET_SYS_PARAMS } from "utils/ApiPathConst"; | import { GET_SYS_PARAMS } from "utils/ApiPathConst"; | ||||
| import SafeHtml from 'components/SafeHtml'; | |||||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | // ==============================|| DASHBOARD - DEFAULT ||============================== // | ||||
| @@ -45,7 +46,7 @@ const Table = ({onRowClick, searchCriteria, refreshTrigger}) => { | |||||
| flex: 1, | flex: 1, | ||||
| minWidth: 400, | minWidth: 400, | ||||
| renderCell:(params)=>{ | renderCell:(params)=>{ | ||||
| return <div dangerouslySetInnerHTML={{__html: params.value}} /> | |||||
| return <SafeHtml html={params.value} /> | |||||
| } | } | ||||
| }, | }, | ||||
| ]; | ]; | ||||
| @@ -10,6 +10,7 @@ import backbroundImg from 'assets/images/bg_ml.jpg'; | |||||
| import lgceImg from 'assets/images/2025_lgce.jpg'; | import lgceImg from 'assets/images/2025_lgce.jpg'; | ||||
| import 'assets/style/loginStyles.css'; | import 'assets/style/loginStyles.css'; | ||||
| import { SysContext } from 'components/SysSettingProvider'; | import { SysContext } from 'components/SysSettingProvider'; | ||||
| import SafeHtml from 'components/SafeHtml'; | |||||
| const AuthCard = Loadable(lazy(() => import('./AuthCardCustom'))); | const AuthCard = Loadable(lazy(() => import('./AuthCardCustom'))); | ||||
| const BackgroundHead = { | const BackgroundHead = { | ||||
| @@ -175,11 +176,9 @@ const AuthWrapper = ({ children }) => { | |||||
| '& p': { marginBottom: '12px', lineHeight: 1.7 } | '& p': { marginBottom: '12px', lineHeight: 1.7 } | ||||
| }} | }} | ||||
| > | > | ||||
| <Typography | |||||
| component="div" | |||||
| sx={{ fontSize: 18, lineHeight: 1.8 }} | |||||
| dangerouslySetInnerHTML={{ __html: popupHtml }} | |||||
| /> | |||||
| <Typography component="div" sx={{ fontSize: 18, lineHeight: 1.8 }}> | |||||
| <SafeHtml html={popupHtml} /> | |||||
| </Typography> | |||||
| </DialogContent> | </DialogContent> | ||||
| </Dialog> | </Dialog> | ||||
| @@ -207,24 +206,18 @@ const AuthWrapper = ({ children }) => { | |||||
| > | > | ||||
| <Grid item xs={12} sx={{ px: { xs: 2 }, pl: { md: 4 }, pt: { xs: 4, sm: 2, md: 1.5 }, pb: { xs: 4, sm: 0 } }}> | <Grid item xs={12} sx={{ px: { xs: 2 }, pl: { md: 4 }, pt: { xs: 4, sm: 2, md: 1.5 }, pb: { xs: 4, sm: 0 } }}> | ||||
| {checkPaymentSuspension() ? ( | {checkPaymentSuspension() ? ( | ||||
| <Typography | |||||
| component="div" | |||||
| sx={{ color: 'error.main', textAlign: 'start', p: 1.5 }} | |||||
| dangerouslySetInnerHTML={{ | |||||
| __html: intl.formatMessage({ id: 'suspensionMessageText', defaultMessage: '' }) | |||||
| }} | |||||
| /> | |||||
| <Typography component="div" sx={{ color: 'error.main', textAlign: 'start', p: 1.5 }}> | |||||
| <SafeHtml html={intl.formatMessage({ id: 'suspensionMessageText', defaultMessage: '' })} /> | |||||
| </Typography> | |||||
| ) : ( | ) : ( | ||||
| <Typography | |||||
| component="div" | |||||
| sx={{ textAlign: 'start', p: 1.5 }} | |||||
| dangerouslySetInnerHTML={{ | |||||
| __html: intl.formatMessage({ | |||||
| <Typography component="div" sx={{ textAlign: 'start', p: 1.5 }}> | |||||
| <SafeHtml | |||||
| html={intl.formatMessage({ | |||||
| id: isOnlyOnline ? 'landingMessage' : 'homePageHeaderMessage', | id: isOnlyOnline ? 'landingMessage' : 'homePageHeaderMessage', | ||||
| defaultMessage: '' | defaultMessage: '' | ||||
| }) | |||||
| }} | |||||
| /> | |||||
| })} | |||||
| /> | |||||
| </Typography> | |||||
| )} | )} | ||||
| </Grid> | </Grid> | ||||
| @@ -17,6 +17,7 @@ import { I_AM_SMART_PATH } from "utils/ApiPathConst"; | |||||
| import * as React from 'react'; | import * as React from 'react'; | ||||
| import { FormattedMessage, useIntl } from "react-intl"; | import { FormattedMessage, useIntl } from "react-intl"; | ||||
| import usePageTitle from "components/usePageTitle"; | import usePageTitle from "components/usePageTitle"; | ||||
| import SafeHtml from 'components/SafeHtml'; | |||||
| // ================================|| LOGIN ||================================ // | // ================================|| LOGIN ||================================ // | ||||
| @@ -127,7 +128,7 @@ const RegisterCustom = () => { | |||||
| /> | /> | ||||
| <Box mt={4} ml={2} mr={2} bgcolor="grey.100" p={1.5} > | <Box mt={4} ml={2} mr={2} bgcolor="grey.100" p={1.5} > | ||||
| <Typography textAlign='justify' variant="body1" display="block" gutterBottom> | <Typography textAlign='justify' variant="body1" display="block" gutterBottom> | ||||
| <div dangerouslySetInnerHTML={{ __html: intl.formatMessage({ id: 'MSG.registerIAmSmart' }) }} /> | |||||
| <SafeHtml html={intl.formatMessage({ id: 'MSG.registerIAmSmart' })} /> | |||||
| </Typography> | </Typography> | ||||
| <Link href={intl.formatMessage({ id: "iamsmartLink" })}> | <Link href={intl.formatMessage({ id: "iamsmartLink" })}> | ||||
| <FormattedMessage id="learnMore" /> | <FormattedMessage id="learnMore" /> | ||||
| @@ -160,7 +161,7 @@ const RegisterCustom = () => { | |||||
| </Button> | </Button> | ||||
| <Typography ml={4} mr={4} mt={4} variant="body1" display="block" sx={{ fontWeight: 'bold' }} gutterBottom> | <Typography ml={4} mr={4} mt={4} variant="body1" display="block" sx={{ fontWeight: 'bold' }} gutterBottom> | ||||
| <div dangerouslySetInnerHTML={{ __html: intl.formatMessage({ id: 'MSG.registerPersonal' }) }} /> | |||||
| <SafeHtml html={intl.formatMessage({ id: 'MSG.registerPersonal' })} /> | |||||
| </Typography> | </Typography> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={12} md={6} sx={{ borderLeft: 1, borderColor: 'grey.500' }}> | <Grid item xs={12} md={6} sx={{ borderLeft: 1, borderColor: 'grey.500' }}> | ||||
| @@ -189,7 +190,7 @@ const RegisterCustom = () => { | |||||
| </Typography> | </Typography> | ||||
| </Button> | </Button> | ||||
| <Typography ml={4} mr={4} mt={4} variant="body1" display="block" sx={{ fontWeight: 'bold' }} gutterBottom> | <Typography ml={4} mr={4} mt={4} variant="body1" display="block" sx={{ fontWeight: 'bold' }} gutterBottom> | ||||
| <div dangerouslySetInnerHTML={{ __html: intl.formatMessage({ id: 'MSG.registerOrg' }) }} /> | |||||
| <SafeHtml html={intl.formatMessage({ id: 'MSG.registerOrg' })} /> | |||||
| </Typography> | </Typography> | ||||
| </Grid> | </Grid> | ||||
| </Grid> | </Grid> | ||||
| @@ -35,6 +35,7 @@ import { POST_PUBLIC_USER_REGISTER, POST_CAPTCHA, POST_USERNAME, POST_USER_EMAIL | |||||
| import * as ComboData from "utils/ComboData"; | import * as ComboData from "utils/ComboData"; | ||||
| import Loadable from 'components/Loadable'; | import Loadable from 'components/Loadable'; | ||||
| import SafeHtml from 'components/SafeHtml'; | |||||
| import { lazy } from 'react'; | import { lazy } from 'react'; | ||||
| const UploadFileTable = Loadable(lazy(() => import('./UploadFileTable'))); | const UploadFileTable = Loadable(lazy(() => import('./UploadFileTable'))); | ||||
| const LoadingComponent = Loadable(lazy(() => import('../../extra-pages/LoadingComponent'))); | const LoadingComponent = Loadable(lazy(() => import('../../extra-pages/LoadingComponent'))); | ||||
| @@ -1618,7 +1619,7 @@ const BusCustomFormWizard = (props) => { | |||||
| <Grid container> | <Grid container> | ||||
| <Grid item xs={12} md={12}> | <Grid item xs={12} md={12}> | ||||
| <Typography component="span" variant="h6" height="100%" sx={{ textAlign: "left", /*overflow: "scroll",*/ borderRadius: "inherit", borderStyle: "solid", borderWidth: "1px", borderColor: "#0c489e", display: "block" }}> | <Typography component="span" variant="h6" height="100%" sx={{ textAlign: "left", /*overflow: "scroll",*/ borderRadius: "inherit", borderStyle: "solid", borderWidth: "1px", borderColor: "#0c489e", display: "block" }}> | ||||
| <div style={{padding: 12}} dangerouslySetInnerHTML={{__html: intl.formatMessage({id: "termsAndCon"})}} /> | |||||
| <SafeHtml html={intl.formatMessage({id: "termsAndCon"})} style={{padding: 12}} /> | |||||
| </Typography> | </Typography> | ||||
| </Grid> | </Grid> | ||||
| </Grid> | </Grid> | ||||
| @@ -28,6 +28,7 @@ import { POST_USERNAME, POST_USER_EMAIL, POST_CAPTCHA, POST_PUBLIC_USER_REGISTER | |||||
| import * as ComboData from "utils/ComboData"; | import * as ComboData from "utils/ComboData"; | ||||
| import Loadable from 'components/Loadable'; | import Loadable from 'components/Loadable'; | ||||
| import SafeHtml from 'components/SafeHtml'; | |||||
| import { lazy } from 'react'; | import { lazy } from 'react'; | ||||
| const UploadFileTable = Loadable(lazy(() => import('./UploadFileTable'))); | const UploadFileTable = Loadable(lazy(() => import('./UploadFileTable'))); | ||||
| const PreviewUploadFileTable = Loadable(lazy(() => import('./PreviewUploadFileTable'))); | const PreviewUploadFileTable = Loadable(lazy(() => import('./PreviewUploadFileTable'))); | ||||
| @@ -1946,7 +1947,7 @@ const CustomFormWizard = (props) => { | |||||
| <Grid container> | <Grid container> | ||||
| <Grid item xs={12} md={12}> | <Grid item xs={12} md={12}> | ||||
| <Typography height="100%" component="span" sx={{ textAlign: "left", display: "block", /*overflow: "scroll",*/ borderRadius: "inherit", borderStyle: "solid", borderWidth: "1px", borderColor: "#0c489e" }}> | <Typography height="100%" component="span" sx={{ textAlign: "left", display: "block", /*overflow: "scroll",*/ borderRadius: "inherit", borderStyle: "solid", borderWidth: "1px", borderColor: "#0c489e" }}> | ||||
| <div style={{padding: 12}} dangerouslySetInnerHTML={{__html: intl.formatMessage({id: "termsAndCon"})}} /> | |||||
| <SafeHtml html={intl.formatMessage({id: "termsAndCon"})} style={{padding: 12}} /> | |||||
| </Typography> | </Typography> | ||||
| </Grid> | </Grid> | ||||
| </Grid> | </Grid> | ||||
| @@ -26,6 +26,7 @@ import { POST_IAMSMART_USER_REGISTER, POST_CAPTCHA, POST_USER_EMAIL, POST_CAPTCH | |||||
| import * as ComboData from "utils/ComboData"; | import * as ComboData from "utils/ComboData"; | ||||
| import Loadable from 'components/Loadable'; | import Loadable from 'components/Loadable'; | ||||
| import SafeHtml from 'components/SafeHtml'; | |||||
| import { lazy } from 'react'; | import { lazy } from 'react'; | ||||
| const LoadingComponent = Loadable(lazy(() => import('../../extra-pages/LoadingComponent'))); | const LoadingComponent = Loadable(lazy(() => import('../../extra-pages/LoadingComponent'))); | ||||
| @@ -1055,7 +1056,7 @@ const CustomFormWizard = (props) => { | |||||
| <Grid item xs={12} md={12}> | <Grid item xs={12} md={12}> | ||||
| <Typography component="span" sx={{ textAlign: "left", display: "block", borderRadius: "inherit", borderStyle: "solid", borderWidth: "1px", borderColor: "#0c489e" }}> | <Typography component="span" sx={{ textAlign: "left", display: "block", borderRadius: "inherit", borderStyle: "solid", borderWidth: "1px", borderColor: "#0c489e" }}> | ||||
| <div style={{ padding: 12 }} dangerouslySetInnerHTML={{ __html: intl.formatMessage({ id: "termsAndCon" }) }} /> | |||||
| <SafeHtml html={intl.formatMessage({ id: "termsAndCon" })} style={{ padding: 12 }} /> | |||||
| </Typography> | </Typography> | ||||
| </Grid> | </Grid> | ||||
| </Grid> | </Grid> | ||||
| @@ -29,6 +29,7 @@ const LoadingComponent = Loadable(React.lazy(() => import('../../extra-pages/Loa | |||||
| import { useNavigate } from "react-router-dom"; | import { useNavigate } from "react-router-dom"; | ||||
| import usePageTitle from "components/usePageTitle"; | import usePageTitle from "components/usePageTitle"; | ||||
| import SafeHtml from 'components/SafeHtml'; | |||||
| // ==============================|| DASHBOARD - DEFAULT ||============================== // | // ==============================|| DASHBOARD - DEFAULT ||============================== // | ||||
| @@ -297,7 +298,7 @@ const DashboardDefault = () => { | |||||
| </DialogTitle> | </DialogTitle> | ||||
| <DialogContent style={{ color: '#B00020', display: 'flex', }}> | <DialogContent style={{ color: '#B00020', display: 'flex', }}> | ||||
| <Typography variant="h5" style={{ padding: '16px' }}> | <Typography variant="h5" style={{ padding: '16px' }}> | ||||
| <div dangerouslySetInnerHTML={{ __html: intl.formatMessage({ id: "suspensionMessageText" }) }} /> | |||||
| <SafeHtml html={intl.formatMessage({ id: "suspensionMessageText" })} /> | |||||
| </Typography> | </Typography> | ||||
| </DialogContent> | </DialogContent> | ||||
| <DialogActions> | <DialogActions> | ||||
| @@ -1,3 +1,4 @@ | |||||
| import SafeHtml from 'components/SafeHtml'; | |||||
| const AboutUs = () => { | const AboutUs = () => { | ||||
| @@ -155,7 +156,7 @@ const AboutUs = () => { | |||||
| ` | ` | ||||
| ; | ; | ||||
| return (<div style={{ padding: 12 }} dangerouslySetInnerHTML={{ __html: content }} />); | |||||
| return <SafeHtml html={content} style={{ padding: 12 }} />; | |||||
| } | } | ||||
| @@ -1,3 +1,4 @@ | |||||
| import SafeHtml from 'components/SafeHtml'; | |||||
| const AboutUs = () => { | const AboutUs = () => { | ||||
| @@ -162,7 +163,7 @@ const AboutUs = () => { | |||||
| </div>` | </div>` | ||||
| ; | ; | ||||
| return (<div style={{ padding: 12 }} dangerouslySetInnerHTML={{ __html: content }} />); | |||||
| return <SafeHtml html={content} style={{ padding: 12 }} />; | |||||
| } | } | ||||
| @@ -1,3 +1,4 @@ | |||||
| import SafeHtml from 'components/SafeHtml'; | |||||
| const AboutUs = () => { | const AboutUs = () => { | ||||
| @@ -165,7 +166,7 @@ const AboutUs = () => { | |||||
| ` | ` | ||||
| ; | ; | ||||
| return (<div style={{ padding: 12 }} dangerouslySetInnerHTML={{ __html: content }} />); | |||||
| return <SafeHtml html={content} style={{ padding: 12 }} />; | |||||
| } | } | ||||
| @@ -3,6 +3,7 @@ import { useIntl, FormattedMessage } from 'react-intl'; | |||||
| import usePageTitle from "components/usePageTitle"; | import usePageTitle from "components/usePageTitle"; | ||||
| import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png'; | import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png'; | ||||
| import SafeHtml from 'components/SafeHtml'; | |||||
| const BackgroundHead = { | const BackgroundHead = { | ||||
| backgroundImage: `url(${titleBackgroundImg})`, | backgroundImage: `url(${titleBackgroundImg})`, | ||||
| @@ -38,7 +39,7 @@ const ImportantNotice = () => { | |||||
| </div> | </div> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={10} md={8} lg={6}> | <Grid item xs={10} md={8} lg={6}> | ||||
| <div style={{ padding: 12 }} dangerouslySetInnerHTML={{ __html: htmlContent }} /> | |||||
| <SafeHtml html={htmlContent} style={{ padding: 12 }} /> | |||||
| </Grid> | </Grid> | ||||
| </Grid> | </Grid> | ||||
| ); | ); | ||||
| @@ -1,3 +1,4 @@ | |||||
| import SafeHtml from 'components/SafeHtml'; | |||||
| const Page = () => { | const Page = () => { | ||||
| @@ -83,7 +84,7 @@ const Page = () => { | |||||
| </div> | </div> | ||||
| `; | `; | ||||
| return (<div style={{ padding: 12 }} dangerouslySetInnerHTML={{ __html: content }} />); | |||||
| return <SafeHtml html={content} style={{ padding: 12 }} />; | |||||
| } | } | ||||
| @@ -1,3 +1,5 @@ | |||||
| import SafeHtml from 'components/SafeHtml'; | |||||
| const Page = () => { | const Page = () => { | ||||
| const content = ` | const content = ` | ||||
| <style> | <style> | ||||
| @@ -125,7 +127,7 @@ const Page = () => { | |||||
| </div> | </div> | ||||
| `; | `; | ||||
| return <div style={{ padding: 12 }} dangerouslySetInnerHTML={{ __html: content }} />; | |||||
| return <SafeHtml html={content} style={{ padding: 12 }} />; | |||||
| }; | }; | ||||
| export default Page; | export default Page; | ||||
| @@ -1,3 +1,5 @@ | |||||
| import SafeHtml from 'components/SafeHtml'; | |||||
| const Page = () => { | const Page = () => { | ||||
| const content = ` | const content = ` | ||||
| <style> | <style> | ||||
| @@ -98,7 +100,7 @@ const Page = () => { | |||||
| </div> | </div> | ||||
| `; | `; | ||||
| return <div style={{ padding: 12 }} dangerouslySetInnerHTML={{ __html: content }} />; | |||||
| return <SafeHtml html={content} style={{ padding: 12 }} />; | |||||
| }; | }; | ||||
| export default Page; | export default Page; | ||||
| @@ -3,6 +3,7 @@ import { useIntl, FormattedMessage } from 'react-intl'; | |||||
| import usePageTitle from "components/usePageTitle"; | import usePageTitle from "components/usePageTitle"; | ||||
| import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png'; | import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png'; | ||||
| import SafeHtml from 'components/SafeHtml'; | |||||
| const BackgroundHead = { | const BackgroundHead = { | ||||
| backgroundImage: `url(${titleBackgroundImg})`, | backgroundImage: `url(${titleBackgroundImg})`, | ||||
| @@ -38,7 +39,7 @@ const PrivacyPolicy = () => { | |||||
| </div> | </div> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={10} md={8} lg={6}> | <Grid item xs={10} md={8} lg={6}> | ||||
| <div style={{ padding: 12 }} dangerouslySetInnerHTML={{ __html: htmlContent }} /> | |||||
| <SafeHtml html={htmlContent} style={{ padding: 12 }} /> | |||||
| </Grid> | </Grid> | ||||
| </Grid> | </Grid> | ||||
| ); | ); | ||||
| @@ -12,6 +12,7 @@ import { useNavigate } from "react-router-dom"; | |||||
| import Loadable from 'components/Loadable'; | import Loadable from 'components/Loadable'; | ||||
| const LoadingComponent = Loadable(React.lazy(() => import('pages/extra-pages/LoadingComponent'))); | const LoadingComponent = Loadable(React.lazy(() => import('pages/extra-pages/LoadingComponent'))); | ||||
| import AccountCircleIcon from '@mui/icons-material/AccountCircle'; | import AccountCircleIcon from '@mui/icons-material/AccountCircle'; | ||||
| import SafeHtml from 'components/SafeHtml'; | |||||
| import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | ||||
| const BackgroundHead = { | const BackgroundHead = { | ||||
| @@ -66,7 +67,7 @@ const Index = () => { | |||||
| <AccountCircleIcon color="secondary" sx={{ width: "200px", height: "200px" }} /> | <AccountCircleIcon color="secondary" sx={{ width: "200px", height: "200px" }} /> | ||||
| <Grid item xs={12} md={12} > | <Grid item xs={12} md={12} > | ||||
| <Typography variant="h3" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "center" }}> | <Typography variant="h3" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "center" }}> | ||||
| <div dangerouslySetInnerHTML={{ __html: intl.formatMessage({ id: 'pleaseLloginMessage2' }) }} /> | |||||
| <SafeHtml html={intl.formatMessage({ id: 'pleaseLloginMessage2' })} /> | |||||
| </Typography> | </Typography> | ||||
| </Grid> | </Grid> | ||||
| </center> | </center> | ||||
| @@ -12,6 +12,7 @@ import { useNavigate } from "react-router-dom"; | |||||
| import Loadable from 'components/Loadable'; | import Loadable from 'components/Loadable'; | ||||
| const LoadingComponent = Loadable(React.lazy(() => import('pages/extra-pages/LoadingComponent'))); | const LoadingComponent = Loadable(React.lazy(() => import('pages/extra-pages/LoadingComponent'))); | ||||
| import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; | import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; | ||||
| import SafeHtml from 'components/SafeHtml'; | |||||
| import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png' | ||||
| const BackgroundHead = { | const BackgroundHead = { | ||||
| @@ -69,8 +70,9 @@ const Index = () => { | |||||
| component="div" | component="div" | ||||
| variant="h3" | variant="h3" | ||||
| sx={{ mt: 4, maxWidth: 900, textAlign: 'center' }} | sx={{ mt: 4, maxWidth: 900, textAlign: 'center' }} | ||||
| dangerouslySetInnerHTML={{ __html: intl.formatMessage({ id: 'loginSuccessMessage2' }) }} | |||||
| /> | |||||
| > | |||||
| <SafeHtml html={intl.formatMessage({ id: 'loginSuccessMessage2' })} /> | |||||
| </Typography> | |||||
| ) : ( | ) : ( | ||||
| <Typography component="div" variant="h3" sx={{ mt: 4, maxWidth: 900, textAlign: 'center' }}> | <Typography component="div" variant="h3" sx={{ mt: 4, maxWidth: 900, textAlign: 'center' }}> | ||||
| <FormattedMessage id="iAmSmartLoginSuccessHeading" /> | <FormattedMessage id="iAmSmartLoginSuccessHeading" /> | ||||
| @@ -0,0 +1,78 @@ | |||||
| import DOMPurify from 'dompurify'; | |||||
| const ALLOWED_TAGS = [ | |||||
| 'a', | |||||
| 'b', | |||||
| 'blockquote', | |||||
| 'br', | |||||
| 'caption', | |||||
| 'col', | |||||
| 'colgroup', | |||||
| 'div', | |||||
| 'em', | |||||
| 'h1', | |||||
| 'h2', | |||||
| 'h3', | |||||
| 'h4', | |||||
| 'h5', | |||||
| 'h6', | |||||
| 'hr', | |||||
| 'i', | |||||
| 'img', | |||||
| 'li', | |||||
| 'ol', | |||||
| 'p', | |||||
| 'span', | |||||
| 'strong', | |||||
| 'style', | |||||
| 'sub', | |||||
| 'sup', | |||||
| 'table', | |||||
| 'tbody', | |||||
| 'td', | |||||
| 'tfoot', | |||||
| 'th', | |||||
| 'thead', | |||||
| 'tr', | |||||
| 'u', | |||||
| 'ul' | |||||
| ]; | |||||
| const ALLOWED_ATTR = [ | |||||
| 'href', | |||||
| 'title', | |||||
| 'target', | |||||
| 'rel', | |||||
| 'src', | |||||
| 'alt', | |||||
| 'width', | |||||
| 'height', | |||||
| 'colspan', | |||||
| 'rowspan', | |||||
| 'scope', | |||||
| 'class', | |||||
| 'style' | |||||
| ]; | |||||
| if (typeof window !== 'undefined') { | |||||
| DOMPurify.addHook('afterSanitizeAttributes', (node) => { | |||||
| if (node.tagName === 'A' && node.getAttribute('target') === '_blank') { | |||||
| node.setAttribute('rel', 'noopener noreferrer'); | |||||
| } | |||||
| }); | |||||
| } | |||||
| /** | |||||
| * Strict allow-list sanitize for API / i18n HTML before innerHTML. | |||||
| * Scripts, event handlers, iframes, and javascript: URLs are dropped. | |||||
| */ | |||||
| export function sanitizeHtml(dirty) { | |||||
| if (dirty == null) { | |||||
| return ''; | |||||
| } | |||||
| return DOMPurify.sanitize(String(dirty), { | |||||
| ALLOWED_TAGS, | |||||
| ALLOWED_ATTR, | |||||
| ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto):|[^a-z]|[a-z+.-]+(?:[^a-z+.-:]|$))/i | |||||
| }); | |||||
| } | |||||
| @@ -0,0 +1,53 @@ | |||||
| import { sanitizeHtml } from './sanitizeHtml'; | |||||
| describe('sanitizeHtml', () => { | |||||
| test('strips script tags and event handlers', () => { | |||||
| const clean = sanitizeHtml('<p onclick="alert(1)">Hi<script>alert(1)</script></p>'); | |||||
| expect(clean).toContain('Hi'); | |||||
| expect(clean.toLowerCase()).not.toContain('script'); | |||||
| expect(clean.toLowerCase()).not.toContain('onclick'); | |||||
| expect(clean).not.toContain('alert(1)'); | |||||
| }); | |||||
| test('strips javascript URLs and iframes', () => { | |||||
| const clean = sanitizeHtml( | |||||
| '<a href="javascript:alert(1)">x</a><iframe src="https://evil.example"></iframe>' | |||||
| ); | |||||
| expect(clean.toLowerCase()).not.toContain('javascript'); | |||||
| expect(clean.toLowerCase()).not.toContain('iframe'); | |||||
| expect(clean).not.toContain('evil.example'); | |||||
| }); | |||||
| test('keeps safe formatting, https links, and tables', () => { | |||||
| const clean = sanitizeHtml( | |||||
| '<p>Hello <strong>world</strong></p><a href="https://pnsps.gld.gov.hk/">PNSPS</a><table><tr><td>cell</td></tr></table>' | |||||
| ); | |||||
| expect(clean).toContain('<p>'); | |||||
| expect(clean).toMatch(/<(strong|b)>world<\/(strong|b)>/); | |||||
| expect(clean).toContain('https://pnsps.gld.gov.hk/'); | |||||
| expect(clean).toContain('cell'); | |||||
| }); | |||||
| test('keeps mailto links', () => { | |||||
| const clean = sanitizeHtml('<a href="mailto:[email protected]">mail</a>'); | |||||
| expect(clean).toContain('mailto:[email protected]'); | |||||
| }); | |||||
| test('adds rel on target=_blank links', () => { | |||||
| const clean = sanitizeHtml('<a href="https://egazette.gld.gov.hk/en" target="_blank">Gazette</a>'); | |||||
| expect(clean).toContain('target="_blank"'); | |||||
| expect(clean).toContain('noopener'); | |||||
| }); | |||||
| test('null and empty become empty string', () => { | |||||
| expect(sanitizeHtml(null)).toBe(''); | |||||
| expect(sanitizeHtml(undefined)).toBe(''); | |||||
| expect(sanitizeHtml('')).toBe(''); | |||||
| }); | |||||
| test('img onerror is dropped', () => { | |||||
| const clean = sanitizeHtml('<img src="https://example.com/a.png" onerror="alert(1)">'); | |||||
| expect(clean.toLowerCase()).not.toContain('onerror'); | |||||
| expect(clean).not.toContain('alert(1)'); | |||||
| }); | |||||
| }); | |||||
| @@ -2748,7 +2748,7 @@ | |||||
| dependencies: | dependencies: | ||||
| "@types/jest" "*" | "@types/jest" "*" | ||||
| "@types/trusted-types@^2.0.2": | |||||
| "@types/trusted-types@^2.0.2", "@types/trusted-types@^2.0.7": | |||||
| version "2.0.7" | version "2.0.7" | ||||
| resolved "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz" | resolved "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz" | ||||
| integrity sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw== | integrity sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw== | ||||
| @@ -4680,6 +4680,13 @@ domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: | |||||
| dependencies: | dependencies: | ||||
| domelementtype "^2.2.0" | domelementtype "^2.2.0" | ||||
| dompurify@^3.4.15: | |||||
| version "3.4.15" | |||||
| resolved "https://registry.npmjs.org/dompurify/-/dompurify-3.4.15.tgz" | |||||
| integrity sha512-EUBjM+B+lkDE41iE82DDSCfkoPGfXx8IxFxPMjNzm/Uk4xDet77rTN9wqlxlVg71kK7XGuUMv6wUxJUwwv+Xyw== | |||||
| optionalDependencies: | |||||
| "@types/trusted-types" "^2.0.7" | |||||
| domutils@^1.7.0: | domutils@^1.7.0: | ||||
| version "1.7.0" | version "1.7.0" | ||||
| resolved "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz" | resolved "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz" | ||||
| @@ -5718,6 +5725,11 @@ fs.realpath@^1.0.0: | |||||
| resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" | resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" | ||||
| integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== | integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== | ||||
| fsevents@^2.3.2, fsevents@~2.3.2: | |||||
| version "2.3.3" | |||||
| resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz" | |||||
| integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== | |||||
| function-bind@^1.1.2: | function-bind@^1.1.2: | ||||
| version "1.1.2" | version "1.1.2" | ||||
| resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" | resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" | ||||