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'));
},
});

View File

@@ -297,10 +297,11 @@ class NotificationsController {
* @return WP_REST_Response|WP_Error
*/
public function update_event(WP_REST_Request $request) {
$event_id = $request->get_param('eventId');
$channel_id = $request->get_param('channelId');
$enabled = $request->get_param('enabled');
$recipient = $request->get_param('recipient');
$params = $request->get_json_params();
$event_id = isset($params['eventId']) ? $params['eventId'] : null;
$channel_id = isset($params['channelId']) ? $params['channelId'] : null;
$enabled = isset($params['enabled']) ? $params['enabled'] : null;
$recipient = isset($params['recipient']) ? $params['recipient'] : null;
if (empty($event_id) || empty($channel_id)) {
return new WP_Error(
@@ -315,16 +316,20 @@ class NotificationsController {
// Update settings
if (!isset($settings[$event_id])) {
$settings[$event_id] = [];
$settings[$event_id] = ['channels' => []];
}
$settings[$event_id][$channel_id] = [
'enabled' => $enabled,
if (!isset($settings[$event_id]['channels'])) {
$settings[$event_id]['channels'] = [];
}
$settings[$event_id]['channels'][$channel_id] = [
'enabled' => (bool) $enabled,
'recipient' => $recipient ?? 'admin',
];
// Save settings
update_option('woonoow_notification_settings', $settings);
update_option('woonoow_notification_settings', $settings, false);
// Fire action for addons to react
do_action('woonoow_notification_event_updated', $event_id, $channel_id, $enabled, $recipient);