Skip to main content

Notification Panel Component

Notification Panel Component

Component File: src/features/auth/components/NotificationPanel.tsx Usage: Display system notifications Access Level: All authenticated users (hidden for volunteer/donor portal users) Last Updated: March 13, 2026

---

Recent Updates (March 13, 2026)

  • Cross-Tenant Notification Leak Fix (P0) — Fixed 8 notification creation paths in stripe-webhook/index.ts, send-donation-alert/index.ts, and the notify_on_new_donation DB trigger that used globally-scoped .eq('role', 'parent_org') queries without organization_id filtering. This caused ALL parent_org users across ALL tenants to receive notifications for every other tenant's Stripe events (webhook failures, refunds, payment failures, card expirations, event registrations, donation alerts). Fix: each path now resolves the parent org from organizations.parent_organization_id before querying organization_users. Deleted 18 leaked notification rows across 6 affected users. `send-webhook-failure-alert` is email-only to internal ops (WEBHOOK_FAILURE_OPS_EMAIL / default inbox); it does not insert in-app notifications—`logWebhookFailure` in stripe-webhook still creates scoped in-app `webhook_failure` rows for parent org admins.
  • `webhook_failure` Alert CategorygetCategoryFromType now explicitly maps webhook_failure to 'alert' category (AlertTriangle icon, warning badge) instead of falling through to 'system' (Package icon).
  • `NotificationType` Union Completeness — Added webhook_failure, payment_failed, recurring_payment_failed, card_expiring to the TypeScript union type in notifications.ts.

Previous Updates (March 6, 2026)

  • Orphaned Subscription Auto-Cancel (Webhook)handleInvoicePaymentFailed in stripe-webhook/index.ts now auto-cancels orphaned subscriptions on failure. If a subscription is past_due or incomplete with zero paid invoices, it's cancelled immediately and the notification is suppressed. This is the safety net for subscriptions created by abandoned payment forms that slip past frontend cleanup.
  • DonationsManager Orphaned-Subscription Leak FixedDonationsManager.tsx called createPaymentIntent for recurring embedded payments but never stored the returned subscriptionId or called cancelAbandonedSubscription() on cancel. Added pendingSubscriptionId state + cancel-on-abandon flow (matching DonorLandingPage.tsx and DonationForm.tsx pattern).
  • Stripe Cleanup — Cancelled 20 orphaned/stale subscriptions (13 for Jacqueline Rae cus_SfZLQfAvXrT0zu, 7 for Steven Leipzig cus_TsOcwfP5uT46Uw including his legitimate $1.00/month InFocus sub — cancelled indefinitely per admin request). Deleted 15 spam recurring_payment_failed notification rows from March 6 retry wave.

Previous Updates (February 27, 2026)

  • Donation Notification Deep-Link Fix — Fixed critical bug where clicking a donation notification did not open the donor's profile. Root cause: navigateTo('donors', 'donors') used the pre-rename tool slug; after the slug standardization, the correct slug is 'donor-management'. DonorsCRM never mounted, so pendingSearchResult was never consumed. Fix: Changed both call sites (payment alert + general donor notification) to navigateTo('donors', 'donor-management').
  • Per-Notification Delete Removed — Removed individual hover trash icons from each notification row. Deletion now uses the single-flow pattern: trash icon in header enters selection mode → select items → confirm delete. This reduces visual clutter and matches the playbook §15 deletion UX spec.
  • Auto-Read on Open — Opening the notification panel now automatically marks all unread notifications as read after 1.5 seconds. The manual "Mark all read" button has been removed since it's redundant. The brief delay lets the user see which notifications were new before the unread dot fades.
  • Close Button WCAG Fix — Close button enlarged from h-8 w-8 to min-h-[44px] min-w-[44px] per WCAG 2.5.5 touch target guidelines (playbook §23).

