diff --git a/frontend/app/.server/locales/protected-en.json b/frontend/app/.server/locales/protected-en.json index e9863d56..b2063196 100644 --- a/frontend/app/.server/locales/protected-en.json +++ b/frontend/app/.server/locales/protected-en.json @@ -391,5 +391,12 @@ "required-city": "City, town, or village of birth is required.", "invalid-city": "City, town, or village of birth is invalid." } + }, + "review": { + "page-title": "Review", + "read-carefully": "Review information below for accuracy before creating case.", + "document-uploaded": "Document uploaded", + "choosen-file": "File_chosen.pdf", + "edit-primary-identity-document": "Edit primary identity document" } } diff --git a/frontend/app/.server/locales/protected-fr.json b/frontend/app/.server/locales/protected-fr.json index b5104915..86b4dd54 100644 --- a/frontend/app/.server/locales/protected-fr.json +++ b/frontend/app/.server/locales/protected-fr.json @@ -392,5 +392,12 @@ "required-city": "La ville, la commune ou le village de naissance est requis.", "invalid-city": "La ville, la commune ou le village de naissance est invalide." } + }, + "review": { + "page-title": "Review", + "read-carefully": "Veuillez examiner les informations ci-dessous pour en vérifier l'exactitude avant de créer le dossier.", + "document-uploaded": "Document téléchargé", + "choosen-file": "Fichier_choisi.pdf", + "edit-primary-identity-document": "Modifier le document d'identité principal" } } diff --git a/frontend/app/components/description-list.tsx b/frontend/app/components/description-list.tsx new file mode 100644 index 00000000..fc139242 --- /dev/null +++ b/frontend/app/components/description-list.tsx @@ -0,0 +1,20 @@ +import type { ComponentProps, ReactNode } from 'react'; + +import { cn } from '~/utils/tailwind-utils'; + +export interface DescriptionListItemProps extends ComponentProps<'div'> { + term: ReactNode; +} +export function DescriptionListItem({ className, children, term, ...rest }: DescriptionListItemProps) { + return ( +
+
{term}
+
{children}
+
+ ); +} + +export interface DescriptionListProps extends ComponentProps<'dl'> {} +export function DescriptionList(props: DescriptionListProps) { + return
; +} diff --git a/frontend/app/i18n-routes.ts b/frontend/app/i18n-routes.ts index 4ae3db3d..a7afb386 100644 --- a/frontend/app/i18n-routes.ts +++ b/frontend/app/i18n-routes.ts @@ -174,6 +174,14 @@ export const i18nRoutes = [ fr: '/fr/protege/cas-personnel/contact-information', }, }, + { + id: 'PROT-0014', + file: 'routes/protected/person-case/review.tsx', + paths: { + en: '/en/protected/person-case/review', + fr: '/fr/protege/cas-personnel/revision', + }, + }, // // XState-driven in-person flow (poc) // diff --git a/frontend/app/routes/protected/person-case/review.tsx b/frontend/app/routes/protected/person-case/review.tsx new file mode 100644 index 00000000..a10f223c --- /dev/null +++ b/frontend/app/routes/protected/person-case/review.tsx @@ -0,0 +1,216 @@ +import { useId } from 'react'; + +import type { RouteHandle } from 'react-router'; +import { useFetcher } from 'react-router'; + +import type { SessionData } from 'express-session'; +import { useTranslation } from 'react-i18next'; + +import type { Info, Route } from './+types/review'; + +import { requireAuth } from '~/.server/utils/auth-utils'; +import { i18nRedirect } from '~/.server/utils/route-utils'; +import { Button } from '~/components/button'; +import { ButtonLink } from '~/components/button-link'; +import { DescriptionList, DescriptionListItem } from '~/components/description-list'; +import { PageTitle } from '~/components/page-title'; +import { AppError } from '~/errors/app-error'; +import { ErrorCodes } from '~/errors/error-codes'; +import { getTranslation } from '~/i18n-config.server'; +import { handle as parentHandle } from '~/routes/protected/layout'; + +export const handle = { + i18nNamespace: [...parentHandle.i18nNamespace, 'protected'], +} as const satisfies RouteHandle; + +export async function loader({ context, request }: Route.LoaderArgs) { + requireAuth(context.session, new URL(request.url), ['user']); + const { t } = await getTranslation(request, handle.i18nNamespace); + const inPersonSINCase = validateInPersonSINCaseSession(context.session, request); + return { documentTitle: t('protected:review.page-title'), inPersonSINCase }; +} + +/** + * + * @param session + * @param request + * @returns + */ +function validateInPersonSINCaseSession( + session: AppSession, + request: Request, +): Required> { + const inPersonSINCase = session.inPersonSINCase; + + if (inPersonSINCase === undefined) { + throw i18nRedirect('routes/protected/person-case/privacy-statement.tsx', request); + } + + const { + birthDetails, + contactInformation, + currentNameInfo, + parentDetails, + personalInformation, + previousSin, + primaryDocuments, + privacyStatement, + requestDetails, + secondaryDocument, + } = inPersonSINCase; + + if (privacyStatement === undefined) { + throw i18nRedirect('routes/protected/index.tsx', request); + } + + if (requestDetails === undefined) { + throw i18nRedirect('routes/protected/person-case/request-details.tsx', request); + } + + if (primaryDocuments === undefined) { + throw i18nRedirect('routes/protected/person-case/primary-docs.tsx', request); + } + + if (secondaryDocument === undefined) { + throw i18nRedirect('routes/protected/person-case/secondary-doc.tsx', request); + } + + if (currentNameInfo === undefined) { + throw i18nRedirect('routes/protected/person-case/current-name.tsx', request); + } + + if (personalInformation === undefined) { + throw i18nRedirect('routes/protected/person-case/personal-info.tsx', request); + } + + if (birthDetails === undefined) { + throw i18nRedirect('routes/protected/person-case/birth-details.tsx', request); + } + + if (parentDetails === undefined) { + throw i18nRedirect('routes/protected/person-case/parent-details.tsx', request); + } + + if (previousSin === undefined) { + throw i18nRedirect('routes/protected/person-case/previous-sin.tsx', request); + } + + if (contactInformation === undefined) { + throw i18nRedirect('routes/protected/person-case/contact-information.tsx', request); + } + + return { + birthDetails, + contactInformation, + currentNameInfo, + parentDetails, + personalInformation, + previousSin, + primaryDocuments, + privacyStatement, + requestDetails, + secondaryDocument, + }; +} + +export function meta({ data }: Route.MetaArgs) { + return [{ title: data.documentTitle }]; +} + +export async function action({ context, request }: Route.ActionArgs) { + requireAuth(context.session, new URL(request.url), ['user']); + // const inPersonSINCase = validateInPersonSINCaseSession(context.session, request); + + const formData = await request.formData(); + const action = formData.get('action'); + + switch (action) { + case 'back': { + throw i18nRedirect('routes/protected/person-case/contact-information.tsx', request); + } + + case 'next': { + throw i18nRedirect('routes/protected/index.tsx', request); + } + default: { + throw new AppError(`Unrecognized action: ${action}`, ErrorCodes.UNRECOGNIZED_ACTION); + } + } +} + +export default function Review({ loaderData, actionData, params }: Route.ComponentProps) { + const { inPersonSINCase } = loaderData; + const { t } = useTranslation(handle.i18nNamespace); + const fetcherKey = useId(); + const fetcher = useFetcher({ key: fetcherKey }); + const isSubmitting = fetcher.state !== 'idle'; + + return ( + <> + {t('protected:review.page-title')} +
+ +

