From 81da65b31fbfd62561e029225a16149541f956b3 Mon Sep 17 00:00:00 2001 From: wanlingt <56983748+wanlingt@users.noreply.github.com> Date: Mon, 25 Jul 2022 10:49:59 +0800 Subject: [PATCH] feat(form-v2/react-to-angular/1): add switch to angular option (#4263) * feat: add switch env endpoint for respondents * feat: add switchEnvMessage for respondents * feat: add AdminSwitchEnvMessage * fix: adjust styling * fix: adjust PublicSwitchEnvMessage margin * ref: hardcode angular in params * feat: add react-query env mutations * fix: amend cookie expiry date * fix: update cookie expiry * fix: navigate to old workspace page * fix: add window reload * fix: mutations * chore: add removal TODOs * fix: underline inline links --- frontend/src/features/env/mutations.ts | 28 +++++++++++++ .../features/public-form/PublicFormService.ts | 2 +- .../FormFields/FormFieldsContainer.tsx | 3 ++ .../components/PublicSwitchEnvMessage.tsx | 23 +++++++++++ .../src/features/workspace/WorkspacePage.tsx | 9 +++- .../components/AdminSwitchEnvMessage.tsx | 23 +++++++++++ .../EmptyWorkspace/EmptyWorkspace.tsx | 8 +++- frontend/src/services/EnvService.ts | 21 ++++++++++ .../react-migration.controller.ts | 41 ++++++++++++++++++- src/app/routes/api/v3/admin/admin.routes.ts | 1 + .../api/v3/forms/public-forms.form.routes.ts | 10 +++++ 11 files changed, 163 insertions(+), 6 deletions(-) create mode 100644 frontend/src/features/env/mutations.ts create mode 100644 frontend/src/features/public-form/components/PublicSwitchEnvMessage.tsx create mode 100644 frontend/src/features/workspace/components/AdminSwitchEnvMessage.tsx create mode 100644 frontend/src/services/EnvService.ts diff --git a/frontend/src/features/env/mutations.ts b/frontend/src/features/env/mutations.ts new file mode 100644 index 0000000000..c46a9f5645 --- /dev/null +++ b/frontend/src/features/env/mutations.ts @@ -0,0 +1,28 @@ +// TODO #4279: Remove after React rollout is complete +import { useMutation } from 'react-query' +import { useNavigate } from 'react-router-dom' + +import { + adminChooseEnvironment, + publicChooseEnvironment, +} from '~services/EnvService' + +export const useEnvMutations = () => { + const navigate = useNavigate() + + const publicSwitchEnvMutation = useMutation(() => publicChooseEnvironment(), { + onSuccess: () => window.location.reload(), + }) + + const adminSwitchEnvMutation = useMutation(() => adminChooseEnvironment(), { + onSuccess: () => { + navigate('/#!/forms') + window.location.reload() + }, + }) + + return { + publicSwitchEnvMutation, + adminSwitchEnvMutation, + } +} diff --git a/frontend/src/features/public-form/PublicFormService.ts b/frontend/src/features/public-form/PublicFormService.ts index 817a567806..2f8d1e95e4 100644 --- a/frontend/src/features/public-form/PublicFormService.ts +++ b/frontend/src/features/public-form/PublicFormService.ts @@ -17,7 +17,7 @@ import { createEncryptedSubmissionData, } from './utils/createSubmission' -const PUBLIC_FORMS_ENDPOINT = '/forms' +export const PUBLIC_FORMS_ENDPOINT = '/forms' /** * Gets public view of form, along with any diff --git a/frontend/src/features/public-form/components/FormFields/FormFieldsContainer.tsx b/frontend/src/features/public-form/components/FormFields/FormFieldsContainer.tsx index fb09a64630..9e3ac56c14 100644 --- a/frontend/src/features/public-form/components/FormFields/FormFieldsContainer.tsx +++ b/frontend/src/features/public-form/components/FormFields/FormFieldsContainer.tsx @@ -6,6 +6,8 @@ import { FormAuthType } from '~shared/types/form/form' import { usePublicFormContext } from '~features/public-form/PublicFormContext' import { FormAuth } from '../FormAuth' +// TODO #4279: Remove after React rollout is complete +import { PublicSwitchEnvMessage } from '../PublicSwitchEnvMessage' import { FormFields } from './FormFields' import { FormFieldsSkeleton } from './FormFieldsSkeleton' @@ -47,6 +49,7 @@ export const FormFieldsContainer = (): JSX.Element | null => { {isAuthRequired ? null : } + {renderFields} {isAuthRequired ? null : } diff --git a/frontend/src/features/public-form/components/PublicSwitchEnvMessage.tsx b/frontend/src/features/public-form/components/PublicSwitchEnvMessage.tsx new file mode 100644 index 0000000000..d3e6936d50 --- /dev/null +++ b/frontend/src/features/public-form/components/PublicSwitchEnvMessage.tsx @@ -0,0 +1,23 @@ +// TODO #4279: Remove after React rollout is complete +import { Text } from '@chakra-ui/react' + +import Button from '~components/Button' +import InlineMessage from '~components/InlineMessage' + +import { useEnvMutations } from '~features/env/mutations' + +export const PublicSwitchEnvMessage = (): JSX.Element => { + const { publicSwitchEnvMutation } = useEnvMutations() + + return ( + + + You’re filling this form on the new FormSG. If you have trouble + submitting,{' '} + + + + ) +} diff --git a/frontend/src/features/workspace/WorkspacePage.tsx b/frontend/src/features/workspace/WorkspacePage.tsx index 73a29af7df..16dd929f18 100644 --- a/frontend/src/features/workspace/WorkspacePage.tsx +++ b/frontend/src/features/workspace/WorkspacePage.tsx @@ -21,6 +21,8 @@ import { RolloutAnnouncementModal } from '~features/rollout-announcement/Rollout import { EmergencyContactModal } from '~features/user/emergency-contact/EmergencyContactModal' import { useUser } from '~features/user/queries' +// TODO #4279: Remove after React rollout is complete +import { AdminSwitchEnvMessage } from './components/AdminSwitchEnvMessage' import CreateFormModal from './components/CreateFormModal' import { EmptyWorkspace } from './components/EmptyWorkspace' import { WorkspaceFormRows } from './components/WorkspaceFormRow' @@ -159,10 +161,13 @@ export const WorkspacePage = (): JSX.Element => { + + + { + const { adminSwitchEnvMutation } = useEnvMutations() + + return ( + + + Welcome to the new FormSG! You can still{' '} + {' '} + which is available until 28 May 2022. + + + ) +} diff --git a/frontend/src/features/workspace/components/EmptyWorkspace/EmptyWorkspace.tsx b/frontend/src/features/workspace/components/EmptyWorkspace/EmptyWorkspace.tsx index 3d3ba147a4..49ff207489 100644 --- a/frontend/src/features/workspace/components/EmptyWorkspace/EmptyWorkspace.tsx +++ b/frontend/src/features/workspace/components/EmptyWorkspace/EmptyWorkspace.tsx @@ -1,9 +1,12 @@ import { BiPlus } from 'react-icons/bi' -import { Flex, Text } from '@chakra-ui/react' +import { Box, Flex, Text } from '@chakra-ui/react' import { useIsMobile } from '~hooks/useIsMobile' import Button from '~components/Button' +// TODO #4279: Remove after React rollout is complete +import { AdminSwitchEnvMessage } from '../AdminSwitchEnvMessage' + import { EmptyWorkspaceSvgr } from './EmptyWorkspaceSvgr' export interface EmptyWorkspacePage { @@ -26,6 +29,9 @@ export const EmptyWorkspace = ({ py="1rem" bg="neutral.100" > + + + You don't have any forms yet diff --git a/frontend/src/services/EnvService.ts b/frontend/src/services/EnvService.ts new file mode 100644 index 0000000000..1f0c894e75 --- /dev/null +++ b/frontend/src/services/EnvService.ts @@ -0,0 +1,21 @@ +// TODO #4279: Remove after React rollout is complete +import { UiCookieValues } from '~shared/types' + +import { PUBLIC_FORMS_ENDPOINT } from '~features/public-form/PublicFormService' + +import { ApiService } from './ApiService' + +const ENV_ENDPOINT = '/environment' +const ADMIN_ENDPOINT = '/admin' + +export const publicChooseEnvironment = async (): Promise => { + return ApiService.get( + `${PUBLIC_FORMS_ENDPOINT}/${ENV_ENDPOINT}/${UiCookieValues.Angular}`, + ).then(({ data }) => data) +} + +export const adminChooseEnvironment = async (): Promise => { + return ApiService.get( + `${ADMIN_ENDPOINT}/${ENV_ENDPOINT}/${UiCookieValues.Angular}`, + ).then(({ data }) => data) +} diff --git a/src/app/modules/react-migration/react-migration.controller.ts b/src/app/modules/react-migration/react-migration.controller.ts index 913264f450..89332a8b0e 100644 --- a/src/app/modules/react-migration/react-migration.controller.ts +++ b/src/app/modules/react-migration/react-migration.controller.ts @@ -1,3 +1,4 @@ +// TODO #4279: Remove after React rollout is complete import path from 'path' import { FormAuthType, UiCookieValues } from '../../../../shared/types' @@ -19,13 +20,22 @@ export const RESPONDENT_COOKIE_OPTIONS = { secure: !config.isDev, } +export const RESPONDENT_COOKIE_OPTIONS_WITH_EXPIRY = { + ...RESPONDENT_COOKIE_OPTIONS, + maxAge: 31 * 2 * 24 * 60 * 60, // 2 months +} + export const ADMIN_COOKIE_OPTIONS = { httpOnly: false, - maxAge: 31 * 2 * 24 * 60 * 60, // 2 months sameSite: 'strict' as const, secure: !config.isDev, } +export const ADMIN_COOKIE_OPTIONS_WITH_EXPIRY = { + ...ADMIN_COOKIE_OPTIONS, + maxAge: 31 * 2 * 24 * 60 * 60, // 2 months +} + const logger = createLoggerWithLabel(module) const serveFormReact: ControllerHandler = (_req, res) => { @@ -163,6 +173,33 @@ export const adminChooseEnvironment: ControllerHandler< req.params.ui === UiCookieValues.React ? UiCookieValues.React : UiCookieValues.Angular - res.cookie(config.reactMigration.adminCookieName, ui, ADMIN_COOKIE_OPTIONS) + res.cookie( + config.reactMigration.adminCookieName, + ui, + // When admin chooses to switch environments, we want them to stay on their + // chosen environment until the alternative is stable. + ADMIN_COOKIE_OPTIONS_WITH_EXPIRY, + ) + return res.json({ ui }) +} + +// Note: frontend is expected to refresh after executing this +export const publicChooseEnvironment: ControllerHandler< + SetEnvironmentParams, + unknown, + unknown, + Record +> = (req, res) => { + const ui = + req.params.ui === UiCookieValues.React + ? UiCookieValues.React + : UiCookieValues.Angular + res.cookie( + config.reactMigration.respondentCookieName, + ui, + // When public responded chooses to switch environments, we want them to stay on + // their chosen environment until the alternative is stable. + RESPONDENT_COOKIE_OPTIONS_WITH_EXPIRY, + ) return res.json({ ui }) } diff --git a/src/app/routes/api/v3/admin/admin.routes.ts b/src/app/routes/api/v3/admin/admin.routes.ts index 250cf23b72..bb4e1cdce9 100644 --- a/src/app/routes/api/v3/admin/admin.routes.ts +++ b/src/app/routes/api/v3/admin/admin.routes.ts @@ -8,6 +8,7 @@ export const AdminRouter = Router() AdminRouter.use('/forms', AdminFormsRouter) +// TODO #4279: Remove after React rollout is complete // This endpoint doesn't reaaaallly need to be a verified admin to be used AdminRouter.get( '/environment/:ui(react|angular)', diff --git a/src/app/routes/api/v3/forms/public-forms.form.routes.ts b/src/app/routes/api/v3/forms/public-forms.form.routes.ts index e87cf9e443..e64f637692 100644 --- a/src/app/routes/api/v3/forms/public-forms.form.routes.ts +++ b/src/app/routes/api/v3/forms/public-forms.form.routes.ts @@ -1,6 +1,7 @@ import { Router } from 'express' import * as PublicFormController from '../../../../modules/form/public-form/public-form.controller' +import * as ReactMigrationController from '../../../../modules/react-migration/react-migration.controller' export const PublicFormsFormRouter = Router() @@ -21,3 +22,12 @@ export const PublicFormsFormRouter = Router() PublicFormsFormRouter.route('/:formId([a-fA-F0-9]{24})').get( PublicFormController.handleGetPublicForm, ) + +// TODO #4279: Remove after React rollout is complete +/** + * Switches the environment cookie for a public form + * @route GET /environment/:ui + */ +PublicFormsFormRouter.route('/environment/:ui(react|angular)').get( + ReactMigrationController.publicChooseEnvironment, +)