feat: Newsletter system improvements and validation framework
- 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
This commit is contained in:
@@ -105,6 +105,7 @@ export default function NotificationEvents() {
|
||||
const orderEvents = eventsData?.orders || [];
|
||||
const productEvents = eventsData?.products || [];
|
||||
const customerEvents = eventsData?.customers || [];
|
||||
const marketingEvents = eventsData?.marketing || [];
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
@@ -340,6 +341,77 @@ export default function NotificationEvents() {
|
||||
</div>
|
||||
</SettingsCard>
|
||||
)}
|
||||
|
||||
{/* Marketing Events */}
|
||||
{marketingEvents.length > 0 && (
|
||||
<SettingsCard
|
||||
title={__('Marketing Events')}
|
||||
description={__('Notifications about newsletter and marketing activities')}
|
||||
>
|
||||
<div className="space-y-6">
|
||||
{marketingEvents.map((event: NotificationEvent) => (
|
||||
<div key={event.id} className="pb-6 border-b last:border-0">
|
||||
<div className="mb-4">
|
||||
<h3 className="font-medium text-sm">{event.label}</h3>
|
||||
<p className="text-xs text-muted-foreground mt-1">{event.description}</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
{channels?.map((channel: NotificationChannel) => {
|
||||
const channelEnabled = event.channels?.[channel.id]?.enabled || false;
|
||||
const recipient = event.channels?.[channel.id]?.recipient || 'admin';
|
||||
|
||||
return (
|
||||
<div
|
||||
key={channel.id}
|
||||
className="flex items-center justify-between p-3 rounded-lg border bg-card"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`p-2 rounded-lg ${channelEnabled ? 'bg-green-500/20 text-green-600' : 'bg-muted text-muted-foreground'}`}>
|
||||
{getChannelIcon(channel.id)}
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium">{channel.label}</span>
|
||||
{channel.builtin && (
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
{__('Built-in')}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
{channelEnabled && (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{__('Send to')}: {recipient === 'admin' ? __('Admin') : recipient === 'customer' ? __('Customer') : __('Both')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{channelEnabled && channel.enabled && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => openTemplateEditor(event.id, channel.id)}
|
||||
className="h-8 w-8 p-0"
|
||||
>
|
||||
<Settings className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
<Switch
|
||||
checked={channelEnabled}
|
||||
onCheckedChange={() => toggleChannel(event.id, channel.id, channelEnabled)}
|
||||
disabled={!channel.enabled || updateMutation.isPending}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</SettingsCard>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user