diff --git a/deploy/init.sql b/deploy/init.sql index 1e481ed..9e4589d 100644 --- a/deploy/init.sql +++ b/deploy/init.sql @@ -749,6 +749,9 @@ CREATE POLICY "Board can manage events" TO authenticated USING ( EXISTS (SELECT 1 FROM public.members WHERE id = auth.uid() AND role IN ('board', 'admin')) + ) + WITH CHECK ( + EXISTS (SELECT 1 FROM public.members WHERE id = auth.uid() AND role IN ('board', 'admin')) ); -- EVENT RSVPs POLICIES @@ -771,6 +774,9 @@ CREATE POLICY "Board can manage all RSVPs" TO authenticated USING ( EXISTS (SELECT 1 FROM public.members WHERE id = auth.uid() AND role IN ('board', 'admin')) + ) + WITH CHECK ( + EXISTS (SELECT 1 FROM public.members WHERE id = auth.uid() AND role IN ('board', 'admin')) ); -- PUBLIC RSVPs POLICIES @@ -791,6 +797,9 @@ CREATE POLICY "Board can manage public RSVPs" TO authenticated USING ( EXISTS (SELECT 1 FROM public.members WHERE id = auth.uid() AND role IN ('board', 'admin')) + ) + WITH CHECK ( + EXISTS (SELECT 1 FROM public.members WHERE id = auth.uid() AND role IN ('board', 'admin')) ); -- DOCUMENTS POLICIES @@ -821,6 +830,9 @@ CREATE POLICY "Admin can manage all documents" TO authenticated USING ( EXISTS (SELECT 1 FROM public.members WHERE id = auth.uid() AND role = 'admin') + ) + WITH CHECK ( + EXISTS (SELECT 1 FROM public.members WHERE id = auth.uid() AND role = 'admin') ); -- APP SETTINGS POLICIES @@ -840,6 +852,9 @@ CREATE POLICY "Admin can manage settings" TO authenticated USING ( EXISTS (SELECT 1 FROM public.members WHERE id = auth.uid() AND role = 'admin') + ) + WITH CHECK ( + EXISTS (SELECT 1 FROM public.members WHERE id = auth.uid() AND role = 'admin') ); -- EMAIL TEMPLATES POLICIES @@ -848,6 +863,9 @@ CREATE POLICY "Admin can manage email templates" TO authenticated USING ( EXISTS (SELECT 1 FROM public.members WHERE id = auth.uid() AND role = 'admin') + ) + WITH CHECK ( + EXISTS (SELECT 1 FROM public.members WHERE id = auth.uid() AND role = 'admin') ); -- EMAIL LOGS POLICIES @@ -864,6 +882,9 @@ CREATE POLICY "Admin can manage email logs" TO authenticated USING ( EXISTS (SELECT 1 FROM public.members WHERE id = auth.uid() AND role = 'admin') + ) + WITH CHECK ( + EXISTS (SELECT 1 FROM public.members WHERE id = auth.uid() AND role = 'admin') ); -- INDEXES @@ -1456,6 +1477,9 @@ CREATE POLICY "Admin can manage all notifications" TO authenticated USING ( EXISTS (SELECT 1 FROM public.members WHERE id = auth.uid() AND role = 'admin') + ) + WITH CHECK ( + EXISTS (SELECT 1 FROM public.members WHERE id = auth.uid() AND role = 'admin') ); -- Grant permissions diff --git a/src/lib/components/layout/NotificationCenter.svelte b/src/lib/components/layout/NotificationCenter.svelte index 1761d3d..466c4fc 100644 --- a/src/lib/components/layout/NotificationCenter.svelte +++ b/src/lib/components/layout/NotificationCenter.svelte @@ -1,6 +1,7 @@ + + + Notifications Management | Monaco USA Admin + + +
+ +
+
+

Notification Management

+

Send notifications to members and view notification history

+
+ +
+ + + {#if form?.success} +
+ +

{form.success}

+
+ {/if} + {#if form?.error} +
+ +

{form.error}

+
+ {/if} + + + {#if showCreateForm} +
+

Create New Notification

+ +
{ + submitting = true; + return async ({ update }) => { + await update(); + submitting = false; + if (form?.success) { + showCreateForm = false; + } + }; + }} + class="space-y-6" + > + +
+ +
+ {#each notificationTypes as type} + {@const Icon = type.icon} + + {/each} +
+
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +

Users will be redirected here when they click the notification

+
+ + +
+ +
+ + + + + + {#if recipientType === 'role'} +
+ +
+ {/if} + + + + {#if recipientType === 'single'} +
+ +
+ {/if} +
+
+ + +
+

+ This will send a notification to {getRecipientCount()} member{getRecipientCount() === 1 ? '' : 's'} +

+ +
+
+
+ {/if} + + +
+
+

Recent Notifications

+

Last 100 notifications sent

+
+ + {#if data.notifications.length === 0} +
+ +

No notifications yet

+

Create your first notification above

+
+ {:else} +
+ {#each data.notifications as notification} + {@const typeInfo = getTypeInfo(notification.type)} + {@const Icon = typeInfo.icon} +
+
+ +
+
+
+
+

{notification.title}

+

{notification.message}

+
+ + To: {notification.member ? `${notification.member.first_name} ${notification.member.last_name}` : 'Unknown'} + + + {formatDate(notification.created_at)} + {#if notification.read_at} + + + Read + + {:else} + Unread + {/if} +
+
+
+ + +
+
+
+
+ {/each} +
+ {/if} +
+
diff --git a/src/routes/(app)/notifications/+page.server.ts b/src/routes/(app)/notifications/+page.server.ts new file mode 100644 index 0000000..111cc44 --- /dev/null +++ b/src/routes/(app)/notifications/+page.server.ts @@ -0,0 +1,19 @@ +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async ({ locals }) => { + const { member } = await locals.safeGetSession(); + + if (!member) { + return { notifications: [] }; + } + + const { data: notifications } = await locals.supabase + .from('notifications') + .select('*') + .eq('member_id', member.id) + .order('created_at', { ascending: false }); + + return { + notifications: notifications || [] + }; +}; diff --git a/src/routes/(app)/notifications/+page.svelte b/src/routes/(app)/notifications/+page.svelte new file mode 100644 index 0000000..55c1215 --- /dev/null +++ b/src/routes/(app)/notifications/+page.svelte @@ -0,0 +1,221 @@ + + + + Notifications | Monaco USA + + +
+ +
+
+
+

Notifications

+

+ {#if unreadCount > 0} + You have {unreadCount} unread notification{unreadCount === 1 ? '' : 's'} + {:else} + All caught up! + {/if} +

+
+ {#if unreadCount > 0} + + {/if} +
+ + +
+ + +
+
+ + + {#if filteredNotifications.length === 0} +
+ +

+ {filter === 'unread' ? 'No unread notifications' : 'No notifications yet'} +

+

+ {filter === 'unread' ? 'You\'re all caught up!' : 'When you receive notifications, they\'ll appear here.'} +

+
+ {:else} +
+ {#each filteredNotifications as notification} + {@const Icon = getTypeIcon(notification.type)} + {@const badge = getTypeBadge(notification.type)} + + {/each} +
+ {/if} +