From ff1b5cc75b1719ef47ddcf53f1946426951521b9 Mon Sep 17 00:00:00 2001 From: Kevin Foong <55353265+kevin9foong@users.noreply.github.com> Date: Wed, 20 May 2026 17:25:25 +0800 Subject: [PATCH 1/7] feat: default CreateFormModal to MRF with escape-hatch link to storage mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When mrf-cutover is on, the create-form details screen no longer offers a response-mode choice — Multirespondent is the implicit default. An "old version of FormSG" link composed from the user's beta flags navigates to a new storage-mode-only screen (title + create + back). Submitting routes through the existing storage-mode mutation. Closes #9453. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../CreateFormModal.stories.tsx | 54 ++++++++- .../CreateFormDetailsScreen.tsx | 110 ++++++++++-------- .../CreateFormModalContent.tsx | 4 + .../CreateFormStorageModeScreen.tsx | 107 +++++++++++++++++ .../EscapeHatchLink.tsx | 26 +++++ 5 files changed, 252 insertions(+), 49 deletions(-) create mode 100644 apps/frontend/src/features/workspace/components/CreateFormModal/CreateFormModalContent/CreateFormStorageModeScreen.tsx create mode 100644 apps/frontend/src/features/workspace/components/CreateFormModal/CreateFormModalContent/EscapeHatchLink.tsx diff --git a/apps/frontend/src/features/workspace/components/CreateFormModal/CreateFormModal.stories.tsx b/apps/frontend/src/features/workspace/components/CreateFormModal/CreateFormModal.stories.tsx index 424830a7b7..0a1210989b 100644 --- a/apps/frontend/src/features/workspace/components/CreateFormModal/CreateFormModal.stories.tsx +++ b/apps/frontend/src/features/workspace/components/CreateFormModal/CreateFormModal.stories.tsx @@ -9,13 +9,15 @@ import { useClipboard, useDisclosure, } from '@chakra-ui/react' +import { GrowthBook, GrowthBookProvider } from '@growthbook/growthbook-react' import { Meta, StoryFn } from '@storybook/react' +import { featureFlags } from 'formsg-shared/constants' import { UserId } from 'formsg-shared/types' import { PublicFormViewDto } from 'formsg-shared/types/form' import { Workspace, WorkspaceId } from 'formsg-shared/types/workspace' -import { userHandlers } from '~/mocks/msw/handlers/user' +import { getUser, MOCK_USER, userHandlers } from '~/mocks/msw/handlers/user' import { ApiError } from '~typings/core' @@ -81,6 +83,56 @@ const Template: StoryFn = (args) => { } export const Default = Template.bind({}) +const mrfCutoverOn = new GrowthBook({ + features: { [featureFlags.mrfCutover]: { defaultValue: true } }, +}) + +export const MrfCutoverOn = Template.bind({}) +MrfCutoverOn.decorators = [ + (Story) => ( + + + + ), +] + +export const MrfCutoverOnChildrenBeta = Template.bind({}) +MrfCutoverOnChildrenBeta.decorators = [ + (Story) => ( + + + + ), +] +MrfCutoverOnChildrenBeta.parameters = { + msw: [ + getUser({ + delay: 0, + mockUser: { ...MOCK_USER, betaFlags: { children: true } }, + }), + ], +} + +export const MrfCutoverOnWebhookV1Beta = Template.bind({}) +MrfCutoverOnWebhookV1Beta.decorators = [ + (Story) => ( + + + + ), +] +MrfCutoverOnWebhookV1Beta.parameters = { + msw: [ + getUser({ + delay: 0, + mockUser: { + ...MOCK_USER, + betaFlags: { createStorageModeForV1Webhook: true }, + }, + }), + ], +} + export const StorageModeAckScreen = () => { const secretKey = 'mock-secret-key' diff --git a/apps/frontend/src/features/workspace/components/CreateFormModal/CreateFormModalContent/CreateFormDetailsScreen.tsx b/apps/frontend/src/features/workspace/components/CreateFormModal/CreateFormModalContent/CreateFormDetailsScreen.tsx index 7bd9c253a1..35ba7cd1fd 100644 --- a/apps/frontend/src/features/workspace/components/CreateFormModal/CreateFormModalContent/CreateFormDetailsScreen.tsx +++ b/apps/frontend/src/features/workspace/components/CreateFormModal/CreateFormModalContent/CreateFormDetailsScreen.tsx @@ -29,6 +29,7 @@ import { } from '../CreateFormWizardContext' import { EmailFormRecipientsInput } from './EmailFormRecipientsInput' +import { EscapeHatchLink } from './EscapeHatchLink' import { FormResponseOptions } from './FormResponseOptions' /** The length of form title to start showing warning text */ @@ -63,6 +64,8 @@ export const CreateFormDetailsScreen = (): JSX.Element => { modalHeader, isSingpass, hasMyInfoChildren, + isMrfCutoverEnabled, + goToStorageModeDetails, } = useCreateFormWizard() const { register, @@ -113,63 +116,74 @@ export const CreateFormDetailsScreen = (): JSX.Element => { ) : null} - - - {t('features.workspace.modals.forms.create.details.type.label')} - - - ( - - )} - rules={{ - required: t( - 'features.workspace.modals.forms.create.errors.responseMode.required', - ), - }} - /> - - {errors.responseMode?.message} - {hasMyInfoChildren && ( - - {t( - 'features.workspace.modals.forms.create.errors.noMyInfoChildrenInMrf', - )} - - )} - - {(responseModeValue === FormResponseMode.Encrypt || - responseModeValue === FormResponseMode.Email) && ( + {isMrfCutoverEnabled ? ( + + ) : ( - {t( - 'features.workspace.modals.forms.create.details.notifications.label', - )} + {t('features.workspace.modals.forms.create.details.type.label')} - + + ( + + )} + rules={{ + required: t( + 'features.workspace.modals.forms.create.errors.responseMode.required', + ), + }} + /> + + + {errors.responseMode?.message} + + {hasMyInfoChildren && ( + + {t( + 'features.workspace.modals.forms.create.errors.noMyInfoChildrenInMrf', + )} + + )} )} + {!isMrfCutoverEnabled && + (responseModeValue === FormResponseMode.Encrypt || + responseModeValue === FormResponseMode.Email) && ( + + + {t( + 'features.workspace.modals.forms.create.details.notifications.label', + )} + + + + )} + + + + ) +} diff --git a/apps/frontend/src/features/workspace/components/CreateFormModal/CreateFormModalContent/EscapeHatchLink.tsx b/apps/frontend/src/features/workspace/components/CreateFormModal/CreateFormModalContent/EscapeHatchLink.tsx new file mode 100644 index 0000000000..f357ee6a7e --- /dev/null +++ b/apps/frontend/src/features/workspace/components/CreateFormModal/CreateFormModalContent/EscapeHatchLink.tsx @@ -0,0 +1,26 @@ +import { Text } from '@chakra-ui/react' + +import { composeEscapeHatchCopy } from '~utils/escapeHatchCopy' +import Link from '~components/Link' + +import { useUser } from '~features/user/queries' + +interface EscapeHatchLinkProps { + onClick: () => void +} + +export const EscapeHatchLink = ({ + onClick, +}: EscapeHatchLinkProps): JSX.Element => { + const { user } = useUser() + const { prefix, linkText, suffix } = composeEscapeHatchCopy(user?.betaFlags) + return ( + + {prefix} + + {linkText} + + {suffix} + + ) +} From 6bdfce5ae4d44fcb1bcb625ccf9b4a0f107b6bb4 Mon Sep 17 00:00:00 2001 From: Kevin Foong <55353265+kevin9foong@users.noreply.github.com> Date: Wed, 20 May 2026 17:40:14 +0800 Subject: [PATCH 2/7] fix(create-form-modal): match Figma fidelity for cutover flow - Wrap escape-hatch link in an info InlineMessage to match Figma - Storage-mode page header reads "Set up a Storage mode form" - Add storage-mode subtitle explaining that storage mode is outdated Co-Authored-By: Claude Opus 4.7 (1M context) --- .../CreateFormStorageModeScreen.tsx | 7 ++++++- .../CreateFormModalContent/EscapeHatchLink.tsx | 17 ++++++++++------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/apps/frontend/src/features/workspace/components/CreateFormModal/CreateFormModalContent/CreateFormStorageModeScreen.tsx b/apps/frontend/src/features/workspace/components/CreateFormModal/CreateFormModalContent/CreateFormStorageModeScreen.tsx index f741fc8383..166fd91561 100644 --- a/apps/frontend/src/features/workspace/components/CreateFormModal/CreateFormModalContent/CreateFormStorageModeScreen.tsx +++ b/apps/frontend/src/features/workspace/components/CreateFormModal/CreateFormModalContent/CreateFormStorageModeScreen.tsx @@ -56,12 +56,17 @@ export const CreateFormStorageModeScreen = (): JSX.Element => { onClick={goToMrfDetails} mr="0.5rem" /> - {t('features.workspace.modals.forms.create.title.setup')} + Set up a Storage mode form + + Storage mode is outdated and no longer receives new features. Only + use this if you need a feature not yet available in the current + version. + {t('features.workspace.modals.forms.create.details.name.label')} diff --git a/apps/frontend/src/features/workspace/components/CreateFormModal/CreateFormModalContent/EscapeHatchLink.tsx b/apps/frontend/src/features/workspace/components/CreateFormModal/CreateFormModalContent/EscapeHatchLink.tsx index f357ee6a7e..d84520667c 100644 --- a/apps/frontend/src/features/workspace/components/CreateFormModal/CreateFormModalContent/EscapeHatchLink.tsx +++ b/apps/frontend/src/features/workspace/components/CreateFormModal/CreateFormModalContent/EscapeHatchLink.tsx @@ -1,6 +1,7 @@ import { Text } from '@chakra-ui/react' import { composeEscapeHatchCopy } from '~utils/escapeHatchCopy' +import InlineMessage from '~components/InlineMessage' import Link from '~components/Link' import { useUser } from '~features/user/queries' @@ -15,12 +16,14 @@ export const EscapeHatchLink = ({ const { user } = useUser() const { prefix, linkText, suffix } = composeEscapeHatchCopy(user?.betaFlags) return ( - - {prefix} - - {linkText} - - {suffix} - + + + {prefix} + + {linkText} + + {suffix} + + ) } From f1b5ba390e0de537b8f34044c0299e6f297cac30 Mon Sep 17 00:00:00 2001 From: Kevin Foong <55353265+kevin9foong@users.noreply.github.com> Date: Wed, 20 May 2026 17:46:50 +0800 Subject: [PATCH 3/7] fix(create-form-modal): hide data-classification infobox + use 2.5rem spacing in cutover flow The MRF default screen previously showed both the escape-hatch infobox and the existing data-classification infobox, cluttering the layout. The cutover Figma drops the latter and uses 2.5rem rhythm around the escape-hatch infobox and between input/button on the storage-mode page. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../CreateFormModalContent/CreateFormDetailsScreen.tsx | 8 +++++--- .../CreateFormStorageModeScreen.tsx | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/frontend/src/features/workspace/components/CreateFormModal/CreateFormModalContent/CreateFormDetailsScreen.tsx b/apps/frontend/src/features/workspace/components/CreateFormModal/CreateFormModalContent/CreateFormDetailsScreen.tsx index 35ba7cd1fd..af16ac68b6 100644 --- a/apps/frontend/src/features/workspace/components/CreateFormModal/CreateFormModalContent/CreateFormDetailsScreen.tsx +++ b/apps/frontend/src/features/workspace/components/CreateFormModal/CreateFormModalContent/CreateFormDetailsScreen.tsx @@ -2,6 +2,7 @@ import { Controller, RegisterOptions } from 'react-hook-form' import { useTranslation } from 'react-i18next' import { BiRightArrowAlt } from 'react-icons/bi' import { + Box, Container, FormControl, ModalBody, @@ -62,7 +63,6 @@ export const CreateFormDetailsScreen = (): JSX.Element => { isLoading, isFetching, modalHeader, - isSingpass, hasMyInfoChildren, isMrfCutoverEnabled, goToStorageModeDetails, @@ -117,7 +117,9 @@ export const CreateFormDetailsScreen = (): JSX.Element => { ) : null} {isMrfCutoverEnabled ? ( - + + + ) : ( { )} - + {!isMrfCutoverEnabled && }