Add unlisted event status: hidden from listings but accessible by URL

- Backend: add 'unlisted' to schema enum and Zod validation; allow booking for unlisted events
- Frontend: Event type and guards updated; unlisted events bookable, excluded from public listing/sitemap
- Admin: badge, status dropdown, Make Unlisted / Make Public / Unpublish actions; scanner/emails/tickets include unlisted

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Michilis
2026-02-19 02:21:41 +00:00
parent 958181e049
commit bbfaa1172a
11 changed files with 48 additions and 16 deletions

View File

@@ -75,7 +75,7 @@ export const sqliteEvents = sqliteTable('events', {
price: real('price').notNull().default(0),
currency: text('currency').notNull().default('PYG'),
capacity: integer('capacity').notNull().default(50),
status: text('status', { enum: ['draft', 'published', 'cancelled', 'completed', 'archived'] }).notNull().default('draft'),
status: text('status', { enum: ['draft', 'published', 'unlisted', 'cancelled', 'completed', 'archived'] }).notNull().default('draft'),
bannerUrl: text('banner_url'),
externalBookingEnabled: integer('external_booking_enabled', { mode: 'boolean' }).notNull().default(false),
externalBookingUrl: text('external_booking_url'),

View File

@@ -75,7 +75,7 @@ const baseEventSchema = z.object({
price: z.union([z.number(), z.string()]).transform(parsePrice).pipe(z.number().min(0)).default(0),
currency: z.string().default('PYG'),
capacity: z.union([z.number(), z.string()]).transform((val) => typeof val === 'string' ? parseInt(val, 10) || 50 : val).pipe(z.number().min(1)).default(50),
status: z.enum(['draft', 'published', 'cancelled', 'completed', 'archived']).default('draft'),
status: z.enum(['draft', 'published', 'unlisted', 'cancelled', 'completed', 'archived']).default('draft'),
// Accept relative paths (/uploads/...) or full URLs
bannerUrl: z.string().optional().nullable().or(z.literal('')),
// External booking support - accept boolean or number (0/1 from DB)

View File

@@ -69,7 +69,7 @@ ticketsRouter.post('/', zValidator('json', createTicketSchema), async (c) => {
return c.json({ error: 'Event not found' }, 404);
}
if (event.status !== 'published') {
if (!['published', 'unlisted'].includes(event.status)) {
return c.json({ error: 'Event is not available for booking' }, 400);
}