cluster fuck

This commit is contained in:
NotII
2025-03-08 05:14:50 +00:00
parent b8e6e1dfc0
commit 8ea692eda2
3 changed files with 144 additions and 117 deletions

View File

@@ -16,13 +16,7 @@ import {
FormMessage, FormMessage,
} from '@/components/ui/form'; } from '@/components/ui/form';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue
} from '@/components/ui/select';
import { Switch } from '@/components/ui/switch'; import { Switch } from '@/components/ui/switch';
import { Textarea } from '@/components/ui/textarea'; import { Textarea } from '@/components/ui/textarea';
import { toast } from '@/components/ui/use-toast'; import { toast } from '@/components/ui/use-toast';
@@ -63,6 +57,7 @@ interface EditPromotionFormProps {
export default function EditPromotionForm({ promotion, onSuccess, onCancel }: EditPromotionFormProps) { export default function EditPromotionForm({ promotion, onSuccess, onCancel }: EditPromotionFormProps) {
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const [discountType, setDiscountType] = useState<'percentage' | 'fixed'>(promotion.discountType);
// Format dates from ISO to YYYY-MM-DD for input elements // Format dates from ISO to YYYY-MM-DD for input elements
const formatDateForInput = (dateString: string | null) => { const formatDateForInput = (dateString: string | null) => {
@@ -86,6 +81,16 @@ export default function EditPromotionForm({ promotion, onSuccess, onCancel }: Ed
}, },
}); });
// 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 // Form submission handler
async function onSubmit(data: z.infer<typeof formSchema>) { async function onSubmit(data: z.infer<typeof formSchema>) {
setIsSubmitting(true); setIsSubmitting(true);
@@ -112,22 +117,23 @@ export default function EditPromotionForm({ promotion, onSuccess, onCancel }: Ed
return ( return (
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6"> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<div className="grid grid-cols-1 gap-4"> <div className="grid grid-cols-1 gap-6">
<FormField <FormField
control={form.control} control={form.control}
name="code" name="code"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Promotion Code</FormLabel> <FormLabel className="text-sm font-medium">Promotion Code</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder="SUMMER20" placeholder="SUMMER20"
{...field} {...field}
onChange={(e) => field.onChange(e.target.value.toUpperCase())} onChange={(e) => field.onChange(e.target.value.toUpperCase())}
className="h-10"
/> />
</FormControl> </FormControl>
<FormDescription> <FormDescription className="text-xs">
Enter a unique code for your promotion. Only letters and numbers. Enter a unique code for your promotion. Only letters and numbers.
</FormDescription> </FormDescription>
<FormMessage /> <FormMessage />
@@ -136,33 +142,32 @@ export default function EditPromotionForm({ promotion, onSuccess, onCancel }: Ed
/> />
</div> </div>
<div className="grid grid-cols-3 gap-4"> <div className="grid grid-cols-4 gap-6">
<FormField <FormField
control={form.control} control={form.control}
name="discountType" name="discountType"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem className="col-span-1">
<FormLabel>Discount Type</FormLabel> <FormLabel className="text-sm font-medium">Discount Type</FormLabel>
<Select <FormControl>
onValueChange={(value) => { <RadioGroup
console.log("Selected discount type:", value); onValueChange={(value) => {
field.onChange(value); field.onChange(value);
}} setDiscountType(value as 'percentage' | 'fixed');
value={field.value} }}
> value={field.value}
<FormControl> className="flex flex-col space-y-2"
<SelectTrigger> >
<SelectValue placeholder="Select a discount type" /> <div className="flex items-center space-x-2">
</SelectTrigger> <RadioGroupItem value="percentage" id="edit-percentage" />
</FormControl> <label htmlFor="edit-percentage" className="cursor-pointer">Percentage (%)</label>
<SelectContent> </div>
<SelectItem value="percentage">Percentage (%)</SelectItem> <div className="flex items-center space-x-2">
<SelectItem value="fixed">Fixed Amount (£)</SelectItem> <RadioGroupItem value="fixed" id="edit-fixed" />
</SelectContent> <label htmlFor="edit-fixed" className="cursor-pointer">Fixed Amount (£)</label>
</Select> </div>
<FormDescription> </RadioGroup>
Choose between percentage or fixed amount discount </FormControl>
</FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
@@ -172,19 +177,20 @@ export default function EditPromotionForm({ promotion, onSuccess, onCancel }: Ed
control={form.control} control={form.control}
name="discountValue" name="discountValue"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem className="col-span-1">
<FormLabel>Discount Value</FormLabel> <FormLabel className="text-sm font-medium">Discount Value</FormLabel>
<FormControl> <FormControl>
<Input <Input
type="number" type="number"
step={form.watch('discountType') === 'percentage' ? '1' : '0.01'} step={discountType === 'percentage' ? '1' : '0.01'}
min={0} min={0}
max={form.watch('discountType') === 'percentage' ? 100 : undefined} max={discountType === 'percentage' ? 100 : undefined}
className="h-10"
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription> <FormDescription className="text-xs">
{form.watch('discountType') === 'percentage' {discountType === 'percentage'
? 'Enter a percentage (1-100%)' ? 'Enter a percentage (1-100%)'
: 'Enter an amount in £'} : 'Enter an amount in £'}
</FormDescription> </FormDescription>
@@ -197,32 +203,31 @@ export default function EditPromotionForm({ promotion, onSuccess, onCancel }: Ed
control={form.control} control={form.control}
name="minOrderAmount" name="minOrderAmount"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem className="col-span-1">
<FormLabel>Minimum Order Amount (£)</FormLabel> <FormLabel className="text-sm font-medium">Minimum Order Amount (£)</FormLabel>
<FormControl> <FormControl>
<Input type="number" step="0.01" min="0" {...field} /> <Input type="number" step="0.01" min="0" className="h-10" {...field} />
</FormControl> </FormControl>
<FormDescription> <FormDescription className="text-xs">
Minimum purchase required Minimum purchase required
</FormDescription> </FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
</div>
<div className="grid grid-cols-3 gap-4">
<FormField <FormField
control={form.control} control={form.control}
name="maxUsage" name="maxUsage"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem className="col-span-1">
<FormLabel>Maximum Usage Count</FormLabel> <FormLabel className="text-sm font-medium">Maximum Usage Count</FormLabel>
<FormControl> <FormControl>
<Input <Input
type="number" type="number"
min="1" min="1"
placeholder="Unlimited" placeholder="Unlimited"
className="h-10"
{...field} {...field}
value={field.value === null ? '' : field.value} value={field.value === null ? '' : field.value}
onChange={(e) => { onChange={(e) => {
@@ -231,22 +236,24 @@ export default function EditPromotionForm({ promotion, onSuccess, onCancel }: Ed
}} }}
/> />
</FormControl> </FormControl>
<FormDescription> <FormDescription className="text-xs">
Leave empty for unlimited usage (currently used: {promotion.usageCount} times) Leave empty for unlimited usage (currently used: {promotion.usageCount} times)
</FormDescription> </FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
</div>
<div className="grid grid-cols-2 gap-6">
<FormField <FormField
control={form.control} control={form.control}
name="startDate" name="startDate"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Start Date</FormLabel> <FormLabel className="text-sm font-medium">Start Date</FormLabel>
<FormControl> <FormControl>
<Input type="date" {...field} /> <Input type="date" className="h-10" {...field} />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@@ -258,10 +265,11 @@ export default function EditPromotionForm({ promotion, onSuccess, onCancel }: Ed
name="endDate" name="endDate"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>End Date (Optional)</FormLabel> <FormLabel className="text-sm font-medium">End Date (Optional)</FormLabel>
<FormControl> <FormControl>
<Input <Input
type="date" type="date"
className="h-10"
{...field} {...field}
value={field.value || ''} value={field.value || ''}
onChange={(e) => { onChange={(e) => {
@@ -270,7 +278,7 @@ export default function EditPromotionForm({ promotion, onSuccess, onCancel }: Ed
}} }}
/> />
</FormControl> </FormControl>
<FormDescription> <FormDescription className="text-xs">
Leave empty for no expiration Leave empty for no expiration
</FormDescription> </FormDescription>
<FormMessage /> <FormMessage />

View File

@@ -1,6 +1,6 @@
'use client'; 'use client';
import { useState } from 'react'; import { useState, useEffect } from 'react';
import { z } from 'zod'; import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
@@ -25,6 +25,7 @@ import {
} from '@/components/ui/select'; } from '@/components/ui/select';
import { Switch } from '@/components/ui/switch'; import { Switch } from '@/components/ui/switch';
import { Textarea } from '@/components/ui/textarea'; import { Textarea } from '@/components/ui/textarea';
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
import { toast } from '@/components/ui/use-toast'; import { toast } from '@/components/ui/use-toast';
import { PromotionFormData } from '@/lib/types/promotion'; import { PromotionFormData } from '@/lib/types/promotion';
import { fetchClient } from '@/lib/client-service'; import { fetchClient } from '@/lib/client-service';
@@ -62,6 +63,7 @@ interface NewPromotionFormProps {
export default function NewPromotionForm({ onSuccess, onCancel }: NewPromotionFormProps) { export default function NewPromotionForm({ onSuccess, onCancel }: NewPromotionFormProps) {
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const [discountType, setDiscountType] = useState<'percentage' | 'fixed'>('percentage');
// Initialize form with default values // Initialize form with default values
const form = useForm<z.infer<typeof formSchema>>({ const form = useForm<z.infer<typeof formSchema>>({
@@ -79,6 +81,16 @@ export default function NewPromotionForm({ onSuccess, onCancel }: NewPromotionFo
}, },
}); });
// 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 // Form submission handler
async function onSubmit(data: z.infer<typeof formSchema>) { async function onSubmit(data: z.infer<typeof formSchema>) {
setIsSubmitting(true); setIsSubmitting(true);
@@ -105,22 +117,23 @@ export default function NewPromotionForm({ onSuccess, onCancel }: NewPromotionFo
return ( return (
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6"> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<div className="grid grid-cols-1 gap-4"> <div className="grid grid-cols-1 gap-6">
<FormField <FormField
control={form.control} control={form.control}
name="code" name="code"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Promotion Code</FormLabel> <FormLabel className="text-sm font-medium">Promotion Code</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder="SUMMER20" placeholder="SUMMER20"
{...field} {...field}
onChange={(e) => field.onChange(e.target.value.toUpperCase())} onChange={(e) => field.onChange(e.target.value.toUpperCase())}
className="h-10"
/> />
</FormControl> </FormControl>
<FormDescription> <FormDescription className="text-xs">
Enter a unique code for your promotion. Only letters and numbers. Enter a unique code for your promotion. Only letters and numbers.
</FormDescription> </FormDescription>
<FormMessage /> <FormMessage />
@@ -129,33 +142,32 @@ export default function NewPromotionForm({ onSuccess, onCancel }: NewPromotionFo
/> />
</div> </div>
<div className="grid grid-cols-3 gap-4"> <div className="grid grid-cols-4 gap-6">
<FormField <FormField
control={form.control} control={form.control}
name="discountType" name="discountType"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem className="col-span-1">
<FormLabel>Discount Type</FormLabel> <FormLabel className="text-sm font-medium">Discount Type</FormLabel>
<Select <FormControl>
onValueChange={(value) => { <RadioGroup
console.log("Selected discount type:", value); onValueChange={(value) => {
field.onChange(value); field.onChange(value);
}} setDiscountType(value as 'percentage' | 'fixed');
value={field.value} }}
> value={field.value}
<FormControl> className="flex flex-col space-y-2"
<SelectTrigger> >
<SelectValue placeholder="Select a discount type" /> <div className="flex items-center space-x-2">
</SelectTrigger> <RadioGroupItem value="percentage" id="percentage" />
</FormControl> <label htmlFor="percentage" className="cursor-pointer">Percentage (%)</label>
<SelectContent> </div>
<SelectItem value="percentage">Percentage (%)</SelectItem> <div className="flex items-center space-x-2">
<SelectItem value="fixed">Fixed Amount (£)</SelectItem> <RadioGroupItem value="fixed" id="fixed" />
</SelectContent> <label htmlFor="fixed" className="cursor-pointer">Fixed Amount (£)</label>
</Select> </div>
<FormDescription> </RadioGroup>
Choose between percentage or fixed amount discount </FormControl>
</FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
@@ -165,19 +177,20 @@ export default function NewPromotionForm({ onSuccess, onCancel }: NewPromotionFo
control={form.control} control={form.control}
name="discountValue" name="discountValue"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem className="col-span-1">
<FormLabel>Discount Value</FormLabel> <FormLabel className="text-sm font-medium">Discount Value</FormLabel>
<FormControl> <FormControl>
<Input <Input
type="number" type="number"
step={form.watch('discountType') === 'percentage' ? '1' : '0.01'} step={discountType === 'percentage' ? '1' : '0.01'}
min={0} min={0}
max={form.watch('discountType') === 'percentage' ? 100 : undefined} max={discountType === 'percentage' ? 100 : undefined}
className="h-10"
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription> <FormDescription className="text-xs">
{form.watch('discountType') === 'percentage' {discountType === 'percentage'
? 'Enter a percentage (1-100%)' ? 'Enter a percentage (1-100%)'
: 'Enter an amount in £'} : 'Enter an amount in £'}
</FormDescription> </FormDescription>
@@ -190,32 +203,31 @@ export default function NewPromotionForm({ onSuccess, onCancel }: NewPromotionFo
control={form.control} control={form.control}
name="minOrderAmount" name="minOrderAmount"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem className="col-span-1">
<FormLabel>Minimum Order Amount (£)</FormLabel> <FormLabel className="text-sm font-medium">Minimum Order Amount (£)</FormLabel>
<FormControl> <FormControl>
<Input type="number" step="0.01" min="0" {...field} /> <Input type="number" step="0.01" min="0" className="h-10" {...field} />
</FormControl> </FormControl>
<FormDescription> <FormDescription className="text-xs">
Minimum purchase required Minimum purchase required
</FormDescription> </FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
</div>
<div className="grid grid-cols-3 gap-4">
<FormField <FormField
control={form.control} control={form.control}
name="maxUsage" name="maxUsage"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem className="col-span-1">
<FormLabel>Maximum Usage Count</FormLabel> <FormLabel className="text-sm font-medium">Maximum Usage Count</FormLabel>
<FormControl> <FormControl>
<Input <Input
type="number" type="number"
min="1" min="1"
placeholder="Unlimited" placeholder="Unlimited"
className="h-10"
{...field} {...field}
value={field.value === null ? '' : field.value} value={field.value === null ? '' : field.value}
onChange={(e) => { onChange={(e) => {
@@ -224,22 +236,24 @@ export default function NewPromotionForm({ onSuccess, onCancel }: NewPromotionFo
}} }}
/> />
</FormControl> </FormControl>
<FormDescription> <FormDescription className="text-xs">
Leave empty for unlimited usage Leave empty for unlimited usage
</FormDescription> </FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
</div>
<div className="grid grid-cols-2 gap-6">
<FormField <FormField
control={form.control} control={form.control}
name="startDate" name="startDate"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Start Date</FormLabel> <FormLabel className="text-sm font-medium">Start Date</FormLabel>
<FormControl> <FormControl>
<Input type="date" {...field} /> <Input type="date" className="h-10" {...field} />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@@ -251,10 +265,11 @@ export default function NewPromotionForm({ onSuccess, onCancel }: NewPromotionFo
name="endDate" name="endDate"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>End Date (Optional)</FormLabel> <FormLabel className="text-sm font-medium">End Date (Optional)</FormLabel>
<FormControl> <FormControl>
<Input <Input
type="date" type="date"
className="h-10"
{...field} {...field}
value={field.value || ''} value={field.value || ''}
onChange={(e) => { onChange={(e) => {
@@ -263,7 +278,7 @@ export default function NewPromotionForm({ onSuccess, onCancel }: NewPromotionFo
}} }}
/> />
</FormControl> </FormControl>
<FormDescription> <FormDescription className="text-xs">
Leave empty for no expiration Leave empty for no expiration
</FormDescription> </FormDescription>
<FormMessage /> <FormMessage />

View File

@@ -224,33 +224,37 @@ export default function PromotionsList() {
{/* New Promotion Dialog */} {/* New Promotion Dialog */}
<Dialog open={showNewDialog} onOpenChange={setShowNewDialog}> <Dialog open={showNewDialog} onOpenChange={setShowNewDialog}>
<DialogContent className="max-w-2xl"> <DialogContent className="max-w-4xl">
<DialogHeader> <DialogHeader className="pb-4">
<DialogTitle>Create New Promotion</DialogTitle> <DialogTitle className="text-xl">Create New Promotion</DialogTitle>
<DialogDescription> <DialogDescription>
Add a promotional code to offer discounts to your customers. Add a promotional code to offer discounts to your customers.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<NewPromotionForm onSuccess={handleCreateComplete} onCancel={() => setShowNewDialog(false)} /> <div className="max-h-[70vh] overflow-y-auto pr-1">
<NewPromotionForm onSuccess={handleCreateComplete} onCancel={() => setShowNewDialog(false)} />
</div>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
{/* Edit Promotion Dialog */} {/* Edit Promotion Dialog */}
<Dialog open={!!editingPromotion} onOpenChange={() => editingPromotion && handleCloseEditDialog()}> <Dialog open={!!editingPromotion} onOpenChange={() => editingPromotion && handleCloseEditDialog()}>
<DialogContent className="max-w-2xl"> <DialogContent className="max-w-4xl">
<DialogHeader> <DialogHeader className="pb-4">
<DialogTitle>Edit Promotion</DialogTitle> <DialogTitle className="text-xl">Edit Promotion</DialogTitle>
<DialogDescription> <DialogDescription>
Modify this promotional code's details. Modify this promotional code's details.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
{editingPromotion && ( <div className="max-h-[70vh] overflow-y-auto pr-1">
<EditPromotionForm {editingPromotion && (
promotion={editingPromotion} <EditPromotionForm
onSuccess={handleEditComplete} promotion={editingPromotion}
onCancel={handleCloseEditDialog} onSuccess={handleEditComplete}
/> onCancel={handleCloseEditDialog}
)} />
)}
</div>
</DialogContent> </DialogContent>
</Dialog> </Dialog>