refactor: update zod imports to version 4 and adjust error handling

- Changed all zod imports from "zod" to "zod/v4" across multiple files.
- Updated error handling in forms to use new zod error configuration.
- Refactored custom error mapping to align with zod v4 changes.
- Adjusted password validation logic to utilize new zod features.
- Cleaned up unused locale error mapping code.
- Enhanced type definitions for async iterables in zod utility functions.
This commit is contained in:
Marco Pedone 2025-08-07 14:50:40 +02:00
parent e61b152986
commit 9314026817
53 changed files with 333 additions and 525 deletions

View file

@ -78,7 +78,7 @@
"uuid": "^11.1.0", "uuid": "^11.1.0",
"vaul": "^1.1.2", "vaul": "^1.1.2",
"yaml": "^2.8.1", "yaml": "^2.8.1",
"zod": "^3.25.76" "zod": "^4.0.15"
}, },
"devDependencies": { "devDependencies": {
"@eslint/compat": "^1.3.1", "@eslint/compat": "^1.3.1",
@ -127,7 +127,7 @@
"prettier-plugin-tailwindcss": "^0.6.14", "prettier-plugin-tailwindcss": "^0.6.14",
"tailwind-scrollbar": "^4.0.2", "tailwind-scrollbar": "^4.0.2",
"tailwindcss": "^4.1.11", "tailwindcss": "^4.1.11",
"trpc-ui": "https://gitpkg.vercel.app/jrhe/trpc-ui/packages/trpc-ui?feature/zod-v4-support&scripts.postinstall=npm%20install%20--ignore-scripts%20%26%26%20npm%20run%20build", "trpc-ui": "https://gitpkg.vercel.app/jrhe/trpc-ui/packages/trpc-ui?feature/zod-v4-support&scripts.postinstall=%26%26%20npm%20install%20pnpm%20%26%26%20npm%20install%20--ignore-scripts%20%26%26%20npm%20run%20build",
"tw-animate-css": "^1.3.6", "tw-animate-css": "^1.3.6",
"typescript": "^5.9.2", "typescript": "^5.9.2",
"typescript-eslint": "^8.39.0" "typescript-eslint": "^8.39.0"
@ -13830,6 +13830,16 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/knip/node_modules/zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/kysely": { "node_modules/kysely": {
"version": "0.28.4", "version": "0.28.4",
"resolved": "https://registry.npmjs.org/kysely/-/kysely-0.28.4.tgz", "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.28.4.tgz",
@ -20092,8 +20102,8 @@
}, },
"node_modules/trpc-ui": { "node_modules/trpc-ui": {
"version": "1.0.15", "version": "1.0.15",
"resolved": "https://gitpkg.vercel.app/jrhe/trpc-ui/packages/trpc-ui?feature/zod-v4-support&scripts.postinstall=npm%20install%20--ignore-scripts%20%26%26%20npm%20run%20build", "resolved": "https://gitpkg.vercel.app/jrhe/trpc-ui/packages/trpc-ui?feature/zod-v4-support&scripts.postinstall=%26%26%20npm%20install%20pnpm%20%26%26%20npm%20install%20--ignore-scripts%20%26%26%20npm%20run%20build",
"integrity": "sha512-E1FvgLyMzBFneyBxmYaW4FV4IaUjOcfSyN1fRpMdc3160/fePfSkUVP015FW4qNAuLc4hhQyOVXqefWquR9FUg==", "integrity": "sha512-3cw5HsTSl114eZQH1SGm3NWzb0JnDIdyB3Ins2kELHMFBax2Y7v2GLDAh5vkOc1IGqSwYg3gjbMLytKEug2B9w==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "ISC", "license": "ISC",
@ -20188,6 +20198,16 @@
"url": "https://github.com/sponsors/dcastil" "url": "https://github.com/sponsors/dcastil"
} }
}, },
"node_modules/trpc-ui/node_modules/zod-to-json-schema": {
"version": "3.24.6",
"resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz",
"integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==",
"dev": true,
"license": "ISC",
"peerDependencies": {
"zod": "^3.24.1"
}
},
"node_modules/ts-api-utils": { "node_modules/ts-api-utils": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
@ -21279,24 +21299,14 @@
} }
}, },
"node_modules/zod": { "node_modules/zod": {
"version": "3.25.76", "version": "4.0.15",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "resolved": "https://registry.npmjs.org/zod/-/zod-4.0.15.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "integrity": "sha512-2IVHb9h4Mt6+UXkyMs0XbfICUh1eUrlJJAOupBHUhLRnKkruawyDddYRCs0Eizt900ntIMk9/4RksYl+FgSpcQ==",
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"url": "https://github.com/sponsors/colinhacks" "url": "https://github.com/sponsors/colinhacks"
} }
}, },
"node_modules/zod-to-json-schema": {
"version": "3.24.6",
"resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz",
"integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==",
"dev": true,
"license": "ISC",
"peerDependencies": {
"zod": "^3.24.1"
}
},
"node_modules/zod-validation-error": { "node_modules/zod-validation-error": {
"version": "3.5.3", "version": "3.5.3",
"resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.5.3.tgz", "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.5.3.tgz",

View file

@ -89,7 +89,7 @@
"uuid": "^11.1.0", "uuid": "^11.1.0",
"vaul": "^1.1.2", "vaul": "^1.1.2",
"yaml": "^2.8.1", "yaml": "^2.8.1",
"zod": "^3.25.76" "zod": "^4.0.15"
}, },
"devDependencies": { "devDependencies": {
"@eslint/compat": "^1.3.1", "@eslint/compat": "^1.3.1",
@ -138,8 +138,8 @@
"prettier-plugin-tailwindcss": "^0.6.14", "prettier-plugin-tailwindcss": "^0.6.14",
"tailwind-scrollbar": "^4.0.2", "tailwind-scrollbar": "^4.0.2",
"tailwindcss": "^4.1.11", "tailwindcss": "^4.1.11",
"tw-animate-css": "^1.3.6",
"trpc-ui": "https://gitpkg.vercel.app/jrhe/trpc-ui/packages/trpc-ui?feature/zod-v4-support&scripts.postinstall=%26%26%20npm%20install%20pnpm%20%26%26%20npm%20install%20--ignore-scripts%20%26%26%20npm%20run%20build", "trpc-ui": "https://gitpkg.vercel.app/jrhe/trpc-ui/packages/trpc-ui?feature/zod-v4-support&scripts.postinstall=%26%26%20npm%20install%20pnpm%20%26%26%20npm%20install%20--ignore-scripts%20%26%26%20npm%20run%20build",
"tw-animate-css": "^1.3.6",
"typescript": "^5.9.2", "typescript": "^5.9.2",
"typescript-eslint": "^8.39.0" "typescript-eslint": "^8.39.0"
}, },

View file

