diff --git a/CHANGELOG.md b/CHANGELOG.md index 08dad0f49c..686e718090 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,29 @@ All notable changes to this project will be documented in this file. Dates are d Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). +#### [v6.68.0](https://github.com/opengovsg/FormSG/compare/v6.67.1...v6.68.0) + +- chore(deps-dev): bump @types/lodash from 4.14.195 to 4.14.196 in /shared [`#6570`](https://github.com/opengovsg/FormSG/pull/6570) +- feat: rm bot protection for GSIB users [`#6568`](https://github.com/opengovsg/FormSG/pull/6568) +- chore: lint and update commands in README [`#6567`](https://github.com/opengovsg/FormSG/pull/6567) +- feat: MyInfo over sgID (frontend) [`#6447`](https://github.com/opengovsg/FormSG/pull/6447) +- chore: rm redundant type assertion in url validation [`#6566`](https://github.com/opengovsg/FormSG/pull/6566) +- feat: updated uen validator (ref stdnum) and tests cases [`#6534`](https://github.com/opengovsg/FormSG/pull/6534) +- fix: subtle bugs in MyInfo Child and UI copyedits [`#6562`](https://github.com/opengovsg/FormSG/pull/6562) +- feat: react router params validator for mongo ids [`#6561`](https://github.com/opengovsg/FormSG/pull/6561) +- build: merge v6.67.1 into develop [`#6559`](https://github.com/opengovsg/FormSG/pull/6559) +- build: release v6.67.1 [`#6557`](https://github.com/opengovsg/FormSG/pull/6557) +- fix: github warning about unused import [`#6558`](https://github.com/opengovsg/FormSG/pull/6558) + #### [v6.67.1](https://github.com/opengovsg/FormSG/compare/v6.67.0...v6.67.1) +> 20 July 2023 + - fix: paynow payout being ignored [`#6556`](https://github.com/opengovsg/FormSG/pull/6556) - build: release v6.67.0 [`#6555`](https://github.com/opengovsg/FormSG/pull/6555) +- chore: bump version to 6.67.1 [`b641094`](https://github.com/opengovsg/FormSG/commit/b641094d40a67a888e3a8ac967a43f9d1732bd49) -#### [v6.67.0](https://github.com/opengovsg/FormSG/compare/v6.66.1...v6.67.0) +#### [v6.67.0](https://github.com/opengovsg/FormSG/compare/v6.66.0...v6.67.0) > 20 July 2023 @@ -19,20 +36,8 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). - build: release v6.66.0 [`#6550`](https://github.com/opengovsg/FormSG/pull/6550) - feat: MyInfo Children compound fields beta [`#6523`](https://github.com/opengovsg/FormSG/pull/6523) - ci: adjust timeouts for playwright pipe [`#6551`](https://github.com/opengovsg/FormSG/pull/6551) -- fix: use proof-of-payment for payment receipt/invoice [`#6549`](https://github.com/opengovsg/FormSG/pull/6549) -- build(deps-dev): bump word-wrap from 1.2.3 to 1.2.4 in /frontend [`#6548`](https://github.com/opengovsg/FormSG/pull/6548) -- fix(deps): bump word-wrap from 1.2.3 to 1.2.4 [`#6547`](https://github.com/opengovsg/FormSG/pull/6547) -- build(deps): bump winston-cloudwatch to v6.2.0 [`#6545`](https://github.com/opengovsg/FormSG/pull/6545) -- chore: bump version to v6.66.0 [`0233ba8`](https://github.com/opengovsg/FormSG/commit/0233ba8018532d4978d13717fac065f81e2eb515) - chore: bump version to v6.67.0 [`6ca03c6`](https://github.com/opengovsg/FormSG/commit/6ca03c68cb162a4ae3af01f172425b3305742ae0) -#### [v6.66.1](https://github.com/opengovsg/FormSG/compare/v6.66.0...v6.66.1) - -> 18 July 2023 - -- revert: build(deps): bump winston-cloudwatch version to 6.2.0 [`#6542`](https://github.com/opengovsg/FormSG/pull/6542) -- chore: bump version to v6.66.0 [`b3a9818`](https://github.com/opengovsg/FormSG/commit/b3a9818762e334bc7126c029cdba63156bac85ed) - #### [v6.66.0](https://github.com/opengovsg/FormSG/compare/v6.65.0...v6.66.0) > 19 July 2023 @@ -405,7 +410,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). - fix: only return previous payment id [`#6210`](https://github.com/opengovsg/FormSG/pull/6210) - build: merge release v6.45.0 to develop [`#6205`](https://github.com/opengovsg/FormSG/pull/6205) - build: release v6.45.0 [`#6200`](https://github.com/opengovsg/FormSG/pull/6200) -- chore: bump version to v6.46.0 [`767e7e5`](https://github.com/opengovsg/FormSG/commit/767e7e55c3e8f058963e890b1c249704d6db1459) +- chore: bump version to v6.46.0 [`87fda0e`](https://github.com/opengovsg/FormSG/commit/87fda0e0ec4c937350ebd013a4a9762f4acee802) #### [v6.45.0](https://github.com/opengovsg/FormSG/compare/v6.44.1...v6.45.0) diff --git a/CREDITS.md b/CREDITS.md index d2029eaa44..ba986f91cc 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -2200,6 +2200,35 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- +## Project +python_stdnum + +### Source +https://github.com/arthurdejong/python-stdnum/ + +### License +uen.py - functions for handling Singapore UEN numbers +coding: utf-8 + +Copyright (C) 2020 Leandro Regueiro +Copyright (C) 2020 Arthur de Jong + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA + + ## Project toastr diff --git a/README.md b/README.md index 541882a909..ce8f23e1a3 100755 --- a/README.md +++ b/README.md @@ -197,7 +197,7 @@ npm run test:frontend #### End-to-end tests ```bash -npm run test-e2e +npm run test:e2e-v2 ``` will build both the frontend and backend then run our end-to-end tests. The tests are located at [`__tests__/e2e`](./__tests__/e2e). You will need to stop the Docker dev container to be able to run the end-to-end tests. @@ -205,7 +205,7 @@ will build both the frontend and backend then run our end-to-end tests. The test If you do not need to rebuild the frontend and backend, you can run ```bash -npm run test-e2e-ci +npx playwright test ``` #### Cross-browser testing diff --git a/__tests__/e2e/constants/field.ts b/__tests__/e2e/constants/field.ts index 6689825aa7..1f51524df8 100644 --- a/__tests__/e2e/constants/field.ts +++ b/__tests__/e2e/constants/field.ts @@ -314,7 +314,7 @@ export const ALL_FIELDS: E2eFieldMetadata[] = [ { title: 'Some UEN here please', fieldType: BasicField.Uen, - val: 'T09LL0001B', + val: 'T09SS0050A', }, { title: 'Yes or No?', diff --git a/__tests__/e2e/helpers/createForm.ts b/__tests__/e2e/helpers/createForm.ts index 443cf6ae3a..62fc941040 100644 --- a/__tests__/e2e/helpers/createForm.ts +++ b/__tests__/e2e/helpers/createForm.ts @@ -302,18 +302,12 @@ const addAuthSettings = async ( page.getByRole('heading', { name: 'Enable Singpass authentication' }), ).toBeVisible() - const name = `Singpass${ - formSettings.authType === FormAuthType.SP - ? '' - : formSettings.authType === FormAuthType.SGID - ? ' App-only Login' - : formSettings.authType === FormAuthType.MyInfo - ? ' with MyInfo' - : ' (Corporate)' - }` - await page - .locator('label', { has: page.getByRole('radio', { name }) }) + .locator('label', { + has: page.locator( + `input[type='radio'][value='${formSettings.authType}']`, + ), + }) .first() // Since 'Singpass' will match all radio options, pick the first matching one. .click({ position: { x: 1, y: 1 } }) // Clicking the center of the sgid button launches the sgid contact form, put this here until we get rid of the link diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 8bc1a55fd5..60aec3a455 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "form-frontend", - "version": "6.67.0", + "version": "6.68.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "form-frontend", - "version": "6.67.0", + "version": "6.68.0", "hasInstallScript": true, "dependencies": { "@chakra-ui/react": "^1.8.6", diff --git a/frontend/package.json b/frontend/package.json index 20b768ace6..f8cbc954ce 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "form-frontend", - "version": "6.67.0", + "version": "6.68.0", "homepage": ".", "private": true, "dependencies": { diff --git a/frontend/src/app/AppRouter.tsx b/frontend/src/app/AppRouter.tsx index b34717e646..9cab6884ec 100644 --- a/frontend/src/app/AppRouter.tsx +++ b/frontend/src/app/AppRouter.tsx @@ -40,6 +40,7 @@ import { FormPaymentPage } from '~features/public-form/components/FormPaymentPag import { BillingPage } from '~features/user/billing' import { HashRouterElement } from './HashRouterElement' +import { ParamIdValidator } from './ParamIdValidator' import { PrivateElement } from './PrivateElement' import { PublicElement } from './PublicElement' @@ -109,20 +110,38 @@ export const AppRouter = (): JSX.Element => { } />} + element={ + } />} + /> + } /> } />} + element={ + } /> + } + /> + } /> } />} + element={ + } />} + /> + } /> } />} + element={ + } />} + /> + } > } /> }> @@ -147,11 +166,19 @@ export const AppRouter = (): JSX.Element => { } />} + element={ + } />} + /> + } /> } />} + element={ + } />} + /> + } /> } /> diff --git a/frontend/src/app/ParamIdValidator.tsx b/frontend/src/app/ParamIdValidator.tsx new file mode 100644 index 0000000000..34a381f61d --- /dev/null +++ b/frontend/src/app/ParamIdValidator.tsx @@ -0,0 +1,26 @@ +import { useParams } from 'react-router-dom' + +import { MONGODB_ID_REGEX } from '~constants/routes' + +import NotFoundErrorPage from '~pages/NotFoundError' + +interface ParamIdValidatorProps { + element: React.ReactElement +} + +export const ParamIdValidator = ({ element }: ParamIdValidatorProps) => { + const { formId, submissionId, paymentId } = useParams() + + const isInvalidMongoId = (id?: string) => id && !id.match(MONGODB_ID_REGEX) + + // Bootstrap route validation as suggested by React Router Docs: + // https://reactrouter.com/en/main/start/faq#what-happened-to-regexp-routes-paths + if ( + isInvalidMongoId(formId) || + isInvalidMongoId(submissionId) || + isInvalidMongoId(paymentId) + ) + return + + return element +} diff --git a/frontend/src/constants/routes.ts b/frontend/src/constants/routes.ts index 7c56f70e51..24e9ef6577 100644 --- a/frontend/src/constants/routes.ts +++ b/frontend/src/constants/routes.ts @@ -13,7 +13,7 @@ export const BILLING_ROUTE = '/billing' // the regex in PublicFormPage. export const PUBLICFORM_ROUTE = '/:formId' export const USE_TEMPLATE_REDIRECT_SUBROUTE = 'use-template' -export const FORMID_REGEX = /^([a-fA-F0-9]{24})$/ +export const MONGODB_ID_REGEX = /^([a-fA-F0-9]{24})$/ export const ADMINFORM_ROUTE = '/admin/form' /** Build tab has no subroute, its the index admin form route. */ diff --git a/frontend/src/features/admin-form/common/queries.ts b/frontend/src/features/admin-form/common/queries.ts index fb0db5c058..37d638b91f 100644 --- a/frontend/src/features/admin-form/common/queries.ts +++ b/frontend/src/features/admin-form/common/queries.ts @@ -6,7 +6,7 @@ import { AdminFormDto, PreviewFormViewDto } from '~shared/types/form/form' import { ApiError } from '~typings/core' -import { FORMID_REGEX } from '~constants/routes' +import { MONGODB_ID_REGEX } from '~constants/routes' import { useUser } from '~features/user/queries' @@ -134,7 +134,7 @@ export const usePreviewForm = ( { // Treat preview form as static on load. staleTime: Infinity, - enabled: FORMID_REGEX.test(formId) && enabled, + enabled: MONGODB_ID_REGEX.test(formId) && enabled, }, ) } @@ -150,7 +150,7 @@ export const useFormTemplate = ( { // Treat preview form as static on load. staleTime: Infinity, - enabled: FORMID_REGEX.test(formId) && enabled, + enabled: MONGODB_ID_REGEX.test(formId) && enabled, }, ) } diff --git a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/EditMyInfoChildren/EditMyInfoChildren.tsx b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/EditMyInfoChildren/EditMyInfoChildren.tsx index 22e674c320..9706cb9a69 100644 --- a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/EditMyInfoChildren/EditMyInfoChildren.tsx +++ b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/EditMyInfoChildren/EditMyInfoChildren.tsx @@ -97,9 +97,7 @@ export const EditMyInfoChildren = ({ - - Collect the following child information - + Collect the following child data = new Set([ + MyInfoAttribute.Name, + MyInfoAttribute.DateOfBirth, + MyInfoAttribute.PassportNumber, + MyInfoAttribute.PassportExpiryDate, + // This is disabled due to MyInfo and sgID-MyInfo not using the same + // phone number formats. + // MyInfo phone numbers support country code, while sgID-MyInfo does not. + // MyInfoAttribute.MobileNo, + + // FRM-1189: This is disabled due to slight different formatting. + // We format the Myinfo response by separates lines in addresses with comma + // Whereas sgID separates each line with newline. + // This should be enabled in future work + // MyInfoAttribute.RegisteredAddress, +]) + +/** + * If sgID is used, checks if the corresponding + * MyInfo field is supported by sgID. + */ +const sgIDUnSupported = ( + form: AdminFormDto | undefined, + fieldType: MyInfoAttribute, +): boolean => + form?.authType === FormAuthType.SGID_MyInfo && !SGID_SUPPORTED.has(fieldType) + export const MyInfoFieldPanel = () => { const { data: form, isLoading } = useCreateTabForm() // myInfo should be disabled if @@ -38,12 +70,21 @@ export const MyInfoFieldPanel = () => { // 3. # of myInfo fields >= 30 const isMyInfoDisabled = useMemo( () => - form?.responseMode !== FormResponseMode.Email || - form?.authType !== FormAuthType.MyInfo || - (form ? form.form_fields.filter(isMyInfo).length >= 30 : true), + form + ? form.form_fields.filter(isMyInfo).length >= 30 || + form.responseMode !== FormResponseMode.Email || + (form.authType !== FormAuthType.MyInfo && + form.authType !== FormAuthType.SGID_MyInfo) + : true, [form], ) const isDisabled = isMyInfoDisabled || isLoading + const isDisabledCheck = useCallback( + (fieldType: MyInfoAttribute): boolean => { + return isDisabled || sgIDUnSupported(form, fieldType) + }, + [form, isDisabled], + ) const { user } = useUser() return ( @@ -56,7 +97,7 @@ export const MyInfoFieldPanel = () => { {CREATE_MYINFO_PERSONAL_FIELDS_ORDERED.map((fieldType, index) => ( @@ -73,7 +114,7 @@ export const MyInfoFieldPanel = () => { {CREATE_MYINFO_CONTACT_FIELDS_ORDERED.map((fieldType, index) => ( @@ -91,7 +132,7 @@ export const MyInfoFieldPanel = () => { (fieldType, index) => ( @@ -109,7 +150,7 @@ export const MyInfoFieldPanel = () => { {CREATE_MYINFO_MARRIAGE_FIELDS_ORDERED.map((fieldType, index) => ( @@ -128,7 +169,7 @@ export const MyInfoFieldPanel = () => { (fieldType, index) => ( @@ -154,7 +195,8 @@ const MyInfoText = ({ responseMode, form_fields, }: MyInfoTextProps): JSX.Element => { - const isMyInfoDisabled = authType !== FormAuthType.MyInfo + const isMyInfoDisabled = + authType !== FormAuthType.MyInfo && authType !== FormAuthType.SGID_MyInfo const numMyInfoFields = useMemo( () => form_fields.filter((ff) => isMyInfo(ff)).length, [form_fields], @@ -178,7 +220,10 @@ const MyInfoText = ({ return ( - {`Only 30 MyInfo fields are allowed in Email mode (${numMyInfoFields}/30).`}{' '} + {authType === FormAuthType.SGID_MyInfo + ? 'Some MyInfo fields are not yet supported in your selected authentication type. ' + : null} + {`Only 30 MyInfo fields are allowed in Email mode (${numMyInfoFields}/30). `} Learn more diff --git a/frontend/src/features/admin-form/create/builder-and-design/constants.ts b/frontend/src/features/admin-form/create/builder-and-design/constants.ts index 8d72f19b21..82dc078800 100644 --- a/frontend/src/features/admin-form/create/builder-and-design/constants.ts +++ b/frontend/src/features/admin-form/create/builder-and-design/constants.ts @@ -141,6 +141,9 @@ export const CREATE_MYINFO_CHILDREN_SUBFIELDS_OPTIONS: { label: string }[] = Object.values(MyInfoChildAttributes) .filter((e) => e !== MyInfoChildAttributes.ChildName) + // TODO awaiting approval from MyInfo to get child vaccination status. + // Disabling in the frontend for now. + .filter((e) => e !== MyInfoChildAttributes.ChildVaxxStatus) .map((value) => { return { value, diff --git a/frontend/src/features/admin-form/settings/components/AuthSettingsSection/AuthSettingsSection.tsx b/frontend/src/features/admin-form/settings/components/AuthSettingsSection/AuthSettingsSection.tsx index f90c3b20f7..f824086b98 100644 --- a/frontend/src/features/admin-form/settings/components/AuthSettingsSection/AuthSettingsSection.tsx +++ b/frontend/src/features/admin-form/settings/components/AuthSettingsSection/AuthSettingsSection.tsx @@ -6,28 +6,22 @@ import { useMemo, useState, } from 'react' -import { Box, Flex, Icon, Skeleton } from '@chakra-ui/react' +import { Box, Flex, Skeleton, Spacer, Text } from '@chakra-ui/react' import { FormAuthType, FormSettings, FormStatus } from '~shared/types/form' -import { BxsHelpCircle } from '~assets/icons/BxsHelpCircle' -import { OGP_SGID } from '~constants/links' +import { GUIDE_SPCP_ESRVCID } from '~constants/links' import InlineMessage from '~components/InlineMessage' import Link from '~components/Link' import Radio from '~components/Radio' -import Tooltip from '~components/Tooltip' +import { Tag } from '~components/Tag' import { useAdminForm } from '~features/admin-form/common/queries' import { isMyInfo } from '~features/myinfo/utils' import { useMutateFormSettings } from '../../mutations' -import { - AUTHTYPE_TO_TEXT, - CP_TOOLTIP, - SGID_TOOLTIP, - STORAGE_MODE_AUTHTYPES, -} from './constants' +import { AUTHTYPE_TO_TEXT, STORAGE_MODE_AUTHTYPES } from './constants' import { EsrvcIdBox } from './EsrvcIdBox' const esrvcidRequired = (authType: FormAuthType) => { @@ -131,14 +125,31 @@ export const AuthSettingsSection = ({ return ( + + Authenticate respondents by NRIC.{' '} + e.stopPropagation()} + > + Learn more about Singpass authentication + + {isFormPublic ? ( - + To change authentication method, close your form to new responses. ) : containsMyInfoFields ? ( - - Authentication method cannot be changed without first removing MyInfo - fields. You can still update your e-service ID. + + To change authentication method, remove existing Myinfo fields on your + form. You can still update your e-service ID. ) : null} - + {text} - {authType === FormAuthType.SGID ? ( + {authType === FormAuthType.SGID || + authType === FormAuthType.SGID_MyInfo ? ( <> - - - - e.stopPropagation()} - > - Contact us to find out more - + + + Free + ) : null} - {authType === FormAuthType.CP ? ( - - - - ) : null} diff --git a/frontend/src/features/admin-form/settings/components/AuthSettingsSection/EsrvcIdBox.tsx b/frontend/src/features/admin-form/settings/components/AuthSettingsSection/EsrvcIdBox.tsx index 7052a1d8f2..c7bf034e6f 100644 --- a/frontend/src/features/admin-form/settings/components/AuthSettingsSection/EsrvcIdBox.tsx +++ b/frontend/src/features/admin-form/settings/components/AuthSettingsSection/EsrvcIdBox.tsx @@ -7,16 +7,14 @@ import { InputGroup, InputRightElement, Stack, + Text, VisuallyHidden, } from '@chakra-ui/react' import { FormAuthType, FormSettings } from '~shared/types/form' -import { GUIDE_SPCP_ESRVCID } from '~constants/links' -import { useMdComponents } from '~hooks/useMdComponents' import FormErrorMessage from '~components/FormControl/FormErrorMessage' import Input from '~components/Input' -import { MarkdownText } from '~components/MarkdownText' import Spinner from '~components/Spinner' import { useMutateFormSettings } from '../../mutations' @@ -47,15 +45,6 @@ export const EsrvcIdBox = ({ mode: 'onChange', }) - const mdComponents = useMdComponents({ - styles: { - text: { - textStyle: 'body-2', - color: 'secondary.400', - }, - }, - }) - const onSubmit = handleSubmit(({ esrvcId }) => { if (esrvcId.trim() === initialEsrvcId) return return mutateFormEsrvcId.mutate(esrvcId.trim(), { @@ -74,22 +63,27 @@ export const EsrvcIdBox = ({ const renderedHelperText = useMemo(() => { switch (settings.authType) { case FormAuthType.SP: - return `Find out [how to get your Singpass e-service ID](${GUIDE_SPCP_ESRVCID}).` case FormAuthType.CP: - return `Corppass now uses Singpass to authenticate corporate users. You will still need a separate **Corppass e-service ID**. Find out [how to get your Corppass e-service ID](${GUIDE_SPCP_ESRVCID}).` case FormAuthType.MyInfo: - return `Find out [how to get your MyInfo e-service ID](${GUIDE_SPCP_ESRVCID}).` + return 'Contact spcp.transoffice@accenture.com for your e-service ID' default: return '' } }, [settings.authType]) + const placeHolder = useMemo( + () => + `Enter ${ + settings.authType === FormAuthType.CP ? 'Corppass' : 'Singpass' + } e-service ID`, + [settings.authType], + ) return (
- + {renderedHelperText} - + e-service ID: @@ -111,7 +105,7 @@ export const EsrvcIdBox = ({ })} isDisabled={isDisabled} isReadOnly={mutateFormEsrvcId.isLoading} - placeholder="Enter Singpass e-service ID" + placeholder={placeHolder} /> {errors.esrvcId?.message} diff --git a/frontend/src/features/admin-form/settings/components/AuthSettingsSection/constants.ts b/frontend/src/features/admin-form/settings/components/AuthSettingsSection/constants.ts index 220a6f3300..6a4357d815 100644 --- a/frontend/src/features/admin-form/settings/components/AuthSettingsSection/constants.ts +++ b/frontend/src/features/admin-form/settings/components/AuthSettingsSection/constants.ts @@ -9,29 +9,21 @@ export type StorageFormAuthType = export const STORAGE_MODE_AUTHTYPES: Record = { [FormAuthType.NIL]: 'None', + [FormAuthType.SGID]: 'Singpass App-only Login', [FormAuthType.SP]: 'Singpass', - [FormAuthType.SGID]: 'Singpass App-only Login (Free)', [FormAuthType.CP]: 'Singpass (Corporate)', } // Not using STORAGE_MODE_AUTHTYPES due to wanting a different order. export const EMAIL_MODE_AUTHTYPES: Record = { [FormAuthType.NIL]: 'None', + [FormAuthType.SGID]: 'Singpass App-only Login', + [FormAuthType.SGID_MyInfo]: 'Singpass App-only with Myinfo', [FormAuthType.SP]: 'Singpass', - [FormAuthType.SGID]: 'Singpass App-only Login (Free)', - [FormAuthType.MyInfo]: 'Singpass with MyInfo', + [FormAuthType.MyInfo]: 'Singpass with Myinfo', [FormAuthType.CP]: 'Singpass (Corporate)', } export const AUTHTYPE_TO_TEXT = { [FormResponseMode.Email]: EMAIL_MODE_AUTHTYPES, [FormResponseMode.Encrypt]: STORAGE_MODE_AUTHTYPES, } - -export const SGID_TOOLTIP = `Free Singpass authentication via Singpass app QR\ - code login. Respondents must have the Singpass mobile app installed to log in\ - and submit responses. Password login is not supported. Form admin will receive\ - respondent's NRIC.` - -export const CP_TOOLTIP = `Corppass no longer has its own login page, and now\ - uses Singpass to authenticate corporate users. You will still need a separate\ - Corppass e-service ID.` diff --git a/frontend/src/features/logic/utils/typeguards.ts b/frontend/src/features/logic/utils/typeguards.ts index afda47e954..7bd39d5429 100644 --- a/frontend/src/features/logic/utils/typeguards.ts +++ b/frontend/src/features/logic/utils/typeguards.ts @@ -45,6 +45,6 @@ export const isLogicableField = (args: { export const isValueStringArray = ( value: FormCondition['value'], ): value is string[] => { - // use .some because of limitation of typecript in calling .every() on union of array types: https://github.com/microsoft/TypeScript/issues/44373 + // use .some because of limitation of typescript in calling .every() on union of array types: https://github.com/microsoft/TypeScript/issues/44373 return Array.isArray(value) && !value.some((v) => typeof v === 'number') } diff --git a/frontend/src/features/public-form/PublicFormProvider.tsx b/frontend/src/features/public-form/PublicFormProvider.tsx index 4ec473826a..1e0a06d7b1 100644 --- a/frontend/src/features/public-form/PublicFormProvider.tsx +++ b/frontend/src/features/public-form/PublicFormProvider.tsx @@ -28,7 +28,7 @@ import { PublicFormDto, } from '~shared/types/form' -import { FORMID_REGEX } from '~constants/routes' +import { MONGODB_ID_REGEX } from '~constants/routes' import { useBrowserStm } from '~hooks/payments' import { useTimeout } from '~hooks/useTimeout' import { useToast } from '~hooks/useToast' @@ -93,7 +93,7 @@ export function useCommonFormProvider(formId: string) { return vfnTransaction.transactionId }, [createTransactionMutation, vfnTransaction]) - const isNotFormId = useMemo(() => !FORMID_REGEX.test(formId), [formId]) + const isNotFormId = useMemo(() => !MONGODB_ID_REGEX.test(formId), [formId]) const expiryInMs = useMemo(() => { if (!vfnTransaction?.expireAt) return null @@ -135,9 +135,12 @@ export const PublicFormProvider = ({ } }, [submissionData]) + // Only load catpcha if enabled on form and the user is not on GSIB + const enableCaptcha = data && data.form.hasCaptcha && !data.isIntranetUser + const { data: { captchaPublicKey, turnstileSiteKey, useFetchForSubmissions } = {}, - } = useEnv(/* enabled= */ !!data?.form.hasCaptcha) + } = useEnv(/* enabled= */ enableCaptcha) // Feature flag to control turnstile captcha rollout // defaults to false @@ -156,7 +159,7 @@ export const PublicFormProvider = ({ getTurnstileResponse, containerID: turnstileContainerID, } = useTurnstile({ - sitekey: data?.form.hasCaptcha ? turnstileSiteKey : undefined, + sitekey: enableCaptcha ? turnstileSiteKey : undefined, enableUsage: enableTurnstileFeatureFlag, }) @@ -165,7 +168,7 @@ export const PublicFormProvider = ({ getCaptchaResponse, containerId: recaptchaContainerID, } = useRecaptcha({ - sitekey: data?.form.hasCaptcha ? captchaPublicKey : undefined, + sitekey: enableCaptcha ? captchaPublicKey : undefined, enableUsage: !enableTurnstileFeatureFlag, }) @@ -592,7 +595,7 @@ export const PublicFormProvider = ({ isAuthRequired, captchaContainerId: containerID, expiryInMs, - isLoading: isLoading || (!!data?.form.hasCaptcha && !hasLoaded), + isLoading: isLoading || (!!enableCaptcha && !hasLoaded), isPaymentEnabled, isPreview: false, setNumVisibleFields, diff --git a/frontend/src/features/public-form/components/FormAuth/FormAuth.tsx b/frontend/src/features/public-form/components/FormAuth/FormAuth.tsx index 4bd3f32c30..c495abd56d 100644 --- a/frontend/src/features/public-form/components/FormAuth/FormAuth.tsx +++ b/frontend/src/features/public-form/components/FormAuth/FormAuth.tsx @@ -42,6 +42,7 @@ export const FormAuth = ({ authType }: FormAuthProps): JSX.Element => { 'Corporate entity login is required for this form.\nYour Singpass ID and corporate Entity ID will be included with your form submission.', } case FormAuthType.SGID: + case FormAuthType.SGID_MyInfo: return { authType: 'Singpass app', helpText: diff --git a/frontend/src/features/public-form/queries.ts b/frontend/src/features/public-form/queries.ts index c246ce9f43..c6f5bb2dc3 100644 --- a/frontend/src/features/public-form/queries.ts +++ b/frontend/src/features/public-form/queries.ts @@ -4,7 +4,7 @@ import { PublicFormViewDto } from '~shared/types/form/form' import { ApiError } from '~typings/core' -import { FORMID_REGEX } from '~constants/routes' +import { MONGODB_ID_REGEX } from '~constants/routes' import { getPublicFormView } from './PublicFormService' @@ -26,7 +26,7 @@ export const usePublicFormView = ( { // Treat form as static on load. staleTime: Infinity, - enabled: FORMID_REGEX.test(formId) && enabled, + enabled: MONGODB_ID_REGEX.test(formId) && enabled, }, ) } diff --git a/frontend/src/hooks/useLocalStorage.ts b/frontend/src/hooks/useLocalStorage.ts index a4826d6d1c..b9cc37ae94 100644 --- a/frontend/src/hooks/useLocalStorage.ts +++ b/frontend/src/hooks/useLocalStorage.ts @@ -11,7 +11,7 @@ export const useLocalStorage = ( // Get from local storage then // parse stored json or return initialValue const readValue = useCallback(() => { - // Prevent build error "window is undefined" but keep keep working + // Prevent build error "window is undefined" and keep it working if (typeof window === 'undefined') { return initialValue } diff --git a/frontend/src/hooks/useMdComponents.tsx b/frontend/src/hooks/useMdComponents.tsx index de5a09e06c..35f515cdfd 100644 --- a/frontend/src/hooks/useMdComponents.tsx +++ b/frontend/src/hooks/useMdComponents.tsx @@ -84,7 +84,7 @@ export const useMdComponents = ({ a: ({ node, ...props }) => { const { href } = props const isExternal = - typeof href === 'string' && !href.startsWith(window.location.origin) + (href && !href.startsWith(window.location.origin)) || false return }, diff --git a/frontend/src/templates/Field/ChildrenCompound/ChildrenCompoundField.tsx b/frontend/src/templates/Field/ChildrenCompound/ChildrenCompoundField.tsx index e331d9cd8e..b7a195d8ea 100644 --- a/frontend/src/templates/Field/ChildrenCompound/ChildrenCompoundField.tsx +++ b/frontend/src/templates/Field/ChildrenCompound/ChildrenCompoundField.tsx @@ -124,24 +124,27 @@ export const ChildrenCompoundField = ({ aria-describedby={`children-desc-${schema._id}`} aria-labelledby={`${schema._id}-label`} > - - {fields.map((field, currChildBodyIdx) => ( - - ))} - + <> + + + {fields.map((field, currChildBodyIdx) => ( + + ))} + + {schema.allowMultiple ? (