aggiunta libreria per stile e validazione

This commit is contained in:
Dmitri 2025-11-19 00:06:55 +01:00
parent 77758f2c29
commit fbd90e1649
Signed by: kanopo
GPG Key ID: 759ADD40E3132AC7
3 changed files with 1274 additions and 139 deletions

1103
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,12 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@fontsource/roboto": "^5.2.8",
"@hookform/resolvers": "^5.2.2",
"@mui/material": "^7.3.5",
"@mui/x-date-pickers": "^8.18.0",
"@tanstack/react-query": "^5.90.9",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
@ -11,13 +17,16 @@
"@types/node": "^16.18.3",
"@types/react": "^18.0.25",
"@types/react-dom": "^18.0.9",
"date-fns": "^4.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.66.1",
"react-router": "^6.4.3",
"react-router-dom": "^6.4.3",
"react-scripts": "5.0.1",
"typescript": "^4.9.3",
"web-vitals": "^2.1.4"
"web-vitals": "^2.1.4",
"yup": "^1.7.1"
},
"scripts": {
"start": "react-scripts start",

View File

@ -1,48 +1,102 @@
import { useMutation, useQuery } from "@tanstack/react-query";
import { useState } from "react";
import { BaseResponse, FormState } from "../interfaces";
import { useMutation } from "@tanstack/react-query";
import { Controller, SubmitHandler, useForm, useWatch } from "react-hook-form";
import { Button, Checkbox, FormControlLabel, Stack, TextField } from "@mui/material";
import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers";
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
import * as yup from "yup";
import { differenceInYears, isValid } from "date-fns";
import { yupResolver } from "@hookform/resolvers/yup";
import { useEffect } from "react";
const schema = yup.object({
name: yup.string().required("Name is required"),
age: yup.number()
.typeError("Age must be a number")
.positive()
.integer()
.required("Age is required"),
married: yup.boolean()
.required(),
dateOfBirth: yup.date()
.typeError("Devi inserire una data valida")
.required("DOB is required")
.test("dob-match", "Date of Birth does not match the Age provided", function(value) {
const { age } = this.parent;
if (!value || typeof age !== 'number') return true;
if (!isValid(value)) return false;
const calculatedAge = differenceInYears(new Date(), value);
return calculatedAge === age;
}),
}).required();
type FormSchemaType = yup.InferType<typeof schema>;
interface BaseResponse {
success: boolean;
}
const CheckAgeForm = () => {
const [form, setForm] = useState<FormState>({
name: "",
age: 0,
dateOfBirth: new Date()
})
const validate = useMutation({
mutationFn: async (data: FormState): Promise<BaseResponse> => {
const res = await fetch('http://localhost:3001/info/validate-form', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
...data
})
})
// TODO: usare axios o altre librerie per avere un minimo di typesafety, qui sto pregando che il risultato sia quello che mi aspetto e sinceramente non so se dia errore fino a quando non prova a chiamare un oggetto annidato
const resData: BaseResponse = await res.json()
console.log(resData)
return resData
}
})
const handleSand = async () => {
await validate.mutateAsync({
...form
})
}
const handleReset = () => {
validate.reset()
setForm({
const {
control,
handleSubmit,
reset,
trigger,
setValue,
formState: { errors, isValid, isDirty }
} = useForm<FormSchemaType>({
mode: "onChange",
resolver: yupResolver(schema),
defaultValues: {
name: "",
age: 0,
married: false,
dateOfBirth: new Date()
})
}
},
});
const watchedAge = useWatch({ control, name: "age" });
const isAbove18 = (watchedAge || 0) >= 18;
useEffect(() => {
if (!isAbove18) {
setValue("married", false, { shouldValidate: true });
}
if (control._fields['dateOfBirth']) {
trigger("dateOfBirth");
}
}, [watchedAge, isAbove18, control]);
const validate = useMutation({
mutationFn: async (data: FormSchemaType): Promise<BaseResponse> => {
const res = await fetch('http://localhost:3001/info/validate-form', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
const resData: BaseResponse = await res.json();
return resData;
}
});
const onSubmit: SubmitHandler<FormSchemaType> = async (data) => {
await validate.mutateAsync(data);
};
const handleReset = () => {
validate.reset();
reset({
name: "",
age: 0,
married: false,
dateOfBirth: new Date()
});
};
const isAbove18 = form.age >= 18
if (validate.isError) {
return (
@ -55,96 +109,115 @@ const CheckAgeForm = () => {
if (validate.isPending) {
return (
<div>
<h1>INVIO IN CORSO</h1>
<button onClick={handleReset}>ANNULLA</button>
</div>
<div><h1>INVIO IN CORSO...</h1></div>
);
}
if (validate.isSuccess) {
return (
<div>
{validate.data?.success === true && <h1>DATI INVIATI VALIDI</h1>}
{validate.data?.success === false && <h1>DATI INVIATI NON VALIDI</h1>}
<button onClick={handleReset}>INVIA UN ALTRO VALORE</button>
{validate.data?.success ? <div>
<h1>DATI VALIDI</h1>
<p>
{JSON.stringify(validate.data)}
</p>
</div>
: <h1>DATI NON VALIDI</h1>}
<button onClick={handleReset}>RESET</button>
</div>
);
}
return (
<div>
<input
type="text"
placeholder="name"
value={form.name}
onChange={(e) => {
setForm((prev) => {
return {
...prev,
name: e.target.value
}
});
}}
/>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<Stack sx={{ width: "90%", margin: "auto", gap: 2, mt: 5 }}>
<input
type="number"
placeholder="age"
value={form.age}
onChange={(e) => {
setForm((prev) => {
const age = Number.parseInt(e.target.value) ?? 0
return {
...prev,
age
}
});
}}
/>
<Controller
name="name"
control={control}
render={({ field }) => (
<TextField
{...field}
label="Name"
error={!!errors.name}
helperText={errors.name?.message}
/>
)}
/>
{
(isAbove18) && (
<input
type="checkbox"
placeholder="married?"
checked={form.married}
onChange={() => {
setForm((prev) => {
return {
...prev,
married: !(prev.married ?? false)
<Controller
name="age"
control={control}
render={({ field }) => (
<TextField
{...field}
label="Age"
type="number"
error={!!errors.age}
helperText={errors.age?.message}
/>
)}
/>
<Controller
name="dateOfBirth"
control={control}
render={({ field }) => (
<DatePicker
{...field}
label="Date of birth"
slotProps={{
textField: {
error: !!errors.dateOfBirth,
helperText: errors.dateOfBirth?.message
}
});
}}
/>
)
}
<input
type="date"
placeholder="date of birth"
value={form.dateOfBirth.toISOString().split("T")[0]}
onChange={(e) => {
setForm((prev) => {
const newDate = new Date(e.target.value);
return {
...prev,
dateOfBirth: newDate,
};
});
}}
/>
}}
/>
)}
/>
<button
onClick={handleSand}
>
VALIDA
</button>
<FormControlLabel
control={
<Controller
name="married"
control={control}
render={({ field }) => (
<Checkbox
checked={!!field.value}
onChange={(e) => field.onChange(e.target.checked)}
disabled={!isAbove18}
/>
)}
/>
}
label={"Married?"}
/>
</div>
)
<Stack
sx={{
width: "100%",
gap: 2,
flexDirection: "row",
justifyContent: "flex-end"
}}
>
<Button
disabled={!isDirty}
variant="outlined"
onClick={handleReset}
>
UNDO
</Button>
<Button
disabled={!isValid}
variant="contained"
onClick={handleSubmit(onSubmit)}>
VALIDA
</Button>
</Stack>
</Stack>
</LocalizationProvider>
);
}
export default CheckAgeForm
export default CheckAgeForm;