Skip to content

Commit

Permalink
feat(frontend): Update secondary screen (#342)
Browse files Browse the repository at this point in the history
  • Loading branch information
Dario-Au authored Mar 6, 2025
1 parent 90778d1 commit 6f4ea52
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 50 deletions.
30 changes: 23 additions & 7 deletions frontend/app/components/date-picker-field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ const inputStyles = {
*/
export interface DatePickerFieldProps {
defaultValue?: string;
defaultYear?: number;
defaultMonth?: number;
defaultDay?: number;
disabled?: boolean;
errorMessages?: {
all?: string;
Expand All @@ -49,9 +52,9 @@ export interface DatePickerFieldProps {
id: string;
legend: ReactNode;
names: {
day: string;
month: string;
year: string;
day?: string;
month?: string;
year?: string;
};
required?: boolean;
}
Expand All @@ -64,6 +67,9 @@ export interface DatePickerFieldProps {
*/
export const DatePickerField = ({
defaultValue,
defaultYear,
defaultMonth,
defaultDay,
disabled,
errorMessages,
helpMessagePrimary,
Expand Down Expand Up @@ -118,11 +124,15 @@ export const DatePickerField = ({
}, {} as AriaErrorMessage);

// Extract default date parts from the default value
const { day = '', month = '', year = '' } = extractDateParts(defaultValue ?? '');
const {
day = defaultDay ? padWithZero(defaultDay, 2) : '',
month = defaultMonth ? padWithZero(defaultMonth, 2) : '',
year = defaultYear ? padWithZero(defaultYear, 4) : '',
} = extractDateParts(defaultValue ?? '');

// Define date picker part fields
const datePickerPartFields = {
year: (
year: names.year ? (
<DatePickerYearField
id={id}
defaultValue={year}
Expand All @@ -134,8 +144,10 @@ export const DatePickerField = ({
required={required}
disabled={disabled}
/>
) : (
<></>
),
month: (
month: names.month ? (
<DatePickerMonthField
id={id}
defaultValue={month}
Expand All @@ -149,8 +161,10 @@ export const DatePickerField = ({
required={required}
disabled={disabled}
/>
) : (
<></>
),
day: (
day: names.day ? (
<DatePickerDayField
id={id}
defaultValue={day}
Expand All @@ -162,6 +176,8 @@ export const DatePickerField = ({
required={required}
disabled={disabled}
/>
) : (
<></>
),
} as const satisfies Record<DatePart, JSX.Element>;

Expand Down
81 changes: 39 additions & 42 deletions frontend/app/routes/protected/person-case/secondary-doc.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import type { RouteHandle } from 'react-router';
import { data, redirect, useFetcher } from 'react-router';

import { faExclamationCircle, faXmark } from '@fortawesome/free-solid-svg-icons';
import { isBefore } from 'date-fns';
import { useTranslation } from 'react-i18next';
import * as v from 'valibot';

Expand All @@ -31,7 +30,7 @@ import { getTranslation } from '~/i18n-config.server';
import { handle as parentHandle } from '~/routes/protected/layout';
import type { SecondaryDocumentData } from '~/routes/protected/person-case/state-machine';
import { getStateRoute, loadMachineActor } from '~/routes/protected/person-case/state-machine';
import { getStartOfDayInTimezone, toISODateString } from '~/utils/date-utils';
import { getStartOfDayInTimezone } from '~/utils/date-utils';

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

Expand Down Expand Up @@ -64,55 +63,55 @@ export async function action({ context, params, request }: Route.ActionArgs) {

case 'next': {
const { lang, t } = await getTranslation(request, handle.i18nNamespace);

const schema = v.object({
documentType: v.picklist(
getApplicantSecondaryDocumentChoices().map(({ id }) => id),
t('protected:secondary-identity-document.document-type.invalid'),
),
expiryYear: v.pipe(
v.number(t('protected:secondary-identity-document.expiry-date.required-year')),
v.integer(t('protected:secondary-identity-document.expiry-date.invalid-year')),
v.minValue(
getStartOfDayInTimezone(serverEnvironment.BASE_TIMEZONE).getFullYear(),
t('protected:secondary-identity-document.expiry-date.invalid-year'),
const currentDate = getStartOfDayInTimezone(serverEnvironment.BASE_TIMEZONE);
const schema = v.pipe(
v.object({
documentType: v.picklist(
getApplicantSecondaryDocumentChoices().map(({ id }) => id),
t('protected:secondary-identity-document.document-type.invalid'),
),
/*
TODO: Enable file upload
document: v.pipe(
v.file(t('protected:secondary-identity-document.upload-document.required')),
v.mimeType(
['image/jpeg', 'image/png', 'image/heic'],
t('protected:secondary-identity-document.upload-document.invalid'),
),
v.maxSize(maxImageSizeBits),
),
expiryMonth: v.pipe(
v.number(t('protected:secondary-identity-document.expiry-date.required-month')),
v.integer(t('protected:secondary-identity-document.expiry-date.invalid-month')),
v.minValue(1, t('protected:secondary-identity-document.expiry-date.invalid-month')),
v.maxValue(12, t('protected:secondary-identity-document.expiry-date.invalid-month')),
),
expiryDay: v.pipe(
v.number(t('protected:secondary-identity-document.expiry-date.required-day')),
v.integer(t('protected:secondary-identity-document.expiry-date.invalid-day')),
v.minValue(1, t('protected:secondary-identity-document.expiry-date.invalid-day')),
v.maxValue(31, t('protected:secondary-identity-document.expiry-date.invalid-day')),
),
expiryDate: v.pipe(
v.string(t('protected:secondary-identity-document.expiry-date.required')),
v.custom(
(expiryDate) =>
isBefore(
getStartOfDayInTimezone(serverEnvironment.BASE_TIMEZONE),
getStartOfDayInTimezone(serverEnvironment.BASE_TIMEZONE, String(expiryDate)),
),
*/
expiryYear: v.pipe(
v.number(t('protected:secondary-identity-document.expiry-date.required-year')),
v.integer(t('protected:secondary-identity-document.expiry-date.invalid-year')),
v.minValue(currentDate.getFullYear(), t('protected:secondary-identity-document.expiry-date.invalid-year')),
),
expiryMonth: v.pipe(
v.number(t('protected:secondary-identity-document.expiry-date.required-month')),
v.integer(t('protected:secondary-identity-document.expiry-date.invalid-month')),
v.minValue(1, t('protected:secondary-identity-document.expiry-date.invalid-month')),
v.maxValue(12, t('protected:secondary-identity-document.expiry-date.invalid-month')),
),
}),
v.forward(
v.partialCheck(
[['expiryYear'], ['expiryMonth']],
(input) =>
input.expiryYear > currentDate.getFullYear() ||
(input.expiryYear === currentDate.getFullYear() && input.expiryMonth >= currentDate.getMonth()),
t('protected:secondary-identity-document.expiry-date.invalid'),
),
['expiryMonth'],
),
}) satisfies v.GenericSchema<SecondaryDocumentData>;
) satisfies v.GenericSchema<SecondaryDocumentData>;

const expiryYear = Number(formData.get('expiry-year'));
const expiryMonth = Number(formData.get('expiry-month'));
const expiryDay = Number(formData.get('expiry-day'));

const input = {
documentType: String(formData.get('document-type')),
expiryYear: expiryYear,
expiryMonth: expiryMonth,
expiryDay: expiryDay,
expiryDate: toISODateString(expiryYear, expiryMonth, expiryDay),
} satisfies Partial<v.InferInput<typeof schema>>;

const parseResult = v.safeParse(schema, input, { lang });
Expand Down Expand Up @@ -185,20 +184,18 @@ export default function SecondaryDoc({ loaderData, actionData, params }: Route.C
errorMessage={errors?.documentType?.at(0)}
/>
<DatePickerField
defaultValue={loaderData.defaultFormValues?.expiryDate}
defaultMonth={loaderData.defaultFormValues?.expiryMonth}
defaultYear={loaderData.defaultFormValues?.expiryYear}
id="expiry-date-id"
legend={t('protected:secondary-identity-document.expiry-date.title')}
required
names={{
day: 'expiry-day',
month: 'expiry-month',
year: 'expiry-year',
}}
errorMessages={{
all: errors?.expiryDate?.at(0),
year: errors?.expiryYear?.at(0),
month: errors?.expiryMonth?.at(0),
day: errors?.expiryDay?.at(0),
}}
/>
<InputFile
Expand Down
3 changes: 2 additions & 1 deletion frontend/app/routes/protected/person-case/state-machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ export type RequestDetailsData = {
export type SecondaryDocumentData = {
documentType: string;
// document: File; TODO :: enable me!
expiryDate: string;
expiryMonth: number;
expiryYear: number;
};

export type InPersonSinApplication = Partial<{
Expand Down

0 comments on commit 6f4ea52

Please sign in to comment.