Skip to content

feat: Notification Preferences & Delivery Settings (Closes #242) - #263

Open
olubukolatanko209-doc wants to merge 1 commit into
Lumina-eX:mainfrom
olubukolatanko209-doc:feat/242-notification-preferences
Open

olubukolatanko209-doc wants to merge 1 commit into
Lumina-eX:mainfrom
olubukolatanko209-doc:feat/242-notification-preferences

Conversation

@olubukolatanko209-doc

Copy link
Copy Markdown

Notification Preferences & Delivery Settings

Closes #242

Overview

This PR implements per-user notification preferences with full backend persistence, a redesigned settings UI, and graceful handling of all loading, validation, and error states. Users can now independently configure in-app and email notifications for each event category, choose a delivery cadence, and reset to defaults.

What Changed

Database — lib/db/migrations/011_notification_preferences.sql

  • New notification_preferences table with:
    • Per-event-category toggles for in-app and email channels (milestones, payments, disputes, deadlines)
    • Global channel master switches (email_enabled, push_enabled, in_app_enabled)
    • Delivery cadence column (instant | daily | weekly) with CHECK constraint
    • UNIQUE(user_id) constraint; missing rows treated as platform defaults
    • Index on user_id for fast lookups

Backend service — lib/notification-preferences.ts

  • Typed NotificationPreferences interface with 15 fields
  • getNotificationPreferences(userId) — returns stored prefs or DEFAULT_PREFERENCES on first visit
  • updateNotificationPreferences(userId, input) — UPSERT with partial merge (only supplied fields are updated)
  • resetNotificationPreferences(userId) — DELETE row, returns defaults
  • NotificationPreferencesError with typed error codes

API — app/api/notifications/preferences/route.ts

  • GET /api/notifications/preferences — fetch preferences (60 req/min rate limit)
  • PUT /api/notifications/preferences — update preferences (30 req/min), Zod-validated, all fields optional, rejects empty bodies with 400
  • DELETE /api/notifications/preferences — reset to defaults (10 req/min)
  • All handlers use withAuth + resolveUserIdByWallet for authentication
  • Consistent error shape { error, code } with appropriate HTTP status codes (400, 401, 404, 422, 429, 503)
  • Cache-Control: private, no-store on GET responses

Frontend — app/settings/settings-client.tsx

  • Preferences loaded from API on mount via cancellable useEffect
  • Delivery channels section: Email / Push / In-app master toggle cards
  • Event categories section: table with per-category rows (Milestones, Payments, Disputes, Deadlines) x In-app / Email toggle columns; disabling a channel greys out its column
  • Delivery cadence select: Instant / Daily digest / Weekly digest
  • Security alerts section: clearly marked as Always on with a badge -- non-dismissible per platform policy
  • Save status indicator in footer: Saving... -> Preferences saved -> error message -> idle
  • Reset to defaults button with confirmation dialog before calling DELETE
  • Loading skeleton while prefs are fetching
  • Error banner with graceful fallback to platform defaults on load failure
  • Removed previous localStorage-only save pattern; preferences now persist server-side

Acceptance Criteria

# Criterion Status
1 Preferences loaded from backend when settings page is accessed Done - useEffect calls GET /api/notifications/preferences on mount
2 Changes persist after refresh and across sessions Done - server-side UPSERT via PUT; no localStorage dependency
3 Users can independently configure in-app and email Done - per-category x per-channel toggle matrix
4 Essential security alerts distinguished and non-dismissible Done - dedicated Always on section with badge, no toggle
5 Save and reset actions work with feedback Done - save status indicator + reset confirmation dialog
6 Preference-saving status displayed (saving, saved, failed) Done - NotifSaveStatus state machine: idle -> saving -> saved/error
7 Loading, validation, and server errors handled gracefully Done - loading skeleton, load error banner, save error message, rate limit (429) handling

Files Changed

File Change
lib/db/migrations/011_notification_preferences.sql New migration
lib/notification-preferences.ts New service layer
app/api/notifications/preferences/route.ts New API route (GET/PUT/DELETE)
app/settings/settings-client.tsx Rewritten notification preferences UI

Testing

  • TypeScript type check passes with zero errors in all new/modified files (pre-existing next/* module resolution issues in the repo are unrelated)
  • All imports verified against installed dependencies (zod v3.25.76, lucide-react v0.454, @/lib/db, @/lib/auth/middleware, @/lib/security/rateLimit, @/components/ui/separator)
  • Follows existing codebase patterns: withAuth middleware, resolveUserIdByWallet, enforceRateLimit, Zod validation, snake_case to camelCase DB mapping

Notes

  • The migration uses IF NOT EXISTS guards for idempotency
  • The UPSERT uses ON CONFLICT (user_id) DO UPDATE for atomic partial updates
  • Rate limits are scoped per-wallet-address to prevent abuse
  • Security alert notifications (sign-in alerts, wallet activity) are always delivered regardless of user preferences -- this is enforced at the UI level and should also be enforced in the notification dispatch layer in a follow-up PR

…#242)

Implements issue Lumina-eX#242 — Notification Preferences & Delivery Settings.

## Database
- Add migration 011_notification_preferences.sql with a new
  notification_preferences table:
  - Per-event-category toggles for in-app and email channels
    (milestones, payments, disputes, deadlines)
  - Global channel master switches (email_enabled, push_enabled,
    in_app_enabled)
  - Delivery cadence column (instant | daily | weekly)
  - UNIQUE constraint on user_id; missing row treated as defaults

## Backend — lib/notification-preferences.ts
- New service layer with typed NotificationPreferences interface
- getNotificationPreferences(userId): returns stored prefs or defaults
- updateNotificationPreferences(userId, input): UPSERT with partial merge
- resetNotificationPreferences(userId): DELETE row, returns defaults
- NotificationPreferencesError with typed error codes

## API — app/api/notifications/preferences/route.ts
- GET  /api/notifications/preferences — fetch preferences (60 req/min)
- PUT  /api/notifications/preferences — update preferences (30 req/min),
  Zod-validated, all fields optional, rejects empty bodies
- DELETE /api/notifications/preferences — reset to defaults (10 req/min)
- withAuth middleware, resolveUserIdByWallet, enforceRateLimit on all
  three handlers; consistent error shape { error, code }

## Frontend — app/settings/settings-client.tsx
- Load preferences from API on mount via useEffect (cancellable)
- Global channel toggles: Email / Push / In-app master switches
- Per-event-category table: Milestones / Payments / Disputes / Deadlines
  × In-app / Email columns; channel toggles disable their column
- Delivery cadence select: instant / daily / weekly
- Security alerts section: clearly marked Always on, non-dismissible
- Save status indicator: Saving… / Preferences saved / error message
- Reset to defaults: confirmation dialog before DELETE call
- Loading skeleton while prefs are fetching
- Error banner with graceful fallback to defaults on load failure
@drips-wave

drips-wave Bot commented Oct 17, 2026

Copy link
Copy Markdown

@olubukolatanko209-doc Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@olubukolatanko209-doc olubukolatanko209-doc changed the title feat: Notification Preferences & Delivery Settings (#242) feat: Notification Preferences & Delivery Settings (Closes #242) Oct 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Notification Preferences & Delivery Settings

2 participants