Skip to content

Commit

Permalink
wip: application validation schemas
Browse files Browse the repository at this point in the history
  • Loading branch information
joonatank committed Jan 31, 2025
1 parent d54fcd7 commit 4e3450f
Show file tree
Hide file tree
Showing 8 changed files with 251 additions and 185 deletions.
14 changes: 8 additions & 6 deletions apps/ui/components/application/ApplicationEvent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,12 @@ function ApplicationEventInner({
| "name"
| "appliedReservationsPerWeek"
| "reservationUnits";
const getTranslatedError = (field: FieldName): string => {
const getTranslatedError = (field: FieldName): string | undefined => {
const error = errors.applicationSections?.[index]?.[field];
if (error?.message != null) {
return t(`application:validation.${error.message}`);
}
return "";
return undefined;
};

// convert from minutes to seconds (search page uses minutes, this uses seconds)
Expand All @@ -121,6 +121,8 @@ function ApplicationEventInner({
value: x.value * 60,
}));

const lang = getLocalizationLang(i18n.language);

return (
<Flex $gap="s" $marginTop="s">
<H4 as="h3">{t("application:Page1.basicInformationSubHeading")}</H4>
Expand Down Expand Up @@ -191,9 +193,9 @@ function ApplicationEventInner({
/>
</CheckboxWrapper>
<AutoGrid>
{/* TODO replace with ControlledDateInput */}
<DateInput
// disableConfirmation: is not accessible
language={getLocalizationLang(i18n.language)}
language={lang}
{...register(`applicationSections.${index}.begin`)}
onChange={(v) => {
clearErrors([
Expand All @@ -215,10 +217,10 @@ function ApplicationEventInner({
invalid={errors?.applicationSections?.[index]?.begin != null}
errorText={getTranslatedError("begin")}
/>
{/* TODO replace with ControlledDateInput */}
<DateInput
{...register(`applicationSections.${index}.end`)}
// disableConfirmation: is not accessible
language={getLocalizationLang(i18n.language)}
language={lang}
onChange={(v) => {
clearErrors([
`applicationSections.${index}.begin`,
Expand Down
6 changes: 2 additions & 4 deletions apps/ui/components/application/EmailInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { applicationErrorText } from "@/modules/util";
import type { ApplicationFormPage3Values } from "./Form";
import { SpanTwoColumns } from "./styled";

const EmailInput = () => {
export function EmailInput() {
const { t } = useTranslation();

const {
Expand Down Expand Up @@ -43,6 +43,4 @@ const EmailInput = () => {
/>
</>
);
};

export { EmailInput };
}
146 changes: 128 additions & 18 deletions apps/ui/components/application/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,10 @@ const ApplicationSectionFormValueSchema = z
accordionOpen: z.boolean(),
// form specific: new events don't have pks and we need a unique identifier
formKey: z.string(),
// selected reservation unit to show for this section
reservationUnitPk: z.number(),
priority: z.literal(200).or(z.literal(300)),
// selected reservation unit to show for this section (only used by Page2)
// TODO split the form? so we have three schemas (common + page1 + page2)
reservationUnitPk: z.number().optional(),
priority: z.literal(200).or(z.literal(300)).optional(),
})
.refine((s) => s.maxDuration >= s.minDuration, {
path: ["maxDuration"],
Expand Down Expand Up @@ -164,17 +165,17 @@ function convertDate(date: string | null | undefined): string | undefined {

const AddressFormValueSchema = z.object({
pk: z.number().optional(),
streetAddress: z.string(),
city: z.string(),
postCode: z.string(),
streetAddress: z.string().min(1).max(80),
city: z.string().min(1).max(80),
postCode: z.string().min(1).max(32),
});
export type AddressFormValues = z.infer<typeof AddressFormValueSchema>;

// TODO identifier is only optional for Associations (not for Companies / Communities)
const OrganisationFormValuesSchema = z.object({
pk: z.number().optional(),
name: z.string().min(1).max(255),
identifier: z.string().optional(),
identifier: z.string().max(255).optional(),
yearEstablished: z.number().optional(),
coreBusiness: z.string().min(1).max(255),
address: AddressFormValueSchema,
Expand Down Expand Up @@ -325,17 +326,126 @@ export function ApplicationFormSchemaRefined(round: {
// TODO refine the form (different applicant types require different fields)
// if applicantType === Organisation | Company => organisation.identifier is required
// if hasBillingAddress | applicantType === Individual => billingAddress is required
const ApplicationFormPage3Schema = z.object({
pk: z.number(),
applicantType: ApplicantTypeSchema.optional(),
organisation: OrganisationFormValuesSchema.optional(),
contactPerson: PersonFormValuesSchema.optional(),
billingAddress: AddressFormValueSchema.optional(),
// this is not submitted, we can use it to remove the billing address from submit without losing the frontend state
hasBillingAddress: z.boolean().optional(),
additionalInformation: z.string().optional(),
homeCity: z.number().optional(),
});
export const ApplicationFormPage3Schema = z
.object({
pk: z.number(),
applicantType: ApplicantTypeSchema.optional(),
organisation: OrganisationFormValuesSchema.optional(),
contactPerson: PersonFormValuesSchema.optional(),
billingAddress: AddressFormValueSchema.optional(),
// this is not submitted, we can use it to remove the billing address from submit without losing the frontend state
hasBillingAddress: z.boolean().optional(),
additionalInformation: z.string().optional(),
homeCity: z.number().optional(),
})
.superRefine((val, ctx) => {
switch (val.applicantType) {
case ApplicantTypeChoice.Association:
case ApplicantTypeChoice.Company:
if (val.organisation?.identifier == null) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["organisation", "identifier"],
message: "Required",
});
}
break;
default:
break;
}
if (val.applicantType !== ApplicantTypeChoice.Individual) {
if (val.organisation?.name == null) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["organisation", "name"],
message: "Required",
});
}
if (val.organisation?.coreBusiness == null) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["organisation", "coreBusiness"],
message: "Required",
});
}
if (!val.organisation?.address?.streetAddress) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["organisation", "address", "streetAddress"],
message: "Required",
});
}
if (!val.organisation?.address?.city) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["organisation", "address", "city"],
message: "Required",
});
}
if (!val.organisation?.address?.postCode) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["organisation", "address", "postCode"],
message: "Required",
});
}
}
// TODO need to split
if (!val.contactPerson?.firstName) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["contactPerson", "firstName"],
message: "Required",
});
}
if (!val.contactPerson?.lastName) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["contactPerson", "lastName"],
message: "Required",
});
}
if (!val.contactPerson?.email) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["contactPerson", "email"],
message: "Required",
});
}
if (!val.contactPerson?.phoneNumber) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["contactPerson", "phoneNumber"],
message: "Required",
});
}

// TODO need to check the subfields of the address
if (val.hasBillingAddress) {
if (!val.billingAddress?.streetAddress) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["billingAddress", "streetAddress"],
message: "Required",
});
}
if (!val.billingAddress?.city) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["billingAddress", "city"],
message: "Required",
});
}
if (!val.billingAddress?.postCode) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["billingAddress", "postCode"],
message: "Required",
});
}
}
});

export type ApplicationFormPage3Values = z.infer<
typeof ApplicationFormPage3Schema
>;
Expand Down
Loading

0 comments on commit 4e3450f

Please sign in to comment.