'use client'; import { useState, useEffect } from 'react'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; import { useForm } from 'react-hook-form'; import { Plus, Save, X, Loader2 } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from '@/components/ui/form'; import { Input } from '@/components/ui/input'; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { Switch } from '@/components/ui/switch'; import { Textarea } from '@/components/ui/textarea'; import { toast } from '@/components/ui/use-toast'; import { PromotionFormData } from '@/lib/types/promotion'; import { fetchClient } from '@/lib/api'; import { DatePicker } from '@/components/ui/date-picker'; import ProductSelector from './ProductSelector'; // Form schema validation with Zod const formSchema = z.object({ code: z.string() .min(3, 'Code must be at least 3 characters') .max(20, 'Code cannot exceed 20 characters') .regex(/^[A-Za-z0-9]+$/, 'Only letters and numbers are allowed'), discountType: z.enum(['percentage', 'fixed']), discountValue: z.coerce.number() .positive('Discount value must be positive') .refine(val => val <= 100, { message: 'Percentage discount cannot exceed 100%', path: ['discountValue'], // Only validate this rule if discount type is percentage params: { type: 'percentage' }, }), minOrderAmount: z.coerce.number() .min(0, 'Minimum order amount cannot be negative'), maxUsage: z.coerce.number() .nullable() .optional(), isActive: z.boolean().default(true), startDate: z.string().optional(), endDate: z.string().nullable().optional(), description: z.string().max(200, 'Description cannot exceed 200 characters').optional(), blacklistedProducts: z.array(z.string()).default([]), applicableProducts: z.enum(['all', 'specific', 'exclude_specific']).default('all'), specificProducts: z.array(z.string()).default([]), }); interface NewPromotionFormProps { onSuccess: () => void; onCancel: () => void; } export default function NewPromotionForm({ onSuccess, onCancel }: NewPromotionFormProps) { const [isSubmitting, setIsSubmitting] = useState(false); const [discountType, setDiscountType] = useState<'percentage' | 'fixed'>('percentage'); // Initialize form with default values const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { code: '', discountType: 'percentage', discountValue: 10, minOrderAmount: 0, maxUsage: null, isActive: true, description: '', startDate: new Date().toISOString().split('T')[0], // Today endDate: null, blacklistedProducts: [], applicableProducts: 'all', specificProducts: [], }, }); // Keep local state in sync with form useEffect(() => { const subscription = form.watch((value, { name }) => { if (name === 'discountType') { setDiscountType(value.discountType as 'percentage' | 'fixed'); } }); return () => subscription.unsubscribe(); }, [form, form.watch]); // Form submission handler async function onSubmit(data: z.infer) { setIsSubmitting(true); try { await fetchClient('/promotions', { method: 'POST', body: data, }); toast({ title: 'Success', description: 'Promotion created successfully', }); onSuccess(); } catch (error) { console.error('Error creating promotion:', error); // Error toast is already shown by fetchClient } finally { setIsSubmitting(false); } } return (
( Promotion Code field.onChange(e.target.value.toUpperCase())} className="h-10" /> Enter a unique code for your promotion. Only letters and numbers. )} />
( Discount Type { field.onChange(value); setDiscountType(value as 'percentage' | 'fixed'); }} value={field.value} className="flex flex-col space-y-2" >
)} /> ( Discount Value {discountType === 'percentage' ? 'Enter a percentage (1-100%)' : 'Enter an amount in £'} )} /> ( Minimum Order Amount (£) Minimum purchase required )} /> ( Maximum Usage Count { const value = e.target.value; field.onChange(value === '' ? null : parseInt(value, 10)); }} /> Leave empty for unlimited usage )} />
( Start Date field.onChange(date ? date.toISOString().split('T')[0] : '')} placeholder="Select start date" className="h-10" /> )} /> ( End Date (Optional) field.onChange(date ? date.toISOString().split('T')[0] : null)} placeholder="Select end date (optional)" className="h-10" /> Leave empty for no expiration )} />
(
Active Status Enable or disable this promotion
)} /> {/* Product Applicability Section */}

Product Applicability

( Apply Promotion To
)} /> {/* Show blacklist selector for "all" and "exclude_specific" modes */} {(form.watch('applicableProducts') === 'all' || form.watch('applicableProducts') === 'exclude_specific') && ( ( {form.watch('applicableProducts') === 'all' ? 'Exclude Products (Blacklist)' : 'Products to Exclude'} {form.watch('applicableProducts') === 'all' ? 'Select products that should not be eligible for this promotion' : 'Select products to exclude in addition to those selected above'} )} /> )} {/* Show specific products selector for "specific" and "exclude_specific" modes */} {(form.watch('applicableProducts') === 'specific' || form.watch('applicableProducts') === 'exclude_specific') && ( ( {form.watch('applicableProducts') === 'specific' ? 'Select Specific Products' : 'Products to Exclude'} {form.watch('applicableProducts') === 'specific' ? 'Only selected products will be eligible for this promotion' : 'Selected products will be excluded from this promotion'} )} /> )}
( Description (Optional)