- Fix: Marketing events now display in Staff notifications tab - Reorganize: Move Coupons to Marketing/Coupons for better organization - Add: Comprehensive email/phone validation with extensible filter hooks - Email validation with regex pattern (xxxx@xxxx.xx) - Phone validation with WhatsApp verification support - Filter hooks for external API integration (QuickEmailVerification, etc.) - Fix: Newsletter template routes now use centralized notification email builder - Add: Validation.php class for reusable validation logic - Add: VALIDATION_HOOKS.md documentation with integration examples - Add: NEWSLETTER_CAMPAIGN_PLAN.md architecture for future campaign system - Fix: API delete method call in Newsletter.tsx (delete -> del) - Remove: Duplicate EmailTemplates.tsx (using notification system instead) - Update: Newsletter controller to use centralized Validation class Breaking changes: - Coupons routes moved from /routes/Coupons to /routes/Marketing/Coupons - Legacy /coupons routes maintained for backward compatibility
114 lines
3.5 KiB
TypeScript
114 lines
3.5 KiB
TypeScript
import React, { useRef, useEffect } from 'react';
|
|
import { useNavigate, useParams } from 'react-router-dom';
|
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
import { __ } from '@/lib/i18n';
|
|
import { CouponsApi, type CouponFormData } from '@/lib/api/coupons';
|
|
import { showErrorToast, showSuccessToast, getPageLoadErrorMessage } from '@/lib/errorHandling';
|
|
import { ErrorCard } from '@/components/ErrorCard';
|
|
import { LoadingState } from '@/components/LoadingState';
|
|
import { usePageHeader } from '@/contexts/PageHeaderContext';
|
|
import { Button } from '@/components/ui/button';
|
|
import { useFABConfig } from '@/hooks/useFABConfig';
|
|
import CouponForm from './CouponForm';
|
|
|
|
export default function CouponEdit() {
|
|
const { id } = useParams<{ id: string }>();
|
|
const couponId = Number(id);
|
|
const navigate = useNavigate();
|
|
const queryClient = useQueryClient();
|
|
const formRef = useRef<HTMLFormElement>(null);
|
|
const { setPageHeader, clearPageHeader } = usePageHeader();
|
|
|
|
// Hide FAB on edit page
|
|
useFABConfig('none');
|
|
|
|
// Fetch coupon
|
|
const { data: coupon, isLoading, isError, error } = useQuery({
|
|
queryKey: ['coupon', couponId],
|
|
queryFn: () => CouponsApi.get(couponId),
|
|
enabled: !!couponId,
|
|
});
|
|
|
|
// Update mutation
|
|
const updateMutation = useMutation({
|
|
mutationFn: (data: CouponFormData) => CouponsApi.update(couponId, data),
|
|
onSuccess: (updatedCoupon) => {
|
|
queryClient.invalidateQueries({ queryKey: ['coupons'] });
|
|
queryClient.invalidateQueries({ queryKey: ['coupon', couponId] });
|
|
showSuccessToast(__('Coupon updated successfully'), `${__('Coupon')} ${updatedCoupon.code} ${__('updated')}`);
|
|
navigate('/coupons');
|
|
},
|
|
onError: (error) => {
|
|
showErrorToast(error);
|
|
},
|
|
});
|
|
|
|
// Smart back handler: go back in history if available, otherwise fallback to /coupons
|
|
const handleBack = () => {
|
|
if (window.history.state?.idx > 0) {
|
|
navigate(-1); // Go back in history
|
|
} else {
|
|
navigate('/coupons'); // Fallback to coupons index
|
|
}
|
|
};
|
|
|
|
// Set contextual header
|
|
useEffect(() => {
|
|
const actions = (
|
|
<div className="flex gap-2">
|
|
<Button size="sm" variant="ghost" onClick={handleBack}>
|
|
{__('Back')}
|
|
</Button>
|
|
<Button
|
|
size="sm"
|
|
onClick={() => formRef.current?.requestSubmit()}
|
|
disabled={updateMutation.isPending || isLoading}
|
|
>
|
|
{updateMutation.isPending ? __('Saving...') : __('Save')}
|
|
</Button>
|
|
</div>
|
|
);
|
|
|
|
const title = coupon ? `${__('Edit Coupon')}: ${coupon.code}` : __('Edit Coupon');
|
|
setPageHeader(title, actions);
|
|
return () => clearPageHeader();
|
|
}, [coupon, updateMutation.isPending, isLoading, setPageHeader, clearPageHeader, navigate]);
|
|
|
|
if (isLoading) {
|
|
return <LoadingState message={__('Loading coupon...')} />;
|
|
}
|
|
|
|
if (isError) {
|
|
return (
|
|
<ErrorCard
|
|
title={__('Failed to load coupon')}
|
|
message={getPageLoadErrorMessage(error)}
|
|
onRetry={() => queryClient.invalidateQueries({ queryKey: ['coupon', couponId] })}
|
|
/>
|
|
);
|
|
}
|
|
|
|
if (!coupon) {
|
|
return (
|
|
<ErrorCard
|
|
title={__('Coupon not found')}
|
|
message={__('The requested coupon could not be found')}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
<CouponForm
|
|
mode="edit"
|
|
initial={coupon}
|
|
onSubmit={async (data) => {
|
|
await updateMutation.mutateAsync(data);
|
|
}}
|
|
formRef={formRef}
|
|
hideSubmitButton={true}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|