What's Happening
The notification bell users actually see is fake, and the real notification system sitting next to it is broken and unused.
What's rendered: components/molecules/dashboard/nav-header.jsx imports Notybell from components/atoms/dashboard/Notybell.jsx. That component shows a hardcoded mockNotifications array ("New lesson available", "New message from Ali", …) with a comment // replace with real fetch later, and an always-on red dot badge regardless of whether anything is unread.
What's dead code: a complete real implementation exists — components/molecules/dashboard/Notybell.jsx (NotificationBell, with mark-as-read, mark-all, delete, reconnect UI) backed by hooks/useNotificationSSE.js — but it is imported nowhere, and it cannot work as written:
useNotificationSSE destructures const { token } = useAuth(), but useAuth (hooks/useAuth.js) returns { user, isAuthenticated, loading, logout, refreshUser } — there is no token field. token is always undefined, so fetchNotifications and connectSSE bail immediately.
connectSSE lists itself in its own useCallback dependency array ([token, reconnectAttempts, notifications, connectSSE]). Because connectSSE is a const still in its temporal dead zone when that array is evaluated, mounting the hook throws ReferenceError: Cannot access 'connectSSE' before initialization.
- It fetches relative paths (
/api/notifications, /api/notifications/sse?token=...) — the Next app only has app/api/ai/* and app/api/books/* routes, so these 404; the backend lives at NEXT_PUBLIC_API_URL.
- The JWT is passed as a query-string parameter to the SSE endpoint, which leaks it into server/proxy logs.
- Listing
notifications in connectSSE's deps means every incoming event tears down and recreates the EventSource.
Where to Find This
components/atoms/dashboard/Notybell.jsx — mock bell currently rendered
components/molecules/dashboard/Notybell.jsx — unused real bell UI
hooks/useNotificationSSE.js — broken hook
components/molecules/dashboard/nav-header.jsx — where the bell is mounted
hooks/useAuth.js — shape of what useAuth actually returns
What We Want
- Fix
useNotificationSSE so it can run: get the token from the authToken cookie (js-cookie, as the rest of the app does) instead of the nonexistent useAuth().token; remove the self-referencing dependency; use refs for values the SSE callbacks need (e.g. current notifications) so the connection isn't rebuilt on every event.
- Point the fetches at the real backend (
process.env.NEXT_PUBLIC_API_URL via axiosInstance for the REST calls; absolute URL for the EventSource). Coordinate with the backend on the SSE endpoint path and note in the issue/PR if it doesn't exist yet — the REST list/mark-read/delete endpoints should still be wired regardless, with the SSE connection degrading gracefully (polling fallback or silent disconnect state).
- Replace the mock
Notybell in nav-header.jsx with the real NotificationBell, and delete the mock component (or keep it behind a storybook-style demo only).
- Badge count must reflect
unreadCount (hide the dot at 0), and the existing exponential backoff/reconnect logic should be kept but capped as written.
- Use
router.push instead of window.location.href for notification navigation in NotificationBell so client-side routing is preserved.
Technical Context
EventSource cannot send an Authorization header. If the backend can't read the cookie (CORS + withCredentials on the API origin), a short-lived SSE ticket or keeping the query token may be the pragmatic choice — if so, document the tradeoff in code comments. The current app already sends withCredentials: true on axios.
- The reducer-ish state updates in the hook (
notification_read, all_read, notification_deleted) are sound — reuse them.
- Test the TDZ fix simply by rendering
NotificationBell once; the current code throws on mount.
Acceptance Criteria
What's Happening
The notification bell users actually see is fake, and the real notification system sitting next to it is broken and unused.
What's rendered:
components/molecules/dashboard/nav-header.jsximportsNotybellfromcomponents/atoms/dashboard/Notybell.jsx. That component shows a hardcodedmockNotificationsarray ("New lesson available", "New message from Ali", …) with a comment// replace with real fetch later, and an always-on red dot badge regardless of whether anything is unread.What's dead code: a complete real implementation exists —
components/molecules/dashboard/Notybell.jsx(NotificationBell, with mark-as-read, mark-all, delete, reconnect UI) backed byhooks/useNotificationSSE.js— but it is imported nowhere, and it cannot work as written:useNotificationSSEdestructuresconst { token } = useAuth(), butuseAuth(hooks/useAuth.js) returns{ user, isAuthenticated, loading, logout, refreshUser }— there is notokenfield.tokenis alwaysundefined, sofetchNotificationsandconnectSSEbail immediately.connectSSElists itself in its ownuseCallbackdependency array ([token, reconnectAttempts, notifications, connectSSE]). BecauseconnectSSEis aconststill in its temporal dead zone when that array is evaluated, mounting the hook throwsReferenceError: Cannot access 'connectSSE' before initialization./api/notifications,/api/notifications/sse?token=...) — the Next app only hasapp/api/ai/*andapp/api/books/*routes, so these 404; the backend lives atNEXT_PUBLIC_API_URL.notificationsinconnectSSE's deps means every incoming event tears down and recreates the EventSource.Where to Find This
components/atoms/dashboard/Notybell.jsx— mock bell currently renderedcomponents/molecules/dashboard/Notybell.jsx— unused real bell UIhooks/useNotificationSSE.js— broken hookcomponents/molecules/dashboard/nav-header.jsx— where the bell is mountedhooks/useAuth.js— shape of whatuseAuthactually returnsWhat We Want
useNotificationSSEso it can run: get the token from theauthTokencookie (js-cookie, as the rest of the app does) instead of the nonexistentuseAuth().token; remove the self-referencing dependency; use refs for values the SSE callbacks need (e.g. current notifications) so the connection isn't rebuilt on every event.process.env.NEXT_PUBLIC_API_URLviaaxiosInstancefor the REST calls; absolute URL for theEventSource). Coordinate with the backend on the SSE endpoint path and note in the issue/PR if it doesn't exist yet — the REST list/mark-read/delete endpoints should still be wired regardless, with the SSE connection degrading gracefully (polling fallback or silent disconnect state).Notybellinnav-header.jsxwith the realNotificationBell, and delete the mock component (or keep it behind a storybook-style demo only).unreadCount(hide the dot at 0), and the existing exponential backoff/reconnect logic should be kept but capped as written.router.pushinstead ofwindow.location.hreffor notification navigation inNotificationBellso client-side routing is preserved.Technical Context
EventSourcecannot send anAuthorizationheader. If the backend can't read the cookie (CORS +withCredentialson the API origin), a short-lived SSE ticket or keeping the query token may be the pragmatic choice — if so, document the tradeoff in code comments. The current app already sendswithCredentials: trueon axios.notification_read,all_read,notification_deleted) are sound — reuse them.NotificationBellonce; the current code throws on mount.Acceptance Criteria
mockNotificationsremain in the tree.npm run lintandnpm run buildpass.