Files
2026-06-08 16:09:09 +05:30

139 lines
4.3 KiB
TypeScript

"use client";
/**
* PushToggle — the settings control for browser push notifications. Reads the
* current state on mount (supported? permission? server-configured?
* subscribed?) and lets the user enable/disable. Renders as a settings row
* matching the rest of /settings.
*/
import { useEffect, useState } from "react";
import { toast } from "sonner";
import {
disablePush,
enablePush,
isPushEnabled,
needsIOSInstall,
pushConfigured,
pushPermission,
pushSupported,
} from "@/lib/push";
type State =
| "loading"
| "unsupported"
| "ios-install" // iOS: must add to Home Screen first
| "unavailable" // server has no VAPID keys
| "denied" // browser permission blocked
| "on"
| "off";
export function PushToggle() {
const [state, setState] = useState<State>("loading");
const [busy, setBusy] = useState(false);
useEffect(() => {
let cancelled = false;
(async () => {
if (!pushSupported()) {
return finish(needsIOSInstall() ? "ios-install" : "unsupported");
}
if (pushPermission() === "denied") return finish("denied");
if (!(await pushConfigured())) return finish("unavailable");
finish((await isPushEnabled()) ? "on" : "off");
function finish(s: State) {
if (!cancelled) setState(s);
}
})();
return () => {
cancelled = true;
};
}, []);
async function toggle() {
setBusy(true);
try {
if (state === "on") {
await disablePush();
setState("off");
toast.success("Push notifications turned off");
} else {
await enablePush();
setState("on");
toast.success("Push notifications turned on");
}
} catch (err) {
const msg =
err instanceof Error ? err.message : "Something went wrong.";
// A fresh block surfaces as a permission error — reflect it.
if (pushPermission() === "denied") setState("denied");
toast.error(msg);
} finally {
setBusy(false);
}
}
const description = describe(state);
return (
<ul className="divide-y divide-border">
<li className="flex items-center justify-between gap-4 py-3">
<div className="min-w-0">
<span className="text-sm text-foreground">
Push notifications
</span>
<p className="text-xs text-muted-foreground mt-0.5">{description}</p>
</div>
<div className="shrink-0">
{state === "on" || state === "off" ? (
<button
type="button"
onClick={toggle}
disabled={busy}
aria-pressed={state === "on"}
className={[
"relative inline-flex h-6 w-11 items-center rounded-full transition-colors disabled:opacity-60",
state === "on" ? "bg-primary" : "bg-muted-foreground/30",
].join(" ")}
>
<span
className={[
"inline-block size-5 transform rounded-full bg-white shadow transition-transform",
state === "on" ? "translate-x-5" : "translate-x-0.5",
].join(" ")}
/>
</button>
) : (
<span className="text-xs text-muted-foreground">
{state === "loading"
? "…"
: state === "ios-install"
? null
: "Unavailable"}
</span>
)}
</div>
</li>
</ul>
);
}
function describe(state: State): string {
switch (state) {
case "on":
return "On — you'll be notified about follow requests and more, even when the app is closed.";
case "off":
return "Get notified about follow requests and more, even when the app is closed.";
case "denied":
return "Blocked in your browser. Enable notifications for this site in your browser settings, then reload.";
case "ios-install":
return "On iPhone & iPad, notifications work only from the Home Screen app. Tap the Share icon in Safari, choose “Add to Home Screen”, then open Prabhupāda Says from the new icon and turn this on.";
case "unsupported":
return "This browser doesn't support push notifications.";
case "unavailable":
return "Push notifications aren't available right now.";
default:
return "Checking…";
}
}