"use client";
import React, { useState } from "react";
import { useRouter } from "next/navigation";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { api } from "@/trpc/react";
import { DatePickerWithRange } from "@/components/DatePickerWithRange";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormDescription,
FormMessage,
} from "@/components/ui/form";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Country, State, City } from "country-state-city";
import type { ICity, ICountry, IState } from "country-state-city";
// Zod Schema for Form Validation
const tripFormSchema = z.object({
name: z.string().min(1, { message: "Name is required." }),
destination: z.string().min(1, { message: "Destination is required." }),
country: z.string().min(1, { message: "Country is required." }),
state: z.string().min(1, { message: "State is required." }),
city: z.string().min(1, { message: "City is required." }),
hotelDetails: z.string().optional(),
dateRange: z.object({
from: z.date({
required_error: "Start date is required.",
}),
to: z
.date({
required_error: "End date is required.",
})
.refine((val) => val > new Date(), {
message: "End date must be after start date.",
}),
}),
flightNumber: z.string().optional(),
});
export function CreateTripForm() {
const [selectedCountry, setSelectedCountry] = useState<ICountry | null>(null);
const [selectedState, setSelectedState] = useState<IState | null>(null);
const [selectedCity, setSelectedCity] = useState<ICity | null>(null);
const router = useRouter();
const form = useForm<z.infer<typeof tripFormSchema>>({
resolver: zodResolver(tripFormSchema),
defaultValues: {
name: "",
destination: "",
country: "",
state: "",
city: "",
hotelDetails: "",
dateRange: {
from: new Date(),
to: new Date(),
},
flightNumber: "",
},
});
const createTrip = api.post.create.useMutation({
onSuccess: () => {
router.refresh(); // Refresh the page or navigate if necessary
},
onError: (error: unknown) => {
console.error("Error creating trip:", error);
},
});
function onSubmit(data: z.infer<typeof tripFormSchema>) {
console.log("Form submitted with data:", data); // Debugging statement
const { dateRange, ...rest } = data;
const updatedData = {
...rest,
destination: selectedCity?.name ?? "",
durationOfStay: Math.ceil(
(dateRange.to.getTime() - dateRange.from.getTime()) /
(1000 * 60 * 60 * 24),
),
};
console.log("Updated data:", updatedData); // Debugging statement
createTrip.mutate(updatedData);
}
const handleCountryChange = (value: string) => {
const country = Country.getAllCountries().find((c) => c.isoCode === value);
setSelectedCountry(country ?? null);
setSelectedState(null); // Reset state and city
setSelectedCity(null);
form.setValue("country", value); // Update form state
};
const handleStateChange = (value: string) => {
const state = State.getStatesOfCountry(selectedCountry?.isoCode ?? "").find(
(s) => s.isoCode === value,
);
setSelectedState(state ?? null);
setSelectedCity(null); // Reset city
form.setValue("state", value); // Update form state
};
const handleCityChange = (value: string) => {
const city = City.getCitiesOfState(
selectedState?.countryCode ?? "",
selectedState?.isoCode ?? "",
).find((c) => c.name === value);
setSelectedCity(city ?? null);
form.setValue("city", value); // Update form state
};
const countries = Country.getAllCountries();
const states = selectedCountry
? State.getStatesOfCountry(selectedCountry.isoCode)
: [];
const cities = selectedState
? City.getCitiesOfState(selectedState.countryCode, selectedState.isoCode)
: [];
return (
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="my-10 flex w-full flex-col items-start justify-center gap-y-5 px-10"
>
<h1 className="mb-5 text-center text-2xl font-bold">Create a Trip</h1>
{/* Name Field */}
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input
{...field}
placeholder="Enter trip name"
className="w-full"
/>
</FormControl>
<FormMessage>{form.formState.errors.name?.message}</FormMessage>
</FormItem>
)}
/>
{/* Country Selector */}
<FormField
control={form.control}
name="country"
render={({ field }) => (
<FormItem>
<FormLabel>Country</FormLabel>
<FormControl>
<Select value={field.value} onValueChange={handleCountryChange}>
<SelectTrigger className="w-[280px]">
<SelectValue placeholder="Select a country" />
</SelectTrigger>
<SelectContent>
{countries.map((country) => (
<SelectItem key={country.isoCode} value={country.isoCode}>
{country.name}
</SelectItem>
))}
</SelectContent>
</Select>
</FormControl>
<FormMessage>
{form.formState.errors.country?.message}
</FormMessage>
</FormItem>
)}
/>
{/* State Selector */}
<FormField
control={form.control}
name="state"
render={({ field }) => (
<FormItem>
<FormLabel>State</FormLabel>
<FormControl>
<Select
value={field.value}
onValueChange={handleStateChange}
disabled={!selectedCountry}
>
<SelectTrigger className="w-[280px]">
<SelectValue placeholder="Select a state" />
</SelectTrigger>
<SelectContent>
{states.map((state) => (
<SelectItem key={state.isoCode} value={state.isoCode}>
{state.name}
</SelectItem>
))}
</SelectContent>
</Select>
</FormControl>
<FormMessage>{form.formState.errors.state?.message}</FormMessage>
</FormItem>
)}
/>
{/* City Selector */}
<FormField
control={form.control}
name="city"
render={({ field }) => (
<FormItem>
<FormLabel>City</FormLabel>
<FormControl>
<Select
value={field.value}
onValueChange={handleCityChange}
disabled={!selectedState}
>
<SelectTrigger className="w-[280px]">
<SelectValue placeholder="Select a city" />
</SelectTrigger>
<SelectContent>
{cities.map((city) => (
<SelectItem key={city.name} value={city.name}>
{city.name}
</SelectItem>
))}
</SelectContent>
</Select>
</FormControl>
<FormMessage>{form.formState.errors.city?.message}</FormMessage>
</FormItem>
)}
/>
{/* Date Range Field */}
<FormField
control={form.control}
name="dateRange"
render={({ field }) => (
<FormItem className="flex flex-col">
<FormLabel>Date Range</FormLabel>
<DatePickerWithRange
className="w-[300px]"
onChange={(dateRange) => field.onChange(dateRange)}
value={field.value}
/>
<FormDescription>
Choose the date range for your trip.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
{/* Hotel Details Field */}
<FormField
control={form.control}
name="hotelDetails"
render={({ field }) => (
<FormItem>
<FormLabel>Hotel Details</FormLabel>
<FormControl>
<Input
{...field}
placeholder="Enter hotel details"
className="w-full"
/>
</FormControl>
<FormMessage>
{form.formState.errors.hotelDetails?.message}
</FormMessage>
</FormItem>
)}
/>
{/* Flight Number Field */}
<FormField
control={form.control}
name="flightNumber"
render={({ field }) => (
<FormItem>
<FormLabel>Flight Number</FormLabel>
<FormControl>
<Input
{...field}
placeholder="Enter flight number"
className="w-full"
/>
</FormControl>
<FormMessage>
{form.formState.errors.flightNumber?.message}
</FormMessage>
</FormItem>
)}
/>
{/* Submit Button */}
<Button type="submit" className="mt-4">
Create Trip
</Button>
</form>
</Form>
);
}
export default CreateTripForm;
我的控制台中没有任何内容,甚至没有一个错误。 请帮助我面对这个问题,这是黑客马拉松的代码,我需要快速帮助。 我使用 shadcn 表单,我的 onsubmit 没有被触发,我在单击按钮时尝试了 console.log,它可以工作,在提交时在表单上也可以工作,但 onSubmit 永远不会获取表单值
“每个表单都必须包含在 FORM 元素内。单个文档中可以有多个表单,但 FORM 元素不能嵌套”
(参见:https://www.w3.org/MarkUp/html3/forms.html#:~:text=Every%20form%20must%20be%20enheld,element%20can%27t%20be%20nested。)