Skip to content

Commit b059785

Browse files
committed
use zod v4 + fix all the type errors (slay)
1 parent 6b05e9e commit b059785

File tree

17 files changed

+95
-83
lines changed

17 files changed

+95
-83
lines changed

client/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
},
1313
"dependencies": {
1414
"@durhack/durhack-common": "workspace:*",
15-
"@durhack/web-components": "1.0.1-beta.1",
15+
"@durhack/web-components": "1.0.1-beta.4",
1616
"@hookform/resolvers": "^5.1.1",
1717
"@radix-ui/react-aspect-ratio": "^1.1.7",
1818
"@radix-ui/react-icons": "^1.3.2",

client/src/app/(home)/register-interest-form.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ import { Input } from "@durhack/web-components/ui/input"
77
import { zodResolver } from "@hookform/resolvers/zod"
88
import type * as React from "react"
99
import { useForm } from "react-hook-form"
10-
import { z } from "zod"
10+
import { z } from "zod/v4"
1111

1212
import { siteConfig } from "@/config/site"
1313

1414
const registerInterestFormSchema = z.object({
1515
firstNames: z.string().trim().min(1),
1616
lastNames: z.string().trim().min(1),
17-
email: z.string().email(),
17+
email: z.email(),
1818
})
1919

2020
export function RegisterInterestForm(props: React.HTMLAttributes<HTMLFormElement>) {

client/src/app/dashboard/(application)/contact/page.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,25 @@ import { PhoneInput } from "@durhack/web-components/ui/phone-number-input"
66
import { zodResolver } from "@hookform/resolvers/zod"
77
import { useRouter } from "next/navigation"
88
import { useForm } from "react-hook-form"
9-
import { z } from "zod"
9+
import { z } from "zod/v4"
1010

11-
import "@/lib/zod-phone-extension"
11+
import "@/lib/zod-phone-validator"
1212
import { FormSkeleton } from "@/components/dashboard/form-skeleton"
1313
import { FormSubmitButton } from "@/components/dashboard/form-submit-button"
1414
import type { Application } from "@/hooks/use-application"
1515
import { useApplicationContext } from "@/hooks/use-application-context"
1616
import { isLoaded } from "@/lib/is-loaded"
1717
import { updateApplication } from "@/lib/update-application"
18+
import {zodPhoneNumberValidator} from "@/lib/zod-phone-validator";
1819

1920
type ContactFormFields = {
2021
phone: string
2122
email: string
2223
}
2324

2425
const contactFormSchema = z.object({
25-
phone: z.string().phone(),
26+
phone: zodPhoneNumberValidator(),
27+
email: z.email(),
2628
})
2729

2830
/**

client/src/app/dashboard/(application)/cv/page.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ import {
2323
SelectItem,
2424
SelectTrigger,
2525
SelectValue,
26-
SelectValueClipper,
26+
SelectValueViewport,
2727
} from "@durhack/web-components/ui/select"
2828
import { zodResolver } from "@hookform/resolvers/zod"
2929
import { useRouter } from "next/navigation"
3030
import * as React from "react"
3131
import { useForm } from "react-hook-form"
32-
import { z } from "zod"
32+
import { z } from "zod/v4"
3333

3434
import { FormSkeleton } from "@/components/dashboard/form-skeleton"
3535
import { FormSubmitButton } from "@/components/dashboard/form-submit-button"
@@ -41,7 +41,7 @@ import { cn } from "@/lib/utils"
4141

4242
type CvFormFields = {
4343
cvUploadChoice: "indeterminate" | "upload" | "remind" | "no-upload"
44-
cvFiles: File[]
44+
cvFiles?: File[] | undefined
4545
}
4646

4747
const cvFormSchema = z.discriminatedUnion(
@@ -78,7 +78,7 @@ const cvFormSchema = z.discriminatedUnion(
7878
.length(1, "Please provide exactly one CV file!"),
7979
}),
8080
],
81-
{ errorMap: () => ({ message: "Please select an option." }) },
81+
{ error: () => ({ message: "Please select an option." }) },
8282
)
8383

8484
/**
@@ -91,7 +91,7 @@ function CvForm({ application }: { application: Application }) {
9191
const [showForm, setShowForm] = React.useState<boolean>(() => application?.cvUploadChoice === "upload")
9292

9393
const form = useForm<CvFormFields, unknown, z.infer<typeof cvFormSchema>>({
94-
resolver: zodResolver(cvFormSchema),
94+
resolver: zodResolver<CvFormFields, unknown, z.infer<typeof cvFormSchema>>(cvFormSchema),
9595
defaultValues: {
9696
cvUploadChoice: application.cvUploadChoice ?? "indeterminate",
9797
cvFiles: [],
@@ -150,9 +150,9 @@ function CvForm({ application }: { application: Application }) {
150150
>
151151
<FormControl>
152152
<SelectTrigger ref={ref}>
153-
<SelectValueClipper>
153+
<SelectValueViewport>
154154
<SelectValue />
155-
</SelectValueClipper>
155+
</SelectValueViewport>
156156
</SelectTrigger>
157157
</FormControl>
158158
<SelectContent>

client/src/app/dashboard/(application)/education/page.tsx

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import { zodResolver } from "@hookform/resolvers/zod"
2929
import { useRouter } from "next/navigation"
3030
import { useForm } from "react-hook-form"
3131
import useSWRImmutable from "swr/immutable"
32-
import { z } from "zod"
32+
import { z } from "zod/v4"
3333

3434
import { FormSkeleton } from "@/components/dashboard/form-skeleton"
3535
import { FormSubmitButton } from "@/components/dashboard/form-submit-button"
@@ -38,11 +38,12 @@ import type { Application } from "@/hooks/use-application"
3838
import { useApplicationContext } from "@/hooks/use-application-context"
3939
import { isLoaded } from "@/lib/is-loaded"
4040
import { updateApplication } from "@/lib/update-application"
41-
import "@/lib/zod-iso3-extension"
41+
import { zodIso3 } from "@/lib/zod-iso3-validator"
42+
import {isString} from "@/lib/type-guards";
4243

4344
type EducationFormFields = {
4445
university: string
45-
graduationYear: string
46+
graduationYear: unknown
4647
levelOfStudy: string
4748
disciplinesOfStudy: DisciplineOfStudy[]
4849
countryOfResidence: string
@@ -61,15 +62,18 @@ export type CountryOption = {
6162

6263
const educationFormSchema = z.object({
6364
university: z.string().trim().min(1, { message: "Please select your institution." }),
64-
graduationYear: z.coerce
65-
.number({ invalid_type_error: "Please provide a valid year." })
65+
graduationYear: z.coerce.number({
66+
error: (issue) => issue.input === undefined
67+
? "This field is required"
68+
: "Please provide a valid year."
69+
})
6670
.positive("Please provide a valid year.")
6771
.int("Oh, come on. Really?")
68-
.min(1900, { message: "Be serious. You didn't graduate before 1900." })
69-
.max(2100, { message: "What on earth are you studying?!?" }),
72+
.min(1900, { error: "Be serious. You didn't graduate before 1900." })
73+
.max(2100, { error: "What on earth are you studying?!?" }),
7074
disciplinesOfStudy: z
7175
.array(disciplineOfStudySchema)
72-
.min(1, { message: "Please select your discipline(s) of study." }),
76+
.min(1, { error: "Please select your discipline(s) of study." }),
7377
levelOfStudy: z.enum(
7478
[
7579
"secondary",
@@ -83,9 +87,9 @@ const educationFormSchema = z.object({
8387
"not-a-student",
8488
"prefer-not-to-answer",
8589
],
86-
{ message: "Please select your level of study." },
90+
{ error: "Please select your level of study." },
8791
),
88-
countryOfResidence: z.string().iso3(),
92+
countryOfResidence: zodIso3(),
8993
})
9094

9195
async function optionsFetcher<OptionType>(path: string): Promise<OptionType[]> {
@@ -112,7 +116,7 @@ function EducationForm({ schoolOptions, countryOptions, application }: Education
112116
const { mutateApplication } = useApplicationContext()
113117

114118
const form = useForm<EducationFormFields, unknown, z.infer<typeof educationFormSchema>>({
115-
resolver: zodResolver(educationFormSchema),
119+
resolver: zodResolver<EducationFormFields, unknown, z.infer<typeof educationFormSchema>>(educationFormSchema),
116120
defaultValues: {
117121
university: application.university ?? "",
118122
graduationYear: application.graduationYear?.toString() ?? "",
@@ -160,11 +164,17 @@ function EducationForm({ schoolOptions, countryOptions, application }: Education
160164
<FormField
161165
control={form.control}
162166
name="graduationYear"
163-
render={({ field }) => (
167+
render={({ field: { value, ...field } }) => (
164168
<FormItem>
165169
<FormLabel>Graduation Year</FormLabel>
166170
<FormControl>
167-
<Input type="number" inputMode="numeric" placeholder="Enter graduation year..." {...field} />
171+
<Input
172+
type="number"
173+
inputMode="numeric"
174+
placeholder="Enter graduation year..."
175+
value={isString(value) ? value : ""}
176+
{...field}
177+
/>
168178
</FormControl>
169179
<FormMessage />
170180
</FormItem>

client/src/app/dashboard/(application)/extra/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { Textarea } from "@durhack/web-components/ui/textarea"
2222
import { zodResolver } from "@hookform/resolvers/zod"
2323
import { useRouter } from "next/navigation"
2424
import { useForm } from "react-hook-form"
25-
import { z } from "zod"
25+
import { z } from "zod/v4"
2626

2727
import { FormSkeleton } from "@/components/dashboard/form-skeleton"
2828
import { FormSubmitButton } from "@/components/dashboard/form-submit-button"
@@ -67,7 +67,7 @@ function ExtraDetailsForm({ application }: { application: Application }) {
6767
const { mutateApplication } = useApplicationContext()
6868

6969
const form = useForm<ExtraDetailsFormFields, unknown, z.infer<typeof extraDetailsFormSchema>>({
70-
resolver: zodResolver(extraDetailsFormSchema),
70+
resolver: zodResolver<ExtraDetailsFormFields, unknown, z.infer<typeof extraDetailsFormSchema>>(extraDetailsFormSchema),
7171
defaultValues: {
7272
tShirtSize: application.tShirtSize ?? "",
7373
hackathonExperience: application.hackathonExperience ?? "",

client/src/app/dashboard/(application)/personal/page.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,22 @@ import {
1313
import { zodResolver } from "@hookform/resolvers/zod"
1414
import { useRouter } from "next/navigation"
1515
import { useForm } from "react-hook-form"
16-
import { z } from "zod"
16+
import { z } from "zod/v4"
1717

1818
import { FormSkeleton } from "@/components/dashboard/form-skeleton"
1919
import { FormSubmitButton } from "@/components/dashboard/form-submit-button"
2020
import type { Application } from "@/hooks/use-application"
2121
import { useApplicationContext } from "@/hooks/use-application-context"
2222
import { isLoaded } from "@/lib/is-loaded"
2323
import { updateApplication } from "@/lib/update-application"
24+
import { isString } from "@/lib/type-guards"
2425

2526
type PersonalFormFields = {
2627
firstNames: string
2728
lastNames: string
2829
preferredNames: string
2930
pronouns: string
30-
age: string
31+
age: unknown
3132
gender: string
3233
ethnicity: string
3334
}
@@ -38,7 +39,7 @@ const personalFormSchema = z.object({
3839
preferredNames: z.string().trim().max(256),
3940
pronouns: z.enum(["prefer-not-to-answer", "he/him", "she/her", "they/them", "xe/xem", "other"]),
4041
age: z.coerce
41-
.number({ invalid_type_error: "Please provide a valid age." })
42+
.number("Please provide a valid age.")
4243
.positive("Please provide a valid age.")
4344
.min(16, { message: "Age must be >= 16" })
4445
.max(256, { message: "Ain't no way you're that old." })
@@ -60,7 +61,7 @@ function PersonalForm({ application }: { application: Application }) {
6061
const { mutateApplication } = useApplicationContext()
6162

6263
const form = useForm<PersonalFormFields, unknown, z.infer<typeof personalFormSchema>>({
63-
resolver: zodResolver(personalFormSchema),
64+
resolver: zodResolver<PersonalFormFields, unknown, z.infer<typeof personalFormSchema>>(personalFormSchema),
6465
defaultValues: {
6566
pronouns: application.pronouns ?? "prefer-not-to-answer",
6667
firstNames: application.firstNames ?? "",
@@ -166,11 +167,11 @@ function PersonalForm({ application }: { application: Application }) {
166167
<FormField
167168
control={form.control}
168169
name="age"
169-
render={({ field }) => (
170+
render={({ field: { value, ...field } }) => (
170171
<FormItem>
171172
<FormLabel>Age as of 1st November 2025</FormLabel>
172173
<FormControl>
173-
<Input placeholder="Enter age..." {...field} />
174+
<Input placeholder="Enter age..." value={isString(value) ? value : ""} {...field} />
174175
</FormControl>
175176
<FormMessage />
176177
</FormItem>

client/src/app/dashboard/(application)/submit/page.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import Link from "next/link"
1515
import { useRouter } from "next/navigation"
1616
import type * as React from "react"
1717
import { useForm, useFormContext } from "react-hook-form"
18-
import { z } from "zod"
18+
import { z } from "zod/v4"
1919

2020
import { useBackgroundContext } from "@/app/dashboard/background-context"
2121
import { FormSkeleton } from "@/components/dashboard/form-skeleton"
@@ -37,11 +37,11 @@ type SubmitFormFields = {
3737
}
3838

3939
const submitFormSchema = z.object({
40-
mlhCodeOfConduct: z.literal(true, { errorMap: () => ({ message: "Required" }) }),
41-
mlhTerms: z.literal(true, { errorMap: () => ({ message: "Required" }) }),
40+
mlhCodeOfConduct: z.literal(true, { error: () => ({ message: "Required" }) }),
41+
mlhTerms: z.literal(true, { error: () => ({ message: "Required" }) }),
4242
mlhMarketing: z.boolean(),
43-
dsuPrivacy: z.literal(true, { errorMap: () => ({ message: "Required" }) }),
44-
hukPrivacy: z.literal(true, { errorMap: () => ({ message: "Required" }) }),
43+
dsuPrivacy: z.literal(true, { error: () => ({ message: "Required" }) }),
44+
hukPrivacy: z.literal(true, { error: () => ({ message: "Required" }) }),
4545
hukMarketing: z.boolean(),
4646
media: z.boolean({ message: "Please specify" }),
4747
})
@@ -91,7 +91,7 @@ function SubmitForm({ application }: { application: Application }) {
9191
const { mutateApplication } = useApplicationContext()
9292

9393
const form = useForm<SubmitFormFields, unknown, z.infer<typeof submitFormSchema>>({
94-
resolver: zodResolver(submitFormSchema),
94+
resolver: zodResolver<SubmitFormFields, unknown, z.infer<typeof submitFormSchema>>(submitFormSchema),
9595
defaultValues: {
9696
mlhCodeOfConduct: application.consents.find((consent) => consent.name === "mlhCodeOfConduct")?.choice ?? false,
9797
mlhTerms: application.consents.find((consent) => consent.name === "mlhTerms")?.choice ?? false,

client/src/lib/type-guards.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export function isString(value: unknown): value is string {
2+
return typeof value === "string" || value instanceof String
3+
}
4+
5+
export function isNumber(value: unknown): value is number {
6+
return typeof value === "number" || value instanceof Number
7+
}
8+
9+
export function isBoolean(value: unknown): value is boolean {
10+
return typeof value === "boolean" || value instanceof Boolean
11+
}

client/src/lib/zod-iso3-extension.ts

Lines changed: 0 additions & 18 deletions
This file was deleted.

0 commit comments

Comments
 (0)