diff --git a/database/init.sql b/database/init.sql index 5c3d39d..c9683cd 100644 --- a/database/init.sql +++ b/database/init.sql @@ -14,6 +14,8 @@ CREATE TABLE IF NOT EXISTS events ( date DATE NOT NULL, time TIME NOT NULL, location VARCHAR(200) NOT NULL, + location_type VARCHAR(20) NOT NULL DEFAULT 'text' CHECK (location_type IN ('text','maps')), + location_url VARCHAR(500), type VARCHAR(20) NOT NULL CHECK (type IN ('limited','unlimited')), attendee_limit INTEGER CHECK (attendee_limit > 0), user_id VARCHAR(100) NOT NULL, @@ -37,6 +39,7 @@ CREATE TABLE IF NOT EXISTS rsvps ( -- ======================================= CREATE INDEX IF NOT EXISTS idx_events_user_id ON events(user_id); CREATE INDEX IF NOT EXISTS idx_events_date ON events(date); +CREATE INDEX IF NOT EXISTS idx_events_location_type ON events(location_type); CREATE INDEX IF NOT EXISTS idx_rsvps_event_id ON rsvps(event_id); CREATE INDEX IF NOT EXISTS idx_rsvps_user_id ON rsvps(user_id); diff --git a/src/lib/database/schema.ts b/src/lib/database/schema.ts index fdc786a..f3f8080 100644 --- a/src/lib/database/schema.ts +++ b/src/lib/database/schema.ts @@ -17,6 +17,7 @@ import type { InferInsertModel, InferSelectModel } from 'drizzle-orm'; // --- Enums (matching the SQL CHECK constraints) export const eventTypeEnum = pgEnum('event_type', ['limited', 'unlimited']); export const visibilityEnum = pgEnum('visibility', ['public', 'private']); +export const locationTypeEnum = pgEnum('location_type', ['text', 'maps']); // --- Events table export const events = pgTable( @@ -27,6 +28,8 @@ export const events = pgTable( date: date('date', { mode: 'string' }).notNull(), // ISO 'YYYY-MM-DD' time: time('time', { withTimezone: false }).notNull(), // 'HH:MM:SS' location: varchar('location', { length: 200 }).notNull(), + locationType: locationTypeEnum('location_type').notNull().default('text'), + locationUrl: varchar('location_url', { length: 500 }), type: eventTypeEnum('type').notNull(), attendeeLimit: integer('attendee_limit'), // nullable in SQL userId: varchar('user_id', { length: 100 }).notNull(), diff --git a/src/lib/i18n/messages.json b/src/lib/i18n/messages.json index 83d7ec5..c38b4f8 100644 --- a/src/lib/i18n/messages.json +++ b/src/lib/i18n/messages.json @@ -14,6 +14,13 @@ "date": "Date", "time": "Time", "location": "Location", + "locationType": "Location Type", + "locationText": "Text", + "locationMaps": "Google Maps", + "locationTextDescription": "Enter location as plain text.", + "locationMapsDescription": "Enter Google Maps link.", + "googleMapsUrl": "Google Maps URL", + "googleMapsUrlPlaceholder": "https://maps.google.com/...", "type": "Type", "visibility": "Visibility", "public": "Public", @@ -134,6 +141,13 @@ "timeLabel": "Time", "locationLabel": "Location", "locationPlaceholder": "Enter location", + "locationTypeLabel": "Location Type", + "locationTextOption": "Plain Text", + "locationMapsOption": "Google Maps", + "locationTextDescription": "Enter location as plain text", + "locationMapsDescription": "Enter Google Maps link", + "googleMapsUrlLabel": "Google Maps URL", + "googleMapsUrlPlaceholder": "https://maps.google.com/...", "typeLabel": "Type", "unlimitedOption": "Unlimited", "limitedOption": "Limited", diff --git a/src/lib/types.ts b/src/lib/types.ts index 1d7ec05..4f7dad7 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -1,6 +1,7 @@ export type EventType = 'limited' | 'unlimited'; export type EventVisibility = 'public' | 'private'; export type ActionType = 'add' | 'remove'; +export type LocationType = 'text' | 'maps'; export interface Event { id: string; @@ -8,6 +9,8 @@ export interface Event { date: string; time: string; location: string; + location_type: LocationType; + location_url?: string; type: EventType; attendee_limit?: number; visibility: EventVisibility; @@ -29,6 +32,8 @@ export interface CreateEventData { date: string; time: string; location: string; + location_type: LocationType; + location_url?: string; type: EventType; attendee_limit?: number; visibility: EventVisibility; @@ -40,6 +45,8 @@ export interface DatabaseEvent { date: string; time: string; location: string; + location_type: LocationType; + location_url?: string; type: EventType; attendee_limit?: number; visibility: EventVisibility; diff --git a/src/routes/create/+page.server.ts b/src/routes/create/+page.server.ts index 55385dc..4f935f6 100644 --- a/src/routes/create/+page.server.ts +++ b/src/routes/create/+page.server.ts @@ -21,6 +21,8 @@ export const actions: Actions = { const date = formData.get('date') as string; const time = formData.get('time') as string; const location = formData.get('location') as string; + const locationType = formData.get('location_type') as 'text' | 'maps'; + const locationUrl = formData.get('location_url') as string; const type = formData.get('type') as 'limited' | 'unlimited'; const attendeeLimit = formData.get('attendee_limit') as string; const visibility = formData.get('visibility') as 'public' | 'private'; @@ -33,6 +35,8 @@ export const actions: Actions = { if (!date) missingFields.push('date'); if (!time) missingFields.push('time'); if (!location?.trim()) missingFields.push('location'); + if (!locationType) missingFields.push('location_type'); + if (locationType === 'maps' && !locationUrl?.trim()) missingFields.push('location_url'); if (!userId) missingFields.push('userId'); if (missingFields.length > 0) { @@ -43,6 +47,8 @@ export const actions: Actions = { date, time, location, + location_type: locationType, + location_url: locationUrl, type, attendee_limit: attendeeLimit, visibility @@ -53,14 +59,34 @@ export const actions: Actions = { if (new Date(date) < new Date()) { return fail(400, { error: 'Date cannot be in the past.', - values: { name, date, time, location, type, attendee_limit: attendeeLimit, visibility } + values: { + name, + date, + time, + location, + location_type: locationType, + location_url: locationUrl, + type, + attendee_limit: attendeeLimit, + visibility + } }); } if (type === 'limited' && (!attendeeLimit || parseInt(attendeeLimit) < 2)) { return fail(400, { error: 'Limit must be at least 2 for limited events.', - values: { name, date, time, location, type, attendee_limit: attendeeLimit, visibility } + values: { + name, + date, + time, + location, + location_type: locationType, + location_url: locationUrl, + type, + attendee_limit: attendeeLimit, + visibility + } }); } @@ -74,6 +100,8 @@ export const actions: Actions = { date: date, time: time, location: location.trim(), + locationType: locationType, + locationUrl: locationType === 'maps' ? locationUrl?.trim() : null, type: type, attendeeLimit: type === 'limited' ? parseInt(attendeeLimit) : null, visibility: visibility, diff --git a/src/routes/create/+page.svelte b/src/routes/create/+page.svelte index 7aaf365..eb8440c 100644 --- a/src/routes/create/+page.svelte +++ b/src/routes/create/+page.svelte @@ -1,5 +1,5 @@