diff --git a/API_ROUTES.md b/API_ROUTES.md index 816176a..7e72fb3 100644 --- a/API_ROUTES.md +++ b/API_ROUTES.md @@ -69,20 +69,27 @@ DELETE /customers/{id} # Delete customer - CustomersController will own `/customers` for full customer management - No conflict because routes are specific -### Coupons Module (`CouponsController.php` - Future) +### Coupons Module (`CouponsController.php`) ✅ IMPLEMENTED ``` -GET /coupons # List coupons +GET /coupons # List coupons (with pagination, search, filter) GET /coupons/{id} # Get single coupon POST /coupons # Create coupon PUT /coupons/{id} # Update coupon DELETE /coupons/{id} # Delete coupon -GET /coupons/validate # Validate coupon code +POST /coupons/validate # Validate coupon code (OrdersController) ``` -**⚠️ Important:** -- OrdersController may need `/orders/{id}/coupons` for order-specific coupon operations -- CouponsController owns `/coupons` for coupon management -- Use sub-resources to avoid conflicts +**Implementation Details:** +- **List:** Supports pagination (`page`, `per_page`), search (`search`), filter by type (`discount_type`) +- **Create:** Validates code uniqueness, requires `code`, `amount`, `discount_type` +- **Update:** Full coupon data update, code cannot be changed after creation +- **Delete:** Supports force delete via query param +- **Validate:** Handled by OrdersController for order context + +**Note:** +- `/coupons/validate` is in OrdersController (order-specific validation) +- CouponsController owns `/coupons` for coupon CRUD management +- No conflict because validate is a specific action route ### Settings Module (`SettingsController.php`) ``` diff --git a/admin-spa/src/routes/Coupons/CouponForm.tsx b/admin-spa/src/routes/Coupons/CouponForm.tsx new file mode 100644 index 0000000..ba7ada6 --- /dev/null +++ b/admin-spa/src/routes/Coupons/CouponForm.tsx @@ -0,0 +1,303 @@ +import React, { useState } from 'react'; +import { __ } from '@/lib/i18n'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Textarea } from '@/components/ui/textarea'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Checkbox } from '@/components/ui/checkbox'; +import { Button } from '@/components/ui/button'; +import type { Coupon, CouponFormData } from '@/lib/api/coupons'; + +interface CouponFormProps { + mode: 'create' | 'edit'; + initial?: Coupon | null; + onSubmit: (data: CouponFormData) => Promise | void; + formRef?: React.RefObject; + hideSubmitButton?: boolean; +} + +export default function CouponForm({ + mode, + initial, + onSubmit, + formRef, + hideSubmitButton = false, +}: CouponFormProps) { + const [formData, setFormData] = useState({ + code: initial?.code || '', + amount: initial?.amount || 0, + discount_type: initial?.discount_type || 'percent', + description: initial?.description || '', + date_expires: initial?.date_expires || null, + individual_use: initial?.individual_use || false, + usage_limit: initial?.usage_limit || null, + usage_limit_per_user: initial?.usage_limit_per_user || null, + free_shipping: initial?.free_shipping || false, + exclude_sale_items: initial?.exclude_sale_items || false, + minimum_amount: initial?.minimum_amount || null, + maximum_amount: initial?.maximum_amount || null, + }); + + const [submitting, setSubmitting] = useState(false); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setSubmitting(true); + try { + await onSubmit(formData); + } finally { + setSubmitting(false); + } + }; + + const updateField = (field: keyof CouponFormData, value: any) => { + setFormData(prev => ({ ...prev, [field]: value })); + }; + + return ( +
+ {/* General Settings */} + + + {__('General')} + + + {/* Coupon Code */} +
+ + updateField('code', e.target.value.toUpperCase())} + placeholder={__('e.g., SUMMER2024')} + required + disabled={mode === 'edit'} // Can't change code after creation + /> +

+ {__('Unique code that customers will enter at checkout')} +

+
+ + {/* Description */} +
+ +