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:
Dwindi Ramadhana
2025-12-26 10:59:48 +07:00
parent 0b08ddefa1
commit 0b2c8a56d6
23 changed files with 1132 additions and 232 deletions

View File

@@ -50,41 +50,21 @@ export default function Addresses() {
const loadAddresses = async () => {
try {
const response: any = await api.get('/account/addresses');
console.log('API response:', response);
console.log('Type of response:', typeof response);
console.log('Is array:', Array.isArray(response));
console.log('Response keys:', response ? Object.keys(response) : 'null');
console.log('Response values:', response ? Object.values(response) : 'null');
// Handle different response structures
let data: Address[] = [];
if (Array.isArray(response)) {
// Direct array response
data = response;
console.log('Using direct array');
} else if (response && typeof response === 'object') {
// Log all properties to debug
console.log('Checking object properties...');
// Check common wrapper properties
if (Array.isArray(response.data)) {
data = response.data;
console.log('Using response.data');
} else if (Array.isArray(response.addresses)) {
data = response.addresses;
console.log('Using response.addresses');
} else if (response.length !== undefined && typeof response === 'object') {
// Might be array-like object, convert to array
data = Object.values(response).filter((item: any) => item && typeof item === 'object' && item.id) as Address[];
console.log('Converted object to array:', data);
} else {
console.error('API returned unexpected structure:', response);
console.error('Available keys:', Object.keys(response));
}
}
console.log('Final addresses array:', data);
setAddresses(data);
} catch (error) {
console.error('Load addresses error:', error);
@@ -124,16 +104,11 @@ export default function Addresses() {
const handleSave = async () => {
try {
console.log('Saving address:', formData);
if (editingAddress) {
console.log('Updating address ID:', editingAddress.id);
const response = await api.put(`/account/addresses/${editingAddress.id}`, formData);
console.log('Update response:', response);
await api.put(`/account/addresses/${editingAddress.id}`, formData);
toast.success('Address updated successfully');
} else {
console.log('Creating new address');
const response = await api.post('/account/addresses', formData);
console.log('Create response:', response);
await api.post('/account/addresses', formData);
toast.success('Address added successfully');
}
setShowModal(false);