diff --git a/server/api/events/[id]/tickets/available.get.js b/server/api/events/[id]/tickets/available.get.js index 4107c0a..2fd64a3 100644 --- a/server/api/events/[id]/tickets/available.get.js +++ b/server/api/events/[id]/tickets/available.get.js @@ -1,8 +1,10 @@ import Member from "../../../../models/member.js"; +import Series from "../../../../models/series.js"; import { loadPublicEvent } from "../../../../utils/loadEvent.js"; import { calculateTicketPrice, checkTicketAvailability, + checkUserSeriesPass, formatPrice, } from "../../../../utils/tickets.js"; @@ -34,6 +36,36 @@ export default defineEventHandler(async (event) => { return { available: false, reason: "Registration deadline has passed" }; } + // Series-pass gate: when an event is linked to a series that requires a + // pass and doesn't allow drop-ins, surface a structured response so the UI + // can route to the series page instead of rendering a generic "Sold Out" + // block. The register/purchase handlers enforce this gate independently. + if ( + eventData.tickets?.requiresSeriesTicket && + eventData.tickets?.seriesTicketReference + ) { + const series = await Series.findById( + eventData.tickets.seriesTicketReference, + ); + if (series && !series.tickets?.allowIndividualEventTickets) { + const hasPass = userEmail + ? checkUserSeriesPass(series, userEmail).hasPass + : false; + if (!hasPass) { + return { + available: false, + reason: "series_pass_required", + requiresSeriesPass: true, + series: { + id: series.id, + title: series.title, + slug: series.slug, + }, + }; + } + } + } + let member = null; if (userEmail) { member = await Member.findOne({ email: userEmail.toLowerCase() });