Skip to content

Instantly share code, notes, and snippets.

@shanksxz
Created December 20, 2024 15:54
Show Gist options
  • Select an option

  • Save shanksxz/5ed5868c9f4f84289d5e894eb90b63c0 to your computer and use it in GitHub Desktop.

Select an option

Save shanksxz/5ed5868c9f4f84289d5e894eb90b63c0 to your computer and use it in GitHub Desktop.
mutli-step
"use client"
import { useState } from 'react'
import { useForm, useFieldArray, SubmitHandler } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import { Check, ChevronRight, ChevronLeft, Plus, Trash2 } from 'lucide-react'
import { motion, AnimatePresence } from 'framer-motion'
import { Button } from "src/components/ui/button"
import { Input } from "src/components/ui/input"
import { Label } from "src/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "src/components/ui/select"
import { collaborationOptions, FormData, formSchema, stepTitles } from '@/validators'
import { toast } from 'sonner'
import { useRouter } from 'next/navigation'
const steps = [
{
id: 'Step 1',
name: 'Company Details',
fields: ['collaborationType', 'companyName', 'name', 'email', 'phoneNumber', 'companyAddress']
},
{
id: 'Step 2',
name: 'Contact Information',
fields: ['companyDescription', 'query', 'crops']
},
{
id: 'Step 3',
name: 'Thank You',
fields: []
}
]
export default function CollaboratorForm() {
const router = useRouter();
const [currentStep, setCurrentStep] = useState(0)
const {
trigger,
control,
handleSubmit,
formState: { errors },
register,
setValue,
watch,
} = useForm<FormData>({
resolver: zodResolver(formSchema),
defaultValues: {
crops: [{ cropName: "", priceRangeFrom: "", priceRangeTo: "" }],
},
mode: "onChange",
});
const { fields, append, remove } = useFieldArray({
control,
name: "crops",
});
const next = async () => {
const fields = steps[currentStep]?.fields || []
const isValid = await trigger(fields as (keyof FormData)[])
if (!isValid) {
toast.error('Please fill all the required fields');
return;
}
if (currentStep < steps.length - 1) {
if (currentStep === steps.length - 2) {
console.log('submit');
toast.success('Collaborator added successfully');
}
setCurrentStep(step => step + 1)
}
}
const prev = () => {
if (currentStep > 0) {
setCurrentStep(step => step - 1)
}
}
const onSubmit: SubmitHandler<FormData> = async (data) => {
console.log(data);
};
return (
<div className="min-h-screen font-raleway flex items-center justify-center p-4 ">
<div className="bg-white rounded-2xl shadow-2xl p-8 w-full max-w-2xl transition-all duration-300 ease-in-out transform ">
<div className="relative mb-12">
<div className="flex justify-between">
{stepTitles.map((title, index) => (
<div key={index} className="flex flex-col items-center relative z-10">
<div className={`w-12 h-12 rounded-full flex items-center justify-center ${currentStep > index ? 'bg-emerald-600 text-white' : 'bg-gray-200 text-gray-600'
} mb-2 transition-all duration-300 ease-in-out transform ${currentStep === index + 1 ? 'scale-110 ring-4 ring-emerald-200' : ''
}`}>
{currentStep > index ? <Check className="w-6 h-6" /> : index + 1}
</div>
<span className={`text-lg font-medium ${currentStep > index ? 'text-emerald-600' : 'text-gray-600'
} transition-colors duration-300`}>{title}</span>
</div>
))}
</div>
<div className="absolute top-6 left-0 w-full h-1 bg-gray-200">
<motion.div
className="h-full bg-emerald-600 rounded-full"
initial={{ width: '0%' }}
animate={{ width: `${((currentStep - 1) / (stepTitles.length - 1)) * 100}%` }}
transition={{ duration: 0.5, ease: "easeInOut" }}
/>
</div>
</div>
<AnimatePresence mode="wait">
<motion.div
key={currentStep}
initial={{ opacity: 0, y: 50 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -50 }}
transition={{ duration: 0.5, ease: "easeInOut" }}
>
<form onSubmit={handleSubmit(onSubmit)}>
{currentStep === 0 && (
<div className="grid md:grid-cols-1 mx-auto gap-8">
<div className="space-y-6">
<div className="relative">
<Select
onValueChange={(value) => setValue("collaborationType", value as FormData['collaborationType'])}
defaultValue={watch("collaborationType")}
>
<SelectTrigger className="w-full">
<SelectValue placeholder="Select Collaboration Type" />
</SelectTrigger>
<SelectContent>
{collaborationOptions.map(option => (
<SelectItem key={option} value={option}>{option}</SelectItem>
))}
</SelectContent>
</Select>
</div>
{(['companyName', 'name', 'email', 'phoneNumber', 'companyAddress'] as const).map((field) => (
<div key={field} className="space-y-2">
<Label htmlFor={field}>
{field.charAt(0).toUpperCase() + field.slice(1).replace(/([A-Z])/g, ' $1').trim()}
</Label>
<Input
id={field}
type={field === 'email' ? 'email' : field === 'phoneNumber' ? 'tel' : 'text'}
{...register(field)}
/>
{errors[field] && (
<p className="text-red-500 text-sm">{errors[field]?.message}</p>
)}
</div>
))}
<Button type="button" onClick={next} className="w-full">
Next
</Button>
</div>
</div>
)}
{currentStep === 1 && (
<div className="space-y-8">
<div className="bg-gray-50 p-6 rounded-lg shadow-inner">
<h3 className="font-semibold text-xl mb-4 text-emerald-700">Company Details</h3>
<div className="grid md:grid-cols-2 gap-4 text-sm">
{(['collaborationType', 'companyName', 'name', 'email', 'phoneNumber', 'companyAddress'] as const).map((field) => (
<p key={field} className="flex items-start">
<span className="font-medium text-gray-700 mr-2">{field.charAt(0).toUpperCase() + field.slice(1).replace(/([A-Z])/g, ' $1').trim()}:</span>
<span className="text-gray-600">{watch(field)}</span>
</p>
))}
</div>
</div>
<div className="space-y-6">
<div className="space-y-2">
<Label htmlFor="companyDescription">Describe your company</Label>
<textarea
id="companyDescription"
{...register("companyDescription")}
className="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-transparent transition-all duration-300 ease-in-out h-32 resize-none"
/>
</div>
<div className="space-y-2">
<Label htmlFor="query">Any queries?</Label>
<textarea
id="query"
{...register("query")}
className="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-transparent transition-all duration-300 ease-in-out h-24 resize-none"
/>
</div>
{watch("collaborationType") === "Stubble purchasing company" && (
<div className="space-y-4">
<h4 className="font-semibold text-lg text-emerald-700">Crop Information</h4>
{fields.map((field, index) => (
<div key={field.id} className="space-y-4 p-4 border border-gray-200 rounded-lg">
<div className="space-y-2">
<Label htmlFor={`crops.${index}.cropName`}>Crop Name</Label>
<Input
{...register(`crops.${index}.cropName` as const)}
/>
{errors.crops?.[index]?.cropName && (
<p className="text-red-500 text-sm">{errors.crops[index]?.cropName?.message}</p>
)}
</div>
<div className="flex space-x-4">
<div className="space-y-2 w-1/2">
<Label htmlFor={`crops.${index}.priceRangeFrom`}>Price Range From</Label>
<Input
type="number"
{...register(`crops.${index}.priceRangeFrom` as const)}
/>
{errors.crops?.[index]?.priceRangeFrom && (
<p className="text-red-500 text-sm">{errors.crops[index]?.priceRangeFrom?.message}</p>
)}
</div>
<div className="space-y-2 w-1/2">
<Label htmlFor={`crops.${index}.priceRangeTo`}>Price Range To</Label>
<Input
type="number"
{...register(`crops.${index}.priceRangeTo` as const)}
/>
{errors.crops?.[index]?.priceRangeTo && (
<p className="text-red-500 text-sm">{errors.crops[index]?.priceRangeTo?.message}</p>
)}
</div>
</div>
{index > 0 && (
<Button
type="button"
variant="destructive"
size="sm"
onClick={() => remove(index)}
className="mt-2"
>
<Trash2 className="w-4 h-4 mr-2" />
Remove Crop
</Button>
)}
</div>
))}
<Button type="button" onClick={() => append({ cropName: "", priceRangeFrom: "", priceRangeTo: "" })} variant="outline" className="mt-4">
<Plus className="w-4 h-4 mr-2" />
Add Another Crop
</Button>
</div>
)}
</div>
<div className="flex justify-between">
<Button type="button" onClick={prev} variant="outline">
<ChevronLeft className="w-5 h-5 mr-2" />
Back
</Button>
<Button type="button" onClick={next}>
Next
<ChevronRight className="w-5 h-5 ml-2" />
</Button>
</div>
</div>
)}
{currentStep === 2 && (
<div className="text-center space-y-8">
<div className="flex justify-center">
<div className="bg-emerald-500 rounded-full p-6 transition-all duration-300 ease-in-out transform hover:scale-110 hover:rotate-12">
<Check className="text-white w-20 h-20" />
</div>
</div>
<h2 className="text-3xl font-bold text-emerald-700">Thank You for Collaborating!</h2>
<p className="text-xl text-gray-600 max-w-md mx-auto">We're excited to work with you. We'll contact you soon to discuss the next steps and how we can grow together.</p>
</div>
)}
</form>
</motion.div>
</AnimatePresence>
</div>
</div>
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment