From 26eb7cb8986386eafb336d93a57cac2f610c5837 Mon Sep 17 00:00:00 2001
From: dwindown
Date: Tue, 11 Nov 2025 15:15:02 +0700
Subject: [PATCH] feat: Implement push notification settings backend and UI
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## ✅ Push Notification Settings - Fully Functional
### Backend (PHP)
**PushNotificationHandler Updates:**
- Added `SETTINGS_KEY` constant
- `ensure_default_settings()` - Initialize defaults
- `get_default_settings()` - Return default config
- `get_settings()` - Fetch current settings
- `update_settings()` - Save settings
**Default Settings:**
```php
[
'use_logo' => true,
'use_product_images' => true,
'use_gravatar' => false,
'click_action' => '/wp-admin/admin.php?page=woonoow#/orders',
'require_interaction' => false,
'silent' => false,
]
```
**NotificationsController:**
- `GET /notifications/push/settings` - Fetch settings
- `POST /notifications/push/settings` - Update settings
- Permission-protected endpoints
### Frontend (React)
**ChannelConfig Component:**
- Fetches push settings on open
- Real-time state management
- Connected switches and inputs
- Save mutation with loading state
- Toast notifications for success/error
- Disabled state during save
**Settings Available:**
1. **Branding**
- Use Store Logo
- Use Product Images
- Use Customer Gravatar
2. **Behavior**
- Click Action URL (input)
- Require Interaction
- Silent Notifications
### Features
✅ **Backend Storage** - Settings saved in wp_options
✅ **REST API** - GET and POST endpoints
✅ **Frontend UI** - Full CRUD interface
✅ **State Management** - React Query integration
✅ **Loading States** - Skeleton and button states
✅ **Error Handling** - Toast notifications
✅ **Default Values** - Sensible defaults
---
**Next: Email channel toggle** 📧
---
.../Settings/Notifications/ChannelConfig.tsx | 103 +++++++++++++++---
includes/Api/NotificationsController.php | 64 +++++++++++
.../Notifications/PushNotificationHandler.php | 68 ++++++++++++
3 files changed, 222 insertions(+), 13 deletions(-)
diff --git a/admin-spa/src/routes/Settings/Notifications/ChannelConfig.tsx b/admin-spa/src/routes/Settings/Notifications/ChannelConfig.tsx
index cf382bd..2e24428 100644
--- a/admin-spa/src/routes/Settings/Notifications/ChannelConfig.tsx
+++ b/admin-spa/src/routes/Settings/Notifications/ChannelConfig.tsx
@@ -1,4 +1,6 @@
-import React from 'react';
+import React, { useState, useEffect } from 'react';
+import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
+import { api } from '@/lib/api';
import {
Dialog,
DialogContent,
@@ -10,7 +12,8 @@ import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import { Switch } from '@/components/ui/switch';
-import { ExternalLink } from 'lucide-react';
+import { ExternalLink, Loader2 } from 'lucide-react';
+import { toast } from 'sonner';
import { __ } from '@/lib/i18n';
interface ChannelConfigProps {
@@ -71,6 +74,45 @@ export default function ChannelConfig({ open, onClose, channelId, channelLabel }
// Push notification configuration
if (channelId === 'push') {
+ const queryClient = useQueryClient();
+ const [settings, setSettings] = useState({
+ use_logo: true,
+ use_product_images: true,
+ use_gravatar: false,
+ click_action: '/wp-admin/admin.php?page=woonoow#/orders',
+ require_interaction: false,
+ silent: false,
+ });
+
+ // Fetch push settings
+ const { data: pushSettings, isLoading } = useQuery({
+ queryKey: ['push-settings'],
+ queryFn: () => api.get('/notifications/push/settings'),
+ enabled: open,
+ });
+
+ // Update local state when data is fetched
+ useEffect(() => {
+ if (pushSettings) {
+ setSettings(pushSettings);
+ }
+ }, [pushSettings]);
+
+ // Save settings mutation
+ const saveMutation = useMutation({
+ mutationFn: async () => {
+ return api.post('/notifications/push/settings', settings);
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['push-settings'] });
+ toast.success(__('Push notification settings saved'));
+ onClose();
+ },
+ onError: (error: any) => {
+ toast.error(error?.message || __('Failed to save settings'));
+ },
+ });
+
return (
-
+
+ )
+ }
-
diff --git a/includes/Api/NotificationsController.php b/includes/Api/NotificationsController.php
index dbee244..e343c3f 100644
--- a/includes/Api/NotificationsController.php
+++ b/includes/Api/NotificationsController.php
@@ -120,6 +120,24 @@ class NotificationsController {
'permission_callback' => '__return_true',
],
]);
+
+ // GET /woonoow/v1/notifications/push/settings
+ register_rest_route($this->namespace, '/' . $this->rest_base . '/push/settings', [
+ [
+ 'methods' => 'GET',
+ 'callback' => [$this, 'get_push_settings'],
+ 'permission_callback' => [$this, 'check_permission'],
+ ],
+ ]);
+
+ // POST /woonoow/v1/notifications/push/settings
+ register_rest_route($this->namespace, '/' . $this->rest_base . '/push/settings', [
+ [
+ 'methods' => 'POST',
+ 'callback' => [$this, 'update_push_settings'],
+ 'permission_callback' => [$this, 'check_permission'],
+ ],
+ ]);
}
/**
@@ -468,4 +486,50 @@ class NotificationsController {
'message' => __('Unsubscribed from push notifications', 'woonoow'),
], 200);
}
+
+ /**
+ * Get push notification settings
+ *
+ * @param WP_REST_Request $request Request object
+ * @return WP_REST_Response
+ */
+ public function get_push_settings(WP_REST_Request $request) {
+ $settings = PushNotificationHandler::get_settings();
+
+ return new WP_REST_Response($settings, 200);
+ }
+
+ /**
+ * Update push notification settings
+ *
+ * @param WP_REST_Request $request Request object
+ * @return WP_REST_Response
+ */
+ public function update_push_settings(WP_REST_Request $request) {
+ $settings = $request->get_json_params();
+
+ if (empty($settings)) {
+ return new WP_Error(
+ 'invalid_settings',
+ __('Settings data is required', 'woonoow'),
+ ['status' => 400]
+ );
+ }
+
+ $success = PushNotificationHandler::update_settings($settings);
+
+ if (!$success) {
+ return new WP_Error(
+ 'update_failed',
+ __('Failed to update push notification settings', 'woonoow'),
+ ['status' => 500]
+ );
+ }
+
+ return new WP_REST_Response([
+ 'success' => true,
+ 'message' => __('Push notification settings updated', 'woonoow'),
+ 'settings' => PushNotificationHandler::get_settings(),
+ ], 200);
+ }
}
diff --git a/includes/Core/Notifications/PushNotificationHandler.php b/includes/Core/Notifications/PushNotificationHandler.php
index f38ee89..967b18d 100644
--- a/includes/Core/Notifications/PushNotificationHandler.php
+++ b/includes/Core/Notifications/PushNotificationHandler.php
@@ -21,12 +21,80 @@ class PushNotificationHandler {
*/
const VAPID_KEYS_KEY = 'woonoow_push_vapid_keys';
+ /**
+ * Option key for push settings
+ */
+ const SETTINGS_KEY = 'woonoow_push_settings';
+
/**
* Initialize push notifications
*/
public static function init() {
// Generate VAPID keys if not exists
self::ensure_vapid_keys();
+
+ // Ensure default settings exist
+ self::ensure_default_settings();
+ }
+
+ /**
+ * Ensure default push settings exist
+ *
+ * @return array
+ */
+ public static function ensure_default_settings() {
+ $settings = get_option(self::SETTINGS_KEY);
+
+ if (!$settings) {
+ $settings = self::get_default_settings();
+ update_option(self::SETTINGS_KEY, $settings);
+ }
+
+ return $settings;
+ }
+
+ /**
+ * Get default push settings
+ *
+ * @return array
+ */
+ public static function get_default_settings() {
+ return [
+ 'use_logo' => true,
+ 'use_product_images' => true,
+ 'use_gravatar' => false,
+ 'click_action' => '/wp-admin/admin.php?page=woonoow#/orders',
+ 'require_interaction' => false,
+ 'silent' => false,
+ ];
+ }
+
+ /**
+ * Get push settings
+ *
+ * @return array
+ */
+ public static function get_settings() {
+ $settings = get_option(self::SETTINGS_KEY);
+
+ if (!$settings) {
+ $settings = self::get_default_settings();
+ }
+
+ return $settings;
+ }
+
+ /**
+ * Update push settings
+ *
+ * @param array $settings
+ * @return bool
+ */
+ public static function update_settings($settings) {
+ $current = self::get_settings();
+ $updated = array_merge($current, $settings);
+
+ return update_option(self::SETTINGS_KEY, $updated);
}
/**