diff --git a/crawler/frontend/package-lock.json b/crawler/frontend/package-lock.json index 79e0ad3..f79dc52 100644 --- a/crawler/frontend/package-lock.json +++ b/crawler/frontend/package-lock.json @@ -8,6 +8,7 @@ "name": "wrongmove", "version": "0.0.0", "dependencies": { + "@hookform/resolvers": "^5.1.1", "@radix-ui/react-dialog": "^1.1.14", "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-scroll-area": "^1.2.9", @@ -30,10 +31,12 @@ "oidc-client-ts": "^3.2.1", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-hook-form": "^7.58.1", "react-oidc-context": "^3.3.0", "rivets": "^0.9.6", "tailwind-merge": "^3.3.1", - "tailwindcss": "^4.1.10" + "tailwindcss": "^4.1.10", + "zod": "^3.25.67" }, "devDependencies": { "@eslint/js": "^9.25.0", @@ -670,6 +673,18 @@ "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", "license": "MIT" }, + "node_modules/@hookform/resolvers": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.1.1.tgz", + "integrity": "sha512-J/NVING3LMAEvexJkyTLjruSm7aOFx7QX21pzkiJfMoNG0wl5aFEjLTl7ay7IQb9EWY6AkrBy7tHL2Alijpdcg==", + "license": "MIT", + "dependencies": { + "@standard-schema/utils": "^0.3.0" + }, + "peerDependencies": { + "react-hook-form": "^7.55.0" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1824,6 +1839,12 @@ "win32" ] }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, "node_modules/@swc/core": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.12.1.tgz", @@ -5041,6 +5062,22 @@ "react": "^19.1.0" } }, + "node_modules/react-hook-form": { + "version": "7.58.1", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.58.1.tgz", + "integrity": "sha512-Lml/KZYEEFfPhUVgE0RdCVpnC4yhW+PndRhbiTtdvSlQTL8IfVR+iQkBjLIvmmc6+GGoVeM11z37ktKFPAb0FA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-oidc-context": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/react-oidc-context/-/react-oidc-context-3.3.0.tgz", @@ -5754,6 +5791,15 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.25.67", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", + "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/crawler/frontend/package.json b/crawler/frontend/package.json index 710a064..56568e1 100644 --- a/crawler/frontend/package.json +++ b/crawler/frontend/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "@hookform/resolvers": "^5.1.1", "@radix-ui/react-dialog": "^1.1.14", "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-scroll-area": "^1.2.9", @@ -32,10 +33,12 @@ "oidc-client-ts": "^3.2.1", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-hook-form": "^7.58.1", "react-oidc-context": "^3.3.0", "rivets": "^0.9.6", "tailwind-merge": "^3.3.1", - "tailwindcss": "^4.1.10" + "tailwindcss": "^4.1.10", + "zod": "^3.25.67" }, "devDependencies": { "@eslint/js": "^9.25.0", diff --git a/crawler/frontend/src/components/Parameters.tsx b/crawler/frontend/src/components/Parameters.tsx index 9c3e5c6..33d7814 100644 --- a/crawler/frontend/src/components/Parameters.tsx +++ b/crawler/frontend/src/components/Parameters.tsx @@ -1,40 +1,100 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { DialogTitle } from "@radix-ui/react-dialog"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; import { Button } from "./ui/button"; -import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "./ui/dialog"; +import { Dialog, DialogContent, DialogTrigger } from "./ui/dialog"; +import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "./ui/form"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select"; + +enum Metric { + qmprice = 'Price per square meter', + rooms = 'Number of rooms', + qm = 'Area', + price = 'Price', +} + + +interface ParameterValues { + metric: Metric +} + export function Parameters( props: { isOpen: boolean, - onSubmit: () => void, + onSubmit: (fromValues: ParameterValues) => void, } ) { + const { + register, + watch, + formState: { errors }, + } = useForm() + // const onSubmit: SubmitHandler = (data) => console.log(data) + console.log(watch("metric")) + console.log(errors) + + const formSchema = z.object({ + metric: z.nativeEnum(Metric, { required_error: "Metric is required" }), + }) + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + metric: Metric.qmprice, + }, + }) + // 2. Define a submit handler. + function onSubmit(values: z.infer) { + // Do something with the form values. + // ✅ This will be type-safe and validated. + console.log(values) + props.onSubmit(values) + } + + return <> - {/* */} -
- - - - - - Parameters - - - - - - -
+ + + + + + Visualization Parameters + + +
+ + ( + + Metric to visualize + + + This is your public display name. + + + + )} + /> + + + +
} diff --git a/crawler/frontend/src/components/ui/form.tsx b/crawler/frontend/src/components/ui/form.tsx new file mode 100644 index 0000000..7d7474c --- /dev/null +++ b/crawler/frontend/src/components/ui/form.tsx @@ -0,0 +1,165 @@ +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { Slot } from "@radix-ui/react-slot" +import { + Controller, + FormProvider, + useFormContext, + useFormState, + type ControllerProps, + type FieldPath, + type FieldValues, +} from "react-hook-form" + +import { cn } from "@/lib/utils" +import { Label } from "@/components/ui/label" + +const Form = FormProvider + +type FormFieldContextValue< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, +> = { + name: TName +} + +const FormFieldContext = React.createContext( + {} as FormFieldContextValue +) + +const FormField = < + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, +>({ + ...props +}: ControllerProps) => { + return ( + + + + ) +} + +const useFormField = () => { + const fieldContext = React.useContext(FormFieldContext) + const itemContext = React.useContext(FormItemContext) + const { getFieldState } = useFormContext() + const formState = useFormState({ name: fieldContext.name }) + const fieldState = getFieldState(fieldContext.name, formState) + + if (!fieldContext) { + throw new Error("useFormField should be used within ") + } + + const { id } = itemContext + + return { + id, + name: fieldContext.name, + formItemId: `${id}-form-item`, + formDescriptionId: `${id}-form-item-description`, + formMessageId: `${id}-form-item-message`, + ...fieldState, + } +} + +type FormItemContextValue = { + id: string +} + +const FormItemContext = React.createContext( + {} as FormItemContextValue +) + +function FormItem({ className, ...props }: React.ComponentProps<"div">) { + const id = React.useId() + + return ( + +
+ + ) +} + +function FormLabel({ + className, + ...props +}: React.ComponentProps) { + const { error, formItemId } = useFormField() + + return ( +