diff --git a/apps/nextjs/src/app/[locale]/auth/invite/[id]/_registration-form.tsx b/apps/nextjs/src/app/[locale]/auth/invite/[id]/_registration-form.tsx index f7ea9ace5..9baf60958 100644 --- a/apps/nextjs/src/app/[locale]/auth/invite/[id]/_registration-form.tsx +++ b/apps/nextjs/src/app/[locale]/auth/invite/[id]/_registration-form.tsx @@ -4,7 +4,7 @@ import { useRouter } from "next/navigation"; import { Button, PasswordInput, Stack, TextInput } from "@mantine/core"; import { clientApi } from "@homarr/api/client"; -import { useForm, zodResolver } from "@homarr/form"; +import { useZodForm } from "@homarr/form"; import { showErrorNotification, showSuccessNotification, @@ -24,18 +24,17 @@ export const RegistrationForm = ({ invite }: RegistrationFormProps) => { const t = useScopedI18n("user"); const router = useRouter(); const { mutate, isPending } = clientApi.user.register.useMutation(); - const form = useForm({ - validate: zodResolver(validation.user.registration), + const form = useZodForm(validation.user.registration, { initialValues: { username: "", password: "", confirmPassword: "", }, - validateInputOnBlur: true, - validateInputOnChange: true, }); - const handleSubmit = (values: FormType) => { + const handleSubmit = ( + values: z.infer, + ) => { mutate( { ...values, @@ -88,5 +87,3 @@ export const RegistrationForm = ({ invite }: RegistrationFormProps) => { ); }; - -type FormType = z.infer; diff --git a/apps/nextjs/src/app/[locale]/auth/login/_login-form.tsx b/apps/nextjs/src/app/[locale]/auth/login/_login-form.tsx index ec5edf3a5..d98d387b0 100644 --- a/apps/nextjs/src/app/[locale]/auth/login/_login-form.tsx +++ b/apps/nextjs/src/app/[locale]/auth/login/_login-form.tsx @@ -13,7 +13,7 @@ import { import { IconAlertTriangle } from "@tabler/icons-react"; import { signIn } from "@homarr/auth/client"; -import { useForm, zodResolver } from "@homarr/form"; +import { useZodForm } from "@homarr/form"; import { showErrorNotification, showSuccessNotification, @@ -27,15 +27,16 @@ export const LoginForm = () => { const router = useRouter(); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(); - const form = useForm({ - validate: zodResolver(validation.user.signIn), + const form = useZodForm(validation.user.signIn, { initialValues: { name: "", password: "", }, }); - const handleSubmitAsync = async (values: FormType) => { + const handleSubmitAsync = async ( + values: z.infer, + ) => { setIsLoading(true); setError(undefined); await signIn("credentials", { @@ -92,5 +93,3 @@ export const LoginForm = () => { ); }; - -type FormType = z.infer; diff --git a/apps/nextjs/src/app/[locale]/boards/[name]/settings/_background.tsx b/apps/nextjs/src/app/[locale]/boards/[name]/settings/_background.tsx index 08c936336..a60f81395 100644 --- a/apps/nextjs/src/app/[locale]/boards/[name]/settings/_background.tsx +++ b/apps/nextjs/src/app/[locale]/boards/[name]/settings/_background.tsx @@ -7,11 +7,12 @@ import { backgroundImageRepeats, backgroundImageSizes, } from "@homarr/definitions"; -import { useForm } from "@homarr/form"; +import { useZodForm } from "@homarr/form"; import type { TranslationObject } from "@homarr/translation"; import { useI18n } from "@homarr/translation/client"; import type { SelectItemWithDescriptionBadge } from "@homarr/ui"; import { SelectWithDescriptionBadge } from "@homarr/ui"; +import { validation } from "@homarr/validation"; import type { Board } from "../../_types"; import { useSavePartialSettingsMutation } from "./_shared"; @@ -23,7 +24,7 @@ export const BackgroundSettingsContent = ({ board }: Props) => { const t = useI18n(); const { mutate: savePartialSettings, isPending } = useSavePartialSettingsMutation(board); - const form = useForm({ + const form = useZodForm(validation.board.savePartialSettings, { initialValues: { backgroundImageUrl: board.backgroundImageUrl ?? "", backgroundImageAttachment: board.backgroundImageAttachment, diff --git a/apps/nextjs/src/app/[locale]/boards/[name]/settings/_colors.tsx b/apps/nextjs/src/app/[locale]/boards/[name]/settings/_colors.tsx index 4b38a2d5f..f80511d06 100644 --- a/apps/nextjs/src/app/[locale]/boards/[name]/settings/_colors.tsx +++ b/apps/nextjs/src/app/[locale]/boards/[name]/settings/_colors.tsx @@ -17,8 +17,9 @@ import { } from "@mantine/core"; import { useDisclosure } from "@mantine/hooks"; -import { useForm } from "@homarr/form"; +import { useZodForm } from "@homarr/form"; import { useI18n } from "@homarr/translation/client"; +import { validation } from "@homarr/validation"; import type { Board } from "../../_types"; import { generateColors } from "../../(content)/_theme"; @@ -33,7 +34,7 @@ const hexRegex = /^#[0-9a-fA-F]{6}$/; const progressPercentageLabel = (value: number) => `${value}%`; export const ColorSettingsContent = ({ board }: Props) => { - const form = useForm({ + const form = useZodForm(validation.board.savePartialSettings, { initialValues: { primaryColor: board.primaryColor, secondaryColor: board.secondaryColor, @@ -114,15 +115,16 @@ export const ColorSettingsContent = ({ board }: Props) => { }; interface ColorsPreviewProps { - previewColor: string; + previewColor: string | undefined; } const ColorsPreview = ({ previewColor }: ColorsPreviewProps) => { const theme = useMantineTheme(); - const colors = hexRegex.test(previewColor) - ? generateColors(previewColor) - : generateColors("#000000"); + const colors = + previewColor && hexRegex.test(previewColor) + ? generateColors(previewColor) + : generateColors("#000000"); return ( diff --git a/apps/nextjs/src/app/[locale]/boards/[name]/settings/_general.tsx b/apps/nextjs/src/app/[locale]/boards/[name]/settings/_general.tsx index d8e3eec82..a47624d9e 100644 --- a/apps/nextjs/src/app/[locale]/boards/[name]/settings/_general.tsx +++ b/apps/nextjs/src/app/[locale]/boards/[name]/settings/_general.tsx @@ -17,8 +17,9 @@ import { } from "@mantine/hooks"; import { IconAlertTriangle } from "@tabler/icons-react"; -import { useForm } from "@homarr/form"; +import { useZodForm } from "@homarr/form"; import { useI18n } from "@homarr/translation/client"; +import { validation } from "@homarr/validation"; import type { Board } from "../../_types"; import { useUpdateBoard } from "../../(content)/_client"; @@ -38,20 +39,30 @@ export const GeneralSettingsContent = ({ board }: Props) => { const { mutate: savePartialSettings, isPending } = useSavePartialSettingsMutation(board); - const form = useForm({ - initialValues: { - pageTitle: board.pageTitle ?? "", - logoImageUrl: board.logoImageUrl ?? "", - metaTitle: board.metaTitle ?? "", - faviconImageUrl: board.faviconImageUrl ?? "", + const form = useZodForm( + validation.board.savePartialSettings + .pick({ + pageTitle: true, + logoImageUrl: true, + metaTitle: true, + faviconImageUrl: true, + }) + .required(), + { + initialValues: { + pageTitle: board.pageTitle ?? "", + logoImageUrl: board.logoImageUrl ?? "", + metaTitle: board.metaTitle ?? "", + faviconImageUrl: board.faviconImageUrl ?? "", + }, + onValuesChange({ pageTitle }) { + updateBoard((previous) => ({ + ...previous, + pageTitle, + })); + }, }, - onValuesChange({ pageTitle }) { - updateBoard((previous) => ({ - ...previous, - pageTitle, - })); - }, - }); + ); const metaTitleStatus = useMetaTitlePreview(form.values.metaTitle); const faviconStatus = useFaviconPreview(form.values.faviconImageUrl); diff --git a/apps/nextjs/src/app/[locale]/boards/[name]/settings/_layout.tsx b/apps/nextjs/src/app/[locale]/boards/[name]/settings/_layout.tsx index ea8865a7e..c4ed09d4c 100644 --- a/apps/nextjs/src/app/[locale]/boards/[name]/settings/_layout.tsx +++ b/apps/nextjs/src/app/[locale]/boards/[name]/settings/_layout.tsx @@ -2,8 +2,9 @@ import { Button, Grid, Group, Input, Slider, Stack } from "@mantine/core"; -import { useForm } from "@homarr/form"; +import { useZodForm } from "@homarr/form"; import { useI18n } from "@homarr/translation/client"; +import { validation } from "@homarr/validation"; import type { Board } from "../../_types"; import { useSavePartialSettingsMutation } from "./_shared"; @@ -15,11 +16,14 @@ export const LayoutSettingsContent = ({ board }: Props) => { const t = useI18n(); const { mutate: savePartialSettings, isPending } = useSavePartialSettingsMutation(board); - const form = useForm({ - initialValues: { - columnCount: board.columnCount, + const form = useZodForm( + validation.board.savePartialSettings.pick({ columnCount: true }).required(), + { + initialValues: { + columnCount: board.columnCount, + }, }, - }); + ); return (
{ const t = useScopedI18n("user"); const { mutateAsync, error, isPending } = clientApi.user.initUser.useMutation(); - const form = useForm({ - validate: zodResolver(validation.user.init), - validateInputOnBlur: true, - validateInputOnChange: true, + const form = useZodForm(validation.user.init, { initialValues: { username: "", password: "", diff --git a/apps/nextjs/src/app/[locale]/manage/apps/_form.tsx b/apps/nextjs/src/app/[locale]/manage/apps/_form.tsx index d024a9c36..0b0162659 100644 --- a/apps/nextjs/src/app/[locale]/manage/apps/_form.tsx +++ b/apps/nextjs/src/app/[locale]/manage/apps/_form.tsx @@ -3,7 +3,7 @@ import Link from "next/link"; import { Button, Group, Stack, Textarea, TextInput } from "@mantine/core"; -import { useForm, zodResolver } from "@homarr/form"; +import { useZodForm } from "@homarr/form"; import type { TranslationFunction } from "@homarr/translation"; import { useI18n } from "@homarr/translation/client"; import type { z } from "@homarr/validation"; @@ -25,14 +25,13 @@ export const AppForm = (props: AppFormProps) => { props; const t = useI18n(); - const form = useForm({ + const form = useZodForm(validation.app.manage, { initialValues: initialValues ?? { name: "", description: "", iconUrl: "", href: "", }, - validate: zodResolver(validation.app.manage), }); return ( @@ -41,9 +40,7 @@ export const AppForm = (props: AppFormProps) => { { - form.setFieldValue("iconUrl", iconUrl); - }} + {...form.getInputProps("iconUrl")} />