brackt/plan.md
Claude c368110b3d
Add push notifications implementation plan
Documents the approach for adding a browser Notification API toggle
to the draft page sidebar, including files to create/modify and
edge cases to handle.

https://claude.ai/code/session_0149MvVUYDY6pFUAV1fL4K69
2026-02-20 16:48:41 +00:00

114 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

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.

# Plan: Push Notifications Toggle on Draft Page
## Overview
Add a toggle to the draft page sidebar that enables browser push notifications for draft events. When enabled, the user receives desktop notifications when:
1. **It's their turn to pick** (highest priority)
2. **A pick is made by any team** (so they can track the draft from another tab/window)
This uses the **Browser Notification API** (`Notification.requestPermission()` + `new Notification()`), which is the right fit because:
- Users already have an active Socket.IO connection on the draft page
- The primary use case is "I have the draft tab open but I'm looking at another tab/window"
- No server-side push infrastructure, service workers, or VAPID keys needed
- Simple, client-side-only feature — no database changes required
## Implementation Steps
### 1. Create `NotificationSettings` component
**New file:** `app/components/NotificationSettings.tsx`
A toggle switch component (mirroring `AutodraftSettings.tsx` pattern) that:
- Shows a Switch toggle labeled "Notifications"
- On first enable: calls `Notification.requestPermission()` to request browser permission
- Stores the enabled/disabled preference in `localStorage` (keyed per season, e.g. `draftNotifications-{seasonId}`)
- Shows a small help message if the browser permission is `denied`
- Handles the case where the Notification API is unavailable (e.g. insecure context) by hiding the toggle entirely
- Receives no server props — this is entirely client-side state
**Props:**
```typescript
interface NotificationSettingsProps {
seasonId: string;
enabled: boolean;
onEnabledChange: (enabled: boolean) => void;
}
```
### 2. Create `useDraftNotifications` hook
**New file:** `app/hooks/useDraftNotifications.ts`
Custom hook that:
- Manages the notification enabled/disabled state (persisted to `localStorage`)
- Checks browser support for Notification API on mount
- Returns `{ notificationsEnabled, setNotificationsEnabled, isSupported }`
- The actual notification firing will happen in the draft page component where socket events are already handled
### 3. Wire into the draft page
**Modify:** `app/routes/leagues/$leagueId.draft.$seasonId.tsx`
- Import and use `useDraftNotifications` hook
- In the existing `handlePickMade` socket handler (line ~359), add logic:
- If notifications are enabled and `document.hidden` (tab is not focused):
- Fire a `new Notification()` with the pick info (e.g. "Team X drafted Player Y")
- If the pick means it's now the current user's turn, fire a more urgent notification ("It's your turn to pick!")
- Pass notification state down to the `QueueSection` component
### 4. Add toggle to the sidebar
**Modify:** `app/components/draft/QueueSection.tsx`
- Add the `NotificationSettings` component below the `AutodraftSettings` component (inside the same sidebar section)
- Accept new props for notification state
## UI Placement
The toggle will sit in the sidebar's queue section, right below the existing Autodraft toggle:
```
┌─────────────────────────┐
│ My Queue │
│ ┌─────────────────────┐ │
│ │ 1. Player A [×] │ │
│ │ 2. Player B [×] │ │
│ └─────────────────────┘ │
│ │
│ ─── Autodraft ──── [·] │
│ ○ Next Pick │
│ ○ While On │
│ │
│ ─── Notifications ─ [·] │ ← NEW
│ │
└─────────────────────────┘
```
## Notification Content
| Event | Condition | Title | Body |
|-------|-----------|-------|------|
| Pick made | Tab not focused, notifications on | "{League} Draft" | "{TeamName} picked {ParticipantName}" |
| Your turn | Tab not focused, notifications on, it's now user's turn | "{League} Draft" | "It's your turn to pick!" |
Notifications will include `{ tag: "draft-{seasonId}" }` so they replace each other rather than stacking.
## Edge Cases Handled
- **Notification API not available** (HTTP, unsupported browser): Toggle hidden entirely via `isSupported` check
- **Permission denied**: Show inline message "Notifications blocked by browser. Update in browser settings."
- **Permission not yet requested**: Request on first toggle enable
- **Tab is focused**: Don't fire notifications (user can already see the draft)
- **SSR**: All Notification API checks guarded with `typeof window !== "undefined"`
## Files Changed
| File | Change |
|------|--------|
| `app/hooks/useDraftNotifications.ts` | **New** — hook for notification state + localStorage persistence |
| `app/components/NotificationSettings.tsx` | **New** — toggle UI component |
| `app/components/draft/QueueSection.tsx` | **Modified** — add NotificationSettings below AutodraftSettings |
| `app/routes/leagues/$leagueId.draft.$seasonId.tsx` | **Modified** — use hook, fire notifications in socket handlers, pass props to QueueSection |
## What This Does NOT Include
- **Server-side web push** (VAPID keys, web-push library, service worker, push subscriptions table) — not needed for the core use case. Can be added later if users want notifications when the tab is completely closed.
- **Sound notifications** — could be added as a follow-up.
- **Granular notification preferences** (per-event toggles) — a single on/off toggle is sufficient for v1. Can be expanded later.
- **Database storage** — preference stored in localStorage since it's device-specific and doesn't need to sync across devices.