diff --git a/migration-add-user-id-to-rsvps.sql b/database/20250819001_add-user-id-to-rsvps.sql similarity index 100% rename from migration-add-user-id-to-rsvps.sql rename to database/20250819001_add-user-id-to-rsvps.sql diff --git a/database/20250819002_add-user-id-to-events.sql b/database/20250819002_add-user-id-to-events.sql new file mode 100644 index 0000000..753500e --- /dev/null +++ b/database/20250819002_add-user-id-to-events.sql @@ -0,0 +1,34 @@ +-- Migration: Add user_id column to Events table +-- Run this against your existing Supabase database + +-- Add user_id column to existing events table +ALTER TABLE events +ADD COLUMN user_id VARCHAR(100); + +-- Set a default value for existing records (you can modify this if needed) +-- This assigns a unique user ID to each existing event +UPDATE events +SET user_id = 'legacy_user_' || id::text +WHERE user_id IS NULL; + +-- Make the column NOT NULL after setting default values +ALTER TABLE events +ALTER COLUMN user_id SET NOT NULL; + +-- Add index for better performance +CREATE INDEX IF NOT EXISTS idx_events_user_id ON events(user_id); + +-- Verify the migration +SELECT + column_name, + data_type, + is_nullable, + column_default +FROM information_schema.columns +WHERE table_name = 'events' +AND column_name = 'user_id'; + +-- Show sample of updated data +SELECT id, name, user_id, created_at +FROM events +LIMIT 5; diff --git a/supabase-setup.sql b/database/init_supabase-setup.sql similarity index 100% rename from supabase-setup.sql rename to database/init_supabase-setup.sql diff --git a/src/lib/components/Navbar.svelte b/src/lib/components/Navbar.svelte index a5096ed..83053aa 100644 --- a/src/lib/components/Navbar.svelte +++ b/src/lib/components/Navbar.svelte @@ -25,7 +25,7 @@ - +
+ +
diff --git a/src/lib/stores/events-supabase.ts b/src/lib/stores/events-supabase.ts index 2d05a1f..e6db347 100644 --- a/src/lib/stores/events-supabase.ts +++ b/src/lib/stores/events-supabase.ts @@ -28,6 +28,7 @@ function convertDatabaseEvent(dbEvent: DatabaseEvent): Event { location: dbEvent.location, type: dbEvent.type, attendee_limit: dbEvent.attendee_limit, + user_id: dbEvent.user_id, created_at: dbEvent.created_at, updated_at: dbEvent.updated_at }; @@ -49,7 +50,7 @@ export const eventsStore = { subscribeRSVPs: rsvps.subscribe, // Create a new event - createEvent: async (eventData: CreateEventData): Promise => { + createEvent: async (eventData: CreateEventData, userId: string): Promise => { const eventId = generateEventId(); const now = new Date().toISOString(); @@ -62,6 +63,7 @@ export const eventsStore = { location: eventData.location, type: eventData.type, attendee_limit: eventData.attendee_limit, + user_id: userId, created_at: now, updated_at: now }); @@ -72,6 +74,7 @@ export const eventsStore = { const newEvent: Event = { id: eventId, ...eventData, + user_id: userId, created_at: now, updated_at: now }; @@ -245,5 +248,69 @@ export const eventsStore = { console.error('Error fetching event with RSVPs:', error); return undefined; } + }, + + // Get events by user ID + getEventsByUser: async (userId: string): Promise => { + try { + const { data, error } = await supabase + .from('events') + .select('*') + .eq('user_id', userId) + .order('created_at', { ascending: false }); + + if (error) throw error; + + const userEvents = data?.map(convertDatabaseEvent) || []; + + // Update local store + userEvents.forEach((event) => { + events.update((currentEvents) => { + const newMap = new Map(currentEvents); + newMap.set(event.id, event); + return newMap; + }); + }); + + return userEvents; + } catch (error) { + console.error('Error fetching user events:', error); + return []; + } + }, + + // Delete event (only by the user who created it) + deleteEvent: async (eventId: string, userId: string): Promise => { + try { + // First verify the user owns this event + const event = await eventsStore.getEvent(eventId); + if (!event || event.user_id !== userId) { + return false; // User doesn't own this event + } + + // Delete the event (RSVPs will be deleted automatically due to CASCADE) + const { error } = await supabase.from('events').delete().eq('id', eventId); + + if (error) throw error; + + // Remove from local store + events.update((currentEvents) => { + const newMap = new Map(currentEvents); + newMap.delete(eventId); + return newMap; + }); + + // Remove RSVPs from local store + rsvps.update((currentRSVPs) => { + const newMap = new Map(currentRSVPs); + newMap.delete(eventId); + return newMap; + }); + + return true; + } catch (error) { + console.error('Error deleting event:', error); + return false; + } } }; diff --git a/src/lib/types.ts b/src/lib/types.ts index cd603b2..e2e1a72 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -8,6 +8,7 @@ export interface Event { location: string; type: EventType; attendee_limit?: number; + user_id: string; created_at: string; updated_at: string; } @@ -37,6 +38,7 @@ export interface DatabaseEvent { location: string; type: EventType; attendee_limit?: number; + user_id: string; created_at: string; updated_at: string; } diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index e1aeb9c..a198afc 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -43,12 +43,6 @@ > Create Event Now - diff --git a/src/routes/create/+page.svelte b/src/routes/create/+page.svelte index 45040d2..e417cde 100644 --- a/src/routes/create/+page.svelte +++ b/src/routes/create/+page.svelte @@ -2,6 +2,7 @@ import { eventsStore } from '$lib/stores/events-supabase'; import type { CreateEventData, EventType } from '$lib/types'; import { goto } from '$app/navigation'; + import { onMount } from 'svelte'; let eventData: CreateEventData = { name: '', @@ -14,10 +15,26 @@ let errors: Record = {}; let isSubmitting = false; + let currentUserId = ''; // Get today's date in YYYY-MM-DD format for min attribute const today = new Date().toISOString().split('T')[0]; + // Generate or retrieve user ID on mount + onMount(() => { + generateUserId(); + }); + + function generateUserId() { + // Generate a unique user ID and store it in localStorage + let userId = localStorage.getItem('eventCactusUserId'); + if (!userId) { + userId = 'user_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); + localStorage.setItem('eventCactusUserId', userId); + } + currentUserId = userId; + } + function validateForm(): boolean { errors = {}; @@ -58,7 +75,7 @@ // Simulate API call delay await new Promise((resolve) => setTimeout(resolve, 1000)); - const eventId = await eventsStore.createEvent(eventData); + const eventId = await eventsStore.createEvent(eventData, currentUserId); // Redirect to the event page goto(`/event/${eventId}`); diff --git a/src/routes/event/+page.svelte b/src/routes/event/+page.svelte new file mode 100644 index 0000000..4315b5e --- /dev/null +++ b/src/routes/event/+page.svelte @@ -0,0 +1,231 @@ + + + + My Events - Event Cactus + + +
+ +
+ {#if isLoading} +
+
+

Loading your events...

+
+ {:else if error} +
+
⚠️
+

{error}

+ +
+ {:else if userEvents.length === 0} +
+
🎉
+

No Events Yet

+

+ You haven't created any events yet. Start by creating your first event! +

+ +
+ {:else} +
+
+

Your Events ({userEvents.length})

+
+ +
+ {#each userEvents as event} +
+
+

{event.name}

+
+
+ + + + {formatDate(event.date)} at {formatTime(event.time)} +
+
+ + + + + {event.location} +
+
+ + {event.type === 'limited' ? 'Limited' : 'Unlimited'} + + {#if event.type === 'limited' && event.attendee_limit} + • {event.attendee_limit} max + {/if} +
+
+
+ +
+ + +
+
+ {/each} +
+
+ {/if} +
+
+ + +{#if showDeleteModal && eventToDelete} +
+
+
+
+ 🗑️ +
+

Delete Event

+

+ Are you sure you want to delete "{eventToDelete.name}"? + This action cannot be undone and will remove all RSVPs. +

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