feat: add subscribe logic
All checks were successful
Deploy to Netlify / Deploy to Netlify (push) Successful in 1m30s

This commit is contained in:
Nathan Lamy 2025-08-19 22:36:23 +02:00
parent d4f1bb5dcd
commit 8b57952344
5 changed files with 149 additions and 27 deletions

View file

@ -31,7 +31,7 @@ export default function InstallApp() {
}; };
window.addEventListener("beforeinstallprompt", handler); window.addEventListener("beforeinstallprompt", handler);
return () => window.removeEventListener("transitionend", handler); return () => window.removeEventListener("beforeinstallprompt", handler);
}, []); }, []);
const onClick = (evt: React.MouseEvent<HTMLButtonElement>) => { const onClick = (evt: React.MouseEvent<HTMLButtonElement>) => {
@ -64,11 +64,7 @@ export default function InstallApp() {
</Alert> </Alert>
{/* Install PWA button */} {/* Install PWA button */}
<Button <Button className="w-full" onClick={onClick} disabled={!supportsPWA}>
className="w-full"
onClick={onClick}
disabled={!supportsPWA}
>
<Smartphone className="h-4 w-4 sm:h-5 sm:w-5 mr-2" /> <Smartphone className="h-4 w-4 sm:h-5 sm:w-5 mr-2" />
Installer l'application Installer l'application
</Button> </Button>
@ -118,3 +114,10 @@ export default function InstallApp() {
</> </>
); );
} }
export function isInstalled() {
const UA = navigator.userAgent;
const IOS = UA.match(/iPhone|iPad|iPod/);
const standalone = window.matchMedia("(display-mode: standalone)").matches;
return !!(standalone || (IOS && !UA.match(/Safari/)));
}

View file

@ -17,13 +17,47 @@ import {
Trash2, Trash2,
Send, Send,
AlertTriangle, AlertTriangle,
Info, Loader,
} from "lucide-react"; } from "lucide-react";
import InstallApp from "./install-app"; import InstallApp, { isInstalled } from "./install-app";
import {
isNotificationEnabled,
registerNotification,
unregisterNotification,
} from "~/lib/notification";
export default function NotificationSettings() { 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);
})
.finally(() => {
setIsRegistering(false);
});
} else {
// Register notifications
registerNotification()
.then((result) => {
if ("error" in result) {
console.error("Failed to register notifications:", result.error);
} else {
setPushEnabled(false);
}
})
.finally(() => {
setIsRegistering(false);
});
}
}
// TODO: Replace with actual user data // TODO: Replace with actual user data
const [pushEnabled, setPushEnabled] = useState(false);
const [events, setEvents] = useState([ const [events, setEvents] = useState([
{ id: "new-message", label: "New Message", enabled: true }, { id: "new-message", label: "New Message", enabled: true },
{ id: "comment-reply", label: "Comment Reply", enabled: true }, { id: "comment-reply", label: "Comment Reply", enabled: true },
@ -87,24 +121,48 @@ export default function NotificationSettings() {
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-4">
{/* <div className="flex items-start sm:items-center justify-between gap-4"> {isInstalled() ? (
pushEnabled ? (
<div className="flex items-start sm:items-center justify-between gap-4">
<div className="space-y-1 flex-1"> <div className="space-y-1 flex-1">
<p className="font-medium text-sm sm:text-base"> <p className="font-medium text-sm sm:text-base">
Notifications Notifications
</p> </p>
<p className="text-xs sm:text-sm text-muted-foreground"> <p className="text-xs sm:text-sm text-muted-foreground">
Recevez des notifications même lorsque lapplication est fermée. Recevez des notifications même lorsque lapplication est
fermée.
</p> </p>
</div> </div>
<Switch <Switch
checked={pushEnabled} checked={pushEnabled}
onCheckedChange={setPushEnabled} onCheckedChange={togglePushEnabled}
aria-label="Enable push notifications" disabled={isRegistering}
className="flex-shrink-0" className="flex-shrink-0"
/> />
</div> */} </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 /> <InstallApp />
)}
</CardContent> </CardContent>
</Card> </Card>

View file

@ -76,7 +76,7 @@ export default function Profile({ user }: { user: User }) {
<Button <Button
variant="outline" variant="outline"
className="w-full text-white" className="w-full"
onClick={clearCache} onClick={clearCache}
> >
<Trash className="mr-2 h-4 w-4" /> <Trash className="mr-2 h-4 w-4" />

View file

@ -314,3 +314,22 @@ export const useSubjects = () => {
...props, ...props,
}; };
}; };
/**
* === NOTIFICATIONS API ===
*/
export const subscribe = async (data: any) => {
return makePostRequest(
"/notifications/subscribe",
data,
"Échec de l'abonnement aux notifications"
);
}
export const unsubscribe = async (data: any) => {
return makePostRequest(
"/notifications/unsubscribe",
data,
"Échec de la désinscription des notifications"
);
};

42
app/lib/notification.ts Normal file
View file

@ -0,0 +1,42 @@
import { subscribe, unsubscribe } from "./api";
const STORAGE_KEY = "notification_enabled";
export async function registerNotification() {
const permission = await Notification.requestPermission();
if (permission !== "granted") {
return {
error: "E_PERMISSION_DENIED",
};
}
const registration = await navigator.serviceWorker.ready;
try {
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: process.env.VITE_PUBLIC_VAPID_KEY,
});
await subscribe(subscription);
// Store to local storage
localStorage.setItem(STORAGE_KEY, "true");
return subscription;
} catch (err) {
return {
error: "Erreur : " + err,
};
}
}
export function isNotificationEnabled() {
return localStorage.getItem(STORAGE_KEY) === "true";
}
export async function unregisterNotification() {
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.getSubscription();
if (subscription) {
await subscription.unsubscribe();
localStorage.removeItem(STORAGE_KEY);
await unsubscribe(subscription);
}
}