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);
return () => window.removeEventListener("transitionend", handler);
return () => window.removeEventListener("beforeinstallprompt", handler);
}, []);
const onClick = (evt: React.MouseEvent<HTMLButtonElement>) => {
@ -64,11 +64,7 @@ export default function InstallApp() {
</Alert>
{/* Install PWA button */}
<Button
className="w-full"
onClick={onClick}
disabled={!supportsPWA}
>
<Button className="w-full" onClick={onClick} disabled={!supportsPWA}>
<Smartphone className="h-4 w-4 sm:h-5 sm:w-5 mr-2" />
Installer l'application
</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,
Send,
AlertTriangle,
Info,
Loader,
} 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() {
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
const [pushEnabled, setPushEnabled] = useState(false);
const [events, setEvents] = useState([
{ id: "new-message", label: "New Message", enabled: true },
{ id: "comment-reply", label: "Comment Reply", enabled: true },
@ -87,24 +121,48 @@ export default function NotificationSettings() {
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{/* <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={setPushEnabled}
aria-label="Enable push notifications"
className="flex-shrink-0"
/>
</div> */}
{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>

View file

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

View file

@ -314,3 +314,22 @@ export const useSubjects = () => {
...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);
}
}