193 lines
8.1 KiB
TypeScript
193 lines
8.1 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Switch } from '@/components/ui/switch';
|
|
import { Label } from '@/components/ui/label';
|
|
import { Bell, Mail, MessageSquare, Loader2, CheckCircle, AlertCircle } from 'lucide-react';
|
|
import { getNotificationPreferences, updateNotificationPreferences, NotificationPreferences } from '@/services/userPreferenceApi';
|
|
|
|
export function NotificationPreferencesCard() {
|
|
const [preferences, setPreferences] = useState<NotificationPreferences>({
|
|
emailNotificationsEnabled: true,
|
|
pushNotificationsEnabled: true,
|
|
inAppNotificationsEnabled: true
|
|
});
|
|
const [loading, setLoading] = useState(true);
|
|
const [updating, setUpdating] = useState<string | null>(null);
|
|
const [successMessage, setSuccessMessage] = useState<string | null>(null);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
loadPreferences();
|
|
}, []);
|
|
|
|
const loadPreferences = async () => {
|
|
try {
|
|
setLoading(true);
|
|
setError(null);
|
|
const data = await getNotificationPreferences();
|
|
setPreferences(data);
|
|
} catch (err: any) {
|
|
console.error('[NotificationPreferences] Failed to load preferences:', err);
|
|
setError(err.response?.data?.message || 'Failed to load notification preferences');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleToggle = async (key: keyof NotificationPreferences, value: boolean) => {
|
|
try {
|
|
setUpdating(key);
|
|
setError(null);
|
|
setSuccessMessage(null);
|
|
|
|
const updateData = { [key]: value };
|
|
const updated = await updateNotificationPreferences(updateData);
|
|
|
|
setPreferences(updated);
|
|
|
|
// Show success message
|
|
const prefName = key === 'emailNotificationsEnabled' ? 'Email'
|
|
: key === 'pushNotificationsEnabled' ? 'Push'
|
|
: 'In-App';
|
|
setSuccessMessage(`${prefName} notifications ${value ? 'enabled' : 'disabled'}`);
|
|
setTimeout(() => setSuccessMessage(null), 3000);
|
|
} catch (err: any) {
|
|
console.error('[NotificationPreferences] Failed to update preference:', err);
|
|
setError(err.response?.data?.message || 'Failed to update notification preference');
|
|
// Revert the UI change
|
|
loadPreferences();
|
|
} finally {
|
|
setUpdating(null);
|
|
}
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<Card className="shadow-lg hover:shadow-xl transition-all duration-300 border-0 rounded-md">
|
|
<CardContent className="p-12 flex items-center justify-center">
|
|
<Loader2 className="w-8 h-8 animate-spin text-gray-400" />
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Card className="shadow-lg hover:shadow-xl transition-all duration-300 border-0 rounded-md group">
|
|
<CardHeader className="pb-4">
|
|
<div className="flex items-center gap-3">
|
|
<div className="p-3 bg-gradient-to-br from-slate-600 to-slate-700 rounded-md shadow-md group-hover:shadow-lg transition-shadow">
|
|
<Bell className="h-5 w-5 text-white" />
|
|
</div>
|
|
<div>
|
|
<CardTitle className="text-lg font-semibold text-gray-900">Notification Preferences</CardTitle>
|
|
<CardDescription className="text-sm text-gray-600">Control how you receive notifications</CardDescription>
|
|
</div>
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent className="space-y-6">
|
|
{/* Success Message */}
|
|
{successMessage && (
|
|
<div className="flex items-center gap-2 p-3 bg-green-50 border border-green-200 rounded-md">
|
|
<CheckCircle className="w-4 h-4 text-green-600 shrink-0" />
|
|
<p className="text-sm text-green-800 font-medium">{successMessage}</p>
|
|
</div>
|
|
)}
|
|
|
|
{/* Error Message */}
|
|
{error && (
|
|
<div className="flex items-center gap-2 p-3 bg-red-50 border border-red-200 rounded-md">
|
|
<AlertCircle className="w-4 h-4 text-red-600 shrink-0" />
|
|
<p className="text-sm text-red-800 font-medium">{error}</p>
|
|
</div>
|
|
)}
|
|
|
|
{/* Email Notifications */}
|
|
<div className="flex items-center justify-between p-4 bg-gradient-to-br from-gray-50 to-gray-100 rounded-md border border-gray-200 hover:border-gray-300 transition-colors">
|
|
<div className="flex items-center gap-3">
|
|
<div className="p-2 bg-white rounded-md shadow-sm">
|
|
<Mail className="w-5 h-5 text-slate-600" />
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="email-notifications" className="text-sm font-semibold text-gray-900 cursor-pointer">
|
|
Email Notifications
|
|
</Label>
|
|
<p className="text-xs text-gray-600 mt-0.5">Receive notifications via email</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
{updating === 'emailNotificationsEnabled' && (
|
|
<Loader2 className="w-4 h-4 animate-spin text-gray-400" />
|
|
)}
|
|
<Switch
|
|
id="email-notifications"
|
|
checked={preferences.emailNotificationsEnabled}
|
|
onCheckedChange={(checked) => handleToggle('emailNotificationsEnabled', checked)}
|
|
disabled={updating === 'emailNotificationsEnabled'}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Push Notifications */}
|
|
<div className="flex items-center justify-between p-4 bg-gradient-to-br from-gray-50 to-gray-100 rounded-md border border-gray-200 hover:border-gray-300 transition-colors">
|
|
<div className="flex items-center gap-3">
|
|
<div className="p-2 bg-white rounded-md shadow-sm">
|
|
<Bell className="w-5 h-5 text-slate-600" />
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="push-notifications" className="text-sm font-semibold text-gray-900 cursor-pointer">
|
|
Push Notifications
|
|
</Label>
|
|
<p className="text-xs text-gray-600 mt-0.5">Receive browser push notifications</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
{updating === 'pushNotificationsEnabled' && (
|
|
<Loader2 className="w-4 h-4 animate-spin text-gray-400" />
|
|
)}
|
|
<Switch
|
|
id="push-notifications"
|
|
checked={preferences.pushNotificationsEnabled}
|
|
onCheckedChange={(checked) => handleToggle('pushNotificationsEnabled', checked)}
|
|
disabled={updating === 'pushNotificationsEnabled'}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* In-App Notifications */}
|
|
<div className="flex items-center justify-between p-4 bg-gradient-to-br from-gray-50 to-gray-100 rounded-md border border-gray-200 hover:border-gray-300 transition-colors">
|
|
<div className="flex items-center gap-3">
|
|
<div className="p-2 bg-white rounded-md shadow-sm">
|
|
<MessageSquare className="w-5 h-5 text-slate-600" />
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="inapp-notifications" className="text-sm font-semibold text-gray-900 cursor-pointer">
|
|
In-App Notifications
|
|
</Label>
|
|
<p className="text-xs text-gray-600 mt-0.5">Show notifications within the application</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
{updating === 'inAppNotificationsEnabled' && (
|
|
<Loader2 className="w-4 h-4 animate-spin text-gray-400" />
|
|
)}
|
|
<Switch
|
|
id="inapp-notifications"
|
|
checked={preferences.inAppNotificationsEnabled}
|
|
onCheckedChange={(checked) => handleToggle('inAppNotificationsEnabled', checked)}
|
|
disabled={updating === 'inAppNotificationsEnabled'}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="pt-4 border-t border-gray-200">
|
|
<p className="text-xs text-gray-500 text-center">
|
|
These preferences control which notification channels you receive alerts through.
|
|
You'll still receive critical notifications regardless of these settings.
|
|
</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|