Files
Compagnon-du-Lagon---Marama/components/mana-tracker/PushNotificationManager.tsx
syoul d0694df12a Conversion vers données JSON statiques pour compatibilité APK
- Création des fichiers JSON dans public/data/
- Modification de tous les composants pour fetch depuis /data/*.json
- PlaceList, FAQ, Lexique, Tides, SunTimes, Excursions, Notifications
- Données complètes pour Fakarava (plages, restaurants, épiceries)
- Fix docker-compose.build.yml (suppression volume node_modules)
2025-11-23 10:23:13 +01:00

225 lines
7.8 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

"use client";
import { useEffect, useState } from "react";
import { Bell, BellOff, X } from "lucide-react";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Notification } from "@/lib/types/notifications";
export default function PushNotificationManager() {
const [notifications, setNotifications] = useState<Notification[]>([]);
const [permission, setPermission] = useState<NotificationPermission>("default");
const [loading, setLoading] = useState(true);
useEffect(() => {
if (typeof window !== "undefined" && "Notification" in window && window.Notification) {
setPermission(window.Notification.permission);
}
fetchNotifications();
}, []);
const fetchNotifications = async () => {
try {
const response = await fetch("/data/notifications.json");
const data = await response.json();
setNotifications(data);
} catch (error) {
console.error("Erreur lors du chargement des notifications:", error);
} finally {
setLoading(false);
}
};
const requestPermission = async () => {
if (typeof window === "undefined" || !("Notification" in window) || !window.Notification) {
alert("Votre navigateur ne supporte pas les notifications");
return;
}
const permission = await window.Notification.requestPermission();
setPermission(permission);
if (permission === "granted") {
// Enregistrer le service worker pour les notifications push
if ("serviceWorker" in navigator) {
try {
const registration = await navigator.serviceWorker.ready;
// Vérifier périodiquement les nouvelles notifications
setInterval(async () => {
try {
const response = await fetch("/data/notifications.json");
const newNotifications = await response.json();
const unread = newNotifications.filter((n: Notification) => !n.read);
// Afficher une notification pour chaque nouvelle alerte non lue
if (typeof window !== "undefined" && window.Notification) {
unread.forEach((notification: Notification) => {
if (window.Notification.permission === "granted") {
new window.Notification(notification.title, {
body: notification.message,
icon: "/icon-192x192.png",
badge: "/icon-192x192.png",
tag: notification.id,
requireInteraction: notification.type === "whale",
});
}
});
}
} catch (error) {
console.error("Erreur lors de la vérification des notifications:", error);
}
}, 60000); // Vérifier toutes les minutes
} catch (error) {
console.error("Erreur lors de l'enregistrement du service worker:", error);
}
}
}
};
const showTestNotification = () => {
if (permission === "granted" && typeof window !== "undefined" && window.Notification) {
new window.Notification("Test de notification", {
body: "Les notifications fonctionnent correctement !",
icon: "/icon-192x192.png",
badge: "/icon-192x192.png",
});
}
};
const markAsRead = async (id: string) => {
// Dans une version statique, on met à jour uniquement localement
// (sans API backend)
setNotifications(
notifications.map((n) => (n.id === id ? { ...n, read: true } : n))
);
};
const getNotificationIcon = (type: string) => {
switch (type) {
case "whale":
return "🐋";
case "weather":
return "🌦️";
case "excursion":
return "🚤";
default:
return "";
}
};
if (loading) {
return (
<Card>
<CardContent className="p-6">
<div className="animate-pulse">Chargement...</div>
</CardContent>
</Card>
);
}
const unreadCount = notifications.filter((n) => !n.read).length;
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center justify-between">
<span className="flex items-center gap-2">
<Bell className="h-6 w-6 text-primary" />
Notifications
</span>
{unreadCount > 0 && (
<span className="bg-primary text-white text-xs font-bold px-2 py-1 rounded-full">
{unreadCount}
</span>
)}
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{permission === "default" && (
<div className="bg-secondary rounded-xl p-4">
<p className="text-sm text-gray-700 mb-3">
Activez les notifications pour recevoir des alertes importantes (baleines, météo, etc.)
</p>
<Button onClick={requestPermission} className="w-full">
<Bell className="mr-2 h-4 w-4" />
Activer les notifications
</Button>
</div>
)}
{permission === "denied" && (
<div className="bg-red-50 border border-red-200 rounded-xl p-4">
<p className="text-sm text-red-700">
Les notifications sont désactivées. Veuillez les activer dans les paramètres de votre navigateur.
</p>
</div>
)}
{permission === "granted" && (
<div className="space-y-2">
<div className="flex items-center justify-between">
<p className="text-sm text-gray-600">Notifications activées</p>
<Button
onClick={showTestNotification}
variant="outline"
size="sm"
>
Tester
</Button>
</div>
</div>
)}
<div className="space-y-2 max-h-64 overflow-y-auto">
{notifications.length === 0 ? (
<p className="text-sm text-gray-500 text-center py-4">
Aucune notification pour le moment
</p>
) : (
notifications.map((notification) => (
<div
key={notification.id}
className={`border rounded-xl p-3 ${
notification.read
? "bg-gray-50 border-gray-200"
: "bg-secondary border-primary/30"
}`}
>
<div className="flex items-start justify-between gap-2">
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
<span className="text-lg">
{getNotificationIcon(notification.type)}
</span>
<h4 className="font-semibold text-sm">{notification.title}</h4>
{!notification.read && (
<span className="bg-primary text-white text-xs px-1.5 py-0.5 rounded-full">
Nouveau
</span>
)}
</div>
<p className="text-sm text-gray-700">{notification.message}</p>
<p className="text-xs text-gray-500 mt-1">
{new Date(notification.timestamp).toLocaleString("fr-FR")}
</p>
</div>
{!notification.read && (
<button
onClick={() => markAsRead(notification.id)}
className="text-gray-400 hover:text-gray-600"
>
<X className="h-4 w-4" />
</button>
)}
</div>
</div>
))
)}
</div>
</CardContent>
</Card>
);
}