# Notifications System Real-time in-app notifications for Next.js 14 — Supabase Realtime channel, unread badge, mark-as-read, archive, bulk ops, Web Push subscribe/unsubscribe, and a bell dropdown component, all in one file. ## What's included **Server functions (service role)** - `createNotification(userId, type, title, body, opts?)` — inserts a notification row; auto-applies a type emoji as icon if none provided; returns the inserted row - `createPurchaseNotification(userId, blockName)` — pre-built purchase confirmation notification linking to `/dashboard` - `createInviteNotification(userId, workspaceName, token)` — pre-built invite notification linking to `/invite/{token}` - `notifyAllUsers(type, title, body, actionUrl?)` — broadcasts to every user in batches of 500 - `deleteOldNotifications(olderThanDays?)` — prunes read notifications older than N days (default 90); safe to run on a cron **Client hook** - `useNotifications(userId)` — returns `{ items, unread, loading, markRead, markAllRead, archive, archiveAll, refresh }`; loads the 50 most recent non-archived notifications on mount; subscribes to a Supabase Realtime channel for live `INSERT` and `UPDATE` events; fires a native `Notification` on insert if browser permission is granted **Web Push** - `requestPushPermission()` — wraps `Notification.requestPermission()`; returns the permission state - `subscribeToPush(userId)` — registers a service worker push subscription using `NEXT_PUBLIC_VAPID_PUBLIC_KEY`; POSTs the endpoint + keys to `/api/push/subscribe` - `unsubscribeFromPush()` — unsubscribes the current registration and sends a `DELETE` to `/api/push/subscribe` **UI** - `NotificationBell` — bell button with unread badge (caps at `99+`); click opens a dropdown with the notification list, mark-all-read button, per-item archive (✕), and click-to-navigate on notifications with `action_url`; accepts `position: 'left' | 'right'` for dropdown alignment **Types** - `NotifType` — `'info' | 'success' | 'warning' | 'error' | 'purchase' | 'invite'` - `AppNotification` — full notification row interface ## Setup ### 1. Install dependencies ```bash npm install @supabase/supabase-js ``` ### 2. Environment variables ``` NEXT_PUBLIC_SUPABASE_URL=your Supabase project URL NEXT_PUBLIC_SUPABASE_ANON_KEY=anon public key SUPABASE_SERVICE_ROLE_KEY=service role key (server-only) NEXT_PUBLIC_VAPID_PUBLIC_KEY=your VAPID public key # Web Push only ``` Generate VAPID keys: `npx web-push generate-vapid-keys` ### 3. Database ```sql CREATE TABLE notifications ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, type TEXT NOT NULL DEFAULT 'info' CHECK (type IN ('info','success','warning','error','purchase','invite')), title TEXT NOT NULL, body TEXT NOT NULL, action_url TEXT, icon TEXT, read BOOLEAN NOT NULL DEFAULT false, archived BOOLEAN NOT NULL DEFAULT false, metadata JSONB, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); ALTER TABLE notifications ENABLE ROW LEVEL SECURITY; CREATE POLICY "notif_own_select" ON notifications FOR SELECT USING (user_id::text = auth.uid()::text); CREATE POLICY "notif_own_update" ON notifications FOR UPDATE USING (user_id::text = auth.uid()::text); CREATE POLICY "notif_own_delete" ON notifications FOR DELETE USING (user_id::text = auth.uid()::text); CREATE INDEX notif_user_unread_idx ON notifications(user_id, created_at DESC) WHERE read = false AND archived = false; CREATE INDEX notif_user_all_idx ON notifications(user_id, created_at DESC) WHERE archived = false; CREATE TABLE push_subscriptions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, endpoint TEXT UNIQUE NOT NULL, p256dh TEXT NOT NULL, auth_key TEXT NOT NULL, user_agent TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); ALTER TABLE push_subscriptions ENABLE ROW LEVEL SECURITY; CREATE POLICY "push_own" ON push_subscriptions FOR ALL USING (user_id::text = auth.uid()::text); ``` ### 4. Enable Realtime on the notifications table In the Supabase dashboard: **Database → Replication → supabase_realtime publication → Add table → notifications**. ## Usage examples ```tsx // Navbar — wire up the bell 'use client' import { useNotifications, NotificationBell } from '@/blocks/notifications' import { useSession } from 'next-auth/react' export function Navbar() { const { data: session } = useSession() const notifications = useNotifications(session?.user?.id ?? null) return ( ) } ``` ```ts // After a successful purchase (webhook handler) import { createPurchaseNotification } from '@/blocks/notifications' await createPurchaseNotification(userId, 'Auth System') ``` ```ts // Broadcast an announcement to all users (admin route) import { notifyAllUsers } from '@/blocks/notifications' await notifyAllUsers('info', 'New block dropped 🎉', 'Rate Limiting is now live.', '/blocks/ratelimit') ``` ## Notes - `useNotifications` creates a new Supabase client instance on every render — extract the client creation outside the hook or use a shared instance if you render this hook in multiple components simultaneously - The Web Push flow requires you to build `/api/push/subscribe` yourself — the block handles the browser side only; that route needs to save the subscription to `push_subscriptions` (POST) and delete it (DELETE); actually sending push messages requires a server-side `web-push` library call, which is also not included - `notifyAllUsers` fetches all user IDs into memory before inserting — at very large user counts this will be slow and memory-hungry; move to a database function or background job for anything over ~10k users - The bell dropdown closes only when you click the bell again — there is no click-outside-to-close handler; add a `useEffect` with a `document` click listener if you need that behaviour