{t('protected:review.read-carefully')}

+
+
+

{t('protected:primary-identity-document.page-title')}

+ + +

+ {/* TODO: Code Table Value */} + {t( + `protected:primary-identity-document.current-status-in-canada.options.${inPersonSINCase.primaryDocuments.currentStatusInCanada}` as 'protected:primary-identity-document.current-status-in-canada.options.select-option', + )} +

+
+ +

+ {/* TODO: Code Table Value */} + {t( + `protected:primary-identity-document.document-type.options.${inPersonSINCase.primaryDocuments.documentType}` as 'protected:primary-identity-document.document-type.options.select-option', + )} +

+
+ +

{inPersonSINCase.primaryDocuments.registrationNumber}

+
+ +

{inPersonSINCase.primaryDocuments.clientNumber}

+
+ +

{inPersonSINCase.primaryDocuments.givenName}

+
+ +

{inPersonSINCase.primaryDocuments.lastName}

+
+ +

{inPersonSINCase.primaryDocuments.dateOfBirth}

+
+ +

{inPersonSINCase.primaryDocuments.gender}

+
+ +

{inPersonSINCase.primaryDocuments.citizenshipDate}

+
+ +

{t('protected:review.choosen-file')}

+
+
+ + {t('protected:review.edit-primary-identity-document')} + +
+
+
+ + +
+
+
+ + ); +}