@ -50,7 +50,7 @@ import { LoadingPage } from "~/components/loading";
import Link from "next/link"; import Link from "next/link";
import { cn } from "~/lib/utils"; import { cn } from "~/lib/utils";
import { useRef, useState } from "react"; import { useRef, useState } from "react";
import { z } from "zod"; import { z } from "zod/v4";
import { Separator } from "~/components/ui/separator"; import { Separator } from "~/components/ui/separator";
import { import {
Card, Card,
@ -152,8 +152,8 @@ export const AnnuncioActions = () => {
const ContrattoImportSchema = z.object({ const ContrattoImportSchema = z.object({
gestionale_id: z.number().nullable(), gestionale_id: z.number().nullable(),
contratto_decorrenza: z.coerce.date().nullable(), contratto_decorrenza: z.date().nullable(),
contratto_scadenza: z.coerce.date().nullable(), contratto_scadenza: z.date().nullable(),
contratto_tipo: z contratto_tipo: z
.enum([ .enum([
"Transitorio", "Transitorio",

View file

@ -1,7 +1,7 @@
// @ts-nocheck // @ts-nocheck
/* eslint-disable */ /* eslint-disable */
import { z } from "zod"; import { z } from "zod/v4";
/** /**
* Specify your server-side environment variables schema here. This way you can ensure the app isn't * Specify your server-side environment variables schema here. This way you can ensure the app isn't
@ -9,7 +9,7 @@ import { z } from "zod";
*/ */
const server = z.object({ const server = z.object({
NODE_ENV: z.enum(["development", "test", "production"]), NODE_ENV: z.enum(["development", "test", "production"]),
INTERNAL_BASE_URL: z.string().url(), INTERNAL_BASE_URL: z.url(),
PGHOST: z.string(), PGHOST: z.string(),
POSTGRES_USER: z.string(), POSTGRES_USER: z.string(),
POSTGRES_DB: z.string(), POSTGRES_DB: z.string(),
@ -96,7 +96,7 @@ if (!!process.env.SKIP_ENV_VALIDATION === false) {
if (parsed.success === false) { if (parsed.success === false) {
console.error( console.error(
"❌ Invalid environment variables:", "❌ Invalid environment variables:",
parsed.error.flatten().fieldErrors, z.treeifyError(parsed.error).fieldErrors,
); );
throw new Error("Invalid environment variables"); throw new Error("Invalid environment variables");
} }

View file

@ -1,4 +1,4 @@
import { z } from "zod"; import { z } from "zod/v4";
import { import {
Form, Form,
FormControl, FormControl,
@ -7,7 +7,6 @@ import {
FormLabel, FormLabel,
FormMessage, FormMessage,
} from "~/components/custom_ui/form"; } from "~/components/custom_ui/form";
import { customErrorMapWrapper } from "~/hooks/locale";
import { useZodForm } from "~/lib/zodForm"; import { useZodForm } from "~/lib/zodForm";
import Input from "~/components/custom_ui/input"; import Input from "~/components/custom_ui/input";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
@ -54,10 +53,9 @@ export const FormBanlist = ({
}; };
} }
z.setErrorMap(customErrorMapWrapper("it")); z.config(z.locales.it());
const form = useZodForm({ const form = useZodForm(Schema, {
schema: Schema,
defaultValues: defaultValues, defaultValues: defaultValues,
}); });

View file

@ -1,4 +1,4 @@
import { z } from "zod"; import { z } from "zod/v4";
import { import {
Form, Form,
FormControl, FormControl,
@ -7,7 +7,6 @@ import {
FormLabel, FormLabel,
FormMessage, FormMessage,
} from "~/components/custom_ui/form"; } from "~/components/custom_ui/form";
import { customErrorMapWrapper } from "~/hooks/locale";
import { useZodForm } from "~/lib/zodForm"; import { useZodForm } from "~/lib/zodForm";
import Input from "~/components/custom_ui/input"; import Input from "~/components/custom_ui/input";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
@ -73,10 +72,9 @@ export const FormBanners = ({
}; };
} }
z.setErrorMap(customErrorMapWrapper("it")); z.config(z.locales.it());
const form = useZodForm({ const form = useZodForm(Schema, {
schema: Schema,
defaultValues: defaultValues, defaultValues: defaultValues,
}); });

View file

@ -1,4 +1,4 @@
import { z } from "zod"; import { z } from "zod/v4";
import { useZodForm } from "~/lib/zodForm"; import { useZodForm } from "~/lib/zodForm";
import { import {
Form, Form,
@ -11,7 +11,6 @@ import {
import Input from "~/components/custom_ui/input"; import Input from "~/components/custom_ui/input";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { customErrorMapWrapper } from "~/hooks/locale";
import { api } from "~/utils/api"; import { api } from "~/utils/api";
import { useTranslation } from "~/providers/I18nProvider"; import { useTranslation } from "~/providers/I18nProvider";
import { useStrongPassword } from "~/hooks/useStrongPassword"; import { useStrongPassword } from "~/hooks/useStrongPassword";
@ -45,10 +44,9 @@ export const ChangePasswordForm = () => {
const { locale, t } = useTranslation(); const { locale, t } = useTranslation();
z.setErrorMap(customErrorMapWrapper(locale)); z.config(z.locales[locale]());
const form = useZodForm({ const form = useZodForm(ChangePasswordFormSchema, {
schema: ChangePasswordFormSchema,
defaultValues: { defaultValues: {
oldpassword: "", oldpassword: "",
newpassword: "", newpassword: "",

View file

@ -1,4 +1,4 @@
import { z } from "zod"; import { z } from "zod/v4";
import { import {
Form, Form,
FormControl, FormControl,
@ -9,7 +9,6 @@ import {
} from "~/components/custom_ui/form"; } from "~/components/custom_ui/form";
import Input from "~/components/custom_ui/input"; import Input from "~/components/custom_ui/input";
import { Textarea } from "~/components/custom_ui/textarea"; import { Textarea } from "~/components/custom_ui/textarea";
import { customErrorMapWrapper } from "~/hooks/locale";
import { useZodForm } from "~/lib/zodForm"; import { useZodForm } from "~/lib/zodForm";
import { api } from "~/utils/api"; import { api } from "~/utils/api";
import { useTranslation } from "~/providers/I18nProvider"; import { useTranslation } from "~/providers/I18nProvider";
@ -32,7 +31,7 @@ export const FormContattoAnnucio = ({ codice }: { codice: string }) => {
const schema = z.object({ const schema = z.object({
nome: z.string().nonempty("Inserisci un nome valido"), nome: z.string().nonempty("Inserisci un nome valido"),
email: z.string().email(), email: z.email(),
messaggio: z.string().nonempty("Inserisci un messaggio"), messaggio: z.string().nonempty("Inserisci un messaggio"),
telefono: z.string().nonempty("Inserisci un numero di telefono"), telefono: z.string().nonempty("Inserisci un numero di telefono"),
checkbox: z.boolean().refine((val) => val === false), // Honeypot field checkbox: z.boolean().refine((val) => val === false), // Honeypot field
@ -47,8 +46,7 @@ export const FormContattoAnnucio = ({ codice }: { codice: string }) => {
); );
type FromValues = z.infer<typeof schema>; type FromValues = z.infer<typeof schema>;
const form = useZodForm({ const form = useZodForm(schema, {
schema: schema,
defaultValues: { defaultValues: {
...value, ...value,
messaggio: "", messaggio: "",
@ -89,7 +87,7 @@ export const FormContattoAnnucio = ({ codice }: { codice: string }) => {
codice, codice,
}); });
}; };
z.setErrorMap(customErrorMapWrapper(locale)); z.config(z.locales[locale]());
return ( return (
<div className="mx-auto w-full"> <div className="mx-auto w-full">

View file

@ -1,4 +1,4 @@
import { z } from "zod"; import { z } from "zod/v4";
import { import {
Form, Form,
FormControl, FormControl,
@ -7,7 +7,6 @@ import {
FormLabel, FormLabel,
FormMessage, FormMessage,
} from "~/components/custom_ui/form"; } from "~/components/custom_ui/form";
import { customErrorMapWrapper } from "~/hooks/locale";
import { useZodForm } from "~/lib/zodForm"; import { useZodForm } from "~/lib/zodForm";
import Input from "~/components/custom_ui/input"; import Input from "~/components/custom_ui/input";
@ -52,16 +51,15 @@ export const FormEtichette = ({
}; };
} }
z.setErrorMap(customErrorMapWrapper("it")); z.config(z.locales.it());
const form = useZodForm({ const form = useZodForm(Schema, {
schema: Schema,
defaultValues: defaultValues, defaultValues: defaultValues,
}); });
function onSubmit(fields: FormValues) { function onSubmit(fields: FormValues) {
const values = { const values = {
id_etichetta: fields.id_etichetta as EtichetteIdEtichetta, id_etichetta: fields.id_etichetta,
title: fields.title, title: fields.title,
color_hex: fields.color_hex, color_hex: fields.color_hex,
}; };

View file

@ -1,4 +1,4 @@
import { z } from "zod"; import { z } from "zod/v4";
import { import {
Form, Form,
FormControl, FormControl,
@ -7,7 +7,6 @@ import {
FormLabel, FormLabel,
FormMessage, FormMessage,
} from "~/components/custom_ui/form"; } from "~/components/custom_ui/form";
import { customErrorMapWrapper } from "~/hooks/locale";
import { useZodForm } from "~/lib/zodForm"; import { useZodForm } from "~/lib/zodForm";
import Input from "~/components/custom_ui/input"; import Input from "~/components/custom_ui/input";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
@ -46,10 +45,9 @@ export const FormFlags = ({
}; };
} }
z.setErrorMap(customErrorMapWrapper("it")); z.config(z.locales.it());
const form = useZodForm({ const form = useZodForm(Schema, {
schema: Schema,
defaultValues: defaultValues, defaultValues: defaultValues,
}); });

View file

@ -1,5 +1,4 @@
import { customErrorMapWrapper } from "~/hooks/locale"; import { z } from "zod/v4";
import { z } from "zod";
import { useZodForm } from "~/lib/zodForm"; import { useZodForm } from "~/lib/zodForm";
import { import {
Form, Form,
@ -25,7 +24,7 @@ export const FormLogin = () => {
const router = useRouter(); const router = useRouter();
const { redirect } = router.query; const { redirect } = router.query;
const LoginSchema = z.object({ const LoginSchema = z.object({
email: z.string().email("Inserisci una email valida"), email: z.email(),
password: z.string(), password: z.string(),
checkbox: z.boolean().refine((val) => val === false), // Honeypot field checkbox: z.boolean().refine((val) => val === false), // Honeypot field
}); });
@ -38,10 +37,9 @@ export const FormLogin = () => {
checkbox: false, // Honeypot field checkbox: false, // Honeypot field
}; };
const { locale, t } = useTranslation(); const { locale, t } = useTranslation();
z.setErrorMap(customErrorMapWrapper(locale)); z.config(z.locales[locale]());
const form = useZodForm({ const form = useZodForm(LoginSchema, {
schema: LoginSchema,
defaultValues: defaultValues, defaultValues: defaultValues,
}); });

View file

@ -1,5 +1,4 @@
import { customErrorMapWrapper } from "~/hooks/locale"; import { z } from "zod/v4";
import { z } from "zod";
import { useZodForm } from "~/lib/zodForm"; import { useZodForm } from "~/lib/zodForm";
import { import {
Form, Form,
@ -20,7 +19,7 @@ import { useState } from "react";
export const FormPswReset = () => { export const FormPswReset = () => {
const router = useRouter(); const router = useRouter();
const LoginSchema = z.object({ const LoginSchema = z.object({
email: z.string().email("Inserisci una email valida"), email: z.email(),
}); });
type LoginFormValues = z.infer<typeof LoginSchema>; type LoginFormValues = z.infer<typeof LoginSchema>;
@ -29,10 +28,9 @@ export const FormPswReset = () => {
email: "", email: "",
}; };
const { locale, t } = useTranslation(); const { locale, t } = useTranslation();
z.setErrorMap(customErrorMapWrapper(locale)); z.config(z.locales[locale]());
const form = useZodForm({ const form = useZodForm(LoginSchema, {
schema: LoginSchema,
defaultValues: defaultValues, defaultValues: defaultValues,
}); });

View file

@ -1,4 +1,4 @@
import { z } from "zod"; import { z } from "zod/v4";
import { import {
Form, Form,
FormControl, FormControl,
@ -8,7 +8,6 @@ import {
FormLabel, FormLabel,
FormMessage, FormMessage,
} from "~/components/custom_ui/form"; } from "~/components/custom_ui/form";
import { customErrorMapWrapper } from "~/hooks/locale";
import { useZodForm } from "~/lib/zodForm"; import { useZodForm } from "~/lib/zodForm";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import { useTranslation } from "~/providers/I18nProvider"; import { useTranslation } from "~/providers/I18nProvider";
@ -22,13 +21,13 @@ import { Label } from "~/components/ui/label";
import { Badge } from "~/components/ui/badge"; import { Badge } from "~/components/ui/badge";
import type { Servizio } from "~/schemas/public/Servizio"; import type { Servizio } from "~/schemas/public/Servizio";
const schema = z const Schema = z
.object({ .object({
tipologia: z.custom<TipologiaPosizioneEnum>(), tipologia: z.custom<TipologiaPosizioneEnum>(),
budget: z.coerce.number(), budget: z.number(),
entromese: z.number().nullable(), entromese: z.number().nullable(),
n_adulti: z.coerce.number().min(1), n_adulti: z.number().min(1),
n_minori: z.coerce.number(), n_minori: z.number(),
permanenza: z.number().nullable(), permanenza: z.number().nullable(),
motivazione_transitorio: z.string().nullable(), motivazione_transitorio: z.string().nullable(),
reddito: z.string().nullable(), reddito: z.string().nullable(),
@ -43,15 +42,16 @@ const schema = z
}) })
.superRefine(({ n_adulti }, refinementContext) => { .superRefine(({ n_adulti }, refinementContext) => {
if (n_adulti <= 0) { if (n_adulti <= 0) {
refinementContext.addIssue({ refinementContext.issues.push({
code: z.ZodIssueCode.custom, code: "custom",
message: "Inserisci il numero di persone", message: "Inserisci il numero di persone",
path: ["npersone"], path: ["npersone"],
input: "",
}); });
} }
}); });
export type FormValues = z.infer<typeof schema>; export type FormValues = z.infer<typeof Schema>;
export const FormNewServizio = ({ export const FormNewServizio = ({
initialData, initialData,
@ -91,10 +91,9 @@ export const FormNewServizio = ({
} }
const { locale, t } = useTranslation(); const { locale, t } = useTranslation();
z.setErrorMap(customErrorMapWrapper(locale)); z.config(z.locales[locale]());
const form = useZodForm({ const form = useZodForm(Schema, {
schema: schema,
defaultValues: defaultValues, defaultValues: defaultValues,
}); });
@ -181,7 +180,7 @@ export const FormNewServizio = ({
step={50} step={50}
defaultValue={[field.value]} defaultValue={[field.value]}
onValueChange={(vals) => { onValueChange={(vals) => {
field.onChange(vals[0]); field.onChange(Number(vals[0]));
}} }}
rangeClassName="bg-neutral-700" rangeClassName="bg-neutral-700"
thumbClassName="border-neutral-700" thumbClassName="border-neutral-700"
@ -313,8 +312,8 @@ export const FormNewServizio = ({
</div> </div>
<FormControl> <FormControl>
<Counter <Counter
value={field.value} value={Number(field.value)}
setValue={field.onChange} setValue={(value) => field.onChange(Number(value))}
name="n_adulti" name="n_adulti"
onlyPositive onlyPositive
/> />
@ -338,8 +337,8 @@ export const FormNewServizio = ({
</div> </div>
<FormControl> <FormControl>
<Counter <Counter
value={field.value} value={Number(field.value)}
setValue={field.onChange} setValue={(value) => field.onChange(Number(value))}
name="n_minori" name="n_minori"
onlyPositive onlyPositive
/> />

View file

@ -1,5 +1,5 @@
"use client"; "use client";
import { z } from "zod"; import { z } from "zod/v4";
import { import {
Form, Form,
FormControl, FormControl,
@ -10,7 +10,6 @@ import {
FormLabel, FormLabel,
FormMessage, FormMessage,
} from "~/components/custom_ui/form"; } from "~/components/custom_ui/form";
import { customErrorMapWrapper } from "~/hooks/locale";
import { useZodForm } from "~/lib/zodForm"; import { useZodForm } from "~/lib/zodForm";
import { api } from "~/utils/api"; import { api } from "~/utils/api";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
@ -79,7 +78,7 @@ export const FormNewServizioAcquisto = ({
isAdmin?: boolean; isAdmin?: boolean;
}) => { }) => {
const router = useRouter(); const router = useRouter();
const schema = z const Schema = z
.object({ .object({
nome: z.string().nonempty("Inserisci un nome valido"), nome: z.string().nonempty("Inserisci un nome valido"),
cognome: z.string().nonempty("Inserisci un cognome valido"), cognome: z.string().nonempty("Inserisci un cognome valido"),
@ -90,10 +89,10 @@ export const FormNewServizioAcquisto = ({
sede_legale: z.string().nullable(), sede_legale: z.string().nullable(),
legale_rappresentante: z.string().nullable(), legale_rappresentante: z.string().nullable(),
p_iva: z.string().nullable(), p_iva: z.string().nullable(),
email: z.string().email(), email: z.email(),
telefono: z.string(), telefono: z.string(),
sesso: z.string(), sesso: z.string(),
data_nascita: z.coerce.date(), data_nascita: z.date(),
luogo_nascita: z.string(), luogo_nascita: z.string(),
nazione_nascita: z.string(), nazione_nascita: z.string(),
codice_fiscale: z.string(), codice_fiscale: z.string(),
@ -108,10 +107,10 @@ export const FormNewServizioAcquisto = ({
.string() .string()
.refine((v) => v !== "", { params: { type: "nazione" } }), .refine((v) => v !== "", { params: { type: "nazione" } }),
tipologia: z.custom<TipologiaPosizioneEnum>(), tipologia: z.custom<TipologiaPosizioneEnum>(),
budget: z.coerce.number().positive(), budget: z.number().positive(),
entromese: z.number().nullable(), entromese: z.number().nullable(),
n_adulti: z.coerce.number().min(1), n_adulti: z.number().min(1),
n_minori: z.coerce.number(), n_minori: z.number(),
permanenza: z.number().nullable(), permanenza: z.number().nullable(),
motivazione_transitorio: z.string().nullable(), motivazione_transitorio: z.string().nullable(),
reddito: z.string().nullable(), reddito: z.string().nullable(),
@ -152,57 +151,64 @@ export const FormNewServizioAcquisto = ({
refinementContext, refinementContext,
) => { ) => {
if (entromese === null) { if (entromese === null) {
refinementContext.addIssue({ refinementContext.issues.push({
code: z.ZodIssueCode.custom, code: "custom",
message: "Inserisci il mese di inizio", message: "Inserisci il mese di inizio",
path: ["entromese"], path: ["entromese"],
input: "",
}); });
} }
if (azienda) { if (azienda) {
if (!ragione_sociale || ragione_sociale.length < 1) { if (!ragione_sociale || ragione_sociale.length < 1) {
refinementContext.addIssue({ refinementContext.issues.push({
code: z.ZodIssueCode.custom, code: "custom",
message: "Inserisci la ragione sociale", message: "Inserisci la ragione sociale",
path: ["ragione_sociale"], path: ["ragione_sociale"],
input: "",
}); });
} }
if (!sede_legale || sede_legale.length < 1) { if (!sede_legale || sede_legale.length < 1) {
refinementContext.addIssue({ refinementContext.issues.push({
code: z.ZodIssueCode.custom, code: "custom",
message: "Inserisci la sede legale", message: "Inserisci la sede legale",
path: ["sede_legale"], path: ["sede_legale"],
input: "",
}); });
} }
if (!p_iva || p_iva.length < 1) { if (!p_iva || p_iva.length < 1) {
refinementContext.addIssue({ refinementContext.issues.push({
code: z.ZodIssueCode.custom, code: "custom",
message: "Inserisci la partita iva", message: "Inserisci la partita iva",
path: ["p_iva"], path: ["p_iva"],
input: "",
}); });
} }
if (!legale_rappresentante || legale_rappresentante.length < 1) { if (!legale_rappresentante || legale_rappresentante.length < 1) {
refinementContext.addIssue({ refinementContext.issues.push({
code: z.ZodIssueCode.custom, code: "custom",
message: "Inserisci il legale rappresentante", message: "Inserisci il legale rappresentante",
path: ["legale_rappresentante"], path: ["legale_rappresentante"],
input: "",
}); });
} }
} }
if (fatturazione_aziendale) { if (fatturazione_aziendale) {
if (!codice_destinatario || codice_destinatario.length != 7) { if (!codice_destinatario || codice_destinatario.length != 7) {
refinementContext.addIssue({ refinementContext.issues.push({
code: z.ZodIssueCode.custom, code: "custom",
message: "Inserisci un codice destinatario valido", message: "Inserisci un codice destinatario valido",
path: ["codice_destinatario"], path: ["codice_destinatario"],
input: "",
}); });
} }
} }
if (!telefono || telefono.length < 10) { if (!telefono || telefono.length < 10) {
refinementContext.addIssue({ refinementContext.issues.push({
code: z.ZodIssueCode.custom, code: "custom",
message: message:
"Il numero di telefono deve essere lungo almeno 10 caratteri", "Il numero di telefono deve essere lungo almeno 10 caratteri",
input: "",
}); });
} }
checkFiscalCodeValidity({ checkFiscalCodeValidity({
@ -217,20 +223,22 @@ export const FormNewServizioAcquisto = ({
nazioneNascita: nazione_nascita, nazioneNascita: nazione_nascita,
}); });
if (n_adulti <= 0) { if (n_adulti <= 0) {
refinementContext.addIssue({ refinementContext.issues.push({
code: z.ZodIssueCode.custom, code: "custom",
message: "Inserisci il numero di persone", message: "Inserisci il numero di persone",
path: ["n_adulti"], path: ["n_adulti"],
input: "",
}); });
} }
if ( if (
permanenza === null && permanenza === null &&
tipologia === TipologiaPosizioneEnum.Transitorio tipologia === TipologiaPosizioneEnum.Transitorio
) { ) {
refinementContext.addIssue({ refinementContext.issues.push({
code: z.ZodIssueCode.custom, code: "custom",
message: "Inserisci permanenza", message: "Inserisci permanenza",
path: ["permanenza"], path: ["permanenza"],
input: "",
}); });
} }
if (tipologia === TipologiaPosizioneEnum.Transitorio) { if (tipologia === TipologiaPosizioneEnum.Transitorio) {
@ -238,25 +246,27 @@ export const FormNewServizioAcquisto = ({
motivazione_transitorio === "" || motivazione_transitorio === "" ||
motivazione_transitorio === null motivazione_transitorio === null
) { ) {
refinementContext.addIssue({ refinementContext.issues.push({
code: z.ZodIssueCode.custom, code: "custom",
message: "Inserisci la motivazione", message: "Inserisci la motivazione",
path: ["motivazione_transitorio"], path: ["motivazione_transitorio"],
input: "",
}); });
} }
} else { } else {
if (reddito === "" || reddito === null) { if (reddito === "" || reddito === null) {
refinementContext.addIssue({ refinementContext.issues.push({
code: z.ZodIssueCode.custom, code: "custom",
message: "Inserisci il reddito", message: "Inserisci il reddito",
path: ["reddito"], path: ["reddito"],
input: "",
}); });
} }
} }
}, },
); );
type FormValues = z.infer<typeof schema>; type FormValues = z.infer<typeof Schema>;
// This can come from your database or API. // This can come from your database or API.
const defaultValues: FormValues = { const defaultValues: FormValues = {
@ -290,10 +300,9 @@ export const FormNewServizioAcquisto = ({
const { locale, t } = useTranslation(); const { locale, t } = useTranslation();
const nazioni_options = GetNazioni(); const nazioni_options = GetNazioni();
const comuni_options = getComuniNames(); const comuni_options = getComuniNames();
z.setErrorMap(customErrorMapWrapper(locale)); z.config(z.locales[locale]());
const form = useZodForm({ const form = useZodForm(Schema, {
schema: schema,
defaultValues: defaultValues, defaultValues: defaultValues,
}); });
const DatePickerLocale = locale === "it" ? it : enUS; const DatePickerLocale = locale === "it" ? it : enUS;
@ -478,7 +487,7 @@ export const FormNewServizioAcquisto = ({
step={50} step={50}
defaultValue={[field.value]} defaultValue={[field.value]}
onValueChange={(vals) => { onValueChange={(vals) => {
field.onChange(vals[0]); field.onChange(Number(vals[0]));
}} }}
rangeClassName="bg-neutral-700" rangeClassName="bg-neutral-700"
thumbClassName="border-neutral-700" thumbClassName="border-neutral-700"
@ -593,7 +602,7 @@ export const FormNewServizioAcquisto = ({
<FormControl> <FormControl>
<Counter <Counter
value={field.value} value={field.value}
setValue={field.onChange} setValue={(v) => field.onChange(Number(v))}
name="n_adulti" name="n_adulti"
onlyPositive onlyPositive
/> />
@ -615,7 +624,7 @@ export const FormNewServizioAcquisto = ({
<FormControl> <FormControl>
<Counter <Counter
value={field.value} value={field.value}
setValue={field.onChange} setValue={(v) => field.onChange(Number(v))}
name="n_minori" name="n_minori"
onlyPositive onlyPositive
/> />

View file

@ -1,5 +1,5 @@
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { z } from "zod"; import { z } from "zod/v4";
import { import {
Form, Form,
FormControl, FormControl,
@ -8,7 +8,6 @@ import {
FormLabel, FormLabel,
FormMessage, FormMessage,
} from "~/components/custom_ui/form"; } from "~/components/custom_ui/form";
import { customErrorMapWrapper } from "~/hooks/locale";
import { useZodForm } from "~/lib/zodForm"; import { useZodForm } from "~/lib/zodForm";
import { api } from "~/utils/api"; import { api } from "~/utils/api";
import Input from "~/components/custom_ui/input"; import Input from "~/components/custom_ui/input";
@ -36,7 +35,7 @@ export const FormNewUser = ({
const NewUserSchema = z const NewUserSchema = z
.object({ .object({
email: z.string().email("Inserisci una email valida"), email: z.email(),
password: z.string(), password: z.string(),
nome: z.string().nonempty("Inserisci un nome valido"), nome: z.string().nonempty("Inserisci un nome valido"),
cognome: z.string().nonempty("Inserisci un cognome valido"), cognome: z.string().nonempty("Inserisci un cognome valido"),
@ -61,10 +60,9 @@ export const FormNewUser = ({
}; };
const { locale, t } = useTranslation(); const { locale, t } = useTranslation();
z.setErrorMap(customErrorMapWrapper(locale)); z.config(z.locales[locale]());
const form = useZodForm({ const form = useZodForm(NewUserSchema, {
schema: NewUserSchema,
defaultValues: defaultValues, defaultValues: defaultValues,
}); });

View file

@ -1,4 +1,4 @@
import { z } from "zod"; import { z } from "zod/v4";
import { useZodForm } from "~/lib/zodForm"; import { useZodForm } from "~/lib/zodForm";
import { import {
Form, Form,
@ -11,7 +11,6 @@ import {
import Input from "~/components/custom_ui/input"; import Input from "~/components/custom_ui/input";
import { Button, buttonVariants } from "~/components/ui/button"; import { Button, buttonVariants } from "~/components/ui/button";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { customErrorMapWrapper } from "~/hooks/locale";
import { api } from "~/utils/api"; import { api } from "~/utils/api";
import { useTranslation } from "~/providers/I18nProvider"; import { useTranslation } from "~/providers/I18nProvider";
import type { UsersId } from "~/schemas/public/Users"; import type { UsersId } from "~/schemas/public/Users";
@ -57,10 +56,9 @@ export const OverridePasswordForm = ({ id }: { id: UsersId }) => {
const { locale, t } = useTranslation(); const { locale, t } = useTranslation();
z.setErrorMap(customErrorMapWrapper(locale)); z.config(z.locales[locale]());
const form = useZodForm({ const form = useZodForm(OverridePasswordFormSchema, {
schema: OverridePasswordFormSchema,
defaultValues: { defaultValues: {
password: "", password: "",
}, },

View file

@ -1,4 +1,4 @@
import { z } from "zod"; import { z } from "zod/v4";
import { import {
Form, Form,
FormControl, FormControl,
@ -7,7 +7,6 @@ import {
FormLabel, FormLabel,
FormMessage, FormMessage,
} from "~/components/custom_ui/form"; } from "~/components/custom_ui/form";
import { customErrorMapWrapper } from "~/hooks/locale";
import { useZodForm } from "~/lib/zodForm"; import { useZodForm } from "~/lib/zodForm";
import Input from "~/components/custom_ui/input"; import Input from "~/components/custom_ui/input";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
@ -48,7 +47,7 @@ export const FormPrezzo = ({
const PrezzoSchema = z const PrezzoSchema = z
.object({ .object({
idprezziario: z.custom<PrezziarioIdprezziario>(), idprezziario: z.custom<PrezziarioIdprezziario>(),
prezzo_cent: z.coerce.number().positive(), prezzo_cent: z.number().positive(),
testo_condizioni: z.string().nullable(), testo_condizioni: z.string().nullable(),
isAcconto: z.boolean(), isAcconto: z.boolean(),
isSaldo: z.boolean(), isSaldo: z.boolean(),
@ -62,7 +61,7 @@ export const FormPrezzo = ({
isTransitorio: z.boolean(), isTransitorio: z.boolean(),
isStabile: z.boolean(), isStabile: z.boolean(),
}) })
.superRefine(({ testo_condizioni }, refinementContext) => { .superRefine(({ testo_condizioni }, ctx) => {
if (testo_condizioni) { if (testo_condizioni) {
if ( if (
stringhe_condizioni_options.length > 0 && stringhe_condizioni_options.length > 0 &&
@ -73,9 +72,11 @@ export const FormPrezzo = ({
"stringhe_condizioni_options", "stringhe_condizioni_options",
stringhe_condizioni_options, stringhe_condizioni_options,
); );
return refinementContext.addIssue({
code: z.ZodIssueCode.custom, ctx.addIssue({
code: "custom",
message: "Testo condizioni non valido", message: "Testo condizioni non valido",
input: testo_condizioni,
path: ["testo_condizioni"], path: ["testo_condizioni"],
}); });
} }
@ -102,10 +103,9 @@ export const FormPrezzo = ({
isStabile: false, isStabile: false,
}; };
z.setErrorMap(customErrorMapWrapper("it")); z.config(z.locales.it());
const form = useZodForm({ const form = useZodForm(PrezzoSchema, {
schema: PrezzoSchema,
defaultValues: defaultValues, defaultValues: defaultValues,
}); });
@ -238,7 +238,7 @@ export const FormPrezzo = ({
{...field} {...field}
type="number" type="number"
id="prezzo" id="prezzo"
value={field.value / 100} value={Number(field.value) / 100}
onChange={(v) => { onChange={(v) => {
field.onChange(() => parseInt(v.target.value) * 100); field.onChange(() => parseInt(v.target.value) * 100);
}} }}

View file

@ -1,4 +1,4 @@
import { z } from "zod"; import { z } from "zod/v4";
import { useZodForm } from "~/lib/zodForm"; import { useZodForm } from "~/lib/zodForm";
import { import {
Form, Form,
@ -11,7 +11,6 @@ import {
import Input from "~/components/custom_ui/input"; import Input from "~/components/custom_ui/input";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { customErrorMapWrapper } from "~/hooks/locale";
import { api } from "~/utils/api"; import { api } from "~/utils/api";
import { useTranslation } from "~/providers/I18nProvider"; import { useTranslation } from "~/providers/I18nProvider";
import type { Users, UsersId } from "~/schemas/public/Users"; import type { Users, UsersId } from "~/schemas/public/Users";
@ -36,7 +35,7 @@ export const ProfileFormAccount = ({
const profileFormSchema = z.object({ const profileFormSchema = z.object({
nome: z.string().nonempty("Inserisci un nome valido"), nome: z.string().nonempty("Inserisci un nome valido"),
cognome: z.string().nonempty("Inserisci un cognome valido"), cognome: z.string().nonempty("Inserisci un cognome valido"),
email: z.string().email(), email: z.email(),
telefono: z.string().nonempty("Inserisci un numero di telefono"), telefono: z.string().nonempty("Inserisci un numero di telefono"),
isAdmin: z.boolean().optional(), isAdmin: z.boolean().optional(),
}); });
@ -51,10 +50,9 @@ export const ProfileFormAccount = ({
isAdmin: isAdmin, isAdmin: isAdmin,
}; };
const { locale, t } = useTranslation(); const { locale, t } = useTranslation();
z.setErrorMap(customErrorMapWrapper(locale)); z.config(z.locales[locale]());
const form = useZodForm({ const form = useZodForm(profileFormSchema, {
schema: profileFormSchema,
defaultValues: defaultValues, defaultValues: defaultValues,
}); });

View file

@ -1,4 +1,4 @@
import { z } from "zod"; import { z } from "zod/v4";
import { useZodForm } from "~/lib/zodForm"; import { useZodForm } from "~/lib/zodForm";
import { import {
Form, Form,
@ -11,7 +11,6 @@ import {
import Input from "~/components/custom_ui/input"; import Input from "~/components/custom_ui/input";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { customErrorMapWrapper } from "~/hooks/locale";
import { api } from "~/utils/api"; import { api } from "~/utils/api";
import { useTranslation } from "~/providers/I18nProvider"; import { useTranslation } from "~/providers/I18nProvider";
import { differenceInYears, format } from "date-fns"; import { differenceInYears, format } from "date-fns";
@ -44,7 +43,7 @@ export const ProfileFormAnagrafica = ({
}) => { }) => {
const profileFormSchema = z const profileFormSchema = z
.object({ .object({
data_nascita: z.coerce.date().optional(), data_nascita: z.date().optional(),
luogo_nascita: z.string().optional(), luogo_nascita: z.string().optional(),
sesso: z.string().optional(), sesso: z.string().optional(),
nazione_nascita: z.string().optional(), nazione_nascita: z.string().optional(),
@ -84,49 +83,55 @@ export const ProfileFormAnagrafica = ({
) => { ) => {
if (azienda) { if (azienda) {
if (!ragione_sociale || ragione_sociale.length < 1) { if (!ragione_sociale || ragione_sociale.length < 1) {
refinementContext.addIssue({ refinementContext.issues.push({
code: z.ZodIssueCode.custom, code: "custom",
message: "Inserisci la ragione sociale", message: "Inserisci la ragione sociale",
path: ["ragione_sociale"], path: ["ragione_sociale"],
input: "",
}); });
} }
if (!sede_legale || sede_legale.length < 1) { if (!sede_legale || sede_legale.length < 1) {
refinementContext.addIssue({ refinementContext.issues.push({
code: z.ZodIssueCode.custom, code: "custom",
message: "Inserisci la sede legale", message: "Inserisci la sede legale",
path: ["sede_legale"], path: ["sede_legale"],
input: "",
}); });
} }
if (!p_iva || p_iva.length < 1) { if (!p_iva || p_iva.length < 1) {
refinementContext.addIssue({ refinementContext.issues.push({
code: z.ZodIssueCode.custom, code: "custom",
message: "Inserisci la partita iva", message: "Inserisci la partita iva",
path: ["p_iva"], path: ["p_iva"],
input: "",
}); });
} }
if (!legale_rappresentante || legale_rappresentante.length < 1) { if (!legale_rappresentante || legale_rappresentante.length < 1) {
refinementContext.addIssue({ refinementContext.issues.push({
code: z.ZodIssueCode.custom, code: "custom",
message: "Inserisci il legale rappresentante", message: "Inserisci il legale rappresentante",
path: ["legale_rappresentante"], path: ["legale_rappresentante"],
input: "",
}); });
} }
} }
if (fatturazione_aziendale) { if (fatturazione_aziendale) {
if (!codice_destinatario || codice_destinatario.length != 7) { if (!codice_destinatario || codice_destinatario.length != 7) {
refinementContext.addIssue({ refinementContext.issues.push({
code: z.ZodIssueCode.custom, code: "custom",
message: "Inserisci il codice destinatario", message: "Inserisci il codice destinatario",
path: ["codice_destinatario"], path: ["codice_destinatario"],
input: "",
}); });
} }
} }
if (cap_residenza) { if (cap_residenza) {
if (cap_residenza.length !== 5) { if (cap_residenza.length !== 5) {
refinementContext.addIssue({ refinementContext.issues.push({
code: z.ZodIssueCode.custom, code: "custom",
message: "Inserisci un CAP valido", message: "Inserisci un CAP valido",
input: "",
}); });
} }
} }
@ -134,10 +139,11 @@ export const ProfileFormAnagrafica = ({
if (data_nascita) { if (data_nascita) {
const diff = differenceInYears(data_nascita, new Date()); const diff = differenceInYears(data_nascita, new Date());
if (diff === 0) { if (diff === 0) {
refinementContext.addIssue({ refinementContext.issues.push({
code: z.ZodIssueCode.custom, code: "custom",
message: "Inserisci una data valida", message: "Inserisci una data valida",
path: ["data_nascita"], path: ["data_nascita"],
input: "",
}); });
} }
} }
@ -188,10 +194,9 @@ export const ProfileFormAnagrafica = ({
codice_destinatario: userData.codice_destinatario || null, codice_destinatario: userData.codice_destinatario || null,
}; };
const { locale, t } = useTranslation(); const { locale, t } = useTranslation();
z.setErrorMap(customErrorMapWrapper(locale)); z.config(z.locales[locale]());
const form = useZodForm({ const form = useZodForm(profileFormSchema, {
schema: profileFormSchema,
defaultValues: defaultValues, defaultValues: defaultValues,
}); });

View file

@ -1,5 +1,4 @@
import { customErrorMapWrapper } from "~/hooks/locale"; import { z } from "zod/v4";
import { z } from "zod";
import { useZodForm } from "~/lib/zodForm"; import { useZodForm } from "~/lib/zodForm";
import { import {
Form, Form,
@ -40,7 +39,7 @@ export const FormPwSetup = ({ user, servizioId }: FormPwSetupProps) => {
toggleVisibility, toggleVisibility,
} = useStrongPassword(); } = useStrongPassword();
const shema = z const Schema = z
.object({ .object({
password: z.string(), password: z.string(),
checkbox: z.boolean().refine((val) => val === false), // Honeypot field checkbox: z.boolean().refine((val) => val === false), // Honeypot field
@ -53,17 +52,16 @@ export const FormPwSetup = ({ user, servizioId }: FormPwSetupProps) => {
}); });
}); });
type LoginFormValues = z.infer<typeof shema>; type LoginFormValues = z.infer<typeof Schema>;
const defaultValues: LoginFormValues = { const defaultValues: LoginFormValues = {
password: "", password: "",
checkbox: false, // Honeypot field checkbox: false, // Honeypot field
}; };
const { locale, t } = useTranslation(); const { locale, t } = useTranslation();
z.setErrorMap(customErrorMapWrapper(locale)); z.config(z.locales[locale]());
const form = useZodForm({ const form = useZodForm(Schema, {
schema: shema,
defaultValues: defaultValues, defaultValues: defaultValues,
}); });

View file

@ -8,8 +8,7 @@ import {
FormMessage, FormMessage,
} from "~/components/custom_ui/form"; } from "~/components/custom_ui/form";
import Input from "~/components/custom_ui/input"; import Input from "~/components/custom_ui/input";
import { z } from "zod"; import { z } from "zod/v4";
import { customErrorMapWrapper } from "~/hooks/locale";
import { useZodForm } from "~/lib/zodForm"; import { useZodForm } from "~/lib/zodForm";
import { api } from "~/utils/api"; import { api } from "~/utils/api";
import { useTranslation } from "~/providers/I18nProvider"; import { useTranslation } from "~/providers/I18nProvider";
@ -63,22 +62,24 @@ export const FormRstPwFromToken = (props: { token: string }) => {
const complexity = checkStrength(password); const complexity = checkStrength(password);
if (complexity.filter((req) => req.met).length !== complexity.length) { if (complexity.filter((req) => req.met).length !== complexity.length) {
refinementContext.addIssue({ refinementContext.issues.push({
code: z.ZodIssueCode.custom, code: "custom",
message: complexity message: complexity
.filter((req) => !req.met) .filter((req) => !req.met)
.map((req) => req.text) .map((req) => req.text)
.join("\n"), .join("\n"),
path: ["password"], path: ["password"],
input: "",
}); });
} }
setStrengthScore(complexity.filter((req) => req.met).length); setStrengthScore(complexity.filter((req) => req.met).length);
if (password !== repeat_password) { if (password !== repeat_password) {
refinementContext.addIssue({ refinementContext.issues.push({
message: t.pwReset.pwDiverse, message: t.pwReset.pwDiverse,
code: z.ZodIssueCode.custom, code: "custom",
path: ["repeat_password"], path: ["repeat_password"],
input: "",
}); });
} }
}); });
@ -93,8 +94,7 @@ export const FormRstPwFromToken = (props: { token: string }) => {
}, },
}); });
const form = useZodForm({ const form = useZodForm(ResetFormSchema, {
schema: ResetFormSchema,
defaultValues: { defaultValues: {
password: "", password: "",
repeat_password: "", repeat_password: "",
@ -106,7 +106,7 @@ export const FormRstPwFromToken = (props: { token: string }) => {
resetToken: props.token, resetToken: props.token,
}); });
}; };
z.setErrorMap(customErrorMapWrapper(locale)); z.config(z.locales[locale]());
return ( return (
<> <>

View file

@ -1,4 +1,4 @@
import { z } from "zod"; import { z } from "zod/v4";
import { import {
Form, Form,
FormControl, FormControl,
@ -7,7 +7,6 @@ import {
FormLabel, FormLabel,
FormMessage, FormMessage,
} from "~/components/custom_ui/form"; } from "~/components/custom_ui/form";
import { customErrorMapWrapper } from "~/hooks/locale";
import { useZodForm } from "~/lib/zodForm"; import { useZodForm } from "~/lib/zodForm";
import Input from "~/components/custom_ui/input"; import Input from "~/components/custom_ui/input";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
@ -48,10 +47,9 @@ export const FormStringhe = ({
}; };
} }
z.setErrorMap(customErrorMapWrapper("it")); z.config(z.locales.it());
const form = useZodForm({ const form = useZodForm(Schema, {
schema: Schema,
defaultValues: defaultValues, defaultValues: defaultValues,
}); });

View file

@ -7,6 +7,18 @@ import type { ChatMessage } from "~/server/services/messages.service";
import { type OnlineStatus, type WhoIsTyping } from "~/server/sse"; import { type OnlineStatus, type WhoIsTyping } from "~/server/sse";
import { api } from "~/utils/api"; import { api } from "~/utils/api";
type EditEvent = {
eventType: "edit";
data: { messageId: MessagesMessageid; message: string };
};
type DeleteEvent = {
eventType: "delete";
data: MessagesMessageid;
};
type MessageUpdate = EditEvent | DeleteEvent;
/** /**
* Set isTyping with a throttle of 1s * Set isTyping with a throttle of 1s
* Triggers immediately if state changes * Triggers immediately if state changes
@ -167,7 +179,7 @@ export const useLiveChat = (chatId: ChatsChatid, userData: Session) => {
api.messagesSSE.onMessageEvent.useSubscription( api.messagesSSE.onMessageEvent.useSubscription(
{ chatId }, { chatId },
{ {
onData(data) { onData(data: { data: MessageUpdate }) {
switch (data.data.eventType) { switch (data.data.eventType) {
case "edit": case "edit":
handleEdit(data.data.data); handleEdit(data.data.data);

View file

@ -1,195 +0,0 @@
import { eng, ita } from "~/i18n/FormErrorsTranslation";
import * as z from "zod";
const Capitalize = (s: string) => {
if (typeof s !== "string") return "";
return s.charAt(0).toUpperCase() + s.slice(1);
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type ErrorType = Record<string, any>;
function FormErrorTranslation(template: string, obj: ErrorType): string {
return template.replace(/{{[^}]*}}/g, (match) =>
typeof obj[match.replace(/{|}/g, "")] === "string"
? Capitalize(String(obj[match.replace(/{|}/g, "")]))
: String(obj[match.replace(/{|}/g, "")]),
);
}
export const customErrorMapWrapper = (locale: string) => {
const data = locale === "it" ? ita : eng;
const customErrorMap: z.ZodErrorMap = (issue, ctx) => {
let message = "";
switch (issue.code) {
case z.ZodIssueCode.invalid_type:
if (issue.received === z.ZodParsedType.undefined) {
message = data.errors.invalid_type_received_undefined;
} else {
message = FormErrorTranslation(data.errors.invalid_type, {
expected: data.local_types[issue.expected],
received: data.local_types[issue.received],
});
}
break;
case z.ZodIssueCode.invalid_literal:
message = FormErrorTranslation(data.errors.invalid_literal, {
expected: JSON.stringify(issue.expected, jsonStringifyReplacer),
});
break;
case z.ZodIssueCode.unrecognized_keys:
message = FormErrorTranslation(data.errors.unrecognized_keys, {
keys: joinValues(issue.keys, ", "),
count: issue.keys.length,
});
break;
case z.ZodIssueCode.invalid_union:
message = data.errors.invalid_union;
break;
case z.ZodIssueCode.invalid_union_discriminator:
message = FormErrorTranslation(
data.errors.invalid_union_discriminator,
{
options: joinValues(issue.options),
},
);
break;
case z.ZodIssueCode.invalid_enum_value:
message = FormErrorTranslation(data.errors.invalid_enum_value, {
options: joinValues(issue.options),
});
break;
case z.ZodIssueCode.invalid_arguments:
message = data.errors.invalid_arguments;
break;
case z.ZodIssueCode.invalid_return_type:
message = data.errors.invalid_return_type;
break;
case z.ZodIssueCode.invalid_date:
message = data.errors.invalid_date;
break;
case z.ZodIssueCode.invalid_string:
if (typeof issue.validation === "object") {
if ("startsWith" in issue.validation) {
message = FormErrorTranslation(
data.errors.invalid_string.startsWith,
{
startsWith: issue.validation.startsWith,
},
);
} else if ("endsWith" in issue.validation) {
message = FormErrorTranslation(
data.errors.invalid_string.endsWith,
{
endsWith: issue.validation.endsWith,
},
);
}
} else {
message = FormErrorTranslation(
data.errors.invalid_string[
issue.validation as keyof typeof data.errors.invalid_string
],
{
validation: FormErrorTranslation(
data.validations[
issue.validation as keyof typeof data.validations
],
{
defaultValue: issue.validation,
},
),
},
);
}
break;
case z.ZodIssueCode.too_small: {
const minimum =
issue.type === "date"
? new Date(issue.minimum as number)
: issue.minimum;
const incl_excl = issue.exact
? "exact"
: issue.inclusive
? "inclusive"
: "not_inclusive";
message = FormErrorTranslation(
data.errors.too_small[
issue.type as keyof typeof data.errors.too_small
][incl_excl],
{
minimum: minimum,
//count: typeof minimum === "number" ? minimum : undefined,
},
);
break;
}
case z.ZodIssueCode.too_big: {
const maximum =
issue.type === "date"
? new Date(issue.maximum as number)
: issue.maximum;
message = FormErrorTranslation(
data.errors.too_big[issue.type as keyof typeof data.errors.too_big][
issue.exact
? "exact"
: issue.inclusive
? "inclusive"
: "not_inclusive"
],
{
maximum: maximum,
//count: typeof maximum === "number" ? maximum : undefined,
},
);
break;
}
case z.ZodIssueCode.invalid_intersection_types:
message = data.errors.invalid_intersection_types;
break;
case z.ZodIssueCode.not_multiple_of:
message = FormErrorTranslation(data.errors.not_multiple_of, {
multipleOf: issue.multipleOf,
});
break;
case z.ZodIssueCode.not_finite:
message = data.errors.not_finite;
break;
case z.ZodIssueCode.custom:
message =
data.customErrors[
issue.params?.type as keyof typeof data.customErrors
];
break;
default:
message = ctx.defaultError;
}
return { message: message };
};
return customErrorMap;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const jsonStringifyReplacer = (_: string, value: any): any => {
if (typeof value === "bigint") {
return value.toString();
}
return value;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function joinValues<T extends any[]>(array: T, separator = " | "): string {
return (
array
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
.map((val) => (typeof val === "string" ? `'${val}'` : val))
.join(separator)
);
}

View file

@ -1,11 +1,18 @@
import { useState } from "react"; import { useState } from "react";
import { z, type RefinementCtx } from "zod"; import { z, type RefinementCtx } from "zod/v4";
import { useTranslation } from "~/providers/I18nProvider"; import { useTranslation } from "~/providers/I18nProvider";
export const zStrongPassword = z.string().superRefine((password) => { export const zStrongPassword = z.string().superRefine((password, ctx) => {
return [/.{8,}/, /[0-9]/, /[a-z]/, /[A-Z]/].every((regex) => const test = [/.{8,}/, /[0-9]/, /[a-z]/, /[A-Z]/].every((regex) =>
regex.test(password), regex.test(password),
); );
if (!test) {
ctx.addIssue({
code: "custom",
message:
"La password deve contenere almeno 8 caratteri, un numero, una lettera minuscola e una maiuscola.",
});
}
}); });
export const useStrongPassword = () => { export const useStrongPassword = () => {
@ -57,13 +64,14 @@ export const useStrongPassword = () => {
const complexity = checkStrength(password); const complexity = checkStrength(password);
if (complexity.filter((req) => req.met).length !== complexity.length) { if (complexity.filter((req) => req.met).length !== complexity.length) {
refinementContext.addIssue({ refinementContext.issues.push({
code: z.ZodIssueCode.custom, code: "custom",
message: complexity message: complexity
.filter((req) => !req.met) .filter((req) => !req.met)
.map((req) => req.text) .map((req) => req.text)
.join("\n"), .join("\n"),
path: [path], path: [path],
input: "",
}); });
} }
setStrengthScore(complexity.filter((req) => req.met).length); setStrengthScore(complexity.filter((req) => req.met).length);

View file

@ -1,4 +1,4 @@
import { z } from "zod"; import type { z } from "zod/v4";
import { getComuneFromIndex } from "~/i18n/comuni"; import { getComuneFromIndex } from "~/i18n/comuni";
import { searchNazioneByIndex } from "~/i18n/nazioni"; import { searchNazioneByIndex } from "~/i18n/nazioni";
import type { ListOption } from "~/components/custom_ui/multiselect"; import type { ListOption } from "~/components/custom_ui/multiselect";
@ -185,26 +185,29 @@ export const checkFiscalCodeValidity = ({
console.log("gen", gen); console.log("gen", gen);
console.log("codiceFiscale", codiceFiscale); console.log("codiceFiscale", codiceFiscale);
console.log("alternative", getAlternativeCF(gen)); console.log("alternative", getAlternativeCF(gen));
refinementContext.addIssue({ refinementContext.issues.push({
code: z.ZodIssueCode.custom, code: "custom",
message: "Codice fiscale non valido", message: "Codice fiscale non valido",
path: [path], path: [path],
input: "",
}); });
} }
if (control !== codiceFiscale.slice(-1)) { if (control !== codiceFiscale.slice(-1)) {
refinementContext.addIssue({ refinementContext.issues.push({
code: z.ZodIssueCode.custom, code: "custom",
message: "Codice di controllo non valido", message: "Codice di controllo non valido",
path: [path], path: [path],
input: "",
}); });
} }
if (codiceFiscale.length !== 16) { if (codiceFiscale.length !== 16) {
refinementContext.addIssue({ refinementContext.issues.push({
code: z.ZodIssueCode.custom, code: "custom",
message: "Inserisci un codice fiscale valido", message: "Inserisci un codice fiscale valido",
path: [path], path: [path],
input: "",
}); });
} }
if ( if (
@ -212,60 +215,67 @@ export const checkFiscalCodeValidity = ({
codiceFiscale, codiceFiscale,
) )
) { ) {
refinementContext.addIssue({ refinementContext.issues.push({
code: z.ZodIssueCode.custom, code: "custom",
message: "Inserisci un codice fiscale valido", message: "Inserisci un codice fiscale valido",
path: [path], path: [path],
input: "",
}); });
} }
if (codiceFiscale.slice(0, 3) !== getInizialiCF(cognomeUpper)) { if (codiceFiscale.slice(0, 3) !== getInizialiCF(cognomeUpper)) {
refinementContext.addIssue({ refinementContext.issues.push({
code: z.ZodIssueCode.custom, code: "custom",
message: "Codice fiscale incompatibile con cognome inserito", message: "Codice fiscale incompatibile con cognome inserito",
path: [path], path: [path],
input: "",
}); });
} }
if (codiceFiscale.slice(3, 6) !== getInizialiCF(nomeUpper)) { if (codiceFiscale.slice(3, 6) !== getInizialiCF(nomeUpper)) {
refinementContext.addIssue({ refinementContext.issues.push({
code: z.ZodIssueCode.custom, code: "custom",
message: "Codice fiscale incompatibile con nome inserito", message: "Codice fiscale incompatibile con nome inserito",
path: [path], path: [path],
input: "",
}); });
} }
if ( if (
codiceFiscale.slice(6, 8) !== dataNascita.getFullYear().toString().slice(-2) codiceFiscale.slice(6, 8) !== dataNascita.getFullYear().toString().slice(-2)
) { ) {
refinementContext.addIssue({ refinementContext.issues.push({
code: z.ZodIssueCode.custom, code: "custom",
message: "Codice fiscale incompatibile con anno di nascita", message: "Codice fiscale incompatibile con anno di nascita",
path: [path], path: [path],
input: "",
}); });
} }
if (codiceFiscale.slice(8, 9) !== getMonthLetterCF(dataNascita.getMonth())) { if (codiceFiscale.slice(8, 9) !== getMonthLetterCF(dataNascita.getMonth())) {
refinementContext.addIssue({ refinementContext.issues.push({
code: z.ZodIssueCode.custom, code: "custom",
message: "Codice fiscale incompatibile con mese di nascita", message: "Codice fiscale incompatibile con mese di nascita",
path: [path], path: [path],
input: "",
}); });
} }
if (codiceFiscale.slice(9, 11) !== getDayCF(dataNascita.getDate(), sesso)) { if (codiceFiscale.slice(9, 11) !== getDayCF(dataNascita.getDate(), sesso)) {
refinementContext.addIssue({ refinementContext.issues.push({
code: z.ZodIssueCode.custom, code: "custom",
message: "Codice fiscale incompatibile con giorno di nascita", message: "Codice fiscale incompatibile con giorno di nascita",
path: [path], path: [path],
input: "",
}); });
} }
if (nazioneNascita === "0") { if (nazioneNascita === "0") {
if (luogoNascita !== "") { if (luogoNascita !== "") {
const comune = getComuneFromIndex(parseInt(luogoNascita)); const comune = getComuneFromIndex(parseInt(luogoNascita));
if (comune && codiceFiscale.slice(11, 15) !== comune.CATASTO) { if (comune && codiceFiscale.slice(11, 15) !== comune.CATASTO) {
refinementContext.addIssue({ refinementContext.issues.push({
code: z.ZodIssueCode.custom, code: "custom",
message: "Codice fiscale incompatibile con luogo di nascita", message: "Codice fiscale incompatibile con luogo di nascita",
path: [path], path: [path],
input: "",
}); });
} }
} }
@ -273,10 +283,11 @@ export const checkFiscalCodeValidity = ({
if (nazioneNascita !== "") { if (nazioneNascita !== "") {
const nazione = searchNazioneByIndex(parseInt(nazioneNascita)); const nazione = searchNazioneByIndex(parseInt(nazioneNascita));
if (nazione && codiceFiscale.slice(11, 15) !== nazione.CATASTO) { if (nazione && codiceFiscale.slice(11, 15) !== nazione.CATASTO) {
refinementContext.addIssue({ refinementContext.issues.push({
code: z.ZodIssueCode.custom, code: "custom",
message: "Codice fiscale incompatibile con luogo di nascita", message: "Codice fiscale incompatibile con luogo di nascita",
path: [path], path: [path],
input: "",
}); });
} }
} }

View file

@ -1,16 +1,17 @@
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { type UseFormProps, useForm } from "react-hook-form"; import { type FieldValues, type UseFormProps, useForm } from "react-hook-form";
import type { z, ZodTypeAny } from "zod";
export function useZodForm<TSchema extends ZodTypeAny>( import type { z } from "zod";
props: Omit<UseFormProps<z.infer<TSchema>>, "resolver"> & {
schema: TSchema; export function useZodForm<
}, T extends z.ZodType<FieldValues, FieldValues>,
TContext,
>(
schema: T,
props?: Omit<UseFormProps<z.input<T>, TContext, z.output<T>>, "resolver">,
) { ) {
const form = useForm<z.infer<TSchema>>({ return useForm({
resolver: zodResolver(schema),
...props, ...props,
resolver: zodResolver(props.schema),
}); });
return form;
} }

View file

@ -21,7 +21,7 @@ import { cn } from "~/lib/utils";
import type { NextPageWithLayout } from "~/pages/_app"; import type { NextPageWithLayout } from "~/pages/_app";
import { generateSSGHelper } from "~/server/utils/ssgHelper"; import { generateSSGHelper } from "~/server/utils/ssgHelper";
import { api } from "~/utils/api"; import { api } from "~/utils/api";
import { z } from "zod"; import { z } from "zod/v4";
import { import {
Form, Form,
FormControl, FormControl,
@ -32,7 +32,6 @@ import {
} from "~/components/custom_ui/form"; } from "~/components/custom_ui/form";
import { useZodForm } from "~/lib/zodForm"; import { useZodForm } from "~/lib/zodForm";
import { useTranslation } from "~/providers/I18nProvider"; import { useTranslation } from "~/providers/I18nProvider";
import { customErrorMapWrapper } from "~/hooks/locale";
import { CarouselAnnuncio } from "~/components/annuncio_card"; import { CarouselAnnuncio } from "~/components/annuncio_card";
import { zAnnuncioId } from "~/server/utils/zod_types"; import { zAnnuncioId } from "~/server/utils/zod_types";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
@ -76,7 +75,7 @@ const AnnuncioEditForm = ({ data }: { data: AnnunciWithCoverImage }) => {
desc_en: z.string().nonempty("Inserisci una descrizione valida"), desc_en: z.string().nonempty("Inserisci una descrizione valida"),
status: z.string().nonempty("Seleziona uno stato"), status: z.string().nonempty("Seleziona uno stato"),
locatore: z.string().nonempty("Inserisci il locatore"), locatore: z.string().nonempty("Inserisci il locatore"),
email: z.string().email("Inserisci un'email valida"), email: z.email(),
numero: z.string().nonempty("Inserisci un numero di telefono"), numero: z.string().nonempty("Inserisci un numero di telefono"),
}); });
@ -103,10 +102,9 @@ const AnnuncioEditForm = ({ data }: { data: AnnunciWithCoverImage }) => {
numero: data.numero || "", numero: data.numero || "",
}; };
z.setErrorMap(customErrorMapWrapper(locale)); z.config(z.locales[locale]());
const form = useZodForm({ const form = useZodForm(EdiAnnuncioSchema, {
schema: EdiAnnuncioSchema,
defaultValues: defaultValues, defaultValues: defaultValues,
}); });
function onSubmit(fields: EditAnnuncioFormValues) { function onSubmit(fields: EditAnnuncioFormValues) {

View file

@ -1,7 +1,7 @@
import type { NextPage } from "next"; import type { NextPage } from "next";
import Head from "next/head"; import Head from "next/head";
import Link from "next/link"; import Link from "next/link";
import { z } from "zod"; import { z } from "zod/v4";
import { import {
Form, Form,
FormControl, FormControl,
@ -12,7 +12,6 @@ import {
} from "~/components/custom_ui/form"; } from "~/components/custom_ui/form";
import Input from "~/components/custom_ui/input"; import Input from "~/components/custom_ui/input";
import { Textarea } from "~/components/custom_ui/textarea"; import { Textarea } from "~/components/custom_ui/textarea";
import { customErrorMapWrapper } from "~/hooks/locale";
import { useZodForm } from "~/lib/zodForm"; import { useZodForm } from "~/lib/zodForm";
import { DualInputLayout } from "~/components/custom_ui/inputLayouts"; import { DualInputLayout } from "~/components/custom_ui/inputLayouts";
import { api } from "~/utils/api"; import { api } from "~/utils/api";
@ -27,14 +26,13 @@ const Contatto: NextPage = () => {
const ContactFormSchema = z.object({ const ContactFormSchema = z.object({
nome: z.string().nonempty("Inserisci un nome valido"), nome: z.string().nonempty("Inserisci un nome valido"),
cognome: z.string().nonempty("Inserisci un cognome valido"), cognome: z.string().nonempty("Inserisci un cognome valido"),
email: z.string().email(), email: z.email(),
messaggio: z.string().nonempty("Inserisci un messaggio"), messaggio: z.string().nonempty("Inserisci un messaggio"),
numero: z.string().nonempty("Inserisci un numero di telefono"), numero: z.string().nonempty("Inserisci un numero di telefono"),
checkbox: z.boolean().refine((val) => val === false), // Honeypot field checkbox: z.boolean().refine((val) => val === false), // Honeypot field
}); });
const form = useZodForm({ const form = useZodForm(ContactFormSchema, {
schema: ContactFormSchema,
defaultValues: { defaultValues: {
nome: "", nome: "",
cognome: "", cognome: "",
@ -60,7 +58,7 @@ const Contatto: NextPage = () => {
telefono: data.numero, telefono: data.numero,
}); });
}; };
z.setErrorMap(customErrorMapWrapper(locale)); z.config(z.locales[locale]());
return ( return (
<> <>

View file

@ -1,4 +1,4 @@
import { z } from "zod"; import { z } from "zod/v4";
import { import {
adminProcedure, adminProcedure,
createTRPCRouter, createTRPCRouter,

View file

@ -1,4 +1,4 @@
import { z } from "zod"; import { z } from "zod/v4";
import { import {
adminProcedure, adminProcedure,
createTRPCRouter, createTRPCRouter,
@ -36,7 +36,7 @@ export const authRouter = createTRPCRouter({
loginUser: publicProcedure loginUser: publicProcedure
.input( .input(
z.object({ z.object({
email: z.string().email("Inserisci una email valida"), email: z.email(),
password: z.string(), password: z.string(),
}), }),
) )
@ -68,7 +68,7 @@ export const authRouter = createTRPCRouter({
addUserAdmin: adminProcedure addUserAdmin: adminProcedure
.input( .input(
z.object({ z.object({
email: z.string().email("Inserisci una email valida"), email: z.email(),
password: zStrongPassword, password: zStrongPassword,
nome: z.string().nonempty("Inserisci un nome valido"), nome: z.string().nonempty("Inserisci un nome valido"),
cognome: z.string().nonempty("Inserisci un cognome valido"), cognome: z.string().nonempty("Inserisci un cognome valido"),

View file

@ -5,7 +5,7 @@ import {
updateBan, updateBan,
} from "~/server/services/banlist.service"; } from "~/server/services/banlist.service";
import { adminProcedure, createTRPCRouter } from "~/server/api/trpc"; import { adminProcedure, createTRPCRouter } from "~/server/api/trpc";
import { z } from "zod"; import { z } from "zod/v4";
import { zBanlistId, zBanType } from "~/server/utils/zod_types"; import { zBanlistId, zBanType } from "~/server/utils/zod_types";
import { db } from "~/server/db"; import { db } from "~/server/db";

View file

@ -1,4 +1,4 @@
import { z } from "zod"; import { z } from "zod/v4";
import { import {
adminProcedure, adminProcedure,
createTRPCRouter, createTRPCRouter,

View file

@ -1,5 +1,5 @@
import { db } from "~/server/db"; import { db } from "~/server/db";
import { z } from "zod"; import { z } from "zod/v4";
import { import {
createTRPCRouter, createTRPCRouter,
protectedProcedure, protectedProcedure,

View file

@ -1,4 +1,4 @@
import { z } from "zod"; import { z } from "zod/v4";
import { createTRPCRouter, publicProcedure } from "~/server/api/trpc"; import { createTRPCRouter, publicProcedure } from "~/server/api/trpc";
import { zUserId } from "~/server/utils/zod_types"; import { zUserId } from "~/server/utils/zod_types";
import { genMail, type MailsTemplates } from "~/server/services/mailer"; import { genMail, type MailsTemplates } from "~/server/services/mailer";

View file

@ -1,4 +1,4 @@
import { z } from "zod"; import { z } from "zod/v4";
import { createTRPCRouter, publicProcedure } from "~/server/api/trpc"; import { createTRPCRouter, publicProcedure } from "~/server/api/trpc";
import { NewMail } from "~/server/services/mailer"; import { NewMail } from "~/server/services/mailer";
// Create a new ratelimiter, that allows 10 requests per 10 seconds // Create a new ratelimiter, that allows 10 requests per 10 seconds
@ -15,7 +15,7 @@ export const contactRouter = createTRPCRouter({
z.object({ z.object({
nome: z.string(), nome: z.string(),
cognome: z.string(), cognome: z.string(),
email: z.string().email(), email: z.email(),
telefono: z.string(), telefono: z.string(),
messaggio: z.string(), messaggio: z.string(),
}), }),

View file

@ -1,4 +1,4 @@
import { z } from "zod"; import { z } from "zod/v4";
import { import {
createTRPCRouter, createTRPCRouter,
protectedApiProcedure, protectedApiProcedure,

View file

@ -1,4 +1,4 @@
import { z } from "zod"; import { z } from "zod/v4";
import { import {
adminProcedure, adminProcedure,

View file

@ -5,7 +5,7 @@ import {
RemoveInterest, RemoveInterest,
} from "~/server/services/interests.service"; } from "~/server/services/interests.service";
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc"; import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
import z from "zod"; import z from "zod/v4";
import { zAnnuncioId, zUserId } from "~/server/utils/zod_types"; import { zAnnuncioId, zUserId } from "~/server/utils/zod_types";
import { NewMail } from "~/server/services/mailer"; import { NewMail } from "~/server/services/mailer";
import { findUser_byId_MINI } from "~/server/services/user.service"; import { findUser_byId_MINI } from "~/server/services/user.service";

View file

@ -1,6 +1,6 @@
import { tracked, TRPCError } from "@trpc/server"; import { tracked, TRPCError } from "@trpc/server";
import { db } from "~/server/db"; import { db } from "~/server/db";
import { z } from "zod"; import { z } from "zod/v4";
import { import {
adminProcedure, adminProcedure,
createTRPCRouter, createTRPCRouter,

View file

@ -1,5 +1,5 @@
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { z } from "zod"; import { z } from "zod/v4";
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc"; import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
import { db } from "~/server/db"; import { db } from "~/server/db";
// Create a new ratelimiter, that allows 10 requests per 10 seconds // Create a new ratelimiter, that allows 10 requests per 10 seconds

View file

@ -1,4 +1,4 @@
import { z } from "zod"; import { z } from "zod/v4";
import type { import type {
NewPrezziario, NewPrezziario,
PrezziarioUpdate, PrezziarioUpdate,

View file

@ -1,4 +1,4 @@
import { z } from "zod"; import { z } from "zod/v4";
import { import {
adminProcedure, adminProcedure,
createTRPCRouter, createTRPCRouter,

View file

@ -1,4 +1,4 @@
import { z } from "zod"; import { z } from "zod/v4";
import type { BannersIdbanner } from "~/schemas/public/Banners"; import type { BannersIdbanner } from "~/schemas/public/Banners";
import { import {
adminProcedure, adminProcedure,

View file

@ -1,5 +1,5 @@
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { z } from "zod"; import { z } from "zod/v4";
import { import {
adminProcedure, adminProcedure,
createTRPCRouter, createTRPCRouter,

View file

@ -1,4 +1,4 @@
import { z } from "zod"; import { z } from "zod/v4";
import { import {
createTRPCRouter, createTRPCRouter,
protectedProcedure, protectedProcedure,

View file

@ -1,4 +1,4 @@
import { z } from "zod"; import { z } from "zod/v4";
import { import {
adminProcedure, adminProcedure,
createTRPCRouter, createTRPCRouter,
@ -78,7 +78,7 @@ export const usersRouter = createTRPCRouter({
nome: z.string(), nome: z.string(),
cognome: z.string(), cognome: z.string(),
telefono: z.string(), telefono: z.string(),
email: z.string().email(), email: z.email(),
isAdmin: z.boolean().optional(), isAdmin: z.boolean().optional(),
}), }),
) )

View file

@ -11,7 +11,7 @@ import { initTRPC, TRPCError } from "@trpc/server";
import type { CreateNextContextOptions } from "@trpc/server/adapters/next"; import type { CreateNextContextOptions } from "@trpc/server/adapters/next";
import superjson from "superjson"; import superjson from "superjson";
import z, { ZodError } from "zod"; import z, { ZodError } from "zod/v4";
import type { NextApiRequest, NextApiResponse } from "next"; import type { NextApiRequest, NextApiResponse } from "next";
import { env } from "~/env.mjs"; import { env } from "~/env.mjs";
@ -28,7 +28,7 @@ import { ACCESS_TOKEN_COOKIE_NAME, verifyToken } from "~/server/auth/jwt";
export const sessionSchema = z.object({ export const sessionSchema = z.object({
id: zUserId, id: zUserId,
username: z.string(), username: z.string(),
email: z.string().email(), email: z.email(),
isAdmin: z.boolean(), isAdmin: z.boolean(),
isBlocked: z.boolean(), isBlocked: z.boolean(),
nome: z.string(), nome: z.string(),
@ -89,7 +89,7 @@ export const t = initTRPC.context<Context>().create({
data: { data: {
...shape.data, ...shape.data,
zodError: zodError:
error.cause instanceof ZodError ? error.cause.flatten() : null, error.cause instanceof ZodError ? z.treeifyError(error.cause) : null,
}, },
}; };
}, },

View file

@ -3,7 +3,7 @@ import { TRPCError } from "@trpc/server";
import { createSrc, createSrcset } from "~/server/services/imageServer"; import { createSrc, createSrcset } from "~/server/services/imageServer";
import { AnnuncioObjectWithImages } from "~/server/services/annunci.service"; import { AnnuncioObjectWithImages } from "~/server/services/annunci.service";
import { zAnnuncioId } from "~/server/utils/zod_types"; import { zAnnuncioId } from "~/server/utils/zod_types";
import { z } from "zod"; import { z } from "zod/v4";
import { db } from "~/server/db"; import { db } from "~/server/db";
import type { ServizioServizioId } from "~/schemas/public/Servizio"; import type { ServizioServizioId } from "~/schemas/public/Servizio";

View file

@ -10,7 +10,7 @@ import type { UsersAnagraficaUpdate } from "~/schemas/public/UsersAnagrafica";
import { jsonArrayFrom, jsonObjectFrom } from "kysely/helpers/postgres"; import { jsonArrayFrom, jsonObjectFrom } from "kysely/helpers/postgres";
import type { AnnunciId } from "~/schemas/public/Annunci"; import type { AnnunciId } from "~/schemas/public/Annunci";
import { add } from "date-fns"; import { add } from "date-fns";
import { z } from "zod"; import { z } from "zod/v4";
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum"; import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
import { NewMail, type NewMailProps } from "~/server/services/mailer"; import { NewMail, type NewMailProps } from "~/server/services/mailer";
import type { ServizioAnnunciUpdate } from "~/schemas/public/ServizioAnnunci"; import type { ServizioAnnunciUpdate } from "~/schemas/public/ServizioAnnunci";

View file

@ -1,4 +1,4 @@
import { z } from "zod"; import { z } from "zod/v4";
const ClientTypeSchema = z.enum(["company", "person", "pa", "condo"]); const ClientTypeSchema = z.enum(["company", "person", "pa", "condo"]);

View file

@ -16,64 +16,43 @@ const trackedEnvelopeSchema =
* 3. The return value of the async iterable, if any, also conforms to a specified type. * 3. The return value of the async iterable, if any, also conforms to a specified type.
*/ */
export function zAsyncIterable< export function zAsyncIterable<
TYieldIn, TYield,
TYieldOut, TReturn = void,
TReturnIn = void,
TReturnOut = void,
Tracked extends boolean = false, Tracked extends boolean = false,
>(opts: { >(opts: {
/** yield: z.ZodType<TYield, any, any>;
* Validate the value yielded by the async generator return?: z.ZodType<TReturn, any, any>;
*/
yield: z.ZodType<TYieldIn, any, TYieldOut>;
/**
* Validate the return value of the async generator
* @remark not applicable for subscriptions
*/
return?: z.ZodType<TReturnIn, any, TReturnOut>;
/**
* Whether if the yielded values are tracked
* @remark only applicable for subscriptions
*/
tracked?: Tracked; tracked?: Tracked;
}) { }) {
return z const baseSchema = z.custom<
.custom<
AsyncIterable< AsyncIterable<
Tracked extends true ? TrackedEnvelope<TYieldIn> : TYieldIn, Tracked extends true
TReturnIn ? TrackedEnvelope<z.input<typeof opts.yield>>
: z.input<typeof opts.yield>,
z.input<typeof opts.return>
> >
>((val) => isAsyncIterable(val)) >((val: unknown): val is AsyncIterable<any, any> => isAsyncIterable(val), {
.transform(async function* (iter) { message: "Expected AsyncIterable",
});
return baseSchema.transform(async function* (iter) {
const iterator = iter[Symbol.asyncIterator](); const iterator = iter[Symbol.asyncIterator]();
try { try {
let next; let next;
while ((next = await iterator.next()) && !next.done) { while ((next = await iterator.next()) && !next.done) {
if (opts.tracked) { if (opts.tracked) {
const [id, data] = trackedEnvelopeSchema.parse(next.value); const [id, data] = await trackedEnvelopeSchema.parseAsync(next.value);
yield tracked(id, await opts.yield.parseAsync(data)); yield tracked(id, await opts.yield.parseAsync(data));
continue; continue;
} }
yield opts.yield.parseAsync(next.value); yield await opts.yield.parseAsync(next.value);
} }
if (opts.return) { if (opts.return) {
return await opts.return.parseAsync(next.value); return (await opts.return.parseAsync(next.value)) as TReturn;
} }
return; return;
} finally { } finally {
await iterator.return?.(); await iterator.return?.();
} }
}) as z.ZodType< });
AsyncIterable<
Tracked extends true ? TrackedEnvelope<TYieldIn> : TYieldIn,
TReturnIn,
unknown
>,
any,
AsyncIterable<
Tracked extends true ? TrackedEnvelope<TYieldOut> : TYieldOut,
TReturnOut,
unknown
>
>;
} }

View file

@ -1,4 +1,4 @@
import { z } from "zod"; import { z } from "zod/v4";
import type { AnnunciId } from "~/schemas/public/Annunci"; import type { AnnunciId } from "~/schemas/public/Annunci";
import type { ChatsChatid } from "~/schemas/public/Chats"; import type { ChatsChatid } from "~/schemas/public/Chats";
import type { EtichetteIdEtichetta } from "~/schemas/public/Etichette"; import type { EtichetteIdEtichetta } from "~/schemas/public/Etichette";
@ -75,10 +75,9 @@ export const zBanlistId = z.custom<BanlistId>(
"falied to validate BanlistId", "falied to validate BanlistId",
); );
export const zBanType = z.custom<BanType>( export const zBanType = z.enum(BanType, {
(val) => val in BanType, error: "failed to validate BanType",
"falied to validate BanType", });
);
export const zServizioId = z.custom<ServizioServizioId>( export const zServizioId = z.custom<ServizioServizioId>(
(val) => typeof val === "string", (val) => typeof val === "string",