Previous Updates (February 27, 2026)

  • Todo Notification Deep-Link — Clicking a todo_overdue, todo_due_soon, or todo_assigned notification now navigates to the dashboard and highlights the specific to-do item (scrolls into view with 3-second highlight). Previously, these notifications linked to /dashboard with no deep-link, so users had no way to find the referenced to-do. Notification links now include ?todoId={id} and handleNotificationClick extracts the ID and calls setHighlightedTodoId() — matching the existing PendingItemsPanel pattern.
  • Todo Notification Terminology — Notification titles/messages changed from "Task" to "To-Do" to match the UI component naming ("To-Do List"). E.g., "Overdue Task" → "Overdue To-Do", "Task Due Tomorrow" → "To-Do Due Tomorrow".

Previous Updates (February 18, 2026)

  • Clear All / Bulk Delete Fix — Fixed critical bug where "Clear all" and bulk delete confirmation buttons did nothing. Root cause: AlertDialogAction (Radix primitive) auto-closes the dialog on click, unmounting the component before the async onClick handler can execute. Fix: Replaced AlertDialogAction with a regular Button in both confirmation dialogs so the dialog stays open until the async operation completes, then closes programmatically. Panel now auto-closes after a successful "Clear all" since there's nothing left to show.
  • Card Expiry Donor Email Reminders — New donor-facing email system sends branded HTML reminders at 30 days, 14 days, and on expiration. Emails include "Update Payment Method" CTA linking to https://alignmint.app (donor portal OTP login → Stripe billing portal). Dedup via email_logs (card_expiry_reminder type). Suppressed if card was updated in last 7 days. Cron: check-expiring-cards daily at 9 AM UTC.
  • Payment Failure Deep-Link Fix — Fixed critical bug where clicking a payment_failed, recurring_payment_failed, or card_expiring notification did not navigate to the correct page. Root cause: notification link stored as /donors/{uuid} (path segment) but click handler only parsed ?donorId= query params. Now extracts donor UUID from path segments via regex.
  • Role-Aware Routing — Payment failure notifications now route parent_org users to Donor Payment Management (administrationdonor-payment-management) with donor context passed via navigateTo(..., { donorId }) (and setSelectedDonor for compatibility). Fund users route to DonorsCRM (donorsdonor-management) with donor profile opened via pendingSearchResult.
  • Alert Categorypayment_failed, recurring_payment_failed, and card_expiring notification types now correctly categorized as 'alert' (⚠️ AlertTriangle icon, warning badge) instead of falling through to 'system'.

Previous Updates (February 14, 2026)

  • Per-Notification Delete — Historical note from Jan 2026 (superseded on Feb 27 by selection-mode-only delete flow)
  • Clear All Button — Historical note from Jan 2026 (superseded on Feb 27; current UX has no separate Clear All button)
  • Bulk Delete Performance — Replaced N parallel DELETE requests with a single .in('id', ids) bulk query via new deleteNotificationsBulk() DB function
  • Error Recovery — If a delete or clear-all operation fails at the DB level, the panel now re-fetches notifications from the database to restore accurate state (prevents optimistic update divergence)
  • i18n Compliance — All toast messages in useNotificationActions now use t() translation keys instead of hardcoded English strings. New keys added to all 6 locale files: clearAll, clearAllTitle, clearAllConfirm, clearAllSuccess, clearAllFailed

Previous Updates (January 18, 2026)

  • Fixed Donation Notification Deep-Link - Fixed bug where clicking a donation notification navigated to the Donor Hub landing page instead of the specific donor's profile. Root cause: navigateTo('donors', null) was passing null as the tool, so DonorsCRM never mounted to process the pendingSearchResult. Fix: Changed to navigateTo('donors', 'donors') so DonorsCRM mounts and opens the donor profile.

Previous Updates (January 17, 2026)

  • Fixed Navigation Bug - Fixed critical bug where clicking "View" on a notification did not navigate to the target page. Root cause: markNotificationAsRead was using wrong column name (is_read instead of read), causing a database error that prevented navigation.

Previous Updates (January 16, 2026)

  • Database-Synced Actions - All notification actions (mark as read, delete) now properly sync to the database via useNotificationActions hook

