feat(notifications): implement Notifications page (#396) - #434
Conversation
…detail sheet, and live sidebar badge (#396) Replace the coming-soon redirect with a full notifications center at /me/notifications. Notifications are grouped into New/Earlier/Archived sections, clickable items open a BoundlessSheet with full details, Mark All as Read uses motion animations, and the sidebar badge reflects the live unread count via a Zustand store. Made-with: Cursor
|
@JamesVictor-O is attempting to deploy a commit to the Threadflow Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthroughThis PR implements a complete notifications center UI featuring four new React components for displaying notifications with real-time updates, a Zustand store for managing global unread counts, and integration with the sidebar badge. The main page replaces a redirect with a full client-side application that fetches, groups, and manages notifications with polling, mark-as-read functionality, and detail view support. Changes
Sequence DiagramsequenceDiagram
participant User
participant Page as NotificationsPage
participant Store as NotificationStore
participant Hooks as Custom Hooks
participant Sidebar as AppSidebar
User->>Page: Opens notifications page
Page->>Hooks: useNotifications() + useNotificationPolling()
Hooks-->>Page: Initial notifications list + polling started
Page->>Page: Group notifications into New/Earlier/Archived
Page-->>User: Render NotificationSection components
User->>Page: Click notification item
Page->>Page: Open NotificationDetailSheet
Page->>Hooks: Mark notification as read
Hooks->>Store: setUnreadCount(newCount) / decrementUnreadCount()
Store-->>Page: unreadCount updated
Page->>Sidebar: unreadCount synced (via useNotificationStore)
Sidebar-->>User: Badge count updated in real-time
User->>Page: Click "Mark All as Read"
Page->>Hooks: markAllAsRead()
Hooks->>Store: clearUnreadCount()
Store-->>Sidebar: unreadCount = 0
Sidebar-->>User: Badge disappears
Page-->>User: Unread indicators fade out
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (3)
app/me/notifications/page.tsx (1)
93-95: Silent error swallowing may hide issues.The
.catch(() => {})silently ignores errors when marking a notification as read. While this prevents UX disruption, consider at least logging the error for debugging:♻️ Optional: log the error
if (!notification.read) { - markNotificationAsRead([notification.id]).catch(() => {}); + markNotificationAsRead([notification.id]).catch(err => { + console.error('Failed to mark notification as read:', err); + }); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/me/notifications/page.tsx` around lines 93 - 95, The current call to markNotificationAsRead([notification.id]) swallows errors with .catch(() => {}); change this to catch and surface the error (e.g., .catch(err => console.error(...)) or use the app logger) so failures to mark notifications are logged; locate the call that checks notification.read and update the promise error handler for markNotificationAsRead to log the error and, optionally, show a non-blocking user notification.app/me/notifications/components/NotificationFeedItem.tsx (1)
73-78: Consider edge case:amount === 0won't render the badge.The truthiness check
notification.data.amount &&will not render the badge whenamountis0. If zero-amount notifications are valid and should display "$0", consider usingnotification.data.amount !== undefined && notification.data.amount !== nullinstead.If zero amounts are not expected or should be hidden, the current implementation is fine.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/me/notifications/components/NotificationFeedItem.tsx` around lines 73 - 78, The conditional rendering in NotificationFeedItem currently uses the truthy check notification.data.amount && which skips amount === 0; update the condition to explicitly test for presence (e.g., notification.data.amount !== undefined && notification.data.amount !== null or notification.data.amount != null) so a zero value will render the badge when zero-amount notifications should display; keep the existing badge markup inside the same block using the updated condition.app/me/notifications/components/NotificationDetailSheet.tsx (1)
127-128: Type assertion onamountassumes numeric type.The cast
(value as number).toLocaleString()will fail at runtime ifamountis not a number (e.g., if it's a string from the API). Consider a safer approach:🛡️ Safer amount formatting
{key === 'amount' - ? `$${(value as number).toLocaleString()}` + ? `$${Number(value).toLocaleString()}` : key === 'transactionHash'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/me/notifications/components/NotificationDetailSheet.tsx` around lines 127 - 128, In NotificationDetailSheet, the code path that formats amount (the conditional when key === 'amount') unsafely casts value with (value as number).toLocaleString(); change this to validate and coerce the value safely: check typeof value === 'number' first, else attempt to parse a numeric string (e.g., Number(value) or parseFloat) and guard isFinite before calling toLocaleString; if parsing fails, render a sensible fallback (original value or '--'). Update the conditional in NotificationDetailSheet where key === 'amount' to use this safe coercion/guarding logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@app/me/notifications/components/NotificationDetailSheet.tsx`:
- Around line 127-128: In NotificationDetailSheet, the code path that formats
amount (the conditional when key === 'amount') unsafely casts value with (value
as number).toLocaleString(); change this to validate and coerce the value
safely: check typeof value === 'number' first, else attempt to parse a numeric
string (e.g., Number(value) or parseFloat) and guard isFinite before calling
toLocaleString; if parsing fails, render a sensible fallback (original value or
'--'). Update the conditional in NotificationDetailSheet where key === 'amount'
to use this safe coercion/guarding logic.
In `@app/me/notifications/components/NotificationFeedItem.tsx`:
- Around line 73-78: The conditional rendering in NotificationFeedItem currently
uses the truthy check notification.data.amount && which skips amount === 0;
update the condition to explicitly test for presence (e.g.,
notification.data.amount !== undefined && notification.data.amount !== null or
notification.data.amount != null) so a zero value will render the badge when
zero-amount notifications should display; keep the existing badge markup inside
the same block using the updated condition.
In `@app/me/notifications/page.tsx`:
- Around line 93-95: The current call to
markNotificationAsRead([notification.id]) swallows errors with .catch(() => {});
change this to catch and surface the error (e.g., .catch(err =>
console.error(...)) or use the app logger) so failures to mark notifications are
logged; locate the call that checks notification.read and update the promise
error handler for markNotificationAsRead to log the error and, optionally, show
a non-blocking user notification.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (6)
app/me/notifications/components/NotificationDetailSheet.tsxapp/me/notifications/components/NotificationFeedItem.tsxapp/me/notifications/components/NotificationSection.tsxapp/me/notifications/page.tsxcomponents/app-sidebar.tsxlib/stores/notification-store.ts
Summary
Resolves #396 — Implementation of "Notifications" Page (
app/me/notifications)This PR replaces the
/me/notificationscoming-soon redirect with a fully functional, real-time notification center.What's implemented:
BoundlessSheetfrom@/components/sheet/displaying the full notification content, metadata (organization, hackathon, project, amounts, roles, etc.), and timestampsAnimatePresencewhen no unread notifications remainuseNotificationPolling()runs at 30s intervals to keep the feed up-to-date without page refreshnotification-store.ts) that keeps the sidebarunreadCountbadge in sync with the notification feed state. TheAppSidebarnow reads from this store instead of displaying a hardcoded'5'badge<button>elements with descriptivearia-labelattributes, section headers usearia-label, and the BoundlessSheet supports keyboard dismissal (Escape) and focus trappingFiles changed:
app/me/notifications/page.tsxapp/me/notifications/components/NotificationSection.tsxapp/me/notifications/components/NotificationFeedItem.tsxapp/me/notifications/components/NotificationDetailSheet.tsxlib/stores/notification-store.tscomponents/app-sidebar.tsxTest plan
useNotifications()correctly loads and renders the notification feed on page mountuseNotificationPolling()is active and new notifications appear without page refreshBoundlessSheetopens with correct full contentSummary by CodeRabbit