'use client'; import { useState, useEffect } from 'react'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; import { useForm } from 'react-hook-form'; import { 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 { Promotion, PromotionFormData } from '@/lib/types/promotion'; import { fetchClient } from '@/lib/api'; import ProductSelector from './ProductSelector'; // Form schema validation with Zod (same as NewPromotionForm) 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 EditPromotionFormProps { promotion: Promotion; onSuccess: () => void; onCancel: () => void; } export default function EditPromotionForm({ promotion, onSuccess, onCancel }: EditPromotionFormProps) { const [isSubmitting, setIsSubmitting] = useState(false); const [discountType, setDiscountType] = useState<'percentage' | 'fixed'>(promotion.discountType); // Format dates from ISO to YYYY-MM-DD for input elements const formatDateForInput = (dateString: string | null) => { if (!dateString) return ''; return new Date(dateString).toISOString().split('T')[0]; }; // Initialize form with promotion values const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { code: promotion.code, discountType: promotion.discountType, discountValue: promotion.discountValue, minOrderAmount: promotion.minOrderAmount, maxUsage: promotion.maxUsage, isActive: promotion.isActive, description: promotion.description, startDate: formatDateForInput(promotion.startDate), endDate: formatDateForInput(promotion.endDate), blacklistedProducts: promotion.blacklistedProducts || [], applicableProducts: promotion.applicableProducts || 'all', specificProducts: promotion.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/${promotion._id}`, { method: 'PUT', body: data, }); toast({ title: 'Success', description: 'Promotion updated successfully', }); onSuccess(); } catch (error) { console.error('Error updating 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 (currently used: {promotion.usageCount} times) )} />
( Start Date )} /> ( End Date (Optional) { const value = e.target.value; field.onChange(value === '' ? null : value); }} /> 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)