+52
-50
@@ -1,6 +1,6 @@
|
||||
import * as React from "react"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import * as React from "react";
|
||||
import * as LabelPrimitive from "@radix-ui/react-label";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import {
|
||||
Controller,
|
||||
ControllerProps,
|
||||
@@ -8,27 +8,27 @@ import {
|
||||
FieldValues,
|
||||
FormProvider,
|
||||
useFormContext,
|
||||
} from "react-hook-form"
|
||||
} from "react-hook-form";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
||||
const Form = FormProvider
|
||||
const Form = FormProvider;
|
||||
|
||||
type FormFieldContextValue<
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
> = {
|
||||
name: TName
|
||||
}
|
||||
name: TName;
|
||||
};
|
||||
|
||||
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
||||
{} as FormFieldContextValue
|
||||
)
|
||||
{} as FormFieldContextValue,
|
||||
);
|
||||
|
||||
const FormField = <
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
>({
|
||||
...props
|
||||
}: ControllerProps<TFieldValues, TName>) => {
|
||||
@@ -36,21 +36,21 @@ const FormField = <
|
||||
<FormFieldContext.Provider value={{ name: props.name }}>
|
||||
<Controller {...props} />
|
||||
</FormFieldContext.Provider>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const useFormField = () => {
|
||||
const fieldContext = React.useContext(FormFieldContext)
|
||||
const itemContext = React.useContext(FormItemContext)
|
||||
const { getFieldState, formState } = useFormContext()
|
||||
const fieldContext = React.useContext(FormFieldContext);
|
||||
const itemContext = React.useContext(FormItemContext);
|
||||
const { getFieldState, formState } = useFormContext();
|
||||
|
||||
const fieldState = getFieldState(fieldContext.name, formState)
|
||||
const fieldState = getFieldState(fieldContext.name, formState);
|
||||
|
||||
if (!fieldContext) {
|
||||
throw new Error("useFormField should be used within <FormField>")
|
||||
throw new Error("useFormField should be used within <FormField>");
|
||||
}
|
||||
|
||||
const { id } = itemContext
|
||||
const { id } = itemContext;
|
||||
|
||||
return {
|
||||
id,
|
||||
@@ -59,36 +59,36 @@ const useFormField = () => {
|
||||
formDescriptionId: `${id}-form-item-description`,
|
||||
formMessageId: `${id}-form-item-message`,
|
||||
...fieldState,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
type FormItemContextValue = {
|
||||
id: string
|
||||
}
|
||||
id: string;
|
||||
};
|
||||
|
||||
const FormItemContext = React.createContext<FormItemContextValue>(
|
||||
{} as FormItemContextValue
|
||||
)
|
||||
{} as FormItemContextValue,
|
||||
);
|
||||
|
||||
const FormItem = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const id = React.useId()
|
||||
const id = React.useId();
|
||||
|
||||
return (
|
||||
<FormItemContext.Provider value={{ id }}>
|
||||
<div ref={ref} className={cn("space-y-2", className)} {...props} />
|
||||
</FormItemContext.Provider>
|
||||
)
|
||||
})
|
||||
FormItem.displayName = "FormItem"
|
||||
);
|
||||
});
|
||||
FormItem.displayName = "FormItem";
|
||||
|
||||
const FormLabel = React.forwardRef<
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { error, formItemId } = useFormField()
|
||||
const { error, formItemId } = useFormField();
|
||||
|
||||
return (
|
||||
<Label
|
||||
@@ -97,15 +97,16 @@ const FormLabel = React.forwardRef<
|
||||
htmlFor={formItemId}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
FormLabel.displayName = "FormLabel"
|
||||
);
|
||||
});
|
||||
FormLabel.displayName = "FormLabel";
|
||||
|
||||
const FormControl = React.forwardRef<
|
||||
React.ElementRef<typeof Slot>,
|
||||
React.ComponentPropsWithoutRef<typeof Slot>
|
||||
>(({ ...props }, ref) => {
|
||||
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
|
||||
const { error, formItemId, formDescriptionId, formMessageId } =
|
||||
useFormField();
|
||||
|
||||
return (
|
||||
<Slot
|
||||
@@ -119,15 +120,15 @@ const FormControl = React.forwardRef<
|
||||
aria-invalid={!!error}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
FormControl.displayName = "FormControl"
|
||||
);
|
||||
});
|
||||
FormControl.displayName = "FormControl";
|
||||
|
||||
const FormDescription = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { formDescriptionId } = useFormField()
|
||||
const { formDescriptionId } = useFormField();
|
||||
|
||||
return (
|
||||
<p
|
||||
@@ -136,19 +137,19 @@ const FormDescription = React.forwardRef<
|
||||
className={cn("text-[0.8rem] text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
FormDescription.displayName = "FormDescription"
|
||||
);
|
||||
});
|
||||
FormDescription.displayName = "FormDescription";
|
||||
|
||||
const FormMessage = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, children, ...props }, ref) => {
|
||||
const { error, formMessageId } = useFormField()
|
||||
const body = error ? String(error?.message) : children
|
||||
const { error, formMessageId } = useFormField();
|
||||
const body = error ? String(error?.message) : children;
|
||||
|
||||
if (!body) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -160,11 +161,12 @@ const FormMessage = React.forwardRef<
|
||||
>
|
||||
{body}
|
||||
</p>
|
||||
)
|
||||
})
|
||||
FormMessage.displayName = "FormMessage"
|
||||
);
|
||||
});
|
||||
FormMessage.displayName = "FormMessage";
|
||||
|
||||
export {
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
useFormField,
|
||||
Form,
|
||||
FormItem,
|
||||
@@ -173,4 +175,4 @@ export {
|
||||
FormDescription,
|
||||
FormMessage,
|
||||
FormField,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { z } from "zod";
|
||||
|
||||
export const BookingFormSchema = z.object({
|
||||
name: z.string().min(2).max(50),
|
||||
surname: z.string().min(2).max(50),
|
||||
email: z.string().email(),
|
||||
phone: z.string().min(8),
|
||||
});
|
||||
+103
-103
@@ -1,14 +1,24 @@
|
||||
import { APISendCreateBooking } from "@/api/api-booking";
|
||||
import { BookingDetails } from "@/components/booking/booking-details";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Typography } from "@/components/ui/typography";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { BookingFormSchema } from "@/model/FormSchema";
|
||||
import { useAppStore } from "@/store";
|
||||
import { ChangeEvent, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { z } from "zod";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
|
||||
enum PageState {
|
||||
noData,
|
||||
@@ -22,58 +32,29 @@ function BookPage() {
|
||||
const [pageState, setPageState] = useState<PageState>(PageState.showForm);
|
||||
const bookingData = useAppStore((state) => state.newBookingData);
|
||||
const setCustomerData = useAppStore((state) => state.setBookingCustomer);
|
||||
const { toast } = useToast();
|
||||
const navigate = useNavigate();
|
||||
|
||||
if (!bookingData || !bookingData.customerDetails) {
|
||||
return (
|
||||
<Typography variant={"h2"}>You must select some rooms...</Typography>
|
||||
);
|
||||
}
|
||||
const bookingForm = useForm<z.infer<typeof BookingFormSchema>>({
|
||||
resolver: zodResolver(BookingFormSchema),
|
||||
defaultValues: {
|
||||
email: "",
|
||||
name: "",
|
||||
surname: "",
|
||||
phone: "",
|
||||
},
|
||||
});
|
||||
|
||||
function handleOnChange(e: ChangeEvent<HTMLInputElement>) {
|
||||
function onSubmit(values: z.infer<typeof BookingFormSchema>) {
|
||||
if (bookingData == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newCustomerData = bookingData.customerDetails;
|
||||
|
||||
switch (e.target.name) {
|
||||
case "name":
|
||||
newCustomerData.name = e.target.value;
|
||||
break;
|
||||
case "surname":
|
||||
newCustomerData.surname = e.target.value;
|
||||
break;
|
||||
case "email":
|
||||
newCustomerData.email = e.target.value;
|
||||
break;
|
||||
case "phoneNumber":
|
||||
newCustomerData.phoneNumber = e.target.value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
newCustomerData.name = values.name;
|
||||
newCustomerData.surname = values.surname;
|
||||
newCustomerData.email = values.email;
|
||||
newCustomerData.phoneNumber = values.phone;
|
||||
|
||||
setCustomerData(newCustomerData);
|
||||
}
|
||||
|
||||
function handleOnBookingSubmit() {
|
||||
const customerDetails = bookingData?.customerDetails;
|
||||
|
||||
if (
|
||||
!customerDetails ||
|
||||
customerDetails.name == "" ||
|
||||
customerDetails.surname == "" ||
|
||||
customerDetails.phoneNumber == "" ||
|
||||
customerDetails.email == ""
|
||||
) {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Uh oh! You must fill all fields.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setPageState(PageState.sending);
|
||||
console.log(bookingData);
|
||||
@@ -90,70 +71,89 @@ function BookPage() {
|
||||
}
|
||||
};
|
||||
|
||||
if (!bookingData || !bookingData.customerDetails) {
|
||||
return (
|
||||
<Typography variant={"h2"}>You must select some rooms...</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography variant={"h1"}>Booking Details</Typography>
|
||||
<Separator className="my-10" />
|
||||
<div className="lg:grid lg:grid-cols-40/60 lg:gap-x-10">
|
||||
<div>
|
||||
<Typography variant={"h3"}>Personal Details</Typography>
|
||||
<div>
|
||||
<div>
|
||||
<Label htmlFor="name">Name</Label>
|
||||
<Input
|
||||
type="text"
|
||||
name="name"
|
||||
id="name"
|
||||
placeholder="Name"
|
||||
value={bookingData.customerDetails.name ?? ""}
|
||||
onChange={handleOnChange}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="surname">Surname</Label>
|
||||
<Input
|
||||
type="text"
|
||||
name="surname"
|
||||
id="surname"
|
||||
placeholder="Surname"
|
||||
value={bookingData.customerDetails.surname ?? ""}
|
||||
onChange={handleOnChange}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
type="email"
|
||||
name="email"
|
||||
id="email"
|
||||
placeholder="Email"
|
||||
value={bookingData.customerDetails.email ?? ""}
|
||||
onChange={handleOnChange}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="phone">Phone Number</Label>
|
||||
<Input
|
||||
type="tel"
|
||||
name="phoneNumber"
|
||||
id="phone"
|
||||
placeholder="Number"
|
||||
value={bookingData.customerDetails.phoneNumber ?? ""}
|
||||
onChange={handleOnChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Separator className="my-10" />
|
||||
<BookingDetails mode="View"></BookingDetails>
|
||||
<Separator className="my-10" />
|
||||
<Button
|
||||
onClick={handleOnBookingSubmit}
|
||||
disabled={pageState == PageState.sending}
|
||||
>
|
||||
<Typography variant={"h3"} className="text-primary-foreground">
|
||||
Book it now!
|
||||
<Typography variant={"h3"} className="my-4">
|
||||
Personal Details
|
||||
</Typography>
|
||||
<Form {...bookingForm}>
|
||||
<form
|
||||
onSubmit={bookingForm.handleSubmit(onSubmit)}
|
||||
className="space-y-8"
|
||||
>
|
||||
<div className="flex w-full flex-row gap-x-3">
|
||||
<FormField
|
||||
control={bookingForm.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex-1">
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
></FormField>
|
||||
<FormField
|
||||
control={bookingForm.control}
|
||||
name="surname"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex-1">
|
||||
<FormLabel>Surname</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
></FormField>
|
||||
</div>
|
||||
<FormField
|
||||
control={bookingForm.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
></FormField>
|
||||
<FormField
|
||||
control={bookingForm.control}
|
||||
name="phone"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Phone</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
></FormField>
|
||||
<Button type="submit" disabled={pageState == PageState.sending}>
|
||||
Submit
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
<Separator className="my-10 lg:hidden" />
|
||||
<BookingDetails mode="View"></BookingDetails>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user