feat: add subscribe logic
All checks were successful
Deploy to Netlify / Deploy to Netlify (push) Successful in 1m30s
All checks were successful
Deploy to Netlify / Deploy to Netlify (push) Successful in 1m30s
This commit is contained in:
parent
d4f1bb5dcd
commit
8b57952344
5 changed files with 149 additions and 27 deletions
|
|
@ -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/)));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 l’application 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 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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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
42
app/lib/notification.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue