Skip to content

Commit c434a78

Browse files
committed
feat(notifications): Add comprehensive notifications management page
- Create new notifications page with dynamic filtering and interaction - Implement tabs for viewing all and unread notifications - Add notification statistics and summary cards - Support marking individual and all notifications as read - Include notification deletion functionality - Enhance UI with responsive design and intuitive actions - Integrate with existing useNotifications hook for data management - Add visual indicators for notification status and type - Implement time-based notification formatting
1 parent d989983 commit c434a78

File tree

7 files changed

+206
-189
lines changed

7 files changed

+206
-189
lines changed
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
'use client'
2+
3+
import { useState } from 'react'
4+
import { useNotifications } from '@/hooks/useNotifications'
5+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
6+
import { Button } from '@/components/ui/button'
7+
import { Badge } from '@/components/ui/badge'
8+
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
9+
import { Bell, Check, CheckCheck, Trash2, ExternalLink } from 'lucide-react'
10+
import { formatDistanceToNow } from 'date-fns'
11+
import Link from 'next/link'
12+
import { cn } from '@/lib/utils'
13+
14+
export default function NotificationsPage() {
15+
const { notifications, loading, unreadCount, markAsRead, markAllAsRead, deleteNotification } = useNotifications()
16+
const [filter, setFilter] = useState<'all' | 'unread'>('all')
17+
18+
const filteredNotifications = filter === 'unread'
19+
? notifications.filter(n => !n.read)
20+
: notifications
21+
22+
const getNotificationIcon = () => {
23+
return <Bell className="h-5 w-5" />
24+
}
25+
26+
const getNotificationColor = (type: string) => {
27+
switch (type) {
28+
case 'event_approved':
29+
return 'text-green-500'
30+
case 'event_rejected':
31+
return 'text-red-500'
32+
case 'event_changes_requested':
33+
return 'text-orange-500'
34+
default:
35+
return 'text-blue-500'
36+
}
37+
}
38+
39+
if (loading) {
40+
return (
41+
<div className="flex items-center justify-center min-h-screen">
42+
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
43+
</div>
44+
)
45+
}
46+
47+
return (
48+
<div className="container max-w-4xl mx-auto py-8 px-4">
49+
<div className="space-y-6">
50+
{/* Header */}
51+
<div className="flex items-center justify-between">
52+
<div>
53+
<h1 className="text-3xl font-bold tracking-tight">Notifications</h1>
54+
<p className="text-muted-foreground mt-1">
55+
Stay updated with your events and activities
56+
</p>
57+
</div>
58+
{unreadCount > 0 && (
59+
<Button onClick={markAllAsRead} variant="outline" size="sm">
60+
<CheckCheck className="h-4 w-4 mr-2" />
61+
Mark all as read
62+
</Button>
63+
)}
64+
</div>
65+
66+
{/* Stats */}
67+
<div className="grid grid-cols-2 gap-4">
68+
<Card>
69+
<CardHeader className="pb-3">
70+
<CardTitle className="text-sm font-medium">Total</CardTitle>
71+
</CardHeader>
72+
<CardContent>
73+
<div className="text-2xl font-bold">{notifications.length}</div>
74+
</CardContent>
75+
</Card>
76+
<Card>
77+
<CardHeader className="pb-3">
78+
<CardTitle className="text-sm font-medium">Unread</CardTitle>
79+
</CardHeader>
80+
<CardContent>
81+
<div className="text-2xl font-bold text-primary">{unreadCount}</div>
82+
</CardContent>
83+
</Card>
84+
</div>
85+
86+
{/* Tabs */}
87+
<Tabs value={filter} onValueChange={(v) => setFilter(v as 'all' | 'unread')}>
88+
<TabsList className="grid w-full max-w-md grid-cols-2">
89+
<TabsTrigger value="all">
90+
All ({notifications.length})
91+
</TabsTrigger>
92+
<TabsTrigger value="unread">
93+
Unread ({unreadCount})
94+
</TabsTrigger>
95+
</TabsList>
96+
97+
<TabsContent value={filter} className="mt-6 space-y-4">
98+
{filteredNotifications.length === 0 ? (
99+
<Card>
100+
<CardContent className="flex flex-col items-center justify-center py-12">
101+
<Bell className="h-12 w-12 text-muted-foreground mb-4" />
102+
<p className="text-muted-foreground text-center">
103+
{filter === 'unread'
104+
? "You're all caught up! No unread notifications."
105+
: "No notifications yet."}
106+
</p>
107+
</CardContent>
108+
</Card>
109+
) : (
110+
filteredNotifications.map((notification) => (
111+
<Card
112+
key={notification.id}
113+
className={cn(
114+
"transition-all hover:shadow-md",
115+
!notification.read && "border-l-4 border-l-primary bg-primary/5"
116+
)}
117+
>
118+
<CardContent className="p-4">
119+
<div className="flex items-start gap-4">
120+
{/* Icon */}
121+
<div className={cn(
122+
"p-2 rounded-full bg-muted flex-shrink-0",
123+
getNotificationColor(notification.type)
124+
)}>
125+
{getNotificationIcon()}
126+
</div>
127+
128+
{/* Content */}
129+
<div className="flex-1 min-w-0">
130+
<div className="flex items-start justify-between gap-2 mb-1">
131+
<h3 className="font-semibold text-sm">
132+
{notification.title}
133+
</h3>
134+
{!notification.read && (
135+
<Badge variant="default" className="flex-shrink-0">
136+
New
137+
</Badge>
138+
)}
139+
</div>
140+
<p className="text-sm text-muted-foreground mb-2">
141+
{notification.message}
142+
</p>
143+
<div className="flex items-center gap-4 text-xs text-muted-foreground">
144+
<span>
145+
{formatDistanceToNow(new Date(notification.created_at), { addSuffix: true })}
146+
</span>
147+
{notification.company_id && (
148+
<span className="flex items-center gap-1">
149+
<span className="w-1 h-1 rounded-full bg-muted-foreground" />
150+
Company notification
151+
</span>
152+
)}
153+
</div>
154+
</div>
155+
156+
{/* Actions */}
157+
<div className="flex items-center gap-2 flex-shrink-0">
158+
{notification.action_url && (
159+
<Button
160+
variant="ghost"
161+
size="sm"
162+
asChild
163+
>
164+
<Link href={notification.action_url}>
165+
<ExternalLink className="h-4 w-4" />
166+
</Link>
167+
</Button>
168+
)}
169+
{!notification.read && (
170+
<Button
171+
variant="ghost"
172+
size="sm"
173+
onClick={() => markAsRead(notification.id)}
174+
title="Mark as read"
175+
>
176+
<Check className="h-4 w-4" />
177+
</Button>
178+
)}
179+
<Button
180+
variant="ghost"
181+
size="sm"
182+
onClick={() => deleteNotification(notification.id)}
183+
title="Delete"
184+
>
185+
<Trash2 className="h-4 w-4" />
186+
</Button>
187+
</div>
188+
</div>
189+
</CardContent>
190+
</Card>
191+
))
192+
)}
193+
</TabsContent>
194+
</Tabs>
195+
</div>
196+
</div>
197+
)
198+
}

