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",
"vaul": "^1.1.2",
"yaml": "^2.8.1",
"zod": "^3.25.76"
"zod": "^4.0.15"
},
"devDependencies": {
"@eslint/compat": "^1.3.1",
@ -127,7 +127,7 @@
"prettier-plugin-tailwindcss": "^0.6.14",
"tailwind-scrollbar": "^4.0.2",
"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",
"typescript": "^5.9.2",
"typescript-eslint": "^8.39.0"
@ -13830,6 +13830,16 @@
"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": {
"version": "0.28.4",
"resolved": "https://registry.npmjs.org/kysely/-/kysely-0.28.4.tgz",
@ -20092,8 +20102,8 @@
},
"node_modules/trpc-ui": {
"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",
"integrity": "sha512-E1FvgLyMzBFneyBxmYaW4FV4IaUjOcfSyN1fRpMdc3160/fePfSkUVP015FW4qNAuLc4hhQyOVXqefWquR9FUg==",
"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-3cw5HsTSl114eZQH1SGm3NWzb0JnDIdyB3Ins2kELHMFBax2Y7v2GLDAh5vkOc1IGqSwYg3gjbMLytKEug2B9w==",
"dev": true,
"hasInstallScript": true,
"license": "ISC",
@ -20188,6 +20198,16 @@
"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": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
@ -21279,24 +21299,14 @@
}
},
"node_modules/zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"version": "4.0.15",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.0.15.tgz",
"integrity": "sha512-2IVHb9h4Mt6+UXkyMs0XbfICUh1eUrlJJAOupBHUhLRnKkruawyDddYRCs0Eizt900ntIMk9/4RksYl+FgSpcQ==",
"license": "MIT",
"funding": {
"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": {
"version": "3.5.3",
"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",
"vaul": "^1.1.2",
"yaml": "^2.8.1",
"zod": "^3.25.76"
"zod": "^4.0.15"
},
"devDependencies": {
"@eslint/compat": "^1.3.1",
@ -138,8 +138,8 @@
"prettier-plugin-tailwindcss": "^0.6.14",
"tailwind-scrollbar": "^4.0.2",
"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",
"tw-animate-css": "^1.3.6",
"typescript": "^5.9.2",
"typescript-eslint": "^8.39.0"
},

View file

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

View file

@ -1,7 +1,7 @@
// @ts-nocheck
/* 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
@ -9,7 +9,7 @@ import { z } from "zod";
*/
const server = z.object({
NODE_ENV: z.enum(["development", "test", "production"]),
INTERNAL_BASE_URL: z.string().url(),
INTERNAL_BASE_URL: z.url(),
PGHOST: z.string(),
POSTGRES_USER: z.string(),
POSTGRES_DB: z.string(),
@ -96,7 +96,7 @@ if (!!process.env.SKIP_ENV_VALIDATION === false) {
if (parsed.success === false) {
console.error(
"❌ Invalid environment variables:",
parsed.error.flatten().fieldErrors,
z.treeifyError(parsed.error).fieldErrors,
);
throw new Error("Invalid environment variables");
}

View file

@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
import {
Form,
FormControl,
@ -7,7 +7,6 @@ import {
FormLabel,
FormMessage,
} from "~/components/custom_ui/form";
import { customErrorMapWrapper } from "~/hooks/locale";
import { useZodForm } from "~/lib/zodForm";
import Input from "~/components/custom_ui/input";
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({
schema: Schema,
const form = useZodForm(Schema, {
defaultValues: defaultValues,
});

View file

@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
import {
Form,
FormControl,
@ -7,7 +7,6 @@ import {
FormLabel,
FormMessage,
} from "~/components/custom_ui/form";
import { customErrorMapWrapper } from "~/hooks/locale";
import { useZodForm } from "~/lib/zodForm";
import Input from "~/components/custom_ui/input";
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({
schema: Schema,
const form = useZodForm(Schema, {
defaultValues: defaultValues,
});

View file

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

View file

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

View file

@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
import {
Form,
FormControl,
@ -7,7 +7,6 @@ import {
FormLabel,
FormMessage,
} from "~/components/custom_ui/form";
import { customErrorMapWrapper } from "~/hooks/locale";
import { useZodForm } from "~/lib/zodForm";
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({
schema: Schema,
const form = useZodForm(Schema, {
defaultValues: defaultValues,
});
function onSubmit(fields: FormValues) {
const values = {
id_etichetta: fields.id_etichetta as EtichetteIdEtichetta,
id_etichetta: fields.id_etichetta,
title: fields.title,
color_hex: fields.color_hex,
};

View file

@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
import {
Form,
FormControl,
@ -7,7 +7,6 @@ import {
FormLabel,
FormMessage,
} from "~/components/custom_ui/form";
import { customErrorMapWrapper } from "~/hooks/locale";
import { useZodForm } from "~/lib/zodForm";
import Input from "~/components/custom_ui/input";
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({
schema: Schema,
const form = useZodForm(Schema, {
defaultValues: defaultValues,
});

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
import {
Form,
FormControl,
@ -7,7 +7,6 @@ import {
FormLabel,
FormMessage,
} from "~/components/custom_ui/form";
import { customErrorMapWrapper } from "~/hooks/locale";
import { useZodForm } from "~/lib/zodForm";
import Input from "~/components/custom_ui/input";
import { Button } from "~/components/ui/button";
@ -48,7 +47,7 @@ export const FormPrezzo = ({
const PrezzoSchema = z
.object({
idprezziario: z.custom<PrezziarioIdprezziario>(),
prezzo_cent: z.coerce.number().positive(),
prezzo_cent: z.number().positive(),
testo_condizioni: z.string().nullable(),
isAcconto: z.boolean(),
isSaldo: z.boolean(),
@ -62,7 +61,7 @@ export const FormPrezzo = ({
isTransitorio: z.boolean(),
isStabile: z.boolean(),
})
.superRefine(({ testo_condizioni }, refinementContext) => {
.superRefine(({ testo_condizioni }, ctx) => {
if (testo_condizioni) {
if (
stringhe_condizioni_options.length > 0 &&
@ -73,9 +72,11 @@ export const FormPrezzo = ({
"stringhe_condizioni_options",
stringhe_condizioni_options,
);
return refinementContext.addIssue({
code: z.ZodIssueCode.custom,
ctx.addIssue({
code: "custom",
message: "Testo condizioni non valido",
input: testo_condizioni,
path: ["testo_condizioni"],
});
}
@ -102,10 +103,9 @@ export const FormPrezzo = ({
isStabile: false,
};
z.setErrorMap(customErrorMapWrapper("it"));
z.config(z.locales.it());
const form = useZodForm({
schema: PrezzoSchema,
const form = useZodForm(PrezzoSchema, {
defaultValues: defaultValues,
});
@ -238,7 +238,7 @@ export const FormPrezzo = ({
{...field}
type="number"
id="prezzo"
value={field.value / 100}
value={Number(field.value) / 100}
onChange={(v) => {
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 {
Form,
@ -11,7 +11,6 @@ import {
import Input from "~/components/custom_ui/input";
import { Button } from "~/components/ui/button";
import toast from "react-hot-toast";
import { customErrorMapWrapper } from "~/hooks/locale";
import { api } from "~/utils/api";
import { useTranslation } from "~/providers/I18nProvider";
import type { Users, UsersId } from "~/schemas/public/Users";
@ -36,7 +35,7 @@ export const ProfileFormAccount = ({
const profileFormSchema = z.object({
nome: z.string().nonempty("Inserisci un nome 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"),
isAdmin: z.boolean().optional(),
});
@ -51,10 +50,9 @@ export const ProfileFormAccount = ({
isAdmin: isAdmin,
};
const { locale, t } = useTranslation();
z.setErrorMap(customErrorMapWrapper(locale));
z.config(z.locales[locale]());
const form = useZodForm({
schema: profileFormSchema,
const form = useZodForm(profileFormSchema, {
defaultValues: defaultValues,
});

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
import {
Form,
FormControl,
@ -7,7 +7,6 @@ import {
FormLabel,
FormMessage,
} from "~/components/custom_ui/form";
import { customErrorMapWrapper } from "~/hooks/locale";
import { useZodForm } from "~/lib/zodForm";
import Input from "~/components/custom_ui/input";
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({
schema: Schema,
const form = useZodForm(Schema, {
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 { 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
* Triggers immediately if state changes
@ -167,7 +179,7 @@ export const useLiveChat = (chatId: ChatsChatid, userData: Session) => {
api.messagesSSE.onMessageEvent.useSubscription(
{ chatId },
{
onData(data) {
onData(data: { data: MessageUpdate }) {
switch (data.data.eventType) {
case "edit":
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 { z, type RefinementCtx } from "zod";
import { z, type RefinementCtx } from "zod/v4";
import { useTranslation } from "~/providers/I18nProvider";
export const zStrongPassword = z.string().superRefine((password) => {
return [/.{8,}/, /[0-9]/, /[a-z]/, /[A-Z]/].every((regex) =>
export const zStrongPassword = z.string().superRefine((password, ctx) => {
const test = [/.{8,}/, /[0-9]/, /[a-z]/, /[A-Z]/].every((regex) =>
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 = () => {
@ -57,13 +64,14 @@ export const useStrongPassword = () => {
const complexity = checkStrength(password);
if (complexity.filter((req) => req.met).length !== complexity.length) {
refinementContext.addIssue({
code: z.ZodIssueCode.custom,
refinementContext.issues.push({
code: "custom",
message: complexity
.filter((req) => !req.met)
.map((req) => req.text)
.join("\n"),
path: [path],
input: "",
});
}
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 { searchNazioneByIndex } from "~/i18n/nazioni";
import type { ListOption } from "~/components/custom_ui/multiselect";
@ -185,26 +185,29 @@ export const checkFiscalCodeValidity = ({
console.log("gen", gen);
console.log("codiceFiscale", codiceFiscale);
console.log("alternative", getAlternativeCF(gen));
refinementContext.addIssue({
code: z.ZodIssueCode.custom,
refinementContext.issues.push({
code: "custom",
message: "Codice fiscale non valido",
path: [path],
input: "",
});
}
if (control !== codiceFiscale.slice(-1)) {
refinementContext.addIssue({
code: z.ZodIssueCode.custom,
refinementContext.issues.push({
code: "custom",
message: "Codice di controllo non valido",
path: [path],
input: "",
});
}
if (codiceFiscale.length !== 16) {
refinementContext.addIssue({
code: z.ZodIssueCode.custom,
refinementContext.issues.push({
code: "custom",
message: "Inserisci un codice fiscale valido",
path: [path],
input: "",
});
}
if (
@ -212,60 +215,67 @@ export const checkFiscalCodeValidity = ({
codiceFiscale,
)
) {
refinementContext.addIssue({
code: z.ZodIssueCode.custom,
refinementContext.issues.push({
code: "custom",
message: "Inserisci un codice fiscale valido",
path: [path],
input: "",
});
}
if (codiceFiscale.slice(0, 3) !== getInizialiCF(cognomeUpper)) {
refinementContext.addIssue({
code: z.ZodIssueCode.custom,
refinementContext.issues.push({
code: "custom",
message: "Codice fiscale incompatibile con cognome inserito",
path: [path],
input: "",
});
}
if (codiceFiscale.slice(3, 6) !== getInizialiCF(nomeUpper)) {
refinementContext.addIssue({
code: z.ZodIssueCode.custom,
refinementContext.issues.push({
code: "custom",
message: "Codice fiscale incompatibile con nome inserito",
path: [path],
input: "",
});
}
if (
codiceFiscale.slice(6, 8) !== dataNascita.getFullYear().toString().slice(-2)
) {
refinementContext.addIssue({
code: z.ZodIssueCode.custom,
refinementContext.issues.push({
code: "custom",
message: "Codice fiscale incompatibile con anno di nascita",
path: [path],
input: "",
});
}
if (codiceFiscale.slice(8, 9) !== getMonthLetterCF(dataNascita.getMonth())) {
refinementContext.addIssue({
code: z.ZodIssueCode.custom,
refinementContext.issues.push({
code: "custom",
message: "Codice fiscale incompatibile con mese di nascita",
path: [path],
input: "",
});
}
if (codiceFiscale.slice(9, 11) !== getDayCF(dataNascita.getDate(), sesso)) {
refinementContext.addIssue({
code: z.ZodIssueCode.custom,
refinementContext.issues.push({
code: "custom",
message: "Codice fiscale incompatibile con giorno di nascita",
path: [path],
input: "",
});
}
if (nazioneNascita === "0") {
if (luogoNascita !== "") {
const comune = getComuneFromIndex(parseInt(luogoNascita));
if (comune && codiceFiscale.slice(11, 15) !== comune.CATASTO) {
refinementContext.addIssue({
code: z.ZodIssueCode.custom,
refinementContext.issues.push({
code: "custom",
message: "Codice fiscale incompatibile con luogo di nascita",
path: [path],
input: "",
});
}
}
@ -273,10 +283,11 @@ export const checkFiscalCodeValidity = ({
if (nazioneNascita !== "") {
const nazione = searchNazioneByIndex(parseInt(nazioneNascita));
if (nazione && codiceFiscale.slice(11, 15) !== nazione.CATASTO) {
refinementContext.addIssue({
code: z.ZodIssueCode.custom,
refinementContext.issues.push({
code: "custom",
message: "Codice fiscale incompatibile con luogo di nascita",
path: [path],
input: "",
});
}
}

View file

@ -1,16 +1,17 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { type UseFormProps, useForm } from "react-hook-form";
import type { z, ZodTypeAny } from "zod";
import { type FieldValues, type UseFormProps, useForm } from "react-hook-form";
export function useZodForm<TSchema extends ZodTypeAny>(
props: Omit<UseFormProps<z.infer<TSchema>>, "resolver"> & {
schema: TSchema;
},
import type { z } from "zod";
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,
resolver: zodResolver(props.schema),
});
return form;
}

View file

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

View file

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

View file

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

View file

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

View file

@ -5,7 +5,7 @@ import {
updateBan,
} from "~/server/services/banlist.service";
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 { db } from "~/server/db";

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,7 +5,7 @@ import {
RemoveInterest,
} from "~/server/services/interests.service";
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 { NewMail } from "~/server/services/mailer";
import { findUser_byId_MINI } from "~/server/services/user.service";

View file

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

View file

@ -1,5 +1,5 @@
import { TRPCError } from "@trpc/server";
import { z } from "zod";
import { z } from "zod/v4";
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
import { db } from "~/server/db";
// 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 {
NewPrezziario,
PrezziarioUpdate,

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
import {
adminProcedure,
createTRPCRouter,
@ -78,7 +78,7 @@ export const usersRouter = createTRPCRouter({
nome: z.string(),
cognome: z.string(),
telefono: z.string(),
email: z.string().email(),
email: z.email(),
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 superjson from "superjson";
import z, { ZodError } from "zod";
import z, { ZodError } from "zod/v4";
import type { NextApiRequest, NextApiResponse } from "next";
import { env } from "~/env.mjs";
@ -28,7 +28,7 @@ import { ACCESS_TOKEN_COOKIE_NAME, verifyToken } from "~/server/auth/jwt";
export const sessionSchema = z.object({
id: zUserId,
username: z.string(),
email: z.string().email(),
email: z.email(),
isAdmin: z.boolean(),
isBlocked: z.boolean(),
nome: z.string(),
@ -89,7 +89,7 @@ export const t = initTRPC.context<Context>().create({
data: {
...shape.data,
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 { AnnuncioObjectWithImages } from "~/server/services/annunci.service";
import { zAnnuncioId } from "~/server/utils/zod_types";
import { z } from "zod";
import { z } from "zod/v4";
import { db } from "~/server/db";
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 type { AnnunciId } from "~/schemas/public/Annunci";
import { add } from "date-fns";
import { z } from "zod";
import { z } from "zod/v4";
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
import { NewMail, type NewMailProps } from "~/server/services/mailer";
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"]);

View file

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

View file

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