frontend/app/components/settings/notifications.tsx
Nathan Lamy 660017edda
All checks were successful
Deploy to Netlify / Deploy to Netlify (push) Successful in 1m24s
feat: add service worker
2025-08-19 22:56:00 +02:00

311 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState } from "react";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "~/components/ui/card";
import { Switch } from "~/components/ui/switch";
import { Button } from "~/components/ui/button";
import { Badge } from "~/components/ui/badge";
import { Separator } from "~/components/ui/separator";
import {
Bell,
Smartphone,
Monitor,
Trash2,
Send,
AlertTriangle,
Loader,
} from "lucide-react";
import InstallApp, { isInstalled } from "./install-app";
import {
isNotificationEnabled,
registerNotification,
unregisterNotification,
} from "~/lib/notification";
export default function NotificationSettings() {
const [pushEnabled, setPushEnabled] = useState(isNotificationEnabled());
const [isRegistering, setIsRegistering] = useState(false);
function togglePushEnabled() {
setIsRegistering(true);
if (pushEnabled) {
// Unregister notifications
unregisterNotification()
.then(() => {
setPushEnabled(true);
})
.catch((error) => {
console.error("Failed to unregister notifications:", error);
})
.finally(() => {
setIsRegistering(false);
});
} else {
// Register notifications
registerNotification()
.then((result) => {
if ("error" in result && result.error) {
console.error("Failed to register notifications:", result.error);
} else {
setPushEnabled(false);
}
})
.catch((error) => {
console.error("Failed to unregister notifications:", error);
})
.finally(() => {
setIsRegistering(false);
});
}
}
// TODO: Replace with actual user data
const [events, setEvents] = useState([
{ id: "new-message", label: "New Message", enabled: true },
{ id: "comment-reply", label: "Comment Reply", enabled: true },
{ id: "app-update", label: "App Update", enabled: false },
{ id: "weekly-digest", label: "Weekly Digest", enabled: true },
{ id: "security-alert", label: "Security Alert", enabled: true },
{ id: "friend-request", label: "Friend Request", enabled: false },
]);
const [subscriptions] = useState([
{
id: "1",
name: "iPhone 15 Pro",
type: "mobile",
status: "active",
lastSeen: "2 hours ago",
},
{
id: "2",
name: "MacBook Pro",
type: "desktop",
status: "active",
lastSeen: "5 minutes ago",
},
{
id: "3",
name: "Chrome on Windows",
type: "desktop",
status: "revoked",
lastSeen: "3 days ago",
},
]);
const toggleEvent = (eventId: string) => {
setEvents(
events.map((event) =>
event.id === eventId ? { ...event, enabled: !event.enabled } : event
)
);
};
return (
<div className="max-w-4xl mx-auto space-y-6 sm:space-y-8">
<div className="space-y-2">
<h1 className="text-2xl sm:text-3xl font-bold tracking-tight">
Paramètres de notifications
</h1>
<p className="text-sm sm:text-base text-muted-foreground">
Gérez vos préférences de notification et vos appareils abonnés.
</p>
</div>
{/* Enable Push Notifications Section */}
<Card>
<CardHeader>
<div className="flex items-center gap-2">
<Bell className="h-5 w-5" />
<CardTitle>Activer les notifications</CardTitle>
</div>
<CardDescription>
Autorisez cette application à vous envoyer des notifications push.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{isInstalled() ? (
pushEnabled ? (
<div className="flex items-start sm:items-center justify-between gap-4">
<div className="space-y-1 flex-1">
<p className="font-medium text-sm sm:text-base">
Notifications
</p>
<p className="text-xs sm:text-sm text-muted-foreground">
Recevez des notifications même lorsque lapplication est
fermée.
</p>
</div>
<Switch
checked={pushEnabled}
onCheckedChange={togglePushEnabled}
disabled={isRegistering}
className="flex-shrink-0"
/>
</div>
) : (
<Button
variant="outline"
className="w-full"
onClick={togglePushEnabled}
disabled={isRegistering}
>
{isRegistering ? (
<span className="flex items-center gap-2">
<Loader className="animate-spin h-4 w-4" />
Activation...
</span>
) : (
<span className="flex items-center gap-2">
<Bell className="h-4 w-4" />
Activer les notifications
</span>
)}
</Button>
)
) : (
<InstallApp />
)}
</CardContent>
</Card>
{/* Select Notification Events Section */}
<Card>
<CardHeader>
<CardTitle>Select Notification Events</CardTitle>
<CardDescription>
Choose which events you want to receive notifications for
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-3 sm:space-y-4">
{events.map((event, index) => (
<div key={event.id}>
<div className="flex items-start sm:items-center justify-between py-2 gap-4">
<div className="space-y-1 flex-1 min-w-0">
<p className="font-medium text-sm sm:text-base">
{event.label}
</p>
<p className="text-xs sm:text-sm text-muted-foreground leading-relaxed">
{event.id === "new-message" &&
"Get notified when you receive a new message"}
{event.id === "comment-reply" &&
"Get notified when someone replies to your comment"}
{event.id === "app-update" &&
"Get notified when a new app version is available"}
{event.id === "weekly-digest" &&
"Receive a weekly summary of your activity"}
{event.id === "security-alert" &&
"Important security notifications and alerts"}
{event.id === "friend-request" &&
"Get notified when someone sends you a friend request"}
</p>
</div>
<Switch
checked={event.enabled}
onCheckedChange={() => toggleEvent(event.id)}
aria-label={`Toggle ${event.label} notifications`}
className="flex-shrink-0"
/>
</div>
{index < events.length - 1 && (
<Separator className="mt-3 sm:mt-4" />
)}
</div>
))}
</div>
</CardContent>
</Card>
{/* Manage Subscriptions Section */}
<Card>
<CardHeader>
<CardTitle>Manage Subscriptions</CardTitle>
<CardDescription>
View and manage devices that can receive notifications
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-3 sm:space-y-4">
{subscriptions.map((subscription, index) => (
<div key={subscription.id}>
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 sm:gap-4">
<div className="flex items-center gap-3 flex-1 min-w-0">
<div className="flex-shrink-0">
{subscription.type === "mobile" ? (
<Smartphone className="h-4 w-4 sm:h-5 sm:w-5 text-muted-foreground" />
) : (
<Monitor className="h-4 w-4 sm:h-5 sm:w-5 text-muted-foreground" />
)}
</div>
<div className="space-y-1 flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<p className="font-medium text-sm sm:text-base truncate">
{subscription.name}
</p>
{subscription.status === "revoked" && (
<Badge
variant="destructive"
className="flex items-center gap-1 text-xs"
>
<AlertTriangle className="h-3 w-3" />
Revoked
</Badge>
)}
{subscription.status === "active" && (
<Badge variant="secondary" className="text-xs">
Active
</Badge>
)}
</div>
<p className="text-xs sm:text-sm text-muted-foreground">
Last seen {subscription.lastSeen}
</p>
</div>
</div>
<div className="flex items-center gap-2 sm:gap-2 self-start sm:self-center">
<Button
variant="outline"
size="sm"
disabled={subscription.status === "revoked"}
className="flex items-center gap-1 bg-transparent text-xs sm:text-sm px-2 sm:px-3"
>
<Send className="h-3 w-3 sm:h-4 sm:w-4" />
<span className="hidden xs:inline">Test</span>
</Button>
<Button
variant="outline"
size="sm"
className="flex items-center gap-1 text-destructive hover:text-destructive bg-transparent text-xs sm:text-sm px-2 sm:px-3"
>
<Trash2 className="h-3 w-3 sm:h-4 sm:w-4" />
<span className="hidden xs:inline">Delete</span>
</Button>
</div>
</div>
{index < subscriptions.length - 1 && (
<Separator className="mt-3 sm:mt-4" />
)}
</div>
))}
{subscriptions.length === 0 && (
<div className="text-center py-6 sm:py-8 text-muted-foreground">
<Bell className="h-10 w-10 sm:h-12 sm:w-12 mx-auto mb-3 sm:mb-4 opacity-50" />
<p className="text-sm sm:text-base">
No subscribed devices found
</p>
<p className="text-xs sm:text-sm">
Enable push notifications to add this device
</p>
</div>
)}
</div>
</CardContent>
</Card>
</div>
);
}