fix: ui fixes

This commit is contained in:
gosangam
2026-06-11 00:32:04 +05:30
parent 4b159cb4d9
commit ead8ad02e8
16 changed files with 769 additions and 266 deletions
+27 -1
View File
@@ -110,10 +110,23 @@ export async function enablePush(): Promise<void> {
throw new Error("Notification permission was not granted.");
}
const reg = await navigator.serviceWorker.register("/sw.js");
// The BFF origin rides along as a query param so the service worker can
// re-register rotated subscriptions (pushsubscriptionchange) on its own.
const reg = await navigator.serviceWorker.register(
`/sw.js?bff=${encodeURIComponent(BFF_BASE)}`,
);
await navigator.serviceWorker.ready;
let sub = await reg.pushManager.getSubscription();
// A subscription minted under a previous VAPID key still looks alive
// locally, but every send fails with a credential mismatch — drop it and
// subscribe fresh under the current key.
if (sub && !matchesServerKey(sub, public_key)) {
await sub.unsubscribe().catch(() => {
/* stale anyway — subscribe() below replaces it */
});
sub = null;
}
if (!sub) {
sub = await reg.pushManager.subscribe({
userVisibleOnly: true,
@@ -134,6 +147,19 @@ export async function enablePush(): Promise<void> {
if (!res.ok) throw new Error("Could not save your subscription.");
}
/**
* Whether an existing subscription was created with the server's current
* VAPID public key. Browsers that don't expose applicationServerKey (older
* Safari) return null — treat that as a match rather than churn the sub.
*/
function matchesServerKey(sub: PushSubscription, publicKey: string): boolean {
const current = sub.options?.applicationServerKey;
if (!current) return true;
const a = new Uint8Array(current);
const b = urlBase64ToUint8Array(publicKey);
return a.length === b.length && a.every((v, i) => v === b[i]);
}
/** Disable push: unsubscribe locally and remove the subscription server-side. */
export async function disablePush(): Promise<void> {
const reg = await registration();
+2 -9
View File
@@ -143,15 +143,8 @@ export const sadhna = {
/** Acknowledge pending celebrations after showing the confetti. */
ackRewards: () => call<void>("/sadhna/rewards/ack", { method: "POST" }),
/** Whether daily sādhana reminders are enabled for the caller. */
reminderPrefs: () => call<{ enabled: boolean }>("/sadhna/reminders"),
/** Toggle daily sādhana reminders. */
setReminderPrefs: (enabled: boolean) =>
call<{ enabled: boolean }>("/sadhna/reminders", {
method: "PUT",
body: JSON.stringify({ enabled }),
}),
// Reminder prefs moved to the notification matrix —
// see `notifications.prefs()` in lib/social.ts.
/** Report the browser's IANA timezone so reminders fire at local time. */
setTimezone: (timezone: string) =>
+14 -6
View File
@@ -505,18 +505,26 @@ export const notifications = {
markAllRead() {
return call<void>("/notifications/read", { method: "POST" });
},
/** Email-notification preferences (currently aggregated like emails). */
emailPrefs() {
return call<{ like_emails: boolean }>("/notifications/email-prefs");
/** The event × channel notification matrix (Settings → Notifications). */
prefs() {
return call<{ prefs: NotificationPrefs }>("/notifications/prefs");
},
setEmailPrefs(likeEmails: boolean) {
return call<{ like_emails: boolean }>("/notifications/email-prefs", {
/** Write one matrix cell; returns the full re-resolved matrix. */
setPref(event: PrefEvent, channel: PrefChannel, enabled: boolean) {
return call<{ prefs: NotificationPrefs }>("/notifications/prefs", {
method: "PUT",
body: JSON.stringify({ like_emails: likeEmails }),
body: JSON.stringify({ event, channel, enabled }),
});
},
};
// ─── Notification preferences ──────────────────────────────────────────
export type PrefEvent = "follows" | "post_interactions" | "sadhna_reminders";
export type PrefChannel = "in_app" | "email" | "push";
/** event → channel → enabled, as resolved by the BFF (defaults on). */
export type NotificationPrefs = Record<PrefEvent, Record<PrefChannel, boolean>>;
export const relationships = {
/** Send a follow REQUEST. The target must accept before it counts as a
* follow — the returned relationship reflects `follow_requested: true`