diff --git a/src/features/dashboard/components/notifications.tsx b/src/features/dashboard/components/notifications.tsx
new file mode 100644
index 000000000..9023e3238
--- /dev/null
+++ b/src/features/dashboard/components/notifications.tsx
@@ -0,0 +1,440 @@
+import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
+import { Badge } from '@/components/ui/badge'
+import { Button } from '@/components/ui/button'
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from '@/components/ui/card'
+import { useState } from 'react'
+
+const INITIAL_COUNT = 6
+const LOAD_MORE_COUNT = 4
+
+export function Notifications() {
+ const [items, setItems] = useState(initialNotifications)
+ const [visibleCount, setVisibleCount] = useState(INITIAL_COUNT)
+
+ const unreadCount = items.filter((n) => !n.read).length
+ const visibleItems = items.slice(0, visibleCount)
+ const hasMore = visibleCount < items.length
+
+ function markAllAsRead() {
+ setItems((prev) => prev.map((n) => ({ ...n, read: true })))
+ }
+
+ function toggleRead(id: number) {
+ setItems((prev) =>
+ prev.map((n) => (n.id === id ? { ...n, read: !n.read } : n))
+ )
+ }
+
+ function loadMore() {
+ setVisibleCount((prev) => Math.min(prev + LOAD_MORE_COUNT, items.length))
+ }
+
+ return (
+
+
+
+
+ Unread
+
+
+
+ {unreadCount}
+
+ +3 since last hour
+
+
+
+
+
+ Mentions
+
+
+
+ 4
+
+ +2 since yesterday
+
+
+
+
+
+
+ System Alerts
+
+
+
+
+ 2
+
+ 1 critical, 1 warning
+
+
+
+
+
+
+ Read This Week
+
+
+
+
+ 28
+
+ +12% from last week
+
+
+
+
+
+
+
+
+
+ Recent Notifications
+
+ {unreadCount > 0
+ ? `You have ${unreadCount} unread notification${unreadCount !== 1 ? 's' : ''}.`
+ : 'All caught up! No unread notifications.'}
+
+
+ {unreadCount > 0 && (
+
+ )}
+
+
+
+
+
+ {visibleItems.map((notification) => (
+
+ ))}
+
+
+ {hasMore && (
+
+
+
+ )}
+
+
+
+
+ )
+}
+
+const initialNotifications: {
+ id: number
+ name: string
+ avatar: string
+ initials: string
+ message: string
+ time: string
+ type: string
+ badgeVariant: 'default' | 'secondary' | 'destructive' | 'outline'
+ read: boolean
+}[] = [
+ {
+ id: 1,
+ name: 'Olivia Martin',
+ avatar: 'https://randomuser.me/api/portraits/men/3.jpg',
+ initials: 'OM',
+ message: 'Commented on your recent pull request #42.',
+ time: '2 minutes ago',
+ type: 'Comment',
+ badgeVariant: 'secondary',
+ read: false,
+ },
+ {
+ id: 2,
+ name: 'System',
+ avatar: '',
+ initials: 'SY',
+ message: 'CPU usage exceeded 90% threshold on production server.',
+ time: '15 minutes ago',
+ type: 'Alert',
+ badgeVariant: 'destructive',
+ read: false,
+ },
+ {
+ id: 3,
+ name: 'Jackson Lee',
+ avatar: 'https://randomuser.me/api/portraits/men/32.jpg',
+ initials: 'JL',
+ message: 'Mentioned you in the project discussion.',
+ time: '1 hour ago',
+ type: 'Mention',
+ badgeVariant: 'default',
+ read: false,
+ },
+ {
+ id: 4,
+ name: 'Isabella Nguyen',
+ avatar: 'https://randomuser.me/api/portraits/men/45.jpg',
+ initials: 'IN',
+ message: 'Shared the Q4 analytics report with you.',
+ time: '3 hours ago',
+ type: 'Share',
+ badgeVariant: 'outline',
+ read: true,
+ },
+ {
+ id: 5,
+ name: 'William Kim',
+ avatar: 'https://randomuser.me/api/portraits/men/32.jpg',
+ initials: 'WK',
+ message: 'Approved your access request for staging environment.',
+ time: '5 hours ago',
+ type: 'Security',
+ badgeVariant: 'secondary',
+ read: true,
+ },
+ {
+ id: 6,
+ name: 'Sofia Davis',
+ avatar: 'https://randomuser.me/api/portraits/men/30.jpg',
+ initials: 'SD',
+ message: 'Left a review on your latest deployment.',
+ time: 'Yesterday',
+ type: 'Comment',
+ badgeVariant: 'secondary',
+ read: true,
+ },
+ {
+ id: 7,
+ name: 'System',
+ avatar: '',
+ initials: 'SY',
+ message: 'Scheduled maintenance completed successfully.',
+ time: 'Yesterday',
+ type: 'Alert',
+ badgeVariant: 'outline',
+ read: true,
+ },
+ {
+ id: 8,
+ name: 'Olivia Martin',
+ avatar: 'https://randomuser.me/api/portraits/men/31.jpg',
+ initials: 'OM',
+ message: 'Assigned you to issue #128 — fix login redirect.',
+ time: '2 days ago',
+ type: 'Mention',
+ badgeVariant: 'default',
+ read: true,
+ },
+ {
+ id: 9,
+ name: 'Jackson Lee',
+ avatar: 'https://randomuser.me/api/portraits/men/32.jpg',
+ initials: 'JL',
+ message: 'Merged your pull request #39 into main.',
+ time: '2 days ago',
+ type: 'Comment',
+ badgeVariant: 'secondary',
+ read: true,
+ },
+ {
+ id: 10,
+ name: 'System',
+ avatar: '',
+ initials: 'SY',
+ message: 'Your API key will expire in 7 days. Renew it now.',
+ time: '3 days ago',
+ type: 'Security',
+ badgeVariant: 'destructive',
+ read: true,
+ },
+ {
+ id: 11,
+ name: 'Isabella Nguyen',
+ avatar: 'https://randomuser.me/api/portraits/men/32.jpg',
+ initials: 'IN',
+ message: 'Invited you to collaborate on the Design System project.',
+ time: '3 days ago',
+ type: 'Share',
+ badgeVariant: 'outline',
+ read: true,
+ },
+ {
+ id: 12,
+ name: 'William Kim',
+ avatar: 'https://randomuser.me/api/portraits/men/32.jpg',
+ initials: 'WK',
+ message: 'Commented on your Q3 performance review.',
+ time: '4 days ago',
+ type: 'Comment',
+ badgeVariant: 'secondary',
+ read: true,
+ },
+ {
+ id: 13,
+ name: 'Sofia Davis',
+ avatar: 'https://randomuser.me/api/portraits/men/50.jpg',
+ initials: 'SD',
+ message: 'Updated the team onboarding documentation.',
+ time: '5 days ago',
+ type: 'Share',
+ badgeVariant: 'outline',
+ read: true,
+ },
+ {
+ id: 14,
+ name: 'System',
+ avatar: '',
+ initials: 'SY',
+ message: 'Storage usage has reached 80% of your plan limit.',
+ time: '5 days ago',
+ type: 'Alert',
+ badgeVariant: 'destructive',
+ read: true,
+ },
+]
diff --git a/src/features/dashboard/index.tsx b/src/features/dashboard/index.tsx
index bd650239a..30651ac69 100644
--- a/src/features/dashboard/index.tsx
+++ b/src/features/dashboard/index.tsx
@@ -15,6 +15,7 @@ import { ProfileDropdown } from '@/components/profile-dropdown'
import { Search } from '@/components/search'
import { ThemeSwitch } from '@/components/theme-switch'
import { Analytics } from './components/analytics'
+import { Notifications } from './components/notifications'
import { Overview } from './components/overview'
import { RecentSales } from './components/recent-sales'
@@ -52,7 +53,7 @@ export function Dashboard() {
Reports
-
+
Notifications
@@ -186,6 +187,9 @@ export function Dashboard() {
+
+
+
>
diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts
index f45cbfe03..4e4fd8ce3 100644
--- a/src/routeTree.gen.ts
+++ b/src/routeTree.gen.ts
@@ -200,6 +200,7 @@ const AuthenticatedErrorsErrorRoute =
export interface FileRoutesByFullPath {
'/clerk': typeof ClerkAuthenticatedRouteRouteWithChildren
'/settings': typeof AuthenticatedSettingsRouteRouteWithChildren
+ '/clerk/': typeof ClerkauthRouteRouteWithChildren
'/forgot-password': typeof authForgotPasswordRoute
'/otp': typeof authOtpRoute
'/sign-in': typeof authSignInRoute
@@ -292,6 +293,7 @@ export interface FileRouteTypes {
fullPaths:
| '/clerk'
| '/settings'
+ | '/clerk/'
| '/forgot-password'
| '/otp'
| '/sign-in'
@@ -496,8 +498,8 @@ declare module '@tanstack/react-router' {
}
'/clerk/(auth)': {
id: '/clerk/(auth)'
- path: ''
- fullPath: '/clerk'
+ path: '/'
+ fullPath: '/clerk/'
preLoaderRoute: typeof ClerkauthRouteRouteImport
parentRoute: typeof ClerkRouteRoute
}