Previous Updates (January 13, 2026)

  • Click-to-Navigate - Clicking a notification now navigates to the relevant page (if link exists)
  • Selective Delete - Users can now select specific notifications to delete instead of clearing all at once
  • Selection Mode - Click the trash icon to enter selection mode, then select individual notifications
  • Confirmation Dialog - Delete action now requires confirmation before permanently removing notifications
  • Select All/Deselect All - Quick toggle to select or deselect all notifications

---

Overview

The Notification Panel displays system notifications, alerts, and updates to users. It shows donation alerts, approval requests, system messages, and activity updates. Users can mark notifications as read or selectively delete them.

UI Features

Notification Types

  • Donation Alerts - New donations received
  • Approval Requests - Pending approvals (expenses, reimbursements, hours)
  • System Messages - Important system updates
  • Activity Updates - Team activity notifications

Features

  • Unread count badge
  • Mark as read (individual or all)
  • Click-to-navigate — Clicking a notification navigates to the related page
  • Auto-read on open — All unread notifications are marked as read 1.5 seconds after opening the panel
  • Multi-select delete — Click the trash icon in header to enter selection mode with checkboxes and confirmation dialog
  • Select all / Deselect all toggle
  • Real-time connection status indicator (Wifi icon)
  • Timestamp display (relative: "just now", "5m ago", "2h ago", "3d ago")

Notification Structure

Notification Object

  • id (uuid) - Unique identifier
  • type (enum) - 'donation', 'approval', 'system', 'activity'
  • title (string) - Notification title
  • message (string) - Notification message
  • read (boolean) - Read status
  • timestamp (datetime) - When created
  • link (string, nullable) - Navigation link
  • action (string, nullable) - Action button text

State Management

Zustand Store (notificationSlice)

  • notifications - Array of notifications
  • unreadCount - Count of unread notifications
  • notificationsRealtimeConnected - Real-time connection status

useNotificationActions Hook

The useNotificationActions hook provides database-synced actions:

import { useNotificationActions } from '../../../hooks/useNotificationActions';

const { markAsRead, markAllAsRead, deleteNotifications, clearAll } = useNotificationActions();
| Function | Description |
|----------|-------------|
| `markAsRead(id)` | Mark single notification as read (syncs to DB) |
| `markAllAsRead()` | Mark all notifications as read (syncs to DB) |
| `deleteNotifications(ids)` | Delete specific notifications by ID array |
| `clearAll()` | Delete all notifications for current user |

Note: These functions update local state immediately (optimistic update) then persist to the database. If the DB operation fails, the hook automatically re-fetches notifications from the database to restore accurate state. deleteNotifications uses a single bulk .in('id', ids) query for performance.

Navigation Links by Notification Type

| Type | Navigation Target |
|------|-------------------|
| `expense_submitted` | `/fund-accounting?tool=expenses` |
| `expense_approved` | `/tools?tool=reimbursements` |
| `expense_revision_requested` | `/tools?tool=reimbursements` |
| `waiver_pending` | `/tools?tool=sign-waivers` |
| `waiver_signed` | `/administration?tool=waiver-management` |
| `todo_assigned` | `/dashboard?todoId={id}` → Navigates to dashboard, highlights specific to-do via `setHighlightedTodoId` |
| `todo_due_soon` | `/dashboard?todoId={id}` → Same as `todo_assigned` |
| `todo_overdue` | `/dashboard?todoId={id}` → Same as `todo_assigned` |
| `file_uploaded` | `/my-workspace/my-files` (path sync; internal tool `dropbox`) |
| `donation_received` | `/donors?tool=donor-management&donor={donor_id}` → Opens donor profile via `pendingSearchResult` |
| `payment_failed` | Parent org → `administration?tool=donor-payment-management` (via navigation context + selected donor). Fund user → `donors?tool=donor-management` (via `pendingSearchResult`) |
| `recurring_payment_failed` | Same as `payment_failed` |
| `card_expiring` | Same as `payment_failed` |

Related Documentation


Synced from IFMmvp-Frontend documentation: pages/components/03-NOTIFICATION-PANEL.md

Ready to Get Started?

See how Alignmint can simplify your nonprofit's operations. Schedule a free demo with our team and we'll walk you through everything.

Questions? Email us at steven@getalignmint.org

Ready to get started?Start Plus Trial