Skip to content

Commit 8e88500

Browse files
refactor(frontend): privacy statement data storage and validation (#184)
1 parent 14199b3 commit 8e88500

File tree

4 files changed

+48
-32
lines changed

4 files changed

+48
-32
lines changed

frontend/app/.server/locales/protected-en.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"privacy-statement": {
3838
"confirm-privacy-notice-checkbox": {
3939
"title": "I agree to the terms and conditions",
40-
"required": "Privacy notice statement is required."
40+
"required": "Agreeing to the terms and conditions is required."
4141
},
4242
"page-title": "Privacy Statement"
4343
},

frontend/app/.server/locales/protected-fr.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"privacy-statement": {
3838
"confirm-privacy-notice-checkbox": {
3939
"title": "J'accepte les termes et conditions.",
40-
"required": "Privacy notice statement is required."
40+
"required": "L'acceptation des termes et conditions est obligatoire."
4141
},
4242
"page-title": "Déclaration de confidentialité"
4343
},

frontend/app/@types/express-session.d.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,20 @@ declare module 'express-session' {
1616
state: string;
1717
};
1818
/**
19-
* Stores data for an in-person SIN case
19+
* Represents the session data for the in-person SIN case.
2020
*/
2121
inPersonSINCase: {
2222
firstName?: string;
2323
lastName?: string;
24-
confirmPrivacyNotice?: string;
24+
/**
25+
* Represents the privacy statement data for the in-person SIN case.
26+
*/
27+
privacyStatement?: {
28+
/**
29+
* Indicates whether the user has agreed to the terms of the privacy statement.
30+
*/
31+
agreedToTerms: true;
32+
};
2533
currentStatusInCanada?: string;
2634
};
2735
}

frontend/app/routes/protected/person-case/privacy-statement.tsx

+36-28
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { data, useFetcher } from 'react-router';
22
import type { RouteHandle } from 'react-router';
33

44
import { faXmark, faExclamationCircle } from '@fortawesome/free-solid-svg-icons';
5+
import type { SessionData } from 'express-session';
56
import { useTranslation } from 'react-i18next';
67
import * as v from 'valibot';
78

@@ -14,10 +15,13 @@ import { ErrorSummary } from '~/components/error-summary';
1415
import { InputCheckbox } from '~/components/input-checkbox';
1516
import { PageTitle } from '~/components/page-title';
1617
import { Progress } from '~/components/progress';
18+
import { AppError } from '~/errors/app-error';
1719
import { getFixedT } from '~/i18n-config.server';
1820
import { handle as parentHandle } from '~/routes/protected/layout';
1921
import { getLanguage } from '~/utils/i18n-utils';
2022

23+
type PrivacyStatmentSessionData = NonNullable<SessionData['inPersonSINCase']['privacyStatement']>;
24+
2125
export const handle = {
2226
i18nNamespace: [...parentHandle.i18nNamespace, 'protected'],
2327
} as const satisfies RouteHandle;
@@ -28,9 +32,7 @@ export async function loader({ context, request }: Route.LoaderArgs) {
2832

2933
return {
3034
documentTitle: t('protected:privacy-statement.page-title'),
31-
defaultFormValues: {
32-
confirmPrivacyNotice: context.session.inPersonSINCase?.confirmPrivacyNotice,
33-
},
35+
defaultFormValues: context.session.inPersonSINCase?.privacyStatement,
3436
};
3537
}
3638

@@ -42,34 +44,40 @@ export async function action({ context, request }: Route.ActionArgs) {
4244
requireAuth(context.session, new URL(request.url), ['user']);
4345
const lang = getLanguage(request);
4446
const t = await getFixedT(request, handle.i18nNamespace);
47+
4548
const formData = await request.formData();
49+
const action = formData.get('action');
4650

47-
if (formData.get('action') === 'back') {
48-
throw i18nRedirect('routes/protected/index.tsx', request);
49-
}
51+
switch (action) {
52+
case 'back': {
53+
throw i18nRedirect('routes/protected/index.tsx', request);
54+
}
5055

51-
// submit action
52-
const schema = v.object({
53-
confirmPrivacyNotice: v.pipe(
54-
v.string(t('protected:privacy-statement.confirm-privacy-notice-checkbox.required')),
55-
v.trim(),
56-
v.nonEmpty(t('protected:privacy-statement.confirm-privacy-notice-checkbox.required')),
57-
),
58-
});
56+
case 'next': {
57+
const schema = v.object({
58+
agreedToTerms: v.literal(true, t('protected:privacy-statement.confirm-privacy-notice-checkbox.required')),
59+
}) satisfies v.GenericSchema<PrivacyStatmentSessionData>;
5960

60-
const input = { confirmPrivacyNotice: formData.get('confirmPrivacyNotice') as string };
61-
const parsedDataResult = v.safeParse(schema, input, { lang });
61+
const input = {
62+
agreedToTerms: formData.get('agreedToTerms') ? true : undefined,
63+
} satisfies Partial<PrivacyStatmentSessionData>;
6264

63-
if (!parsedDataResult.success) {
64-
return data({ errors: v.flatten<typeof schema>(parsedDataResult.issues).nested }, { status: 400 });
65-
}
65+
const parseResult = v.safeParse(schema, input, { lang });
6666

67-
context.session.inPersonSINCase = {
68-
...(context.session.inPersonSINCase ?? {}),
69-
...input,
70-
};
67+
if (!parseResult.success) {
68+
return data({ errors: v.flatten<typeof schema>(parseResult.issues).nested }, { status: 400 });
69+
}
70+
71+
context.session.inPersonSINCase ??= {};
72+
context.session.inPersonSINCase.privacyStatement = parseResult.output;
7173

72-
throw i18nRedirect('routes/protected/person-case/first-name.tsx', request); //TODO: change it to redirect to file="routes/protected/person-case/request-details.tsx"
74+
throw i18nRedirect('routes/protected/person-case/first-name.tsx', request); //TODO: change it to redirect to file="routes/protected/person-case/request-details.tsx"
75+
}
76+
77+
default: {
78+
throw new AppError(`Unrecognized action: ${action}`);
79+
}
80+
}
7381
}
7482

7583
export default function PrivacyStatement({ loaderData, actionData, params }: Route.ComponentProps) {
@@ -120,10 +128,10 @@ export default function PrivacyStatement({ loaderData, actionData, params }: Rou
120128
</p>
121129

122130
<InputCheckbox
123-
id="confirmPrivacyNotice"
124-
name="confirmPrivacyNotice"
125-
errorMessage={errors?.confirmPrivacyNotice?.[0]}
126-
defaultChecked={loaderData.defaultFormValues.confirmPrivacyNotice === 'on'}
131+
id="agreed-to-terms"
132+
name="agreedToTerms"
133+
errorMessage={errors?.agreedToTerms?.[0]}
134+
defaultChecked={loaderData.defaultFormValues?.agreedToTerms}
127135
required
128136
>
129137
{t('protected:privacy-statement.confirm-privacy-notice-checkbox.title')}

0 commit comments

Comments
 (0)