Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
"react-zxing": "^2.1.0",
"require-in-the-middle": "^8.0.1",
"superjson": "^2.0.0",
"zod": "^3.25.47"
"zod": "^4.4.3"
},
"devDependencies": {
"@biomejs/biome": "2.4.15",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ const AttendanceFormSchema = AttendanceWriteSchema.superRefine((val, ctx) => {
const code = "custom"
ctx.addIssue({ message, code, path: ["deregisterDeadline"] })
}

return true
})

interface AttendanceFormProps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
import { z } from "zod"
import { validateEventWrite } from "../validation"

const EVENT_FORM_DATA_TYPE = Object.values(EventTypeSchema.Values).map((type) => ({
const EVENT_FORM_DATA_TYPE = Object.values(EventTypeSchema.enum).map((type) => ({
value: type,
label: mapEventTypeToLabel(type),
}))
Expand All @@ -36,7 +36,7 @@ const FormValidationSchema = EventSchema.extend({
}).superRefine((data, ctx) => {
const issues = validateEventWrite(data)
for (const issue of issues) {
ctx.addIssue(issue)
ctx.addIssue({ code: "custom", message: issue.message, path: issue.path })
}
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import z from "zod"
import { useDeleteFeedbackFormMutation } from "../mutations"
import { useEventFeedbackPublicResultsTokenGetQuery, useFeedbackAnswersGetQuery } from "../queries"

const typeOptions = Object.values(FeedbackQuestionSchema.shape.type.Values).map((type) => ({
const typeOptions = Object.values(FeedbackQuestionSchema.shape.type.enum).map((type) => ({
value: type,
label: getFeedbackQuestionTypeName(type),
}))
Expand All @@ -63,8 +63,6 @@ const FormValuesSchema = z
const code = "custom"
ctx.addIssue({ message, code, path: ["feedbackForm.answerDeadline"] })
}

return true
})

export type FormValues = z.infer<typeof FormValuesSchema>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ export const PoolFormSchema = z.object({
}
return null
}, z
.number()
.int()
.min(0, "Utsettelse må være mellom 0 og 96 timer (4 dager).")
.max(96, "Utsettelse må være mellom 0 og 96 timer (4 dager).")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
import { addHours, roundToNearestHours } from "date-fns"
import { z } from "zod"

const EVENT_FORM_DATA_TYPE = Object.values(EventTypeSchema.Values).map((type) => ({
const EVENT_FORM_DATA_TYPE = Object.values(EventTypeSchema.enum).map((type) => ({
value: type,
label: mapEventTypeToLabel(type),
}))
Expand All @@ -39,7 +39,7 @@ const FormValidationSchema = EventWriteSchema.extend({
}).superRefine((data, ctx) => {
const issues = validateEventWrite(data)
for (const issue of issues) {
ctx.addIssue(issue)
ctx.addIssue({ code: "custom", message: issue.message, path: issue.path })
}
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const useCompanyWriteForm = ({

return useFormBuilder({
schema: CompanyWriteSchema.extend({
website: z.string().url(),
website: z.url(),
}),
defaultValues,
onSubmit,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ export const useMembershipWriteForm = ({
label: "Type",
required: true,
placeholder: "Velg type",
data: Object.values(MembershipTypeSchema.Values).map((type) => ({
data: Object.values(MembershipTypeSchema.enum).map((type) => ({
value: type,
label: getMembershipTypeName(type) ?? type,
})),
Expand All @@ -144,7 +144,7 @@ export const useMembershipWriteForm = ({
required: false,
clearable: true,
placeholder: "Velg spesialisering",
data: Object.values(MembershipSpecializationSchema.Values)
data: Object.values(MembershipSpecializationSchema.enum)
.filter((specialization) => specialization !== "UNKNOWN")
.map((specialization) => ({
value: specialization,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export const AllGroupsTable = ({ groups }: Props) => {
filters={[
{ columnId: "status", label: "Aktiv", value: "Aktiv" },
{ columnId: "status", label: "Inaktiv", value: "Inaktiv" },
...Object.values(GroupTypeSchema.Values).map((groupType) => {
...Object.values(GroupTypeSchema.enum).map((groupType) => {
const typeName = getGroupTypeName(groupType)

return {
Expand Down
8 changes: 4 additions & 4 deletions apps/dashboard/src/app/(internal)/grupper/write-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export const useGroupWriteForm = ({
FormSchema.superRefine((data, ctx) => {
const issues = validateGroupWrite(data, existingGroupSlugs, defaultValues.slug)
for (const issue of issues) {
ctx.addIssue(issue)
ctx.addIssue({ code: "custom", message: issue.message, path: issue.path })
}
}),
[existingGroupSlugs, defaultValues]
Expand Down Expand Up @@ -108,7 +108,7 @@ export const useGroupWriteForm = ({
memberVisibility: createSelectInput({
label: "Hvilke medlemmer skal vises",
placeholder: "Velg en",
data: Object.values(GroupMemberVisibilitySchema.Values).map((groupMemberVisibilityType) => ({
data: Object.values(GroupMemberVisibilitySchema.enum).map((groupMemberVisibilityType) => ({
value: groupMemberVisibilityType,
label: getGroupMemberVisibilityName(groupMemberVisibilityType),
})),
Expand All @@ -130,7 +130,7 @@ export const useGroupWriteForm = ({
placeholder: "Velg en",
withAsterisk: true,
required: true,
data: Object.values(GroupTypeSchema.Values).map((groupType) => ({
data: Object.values(GroupTypeSchema.enum).map((groupType) => ({
value: groupType,
label: getGroupTypeName(groupType),
})),
Expand All @@ -141,7 +141,7 @@ export const useGroupWriteForm = ({
placeholder: "Velg en",
withAsterisk: true,
required: true,
data: Object.values(GroupRecruitmentMethodSchema.Values).map((recruitmentMethod) => ({
data: Object.values(GroupRecruitmentMethodSchema.enum).map((recruitmentMethod) => ({
value: recruitmentMethod,
label: getGroupRecruitmentMethodName(recruitmentMethod),
})),
Expand Down
2 changes: 1 addition & 1 deletion apps/dashboard/src/app/(internal)/karriere/write-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export const useJobListingWriteForm = ({
employment: createSelectInput({
label: "Type",
placeholder: "Velg type",
data: Object.values(JobListingSchema.shape.employment.Values).map((employment) => ({
data: Object.values(JobListingSchema.shape.employment.enum).map((employment) => ({
value: employment,
label: getJobListingEmploymentName(employment),
})),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const RESULT_ORDER_OPTIONS = [
const FormValidationSchema = ContestWriteSchema.superRefine((data, ctx) => {
const issues = validateContestWrite(data)
for (const issue of issues) {
ctx.addIssue(issue)
ctx.addIssue({ code: "custom", message: issue.message, path: issue.path })
}
})

Expand Down
2 changes: 1 addition & 1 deletion apps/dashboard/src/app/(internal)/prikker/write-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export const useMarkWriteForm = ({

return useFormBuilder({
schema: MarkFormSchema,
// @ts-expect-error: The default should be a string but is typed as a number
// @ts-expect-error: weight needs a string default for the text input but the schema output is number (handled by z.preprocess)
defaultValues: { ...defaultValues, weight: defaultValues.weight?.toString() },
onSubmit,
label,
Expand Down
2 changes: 1 addition & 1 deletion apps/dashboard/src/components/ImageUploadModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { z } from "zod"
const validationSchema = z.object({
title: z.string().max(255).optional(),
alt: z.string().min(1).max(255),
imageUrl: z.string().url(),
imageUrl: z.url(),
})

interface UploadImageModalProps {
Expand Down
5 changes: 2 additions & 3 deletions apps/dashboard/src/components/forms/CheckboxGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { ErrorMessage } from "@hookform/error-message"
import { Checkbox } from "@mantine/core"
import { Controller, type FieldValues } from "react-hook-form"
import type { InputProducerResult } from "./types"
import { getErrorMessage, type InputProducerResult } from "./types"

interface CheckboxGroupsProps {
selected: number[]
Expand Down Expand Up @@ -45,7 +44,7 @@ export function createLabelledCheckboxGroupInput<F extends FieldValues>({
return function LabelledCheckboxGroupInput({ name, state, control }) {
return (
<div>
{state.errors[name] && <ErrorMessage errors={state.errors} name={name} />}
{getErrorMessage(state, name)}
<Controller
control={control}
name={name}
Expand Down
11 changes: 2 additions & 9 deletions apps/dashboard/src/components/forms/CheckboxInput.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
import { ErrorMessage } from "@hookform/error-message"
import { Checkbox, type CheckboxProps } from "@mantine/core"
import type { FieldValues } from "react-hook-form"
import type { InputProducerResult } from "./types"
import { getErrorMessage, type InputProducerResult } from "./types"

export function createCheckboxInput<F extends FieldValues>({
...props
}: Omit<CheckboxProps, "error">): InputProducerResult<F> {
return function FormCheckboxInput({ name, state, register }) {
return (
<Checkbox
{...register(name)}
{...props}
error={state.errors[name] && <ErrorMessage errors={state.errors} name={name} />}
/>
)
return <Checkbox {...register(name)} {...props} error={getErrorMessage(state, name)} />
}
}
5 changes: 2 additions & 3 deletions apps/dashboard/src/components/forms/DateInput.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { getCurrentUTC } from "@dotkomonline/utils"
import { ErrorMessage } from "@hookform/error-message"
import { DatePickerInput, type DatePickerInputProps } from "@mantine/dates"
import { roundToNearestHours } from "date-fns"
import { Controller, type FieldValues } from "react-hook-form"
import type { InputProducerResult } from "./types"
import { getErrorMessage, type InputProducerResult } from "./types"
import { ActionIcon, Stack } from "@mantine/core"
import { IconX } from "@tabler/icons-react"

Expand All @@ -24,7 +23,7 @@ export function createDateInput<F extends FieldValues>({
defaultValue={defaultValue ?? roundToNearestHours(getCurrentUTC(), { roundingMethod: "ceil" })}
value={field.value}
onChange={field.onChange}
error={state.errors[name] && <ErrorMessage errors={state.errors} name={name} />}
error={getErrorMessage(state, name)}
rightSection={
props.required !== true && (
<ActionIcon w="fit-content" color="gray" variant="subtle" onClick={() => field.onChange(null)}>
Expand Down
5 changes: 2 additions & 3 deletions apps/dashboard/src/components/forms/DateTimeInput.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { getCurrentUTC } from "@dotkomonline/utils"
import { ErrorMessage } from "@hookform/error-message"
import { DateTimePicker, type DateTimePickerProps } from "@mantine/dates"
import { roundToNearestHours } from "date-fns"
import { Controller, type FieldValues } from "react-hook-form"
import type { InputProducerResult } from "./types"
import { getErrorMessage, type InputProducerResult } from "./types"
import { IconX } from "@tabler/icons-react"
import { ActionIcon } from "@mantine/core"

Expand All @@ -24,7 +23,7 @@ export function createDateTimeInput<F extends FieldValues>({
defaultValue={defaultValue ?? roundToNearestHours(getCurrentUTC(), { roundingMethod: "ceil" })}
value={field.value}
onChange={field.onChange}
error={state.errors[name] && <ErrorMessage errors={state.errors} name={name} />}
error={getErrorMessage(state, name)}
rightSection={
props.required !== true && (
<ActionIcon w="fit-content" color="gray" variant="subtle" onClick={() => field.onChange(null)}>
Expand Down
5 changes: 2 additions & 3 deletions apps/dashboard/src/components/forms/EventSelectInput.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { useEventAllQuery, useEventWithAttendancesGetQuery } from "@/app/(internal)/arrangementer/queries"
import type { EventId } from "@dotkomonline/types"
import { ErrorMessage } from "@hookform/error-message"
import { Select, type SelectProps } from "@mantine/core"
import { useDebouncedValue } from "@mantine/hooks"
import { useState } from "react"
import { Controller, type FieldValues, useController } from "react-hook-form"
import type { InputProducerResult } from "./types"
import { getErrorMessage, type InputProducerResult } from "./types"

interface Props extends Omit<SelectProps, "error"> {
excludeEventIds?: EventId[]
Expand Down Expand Up @@ -56,7 +55,7 @@ export function createEventSelectInput<F extends FieldValues>({
searchValue={searchQuery}
onSearchChange={handleEventSearch}
searchable={true}
error={state.errors[name] && <ErrorMessage errors={state.errors} name={name} />}
error={getErrorMessage(state, name)}
data={options}
/>
)}
Expand Down
12 changes: 8 additions & 4 deletions apps/dashboard/src/components/forms/Form.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { zodResolver } from "@hookform/resolvers/zod"
import { Button, Flex } from "@mantine/core"
import { type DefaultValues, type UseFormReturn, useForm } from "react-hook-form"
import { type DefaultValues, type FieldPath, type FieldValue, type UseFormReturn, useForm } from "react-hook-form"
import type { z } from "zod"
import type { InputProducerResult } from "./types"

Expand All @@ -9,7 +9,7 @@ function entriesOf<T extends Record<string, unknown>, K extends string & keyof T
}

interface FormBuilderOptions<T extends z.ZodRawShape> {
schema: z.ZodEffects<z.ZodObject<T>> | z.ZodObject<T>
schema: z.ZodObject<T>
fields: Partial<{
[K in keyof z.infer<z.ZodObject<T>>]: InputProducerResult<z.infer<z.ZodObject<T>>>
}>
Expand Down Expand Up @@ -37,11 +37,15 @@ export function useFormBuilder<T extends z.ZodRawShape>({
throw new Error()
}
const Component: InputProducerResult<z.infer<z.ZodObject<T>>> = fc
// zod v4's $InferObjectOutput<T> is opaque in generics. FieldPath<T> cannot index DeepPartial<T> without this cast
const defaultValue = (form.formState.defaultValues as Record<string, unknown>)?.[name as string] as FieldValue<
z.infer<z.ZodObject<T>>
>
return (
<Component
defaultValue={form.formState.defaultValues?.[name]}
defaultValue={defaultValue}
key={name}
name={name}
name={name as FieldPath<z.infer<z.ZodObject<T>>>}
register={form.register}
control={form.control}
state={form.formState}
Expand Down
5 changes: 2 additions & 3 deletions apps/dashboard/src/components/forms/FormSelectInput.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { ErrorMessage } from "@hookform/error-message"
import { Select, type SelectProps } from "@mantine/core"
import { Controller, type FieldValues } from "react-hook-form"
import type { InputProducerResult } from "./types"
import { getErrorMessage, type InputProducerResult } from "./types"

export function createIntegerSelectInput<F extends FieldValues>({
...props
Expand All @@ -22,7 +21,7 @@ export function createIntegerSelectInput<F extends FieldValues>({
}))}
value={field.value?.toString() ?? ""}
onChange={(value) => field.onChange(value !== null ? Number.parseInt(value, 10) : null)}
error={state.errors[name] && <ErrorMessage errors={state.errors} name={name} />}
error={getErrorMessage(state, name)}
/>
)}
/>
Expand Down
10 changes: 2 additions & 8 deletions apps/dashboard/src/components/forms/MultiSelectInput.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { ErrorMessage } from "@hookform/error-message"
import { MultiSelect, type MultiSelectProps } from "@mantine/core"
import { Controller, type FieldValues } from "react-hook-form"
import type { InputProducerResult } from "./types"
import { getErrorMessage, type InputProducerResult } from "./types"

export function createMultipleSelectInput<F extends FieldValues>({
...props
Expand All @@ -12,12 +11,7 @@ export function createMultipleSelectInput<F extends FieldValues>({
control={control}
name={name}
render={({ field }) => (
<MultiSelect
{...props}
error={state.errors[name] && <ErrorMessage errors={state.errors} name={name} />}
onChange={field.onChange}
value={field.value}
/>
<MultiSelect {...props} error={getErrorMessage(state, name)} onChange={field.onChange} value={field.value} />
)}
/>
)
Expand Down
5 changes: 2 additions & 3 deletions apps/dashboard/src/components/forms/NumberInput.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { ErrorMessage } from "@hookform/error-message"
import { NumberInput, type NumberInputProps } from "@mantine/core"
import { Controller, type FieldValues } from "react-hook-form"
import type { InputProducerResult } from "./types"
import { getErrorMessage, type InputProducerResult } from "./types"

export function createNumberInput<F extends FieldValues>({
...props
Expand All @@ -16,7 +15,7 @@ export function createNumberInput<F extends FieldValues>({
{...props}
value={field.value}
onChange={(value) => field.onChange({ target: { value } })}
error={state.errors[name] && <ErrorMessage errors={state.errors} name={name} />}
error={getErrorMessage(state, name)}
/>
)}
/>
Expand Down
Loading
Loading