check-database-status.sql

Lines changed: 0 additions & 57 deletions
This file was deleted.

check-schema-permissions.sql

Lines changed: 0 additions & 55 deletions
This file was deleted.

components/dashboard/CompanyDashboard.tsx

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,6 @@ export function CompanyDashboard({ company }: CompanyDashboardProps) {
326326
<UpcomingEventItem
327327
key={event.id}
328328
event={event}
329-
companySlug={company.slug}
330329
/>
331330
))}
332331
</div>
@@ -449,19 +448,15 @@ function ActivityItem({ activity }: ActivityItemProps) {
449448
// Upcoming Event Item Component
450449
interface UpcomingEventItemProps {
451450
event: UpcomingEvent
452-
companySlug: string
453451
}
454452

455-
function UpcomingEventItem({ event, companySlug }: UpcomingEventItemProps) {
453+
function UpcomingEventItem({ event }: UpcomingEventItemProps) {
456454
const registrationPercentage = event.capacity
457455
? (event.registrations / event.capacity) * 100
458456
: 0
459457

460458
return (
461-
<Link
462-
href={`/dashboard/company/${companySlug}/events/${event.slug}`}
463-
className="block p-3 rounded-lg hover:bg-zinc-800/50 transition-colors border border-zinc-800"
464-
>
459+
<div className="block p-3 rounded-lg border border-zinc-800">
465460
<div className="flex items-start justify-between gap-2">
466461
<div className="flex-1 min-w-0">
467462
<p className="text-sm font-medium text-white truncate">{event.title}</p>
@@ -484,7 +479,7 @@ function UpcomingEventItem({ event, companySlug }: UpcomingEventItemProps) {
484479
</div>
485480
</div>
486481
)}
487-
</Link>
482+
</div>
488483
)
489484
}
490485

components/notifications/NotificationCenter.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { useNotifications } from '@/hooks/useNotifications'
1313
import { NotificationItem } from './NotificationItem'
1414
import { Separator } from '@/components/ui/separator'
1515
import { useState } from 'react'
16+
import Link from 'next/link'
1617

1718
export function NotificationCenter() {
1819
const {
@@ -137,13 +138,11 @@ export function NotificationCenter() {
137138
variant="ghost"
138139
size="sm"
139140
className="w-full text-xs"
140-
onClick={() => {
141-
setOpen(false)
142-
// Navigate to notifications page if you have one
143-
// router.push('/notifications')
144-
}}
141+
asChild
145142
>
146-
View all notifications
143+
<Link href="/protected/notifications" onClick={() => setOpen(false)}>
144+
View all notifications
145+
</Link>
147146
</Button>
148147
</div>
149148
</>

0 commit comments

Comments
 (0)