Some checks failed
ParentFlow CI/CD Pipeline / Backend Tests (push) Has been cancelled
ParentFlow CI/CD Pipeline / Frontend Tests (push) Has been cancelled
ParentFlow CI/CD Pipeline / Security Scanning (push) Has been cancelled
ParentFlow CI/CD Pipeline / Build Docker Images (map[context:maternal-app/maternal-app-backend dockerfile:Dockerfile.production name:backend]) (push) Has been cancelled
ParentFlow CI/CD Pipeline / Build Docker Images (map[context:maternal-web dockerfile:Dockerfile.production name:frontend]) (push) Has been cancelled
ParentFlow CI/CD Pipeline / Deploy to Development (push) Has been cancelled
ParentFlow CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / Lint and Test (push) Has been cancelled
CI/CD Pipeline / E2E Tests (push) Has been cancelled
CI/CD Pipeline / Build Application (push) Has been cancelled
Backend CI/CD Pipeline / Lint and Test Backend (push) Has been cancelled
Backend CI/CD Pipeline / E2E Tests Backend (push) Has been cancelled
Backend CI/CD Pipeline / Build Backend Application (push) Has been cancelled
Backend CI/CD Pipeline / Performance Testing (push) Has been cancelled
Implement database-backed notification settings that persist across restarts: Backend changes: - Updated DashboardService to read/write notification settings from database - Added 6 notification settings keys to dbSettingsMap: * enable_email_notifications (boolean) * enable_push_notifications (boolean) * admin_notifications (boolean) * error_alerts (boolean) * new_user_alerts (boolean) * system_health_alerts (boolean) - Settings are now retrieved from database with fallback defaults Database: - Seeded 6 default notification settings in settings table - All notification toggles default to 'true' - Settings persist across server restarts Frontend: - Admin settings page at /settings already configured - Notifications tab contains all 6 toggle switches - Settings are loaded from GET /api/v1/admin/dashboard/settings - Settings are saved via POST /api/v1/admin/dashboard/settings API Endpoints (already existed, now enhanced): - GET /api/v1/admin/dashboard/settings - Returns all settings including notifications - POST /api/v1/admin/dashboard/settings - Persists notification settings to database Files modified: - maternal-app-backend/src/modules/admin/dashboard/dashboard.service.ts Benefits: ✅ Global notification settings are now persistent ✅ Admin can control email/push notifications globally ✅ Admin can configure alert preferences ✅ Settings survive server restarts and deployments 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
215 lines
7.0 KiB
TypeScript
215 lines
7.0 KiB
TypeScript
'use client';
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
import { useAuth } from '../contexts/AuthContext';
|
|
import {
|
|
isPushNotificationSupported,
|
|
getNotificationPermission,
|
|
subscribeToPush,
|
|
unsubscribeFromPush,
|
|
isPushSubscribed,
|
|
sendTestPushNotification,
|
|
} from '../lib/push-notifications';
|
|
|
|
export default function PushNotificationToggle() {
|
|
const { user, token } = useAuth();
|
|
const [isSupported, setIsSupported] = useState(false);
|
|
const [isSubscribed, setIsSubscribed] = useState(false);
|
|
const [permission, setPermission] = useState<NotificationPermission>('default');
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
checkPushSupport();
|
|
checkSubscriptionStatus();
|
|
}, []);
|
|
|
|
const checkPushSupport = () => {
|
|
const supported = isPushNotificationSupported();
|
|
setIsSupported(supported);
|
|
if (supported) {
|
|
setPermission(getNotificationPermission());
|
|
}
|
|
};
|
|
|
|
const checkSubscriptionStatus = async () => {
|
|
try {
|
|
const subscribed = await isPushSubscribed();
|
|
setIsSubscribed(subscribed);
|
|
} catch (error) {
|
|
console.error('Error checking subscription status:', error);
|
|
}
|
|
};
|
|
|
|
const handleToggle = async () => {
|
|
if (!token) {
|
|
setError('You must be logged in to enable notifications');
|
|
return;
|
|
}
|
|
|
|
setIsLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
if (isSubscribed) {
|
|
// Unsubscribe
|
|
await unsubscribeFromPush(token);
|
|
setIsSubscribed(false);
|
|
setPermission(getNotificationPermission());
|
|
|
|
// Update user preferences to disable push
|
|
await savePreference(token, false);
|
|
} else {
|
|
// Subscribe
|
|
await subscribeToPush(token);
|
|
setIsSubscribed(true);
|
|
setPermission('granted');
|
|
|
|
// Update user preferences to enable push
|
|
await savePreference(token, true);
|
|
}
|
|
} catch (err: any) {
|
|
console.error('Error toggling push notifications:', err);
|
|
setError(err.message || 'Failed to update notification settings');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const savePreference = async (authToken: string, enabled: boolean) => {
|
|
try {
|
|
const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://maternal-api.noru1.ro';
|
|
const response = await fetch(`${apiUrl}/api/v1/preferences/notifications`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${authToken}`,
|
|
},
|
|
body: JSON.stringify({
|
|
pushEnabled: enabled,
|
|
}),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to save preference');
|
|
}
|
|
|
|
console.log('[Push] Preference saved:', enabled);
|
|
} catch (error) {
|
|
console.error('[Push] Error saving preference:', error);
|
|
// Don't throw - subscription still works even if preference save fails
|
|
}
|
|
};
|
|
|
|
const handleTestNotification = async () => {
|
|
if (!token) {
|
|
setError('You must be logged in to send test notifications');
|
|
return;
|
|
}
|
|
|
|
setIsLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
await sendTestPushNotification(token);
|
|
// Show success message (you could use a toast/snackbar here)
|
|
alert('Test notification sent! Check your notifications.');
|
|
} catch (err: any) {
|
|
console.error('Error sending test notification:', err);
|
|
setError(err.message || 'Failed to send test notification');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
if (!isSupported) {
|
|
return (
|
|
<div className="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-4">
|
|
<p className="text-sm text-yellow-800 dark:text-yellow-200">
|
|
Push notifications are not supported in your browser. Please use a modern browser like
|
|
Chrome, Firefox, Edge, or Safari.
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex-1">
|
|
<h3 className="text-lg font-medium text-gray-900 dark:text-white">
|
|
Push Notifications
|
|
</h3>
|
|
<p className="text-sm text-gray-500 dark:text-gray-400">
|
|
Get notified about feeding times, diaper changes, and more
|
|
</p>
|
|
</div>
|
|
<button
|
|
onClick={handleToggle}
|
|
disabled={isLoading || !user}
|
|
className={`
|
|
relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent
|
|
transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2
|
|
disabled:opacity-50 disabled:cursor-not-allowed
|
|
${isSubscribed ? 'bg-primary-600' : 'bg-gray-200 dark:bg-gray-700'}
|
|
`}
|
|
role="switch"
|
|
aria-checked={isSubscribed}
|
|
>
|
|
<span className="sr-only">Enable push notifications</span>
|
|
<span
|
|
className={`
|
|
pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0
|
|
transition duration-200 ease-in-out
|
|
${isSubscribed ? 'translate-x-5' : 'translate-x-0'}
|
|
`}
|
|
/>
|
|
</button>
|
|
</div>
|
|
|
|
{error && (
|
|
<div className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-3">
|
|
<p className="text-sm text-red-800 dark:text-red-200">{error}</p>
|
|
</div>
|
|
)}
|
|
|
|
{permission === 'denied' && (
|
|
<div className="bg-orange-50 dark:bg-orange-900/20 border border-orange-200 dark:border-orange-800 rounded-lg p-3">
|
|
<p className="text-sm text-orange-800 dark:text-orange-200">
|
|
Notifications are blocked. Please enable them in your browser settings and reload the
|
|
page.
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
{isSubscribed && (
|
|
<div className="space-y-2">
|
|
<div className="bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg p-3">
|
|
<p className="text-sm text-green-800 dark:text-green-200">
|
|
✓ You're subscribed to push notifications
|
|
</p>
|
|
</div>
|
|
<button
|
|
onClick={handleTestNotification}
|
|
disabled={isLoading}
|
|
className="
|
|
w-full sm:w-auto px-4 py-2 text-sm font-medium text-primary-700 dark:text-primary-300
|
|
bg-primary-50 dark:bg-primary-900/20 hover:bg-primary-100 dark:hover:bg-primary-900/30
|
|
rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed
|
|
"
|
|
>
|
|
Send Test Notification
|
|
</button>
|
|
</div>
|
|
)}
|
|
|
|
{isLoading && (
|
|
<div className="flex items-center justify-center py-2">
|
|
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-primary-600"></div>
|
|
<span className="ml-2 text-sm text-gray-600 dark:text-gray-400">Processing...</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|