Skip to content

Commit e4dd194

Browse files
authored
Merge pull request #317 from codeunia-dev/feat/multicompanylevel
Feat/multicompanylevel – Enhance event handling, usage tracking, and notifications
2 parents e5a6be4 + c434a78 commit e4dd194

File tree

11 files changed

+236
-197
lines changed

11 files changed

+236
-197
lines changed

app/admin/events/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ export default function AdminEvents() {
360360
</TableCell>
361361
<TableCell>
362362
<Badge variant="outline">
363-
{event.status}
363+
{event.status.charAt(0).toUpperCase() + event.status.slice(1)}
364364
</Badge>
365365
</TableCell>
366366
<TableCell>

app/api/admin/events/route.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,12 @@ export async function GET(request: NextRequest) {
4343

4444
const serviceSupabase = createServiceClient(supabaseUrl, supabaseServiceKey)
4545

46+
// Only show events that need moderation (pending, approved, rejected)
47+
// Exclude drafts as they haven't been submitted for review yet
4648
const { data: events, error } = await serviceSupabase
4749
.from('events')
4850
.select('*')
51+
.in('approval_status', ['pending', 'approved', 'rejected'])
4952
.order('created_at', { ascending: false })
5053
.range(offset, offset + limit - 1)
5154

app/dashboard/company/[slug]/settings/page.tsx

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,11 @@ import {
3030
} from 'lucide-react'
3131
import { useToast } from '@/components/ui/use-toast'
3232
import { SUBSCRIPTION_LIMITS } from '@/types/company'
33+
import { useSubscription } from '@/hooks/useSubscription'
3334

3435
export default function CompanySettingsPage() {
3536
const { currentCompany, userRole, loading: contextLoading, refreshCompany } = useCompanyContext()
37+
const { usage } = useSubscription(currentCompany?.slug)
3638
const { toast } = useToast()
3739
const [loading, setLoading] = useState(false)
3840
const [uploadingLogo, setUploadingLogo] = useState(false)
@@ -793,29 +795,45 @@ export default function CompanySettingsPage() {
793795
<CardContent className="space-y-4">
794796
<div className="space-y-2">
795797
<div className="flex items-center justify-between">
796-
<span className="text-sm text-zinc-400">Events Created</span>
797-
<span className="text-sm font-medium text-white">
798-
{currentCompany.total_events}
798+
<span className="text-sm text-zinc-400">Events Created (This Month)</span>
799+
<span className={`text-sm font-medium ${
800+
usage && subscriptionLimits.events_per_month !== null &&
801+
usage.events_this_month > subscriptionLimits.events_per_month
802+
? 'text-red-400'
803+
: 'text-white'
804+
}`}>
805+
{usage?.events_this_month ?? 0}
799806
{subscriptionLimits.events_per_month !== null &&
800807
` / ${subscriptionLimits.events_per_month}`}
801808
</span>
802809
</div>
803810
<div className="w-full bg-zinc-800 rounded-full h-2">
804811
<div
805-
className="bg-gradient-to-r from-primary to-purple-600 h-2 rounded-full"
812+
className={`h-2 rounded-full ${
813+
usage && subscriptionLimits.events_per_month !== null &&
814+
usage.events_this_month > subscriptionLimits.events_per_month
815+
? 'bg-gradient-to-r from-red-500 to-red-600'
816+
: 'bg-gradient-to-r from-primary to-purple-600'
817+
}`}
806818
style={{
807819
width:
808820
subscriptionLimits.events_per_month === null
809821
? '0%'
810822
: `${Math.min(
811-
(currentCompany.total_events /
823+
((usage?.events_this_month ?? 0) /
812824
subscriptionLimits.events_per_month) *
813825
100,
814826
100
815827
)}%`,
816828
}}
817829
/>
818830
</div>
831+
{usage && subscriptionLimits.events_per_month !== null &&
832+
usage.events_this_month > subscriptionLimits.events_per_month && (
833+
<p className="text-xs text-red-400 mt-1">
834+
⚠️ You have exceeded your monthly limit. Please upgrade your plan.
835+
</p>
836+
)}
819837
</div>
820838
</CardContent>
821839
</Card>
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.

0 commit comments

Comments
 (0)