fix: Critical data structure and mutation bugs

## 🐛 Critical Fixes

### Issue 1: Toggling One Channel Affects Both
**Problem:** Disabling email disabled both email and push
**Root Cause:** Optimistic update with `onSettled` refetch caused race condition
**Fix:** Removed optimistic update, use server response directly

**Before:**
```ts
onMutate: async () => {
  // Optimistic update
  queryClient.setQueryData(...)
}
onSettled: () => {
  // This refetch caused race condition
  queryClient.invalidateQueries(...)
}
```

**After:**
```ts
onSuccess: (data, variables) => {
  // Update cache with verified server response
  queryClient.setQueryData([...], (old) =>
    old.map(channel =>
      channel.id === variables.channelId
        ? { ...channel, enabled: data.enabled }
        : channel
    )
  );
}
```

### Issue 2: Events Cannot Be Enabled
**Problem:** All event channels disabled and cannot be enabled
**Root Cause:** Wrong data structure in `update_event()`

**Before:**
```php
$settings[$event_id][$channel_id] = [...];
// Saved as: { "order_placed": { "email": {...} } }
```

**After:**
```php
$settings[$event_id]['channels'][$channel_id] = [...];
// Saves as: { "order_placed": { "channels": { "email": {...} } } }
```

### Issue 3: POST Data Not Parsed
**Problem:** Event updates not working
**Root Cause:** Using `get_param()` instead of `get_json_params()`
**Fix:** Changed to `get_json_params()` in `update_event()`

### What Was Fixed

1.  Channel toggles work independently
2.  No race conditions from optimistic updates
3.  Event channel data structure matches get_events
4.  Event toggles save correctly
5.  POST data parsed properly
6.  Boolean type enforcement

### Data Structure

**Correct Structure:**
```php
[
  'order_placed' => [
    'channels' => [
      'email' => ['enabled' => true, 'recipient' => 'admin'],
      'push' => ['enabled' => false, 'recipient' => 'admin']
    ]
  ]
]
```

---

**All toggles should now work correctly!** 
This commit is contained in:
dwindown
2025-11-11 16:05:21 +07:00
parent a9ff8e2cea
commit 3ef5087f09
2 changed files with 23 additions and 31 deletions

View File

@@ -47,38 +47,25 @@ export default function NotificationChannels() {
// Toggle channel mutation
const toggleChannelMutation = useMutation({
mutationFn: async ({ channelId, enabled }: { channelId: string; enabled: boolean }) => {
return api.post('/notifications/channels/toggle', { channelId, enabled });
const response = await api.post('/notifications/channels/toggle', { channelId, enabled });
return response;
},
onMutate: async ({ channelId, enabled }) => {
// Cancel outgoing refetches
await queryClient.cancelQueries({ queryKey: ['notification-channels'] });
// Snapshot previous value
const previousChannels = queryClient.getQueryData(['notification-channels']);
// Optimistically update
onSuccess: (data, variables) => {
// Update cache with server response
queryClient.setQueryData(['notification-channels'], (old: any) => {
if (!old) return old;
return old.map((channel: any) =>
channel.id === channelId ? { ...channel, enabled } : channel
channel.id === variables.channelId
? { ...channel, enabled: data.enabled }
: channel
);
});
return { previousChannels };
},
onSuccess: () => {
toast.success(__('Channel updated'));
},
onError: (error: any, variables, context: any) => {
// Rollback on error
if (context?.previousChannels) {
queryClient.setQueryData(['notification-channels'], context.previousChannels);
}
toast.error(error?.message || __('Failed to update channel'));
},
onSettled: () => {
// Refetch after mutation
onError: (error: any) => {
// Refetch on error to sync with server
queryClient.invalidateQueries({ queryKey: ['notification-channels'] });
toast.error(error?.message || __('Failed to update channel'));
},
});