Skip to content

Commit 68dbc41

Browse files
fix(frontend): update validation
1 parent e221ddf commit 68dbc41

File tree

4 files changed

+53
-36
lines changed

4 files changed

+53
-36
lines changed

frontend/app/routes/protected/person-case/primary-docs.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { JSX } from 'react';
22
import { useId, useState } from 'react';
33

4+
import type { RouteHandle } from 'react-router';
45
import { data, redirect, useFetcher } from 'react-router';
56

67
import { useTranslation } from 'react-i18next';
@@ -34,7 +35,9 @@ import { getSingleKey } from '~/utils/i18n-utils';
3435

3536
const log = LogFactory.getLogger(import.meta.url);
3637

37-
export const handle = parentHandle;
38+
export const handle = {
39+
i18nNamespace: [...parentHandle.i18nNamespace, 'protected'],
40+
} as const satisfies RouteHandle;
3841

3942
export function meta({ data }: Route.MetaArgs) {
4043
return [{ title: data.documentTitle }];
@@ -89,7 +92,7 @@ export async function action({ context, params, request }: Route.ActionArgs) {
8992
const parseResult = v.safeParse(primaryDocumentSchema, formValues);
9093

9194
if (!parseResult.success) {
92-
const formErrors = v.flatten(parseResult.issues).nested;
95+
const formErrors = v.flatten<typeof primaryDocumentSchema>(parseResult.issues).nested;
9396

9497
machineActor.send({
9598
type: 'setFormData',

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export async function action({ context, params, request }: Route.ActionArgs) {
5555

5656
case 'next': {
5757
const parseResult = v.safeParse(privacyStatementSchema, {
58-
agreedToTerms: formData.get('agreedToTerms') ? true : undefined,
58+
agreedToTerms: formData.get('agreedToTerms')?.toString() === 'on',
5959
});
6060

6161
if (!parseResult.success) {

frontend/app/routes/protected/person-case/secondary-doc.tsx

+32-25
Original file line numberDiff line numberDiff line change
@@ -57,20 +57,28 @@ export async function action({ context, params, request }: Route.ActionArgs) {
5757
}
5858

5959
case 'next': {
60-
const expiryYear = Number(formData.get('expiry-year'));
61-
const expiryMonth = Number(formData.get('expiry-month'));
60+
const formValues = {
61+
documentType: formData.get('document-type')?.toString(),
62+
expiryYear: formData.get('expiry-year')?.toString(),
63+
expiryMonth: formData.get('expiry-month')?.toString(),
64+
};
6265

63-
const parseResult = v.safeParse(secondaryDocumentSchema, {
64-
documentType: String(formData.get('document-type')),
65-
expiryYear: expiryYear,
66-
expiryMonth: expiryMonth,
67-
});
66+
const parseResult = v.safeParse(secondaryDocumentSchema, formValues);
6867

6968
if (!parseResult.success) {
70-
return data(
71-
{ errors: v.flatten<typeof secondaryDocumentSchema>(parseResult.issues).nested },
72-
{ status: HttpStatusCodes.BAD_REQUEST },
73-
);
69+
const formErrors = v.flatten<typeof secondaryDocumentSchema>(parseResult.issues).nested;
70+
71+
machineActor.send({
72+
type: 'setFormData',
73+
data: {
74+
secondaryDocument: {
75+
values: formValues,
76+
errors: formErrors,
77+
},
78+
},
79+
});
80+
81+
return data({ formValues: formValues, formErrors: formErrors }, { status: HttpStatusCodes.BAD_REQUEST });
7482
}
7583

7684
machineActor.send({ type: 'submitSecondaryDocuments', data: parseResult.output });
@@ -90,27 +98,30 @@ export async function loader({ context, request }: Route.LoaderArgs) {
9098

9199
const { lang, t } = await getTranslation(request, handle.i18nNamespace);
92100
const machineActor = loadMachineActor(context.session, request, 'secondary-docs');
101+
const machineContext = machineActor?.getSnapshot().context;
93102

94103
return {
95104
documentTitle: t('protected:secondary-identity-document.page-title'),
96105
localizedApplicantSecondaryDocumentChoices: getLocalizedApplicantSecondaryDocumentChoices(lang),
97-
defaultFormValues: machineActor?.getSnapshot().context.secondaryDocument,
106+
formValues: machineContext?.formData?.secondaryDocument?.values ?? machineContext?.secondaryDocument,
107+
formErrors: machineContext?.formData?.secondaryDocument?.errors,
98108
};
99109
}
100110

101-
export default function SecondaryDoc({ loaderData, actionData, params }: Route.ComponentProps) {
111+
export default function SecondaryDoc({ actionData, loaderData, params }: Route.ComponentProps) {
102112
const { t } = useTranslation(handle.i18nNamespace);
103113

104114
const fetcherKey = useId();
105115
const fetcher = useFetcher<Info['actionData']>({ key: fetcherKey });
106-
107116
const isSubmitting = fetcher.state !== 'idle';
108-
const errors = fetcher.data?.errors;
117+
118+
const formValues = fetcher.data?.formValues ?? loaderData.formValues;
119+
const formErrors = fetcher.data?.formErrors ?? loaderData.formErrors;
109120

110121
const docOptions = loaderData.localizedApplicantSecondaryDocumentChoices.map(({ id, name }) => ({
111122
value: id,
112123
children: name,
113-
defaultChecked: id === loaderData.defaultFormValues?.documentType,
124+
defaultChecked: id === loaderData.formValues?.documentType,
114125
}));
115126

116127
return (
@@ -126,11 +137,11 @@ export default function SecondaryDoc({ loaderData, actionData, params }: Route.C
126137
name="document-type"
127138
options={docOptions}
128139
required
129-
errorMessage={t(getSingleKey(errors?.documentType))}
140+
errorMessage={t(getSingleKey(formErrors?.documentType))}
130141
/>
131142
<DatePickerField
132-
defaultMonth={loaderData.defaultFormValues?.expiryMonth}
133-
defaultYear={loaderData.defaultFormValues?.expiryYear}
143+
defaultMonth={Number(formValues?.expiryMonth)}
144+
defaultYear={Number(formValues?.expiryYear)}
134145
id="expiry-date-id"
135146
legend={t('protected:secondary-identity-document.expiry-date.title')}
136147
required
@@ -139,8 +150,8 @@ export default function SecondaryDoc({ loaderData, actionData, params }: Route.C
139150
year: 'expiry-year',
140151
}}
141152
errorMessages={{
142-
year: t(getSingleKey(errors?.expiryYear)),
143-
month: t(getSingleKey(errors?.expiryMonth)),
153+
year: t(getSingleKey(formErrors?.expiryYear)),
154+
month: t(getSingleKey(formErrors?.expiryMonth)),
144155
}}
145156
/>
146157
<InputFile
@@ -150,10 +161,6 @@ export default function SecondaryDoc({ loaderData, actionData, params }: Route.C
150161
name="document"
151162
label={t('protected:secondary-identity-document.upload-document.title')}
152163
required
153-
/*
154-
TODO: Enable file upload
155-
errorMessage={t(getSingleKey(errors?.document))}
156-
*/
157164
/>
158165
</div>
159166
<div className="mt-8 flex flex-row-reverse flex-wrap items-center justify-end gap-3">

frontend/app/routes/protected/person-case/validation.server.ts

+15-8
Original file line numberDiff line numberDiff line change
@@ -498,27 +498,34 @@ export const secondaryDocumentSchema = v.pipe(
498498
),
499499
),
500500
expiryYear: v.pipe(
501-
v.number('protected:secondary-identity-document.expiry-date.required-year'),
502-
v.integer('protected:secondary-identity-document.expiry-date.invalid-year'),
501+
stringToIntegerSchema('protected:secondary-identity-document.expiry-date.required-year'),
503502
v.minValue(
504503
getStartOfDayInTimezone(serverEnvironment.BASE_TIMEZONE).getFullYear(),
505504
'protected:secondary-identity-document.expiry-date.invalid-year',
506505
),
507506
),
508507
expiryMonth: v.pipe(
509-
v.number('protected:secondary-identity-document.expiry-date.required-month'),
510-
v.integer('protected:secondary-identity-document.expiry-date.invalid-month'),
508+
stringToIntegerSchema('protected:secondary-identity-document.expiry-date.required-month'),
511509
v.minValue(1, 'protected:secondary-identity-document.expiry-date.invalid-month'),
512510
v.maxValue(12, 'protected:secondary-identity-document.expiry-date.invalid-month'),
513511
),
514512
}),
515513
v.forward(
516514
v.partialCheck(
517515
[['expiryYear'], ['expiryMonth']],
518-
(input) =>
519-
input.expiryYear > getStartOfDayInTimezone(serverEnvironment.BASE_TIMEZONE).getFullYear() ||
520-
(input.expiryYear === getStartOfDayInTimezone(serverEnvironment.BASE_TIMEZONE).getFullYear() &&
521-
input.expiryMonth >= getStartOfDayInTimezone(serverEnvironment.BASE_TIMEZONE).getMonth()),
516+
({ expiryYear, expiryMonth }) => {
517+
const currentYear = getStartOfDayInTimezone(serverEnvironment.BASE_TIMEZONE).getFullYear();
518+
const currentMonth = getStartOfDayInTimezone(serverEnvironment.BASE_TIMEZONE).getMonth();
519+
520+
// expiry date is valid if expiry year is in the future
521+
if (expiryYear > currentYear) return true;
522+
523+
// expiry date is valid if expiry month is in the future
524+
if (expiryYear === currentYear) return expiryMonth >= currentMonth;
525+
526+
// expiry date is invalid
527+
return false;
528+
},
522529
'protected:secondary-identity-document.expiry-date.invalid',
523530
),
524531
['expiryMonth'],

0 commit comments

Comments
 (0)