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 }