Vitron-Front/app/components/notifications/PushSettingToggle.tsx
Arda Samadi fef53fd003 feat(notifications): web push opt-in + unread nav badges
Surfaces three events without a full notification center:
- service worker (public/sw.js) gains push + notificationclick handlers
  (RTL, focuses an existing tab or opens the target URL).
- usePushNotifications hook: permission + VAPID subscribe/unsubscribe
  against the backend push endpoints.
- opt-in UI both ways: a one-time dismissible post-login banner
  (PushPermissionBanner, rendered in MyLayout) and a permanent toggle
  (PushSettingToggle) on buyer profile settings and seller store settings.
- useBadgeCounts polls /api/notif/v1/badges/; small red badges on the
  bottom-nav profile icon (chat + order updates), the profile sidebar
  (پیام‌ها / سفارش‌ها) and the seller dashboard sidebar. Visiting orders
  clears its badge.

Degrades gracefully: if the backend endpoints are absent the counts fall
back to zero and nothing throws.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 14:02:56 +03:30

51 lines
2.1 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { usePushNotifications } from "~/hooks/usePushNotifications";
/**
* A settings row with a switch to enable/disable web push notifications.
* Mirrors the existing theme-toggle row styling on the settings pages.
*/
export function PushSettingToggle({ className = "" }: { className?: string }) {
const { supported, permission, isSubscribed, subscribe, disable, busy } =
usePushNotifications();
const blocked = permission === "denied";
const checked = isSubscribed && permission === "granted";
const onToggle = () => {
if (busy || blocked) return;
if (checked) {
disable();
} else {
subscribe();
}
};
return (
<div className={className}>
<div className="flex w-full items-center justify-between">
<div className="flex flex-col gap-1">
<p className="text-R12 lg:text-B16 font-bold">اعلانها</p>
<p className="text-R10 lg:text-R12 text-GRAY">
{blocked
? "اعلان‌ها در مرورگر مسدود شده‌اند. از تنظیمات مرورگر اجازه دهید."
: !supported
? "مرورگر شما از اعلان پشتیبانی نمی‌کند."
: "دریافت اعلان برای پیام‌ها و وضعیت سفارش‌ها"}
</p>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={checked}
onChange={onToggle}
disabled={busy || blocked || !supported}
className="sr-only peer"
aria-label="اعلان‌ها"
/>
<div className="w-11 h-6 bg-WHITE2 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-WHITE after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-WHITE after:border-GRAY3 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-VITROWN_BLUE border peer-disabled:opacity-50"></div>
</label>
</div>
</div>
);
}