All checks were successful
Deploy to Netlify / Deploy to Netlify (push) Successful in 1m24s
311 lines
11 KiB
TypeScript
311 lines
11 KiB
TypeScript
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 l’application 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>
|
||
);
|
||
}
|