@getmicdrop/venue-calendar 4.2.19 → 4.2.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +809 -809
  2. package/dist/{ArrowLeftIcon-CNxF9YFb.js → ArrowLeftIcon-BqN28ha3.js} +1 -1
  3. package/dist/{BlockPlaceholder-CnvIm7eh.js → BlockPlaceholder-BbAt-Tg-.js} +1 -1
  4. package/dist/{Calendar-D7s5gLk_.js → Calendar-U5hnCpbC.js} +4 -4
  5. package/dist/{CalendarFoundryView-BrsToTIb.js → CalendarFoundryView-Dp9aWtMW.js} +21 -21
  6. package/dist/{CalendarIcon-BBTaRYp7.js → CalendarIcon-rW7mGNf-.js} +1 -1
  7. package/dist/{CartView-cHL2n0fE.js → CartView-C_Jserc3.js} +17 -17
  8. package/dist/{Checkout-Blc7IuZ2.js → Checkout--3sirpfA.js} +19 -19
  9. package/dist/{Checkout-DNVyBcrm.js → Checkout-6iLNyJL4.js} +12 -12
  10. package/dist/{CheckoutTimer-D5i2F8al.js → CheckoutTimer-DeggCjT7.js} +2 -2
  11. package/dist/{ChevronDownIcon-Ba1RZBsy.js → ChevronDownIcon-CRBoka3q.js} +1 -1
  12. package/dist/{ClockIcon-DNJWLc1C.js → ClockIcon-C2SSi7r9.js} +1 -1
  13. package/dist/{CloseIcon-BpvJsO5u.js → CloseIcon-DbyLo0wv.js} +1 -1
  14. package/dist/{CollectionView-DzoCBduF.js → CollectionView-DsY-3zrB.js} +6 -6
  15. package/dist/{Event-D5u7y0w3.js → Event-Cg55HVe2.js} +15 -15
  16. package/dist/{EventPage-Ddslq4xC.js → EventPage-B0D2Nt8W.js} +253 -221
  17. package/dist/{Input-BvutquC_.js → Input-CsEAqNoQ.js} +1 -1
  18. package/dist/{MailIcon-r5G2Lccc.js → MailIcon-AAaLCJoB.js} +1 -1
  19. package/dist/{Modal-kJYg2hMo.js → Modal-CkmAyUI8.js} +2 -2
  20. package/dist/{ModalFooter-DMxM0E8Y.js → ModalFooter-qMsABjsX.js} +1 -1
  21. package/dist/{PasswordInput-D5_BuRnw.js → PasswordInput-D8t781xL.js} +1 -1
  22. package/dist/{ScarcityBadge-R32tzjFi.js → ScarcityBadge-D8jxkAwg.js} +2 -2
  23. package/dist/{SeriesPage-BPm7CNzY.js → SeriesPage-DJd4xn3s.js} +3 -3
  24. package/dist/{Success-CXI2Xtdz.js → Success-B6-9onbn.js} +11 -11
  25. package/dist/{TagIcon-_OxEQnM3.js → TagIcon-W-WW2VlE.js} +1 -1
  26. package/dist/{WarningCircleIcon-D1Kt1Sw1.js → WarningCircleIcon-MDOyrbVe.js} +1 -1
  27. package/dist/{WarningTriIcon-DjcnvD51.js → WarningTriIcon-xGAKz2tQ.js} +1 -1
  28. package/dist/api/api.cjs +1 -1
  29. package/dist/api/api.mjs +40 -26
  30. package/dist/api/index.d.ts +2 -2
  31. package/dist/api/types.d.ts +545 -545
  32. package/dist/api/waitlist.d.ts +13 -0
  33. package/dist/{colors-5g6cmoXX.js → colors-Cda5acAs.js} +48 -34
  34. package/dist/{constants-BWfNecCz.js → constants-CJl8xBgZ.js} +8 -0
  35. package/dist/{copyToClipboard-BTVrJgtZ.js → copyToClipboard-CR4oQ-Cl.js} +1 -1
  36. package/dist/{event-transform-0VCBk_zn.js → event-transform-CiNAjNiG.js} +9 -9
  37. package/dist/{href-D7yXIGzE.js → href-DVMxZ5ld.js} +1 -1
  38. package/dist/{i18n-D7pvVga7.js → i18n-BjGCvdyp.js} +3 -3
  39. package/dist/{labels-7f_yKBtq.js → labels-B3HOg72i.js} +1 -1
  40. package/dist/{modalManager.svelte-DDWCrGed.js → modalManager.svelte-DWKDviaT.js} +1 -1
  41. package/dist/{order-totals-DptlFbmv.js → order-totals-Bb6CAQ2m.js} +1 -1
  42. package/dist/{phoneUtils-PJqYw0dJ.js → phoneUtils-BzmQamKZ.js} +1 -1
  43. package/dist/seo/types.d.ts +156 -156
  44. package/dist/{serverTotals-COgFExPF.js → serverTotals-BgBXBXhP.js} +1 -1
  45. package/dist/{shareCopyFeedback.svelte-DZMLGA3y.js → shareCopyFeedback.svelte-Ds8OOtQ2.js} +2 -2
  46. package/dist/{to-event-card-props-BaPEUAZW.js → to-event-card-props-Bxj-yK9y.js} +1 -1
  47. package/dist/types/index.d.ts +509 -509
  48. package/dist/{utils-DLfxcnPk.js → utils-DMilKJFC.js} +1 -1
  49. package/dist/venue-calendar.css +1 -1
  50. package/dist/venue-calendar.es.js +27 -27
  51. package/dist/venue-calendar.iife.js +22 -22
  52. package/dist/venue-calendar.umd.js +55 -55
  53. package/package.json +2 -2
  54. package/src/lib/theme.js +222 -222
  55. /package/dist/locales/{4.2.19 → 4.2.21}/flow/de.js +0 -0
  56. /package/dist/locales/{4.2.19 → 4.2.21}/flow/es.js +0 -0
  57. /package/dist/locales/{4.2.19 → 4.2.21}/flow/fr.js +0 -0
  58. /package/dist/locales/{4.2.19 → 4.2.21}/flow/id.js +0 -0
  59. /package/dist/locales/{4.2.19 → 4.2.21}/flow/it.js +0 -0
  60. /package/dist/locales/{4.2.19 → 4.2.21}/flow/ja.js +0 -0
  61. /package/dist/locales/{4.2.19 → 4.2.21}/flow/ko.js +0 -0
  62. /package/dist/locales/{4.2.19 → 4.2.21}/flow/nl.js +0 -0
  63. /package/dist/locales/{4.2.19 → 4.2.21}/flow/pl.js +0 -0
  64. /package/dist/locales/{4.2.19 → 4.2.21}/flow/pt-br.js +0 -0
  65. /package/dist/locales/{4.2.19 → 4.2.21}/flow/tr.js +0 -0
  66. /package/dist/locales/{4.2.19 → 4.2.21}/flow/zh.js +0 -0
  67. /package/dist/locales/{4.2.19 → 4.2.21}/main/de.js +0 -0
  68. /package/dist/locales/{4.2.19 → 4.2.21}/main/es.js +0 -0
  69. /package/dist/locales/{4.2.19 → 4.2.21}/main/fr.js +0 -0
  70. /package/dist/locales/{4.2.19 → 4.2.21}/main/id.js +0 -0
  71. /package/dist/locales/{4.2.19 → 4.2.21}/main/it.js +0 -0
  72. /package/dist/locales/{4.2.19 → 4.2.21}/main/ja.js +0 -0
  73. /package/dist/locales/{4.2.19 → 4.2.21}/main/ko.js +0 -0
  74. /package/dist/locales/{4.2.19 → 4.2.21}/main/nl.js +0 -0
  75. /package/dist/locales/{4.2.19 → 4.2.21}/main/pl.js +0 -0
  76. /package/dist/locales/{4.2.19 → 4.2.21}/main/pt-br.js +0 -0
  77. /package/dist/locales/{4.2.19 → 4.2.21}/main/tr.js +0 -0
  78. /package/dist/locales/{4.2.19 → 4.2.21}/main/zh.js +0 -0
@@ -1,545 +1,545 @@
1
- /**
2
- * API Types for MicDrop Public Checkout API
3
- *
4
- * These types define the shape of API requests and responses
5
- * for the public checkout flow (no authentication required).
6
- */
7
-
8
- import type { EventId, SeriesId } from '@getmicdrop/svelte-components';
9
-
10
- // ============================================================================
11
- // Payment & Orders
12
- // ============================================================================
13
-
14
- export interface PaymentIntentRequest {
15
- IP?: string;
16
- productQuantities: Record<string | number, number>;
17
- }
18
-
19
- export interface PaymentIntentResponse {
20
- client_secret: string;
21
- // The residual the buyer is charged via Stripe, in int cents (net of any
22
- // applied gift card). This is the field the checkout Total keys off. The
23
- // orders-service response has NO `amount` key — an earlier `amount` here was
24
- // never populated, so Total rendered $0.00 on every paid order.
25
- stripe_amount?: number;
26
- amount_total: number;
27
- subtotal?: number;
28
- tax?: number;
29
- tax_amount_exclusive?: number;
30
- service_fee?: number;
31
- // The buyer's SECOND fee rail (MIC-1760) — card processing, grossed up and
32
- // passed through at cost, in int cents. orders-service reports it separately
33
- // from `service_fee` and folds it into `stripe_amount`. The PUBLIC receipt
34
- // merges the two into one "Service fees" row (Gus, 2026-08-16), which is also
35
- // what the pre-server estimate (`estimateBuyerFee`) has always shown; leaving
36
- // it unread made the priced total exceed the rows on screen by this amount.
37
- card_processing?: number;
38
- discount?: number;
39
- gift_card_amount?: number;
40
- gift_card_code?: string;
41
- stripe_publishable_key?: string;
42
- // ISO-4217 currency (Stripe returns it lowercase). Feeds the checkout totals.
43
- currency?: string;
44
- // The three fields the $0 path answers with. orders-service finalizes a free
45
- // order in-band on this same endpoint — it generates the tickets, marks the
46
- // cart completed and sends the confirmation — and reports that back here
47
- // instead of on a client secret. Views/Checkout.svelte reads all three to
48
- // decide whether the cart really is still free and really did finalize, so a
49
- // cart that gained a chargeable amount between selection and submit is caught
50
- // rather than waved through.
51
- //
52
- // They were undeclared, which is not a cosmetic gap: this interface has no
53
- // index signature, so every one of those three reads was a hard svelte-check
54
- // error against the shape the server actually sends.
55
- /** False when the cart still needs a Stripe charge — i.e. it is NOT free. */
56
- requires_stripe?: boolean;
57
- /** 'free' on the in-band $0 finalize path. */
58
- payment_type?: string;
59
- /** False when the backend declined to finalize the free order. */
60
- order_finalized?: boolean;
61
- }
62
-
63
- export interface CompleteReservationResponse {
64
- success: boolean;
65
- message?: string;
66
- error?: string;
67
- }
68
-
69
- export interface CancelReservationResponse {
70
- success: boolean;
71
- message?: string;
72
- error?: string;
73
- }
74
-
75
- export interface CreateOrderRequest {
76
- eventId: string | number;
77
- promoCode?: string;
78
- }
79
-
80
- export interface CreateOrderResponse {
81
- uuid: string;
82
- }
83
-
84
- export interface ValidatePaymentRequest {
85
- id: string;
86
- paymentIntentId: string;
87
- tickets: Record<string | number, number>;
88
- firstName: string;
89
- lastName: string;
90
- email: string;
91
- /** Buyer phone collected at checkout (MIC-1705). Persisted on the order. */
92
- phone?: string;
93
- /** Answer to the venue's custom checkout question (MIC-1705). */
94
- questionAnswer?: string;
95
- paymentMethod: string;
96
- mailingList?: boolean;
97
- saleType?: string;
98
- attendees?: AttendeeInfo[];
99
- }
100
-
101
- export interface AttendeeInfo {
102
- ticketId: string | number;
103
- firstName: string;
104
- lastName: string;
105
- email: string;
106
- }
107
-
108
- export interface ValidatePaymentResponse {
109
- success: boolean;
110
- status: string;
111
- orderUUID?: string;
112
- error?: string;
113
- /**
114
- * Fail-closed FLOOR fields. When the event is canceled/postponed between
115
- * cart-creation and finalize, the backend gate refuses to capture the charge
116
- * and sets one of these (alongside `success: false`). Either flag means the
117
- * buyer was NOT charged; the checkout raises the charge-aware
118
- * EventUnavailableModal instead of navigating to success. Optional because
119
- * they are only present on the rejected-finalize path.
120
- */
121
- eventCanceled?: boolean;
122
- eventPostponed?: boolean;
123
- }
124
-
125
- // ============================================================================
126
- // Session Management
127
- // ============================================================================
128
-
129
- export interface ExtendSessionRequest {
130
- orderUuid: string;
131
- }
132
-
133
- export interface ExtendSessionResponse {
134
- success: boolean;
135
- newExpiryTime?: string;
136
- remainingExtensions?: number;
137
- error?: string;
138
- /**
139
- * HTTP status code from the backend on failure. Populated on non-OK
140
- * responses so callers can distinguish "session already expired"
141
- * (typically 410 Gone) from "max extensions reached" or generic 5xx.
142
- * Undefined on network errors and on success.
143
- */
144
- statusCode?: number;
145
- }
146
-
147
- export interface SessionStatus {
148
- expiresAt?: string;
149
- extensionCount?: number;
150
- remainingExtensions?: number;
151
- canExtend?: boolean;
152
- reservationCount?: number;
153
- error?: string;
154
- // 404 from /orders/session/{uuid}: the cart's reservations are
155
- // gone (expired or never existed). Callers polling on an interval
156
- // MUST treat this as terminal and stop — see CartView.svelte.
157
- notFound?: boolean;
158
- // Live event status the session heartbeat used to piggyback so the public
159
- // checkout could flip the buy button on cancel/postpone. As of MIC-1408 the
160
- // checkout rides a dedicated per-event SSE stream (subscribeEventStatus)
161
- // instead, flipping instantly rather than within ~30s — this field is no
162
- // longer consumed there. One of the auth-service Event statuses
163
- // (draft|live|started|ended|canceled|postponed). Absent if the lookup failed.
164
- eventStatus?: string;
165
- }
166
-
167
- // ============================================================================
168
- // Promo Codes
169
- // ============================================================================
170
-
171
- export interface PromoValidationResponse {
172
- valid: boolean;
173
- revealHiddenTickets?: boolean;
174
- revealTicketIds?: number[];
175
- provideDiscount?: boolean;
176
- discountType?: 'percentage' | 'fixed';
177
- amount?: number;
178
- code?: string;
179
- /**
180
- * The code's ticket scope, exactly as the server sends it — `false` here plus
181
- * a non-empty `applyToTicketIds` means the code covers only those types.
182
- * Dropped by validatePromoCode until MIC-2575, which is why a code scoped to
183
- * one type discounted every type. Read through promoTicketIdsFromValidation,
184
- * never field-by-field at a call site.
185
- */
186
- appliesToAllTickets?: boolean;
187
- applyToTicketIds?: number[];
188
- error?: string;
189
- }
190
-
191
- /**
192
- * RAW body of GET /api/v2/public/promo-codes/validate/{eventId}/{code}.
193
- *
194
- * The live endpoint reports the discount magnitude as `discount` and the code
195
- * as `name` — NOT `amount`/`code`. validatePromoCode normalises this into the
196
- * client-facing PromoValidationResponse (amount/code) so callers read one shape.
197
- * The legacy `amount`/`code` keys stay optional here so an older payload (or a
198
- * mocked one) still maps through the same fallback. Verified 2026-07-11 against
199
- * two live promos (seeded GUARD20PCT + pre-existing EARLY20).
200
- */
201
- export interface RawPromoValidationBody {
202
- valid?: boolean;
203
- revealHiddenTickets?: boolean;
204
- revealTicketIds?: number[];
205
- provideDiscount?: boolean;
206
- discountType?: 'percentage' | 'fixed';
207
- /** Discount magnitude — real server field. */
208
- discount?: number;
209
- /** Legacy/mocked magnitude field — fallback only. */
210
- amount?: number;
211
- /** Promo code — real server field. */
212
- name?: string;
213
- /** Legacy/mocked code field — fallback only. */
214
- code?: string;
215
- appliesToAllTickets?: boolean;
216
- applyToTicketIds?: number[];
217
- isGlobal?: boolean;
218
- ticketLimit?: number;
219
- error?: string;
220
- }
221
-
222
- export interface HasPromoCodesResponse {
223
- hasPromoCodes: boolean;
224
- }
225
-
226
- // ============================================================================
227
- // Orders
228
- // ============================================================================
229
-
230
- export interface Order {
231
- uuid: string;
232
- id?: number;
233
- customerEmail: string;
234
- customerFirstName?: string;
235
- customerLastName?: string;
236
- status: string;
237
- totalAmount: number;
238
- subtotal?: number;
239
- serviceFeesAmount: number;
240
- taxAmount: number;
241
- discount?: number;
242
- paymentIntentId?: string;
243
- paymentMethod?: string;
244
- purchasedTickets: PurchasedTicket[];
245
- createdAt?: string;
246
- updatedAt?: string;
247
- }
248
-
249
- export interface PurchasedTicket {
250
- uuid: string;
251
- id?: number;
252
- ticketNumber?: string;
253
- orderId?: string | number;
254
- attendeeFirstName?: string;
255
- attendeeLastName?: string;
256
- attendeeEmail?: string;
257
- ticketName: string;
258
- ticketTypeId?: number;
259
- purchasePrice: number;
260
- status?: string;
261
- checkedIn?: boolean;
262
- checkedInAt?: string;
263
- }
264
-
265
- // ============================================================================
266
- // Events
267
- // ============================================================================
268
-
269
- export interface Event {
270
- eventID: number;
271
- id?: number;
272
- name: string;
273
- title?: string;
274
- slug?: string;
275
- description?: string;
276
- date: string;
277
- startDateTime?: string;
278
- endDateTime?: string;
279
- doorsOpenTime?: string;
280
- timezone?: string;
281
- venueId?: number;
282
- venueName?: string;
283
- venueAddress?: string;
284
- location?: string;
285
- imageUrl?: string;
286
- imageURL?: string;
287
- status?: string;
288
- isPublished?: boolean;
289
- isCancelled?: boolean;
290
- availableTickets?: AvailableTicket[];
291
- ticketsAvailable?: number;
292
- ticketsSold?: number;
293
- minPrice?: number;
294
- maxPrice?: number;
295
- ctaText?: string;
296
- ctaState?: 'available' | 'sold_out' | 'coming_soon' | 'ended';
297
- showPerformers?: boolean;
298
- eventSeriesId?: number;
299
- seriesInstanceNumber?: number;
300
- // ── Passthrough seams (events convergence) ─────────────────────────────
301
- // Carried verbatim from the wire by parseEvent so view-shape projections
302
- // (utils/event-transform.js, public-calendar-flow/transform.ts) compose the
303
- // canonical instead of reading wire fields directly. The object-valued
304
- // fields are intentionally raw (see composition note in
305
- // api/transformers/event.ts) — their consumers own the wire variance.
306
- timeZone?: string;
307
- /**
308
- * The operator's sales dial — effective on public payloads (MIC-2455).
309
- * `computeCtaState` refuses on `paused` / `ended` / `sold_out` and leaves
310
- * `not_started` to the sale-window logic.
311
- */
312
- salesStatus?: string;
313
- eventSummary?: string;
314
- password?: string;
315
- hasPassword?: boolean;
316
- disclaimer?: string;
317
- ticketType?: number;
318
- eventTicketingType?: number;
319
- ageRestriction?: number;
320
- displayAgeRestriction?: boolean;
321
- displayStartTime?: boolean;
322
- displayEndTime?: boolean;
323
- displayDoorsTime?: boolean;
324
- collectionId?: number;
325
- ticketsRemaining?: number;
326
- ticketsTotal?: number;
327
- hasHiddenTickets?: boolean;
328
- stage?: { name?: string } | null;
329
- stageName?: string;
330
- // Widget-filter dimensions on the public month feeds (saved-widget engine):
331
- // stage/category/performer/collection data the embed adapts onto the SC
332
- // FilterableEvent view (lib/widget-filters.js).
333
- stageId?: number;
334
- eventCategoryTypes?: number[] | null;
335
- publicPerformers?: Array<{
336
- id?: number;
337
- displayName?: string;
338
- image?: string;
339
- }> | null;
340
- collectionIds?: number[] | null;
341
- venue?: Record<string, any>;
342
- performers?: Record<string, any>[];
343
- faqs?: Record<string, any>[];
344
- showtimes?: Record<string, any>[];
345
- purchasedTickets?: Record<string, any>[];
346
- }
347
-
348
- export interface AvailableTicket {
349
- id: number;
350
- name: string;
351
- description?: string;
352
- price: number;
353
- quantity: number;
354
- quantitySold?: number;
355
- quantityAvailable?: number;
356
- minPerOrder?: number;
357
- maxPerOrder?: number;
358
- saleStartDate?: string;
359
- saleEndDate?: string;
360
- isHidden?: boolean;
361
- revealWithPromoCode?: boolean;
362
- ticketType?: number; // 0 = GA, 1 = assigned
363
- sectionId?: number;
364
- sortOrder?: number;
365
- }
366
-
367
- export interface EventPerformersResponse {
368
- performers: Performer[];
369
- showPerformers: boolean;
370
- }
371
-
372
- export interface Performer {
373
- id: number;
374
- displayName: string;
375
- avatar?: string;
376
- order?: number;
377
- }
378
-
379
- // ============================================================================
380
- // Venues
381
- // ============================================================================
382
-
383
- export interface Venue {
384
- id: number;
385
- name: string;
386
- slug?: string;
387
- address?: string;
388
- googleLocationNameCache?: string;
389
- city?: string;
390
- state?: string;
391
- zipCode?: string;
392
- country?: string;
393
- timezone?: string;
394
- logoUrl?: string;
395
- serviceFeePercentage?: number;
396
- serviceFeeCents?: number;
397
- /** True = the venue absorbs the service fee; false/absent = the buyer pays it. */
398
- absorbServiceFee?: boolean;
399
- taxPercentage?: number;
400
- organizationId?: number;
401
- }
402
-
403
- // ============================================================================
404
- // Series
405
- // ============================================================================
406
-
407
- export interface SeriesOccurrence {
408
- eventId: EventId;
409
- date: string;
410
- startDateTime: string;
411
- endDateTime?: string;
412
- instanceNumber: number;
413
- status: string;
414
- ticketsAvailable?: number;
415
- ctaState?: string;
416
- }
417
-
418
- export interface SeriesOccurrencesResponse {
419
- seriesId: SeriesId;
420
- occurrences: SeriesOccurrence[];
421
- }
422
-
423
- // ============================================================================
424
- // Series Page
425
- // ============================================================================
426
-
427
- export interface SeriesPageData {
428
- id: number;
429
- title: string;
430
- description: string;
431
- eventSummary?: string;
432
- image?: string;
433
- timeZone: string;
434
- venue: {
435
- id: number;
436
- name: string;
437
- location?: string;
438
- googleLocationNameCache?: string;
439
- faq?: Array<{ question: string; answer: string }> | string;
440
- disclaimer?: string;
441
- };
442
- performers: Array<{
443
- displayName: string;
444
- firstName?: string;
445
- lastName?: string;
446
- profileImage?: string;
447
- }>;
448
- occurrences: Array<{
449
- id: number;
450
- slug: string;
451
- startDateTime: string;
452
- endDateTime: string;
453
- ctaState: {
454
- text: string;
455
- disabled: boolean;
456
- reason?: string;
457
- };
458
- /**
459
- * The occurrence's OWN location. The backend emits this ONLY when the
460
- * occurrence has been moved off the series master venue/stage, so
461
- * presence === "this showtime is not where the rest of the series is".
462
- * Absent for every ordinary occurrence.
463
- */
464
- venue?: {
465
- id: number;
466
- name: string;
467
- address?: string;
468
- city?: string;
469
- state?: string;
470
- googleLocationNameCache?: string;
471
- stageName?: string;
472
- };
473
- }>;
474
- }
475
-
476
- // ============================================================================
477
- // Entity resolution (type-agnostic public id → concrete entity)
478
- // ============================================================================
479
-
480
- /**
481
- * Response shape of `/api/v2/public/resolve/{id}`. The embed deep-link is
482
- * type-agnostic (`#{id}-{slug}`), so the backend resolves which kind of entity
483
- * the id points at; `data` is the corresponding event / series / collection
484
- * payload (same shape the dedicated fetchers return for that type).
485
- */
486
- export interface ResolvedEntity {
487
- type: 'event' | 'series' | 'collection';
488
- id: string | number;
489
- title?: string;
490
- data: any;
491
- }
492
-
493
- // ============================================================================
494
- // Public Collection
495
- // ============================================================================
496
-
497
- export interface PublicCollectionData {
498
- id: number;
499
- collectionTitle: string;
500
- summary?: string;
501
- description?: string;
502
- coverImage?: string;
503
- events: Array<{
504
- id: number;
505
- title: string;
506
- slug?: string;
507
- startDateTime?: string;
508
- endDateTime?: string;
509
- image?: string;
510
- status?: string;
511
- venue?: { name: string };
512
- minPrice?: number;
513
- }>;
514
- // Password-gate variant: a protected collection responds with a gate signal
515
- // instead of the collection body (either `passwordRequired: true` or an
516
- // `error.code === 'PASSWORD_REQUIRED'`). Modelled here as optional wire
517
- // fields so the view can read them without an `as any` escape.
518
- passwordRequired?: boolean;
519
- error?: { code?: string };
520
- }
521
-
522
- // ============================================================================
523
- // API Configuration
524
- // ============================================================================
525
-
526
- export interface ApiConfig {
527
- baseUrl?: string;
528
- timeout?: number;
529
- /** Number of additional attempts after the first try (default: 2 = 3 total tries). */
530
- retries?: number;
531
- /** Base delay before the first retry; doubles each attempt (default: 500ms). */
532
- retryDelay?: number;
533
- onError?: (_error: Error) => void;
534
- }
535
-
536
- // ============================================================================
537
- // Generic API Response
538
- // ============================================================================
539
-
540
- export interface ApiResponse<T> {
541
- success: boolean;
542
- data?: T;
543
- error?: string;
544
- statusCode?: number;
545
- }
1
+ /**
2
+ * API Types for MicDrop Public Checkout API
3
+ *
4
+ * These types define the shape of API requests and responses
5
+ * for the public checkout flow (no authentication required).
6
+ */
7
+
8
+ import type { EventId, SeriesId } from '@getmicdrop/svelte-components';
9
+
10
+ // ============================================================================
11
+ // Payment & Orders
12
+ // ============================================================================
13
+
14
+ export interface PaymentIntentRequest {
15
+ IP?: string;
16
+ productQuantities: Record<string | number, number>;
17
+ }
18
+
19
+ export interface PaymentIntentResponse {
20
+ client_secret: string;
21
+ // The residual the buyer is charged via Stripe, in int cents (net of any
22
+ // applied gift card). This is the field the checkout Total keys off. The
23
+ // orders-service response has NO `amount` key — an earlier `amount` here was
24
+ // never populated, so Total rendered $0.00 on every paid order.
25
+ stripe_amount?: number;
26
+ amount_total: number;
27
+ subtotal?: number;
28
+ tax?: number;
29
+ tax_amount_exclusive?: number;
30
+ service_fee?: number;
31
+ // The buyer's SECOND fee rail (MIC-1760) — card processing, grossed up and
32
+ // passed through at cost, in int cents. orders-service reports it separately
33
+ // from `service_fee` and folds it into `stripe_amount`. The PUBLIC receipt
34
+ // merges the two into one "Service fees" row (Gus, 2026-08-16), which is also
35
+ // what the pre-server estimate (`estimateBuyerFee`) has always shown; leaving
36
+ // it unread made the priced total exceed the rows on screen by this amount.
37
+ card_processing?: number;
38
+ discount?: number;
39
+ gift_card_amount?: number;
40
+ gift_card_code?: string;
41
+ stripe_publishable_key?: string;
42
+ // ISO-4217 currency (Stripe returns it lowercase). Feeds the checkout totals.
43
+ currency?: string;
44
+ // The three fields the $0 path answers with. orders-service finalizes a free
45
+ // order in-band on this same endpoint — it generates the tickets, marks the
46
+ // cart completed and sends the confirmation — and reports that back here
47
+ // instead of on a client secret. Views/Checkout.svelte reads all three to
48
+ // decide whether the cart really is still free and really did finalize, so a
49
+ // cart that gained a chargeable amount between selection and submit is caught
50
+ // rather than waved through.
51
+ //
52
+ // They were undeclared, which is not a cosmetic gap: this interface has no
53
+ // index signature, so every one of those three reads was a hard svelte-check
54
+ // error against the shape the server actually sends.
55
+ /** False when the cart still needs a Stripe charge — i.e. it is NOT free. */
56
+ requires_stripe?: boolean;
57
+ /** 'free' on the in-band $0 finalize path. */
58
+ payment_type?: string;
59
+ /** False when the backend declined to finalize the free order. */
60
+ order_finalized?: boolean;
61
+ }
62
+
63
+ export interface CompleteReservationResponse {
64
+ success: boolean;
65
+ message?: string;
66
+ error?: string;
67
+ }
68
+
69
+ export interface CancelReservationResponse {
70
+ success: boolean;
71
+ message?: string;
72
+ error?: string;
73
+ }
74
+
75
+ export interface CreateOrderRequest {
76
+ eventId: string | number;
77
+ promoCode?: string;
78
+ }
79
+
80
+ export interface CreateOrderResponse {
81
+ uuid: string;
82
+ }
83
+
84
+ export interface ValidatePaymentRequest {
85
+ id: string;
86
+ paymentIntentId: string;
87
+ tickets: Record<string | number, number>;
88
+ firstName: string;
89
+ lastName: string;
90
+ email: string;
91
+ /** Buyer phone collected at checkout (MIC-1705). Persisted on the order. */
92
+ phone?: string;
93
+ /** Answer to the venue's custom checkout question (MIC-1705). */
94
+ questionAnswer?: string;
95
+ paymentMethod: string;
96
+ mailingList?: boolean;
97
+ saleType?: string;
98
+ attendees?: AttendeeInfo[];
99
+ }
100
+
101
+ export interface AttendeeInfo {
102
+ ticketId: string | number;
103
+ firstName: string;
104
+ lastName: string;
105
+ email: string;
106
+ }
107
+
108
+ export interface ValidatePaymentResponse {
109
+ success: boolean;
110
+ status: string;
111
+ orderUUID?: string;
112
+ error?: string;
113
+ /**
114
+ * Fail-closed FLOOR fields. When the event is canceled/postponed between
115
+ * cart-creation and finalize, the backend gate refuses to capture the charge
116
+ * and sets one of these (alongside `success: false`). Either flag means the
117
+ * buyer was NOT charged; the checkout raises the charge-aware
118
+ * EventUnavailableModal instead of navigating to success. Optional because
119
+ * they are only present on the rejected-finalize path.
120
+ */
121
+ eventCanceled?: boolean;
122
+ eventPostponed?: boolean;
123
+ }
124
+
125
+ // ============================================================================
126
+ // Session Management
127
+ // ============================================================================
128
+
129
+ export interface ExtendSessionRequest {
130
+ orderUuid: string;
131
+ }
132
+
133
+ export interface ExtendSessionResponse {
134
+ success: boolean;
135
+ newExpiryTime?: string;
136
+ remainingExtensions?: number;
137
+ error?: string;
138
+ /**
139
+ * HTTP status code from the backend on failure. Populated on non-OK
140
+ * responses so callers can distinguish "session already expired"
141
+ * (typically 410 Gone) from "max extensions reached" or generic 5xx.
142
+ * Undefined on network errors and on success.
143
+ */
144
+ statusCode?: number;
145
+ }
146
+
147
+ export interface SessionStatus {
148
+ expiresAt?: string;
149
+ extensionCount?: number;
150
+ remainingExtensions?: number;
151
+ canExtend?: boolean;
152
+ reservationCount?: number;
153
+ error?: string;
154
+ // 404 from /orders/session/{uuid}: the cart's reservations are
155
+ // gone (expired or never existed). Callers polling on an interval
156
+ // MUST treat this as terminal and stop — see CartView.svelte.
157
+ notFound?: boolean;
158
+ // Live event status the session heartbeat used to piggyback so the public
159
+ // checkout could flip the buy button on cancel/postpone. As of MIC-1408 the
160
+ // checkout rides a dedicated per-event SSE stream (subscribeEventStatus)
161
+ // instead, flipping instantly rather than within ~30s — this field is no
162
+ // longer consumed there. One of the auth-service Event statuses
163
+ // (draft|live|started|ended|canceled|postponed). Absent if the lookup failed.
164
+ eventStatus?: string;
165
+ }
166
+
167
+ // ============================================================================
168
+ // Promo Codes
169
+ // ============================================================================
170
+
171
+ export interface PromoValidationResponse {
172
+ valid: boolean;
173
+ revealHiddenTickets?: boolean;
174
+ revealTicketIds?: number[];
175
+ provideDiscount?: boolean;
176
+ discountType?: 'percentage' | 'fixed';
177
+ amount?: number;
178
+ code?: string;
179
+ /**
180
+ * The code's ticket scope, exactly as the server sends it — `false` here plus
181
+ * a non-empty `applyToTicketIds` means the code covers only those types.
182
+ * Dropped by validatePromoCode until MIC-2575, which is why a code scoped to
183
+ * one type discounted every type. Read through promoTicketIdsFromValidation,
184
+ * never field-by-field at a call site.
185
+ */
186
+ appliesToAllTickets?: boolean;
187
+ applyToTicketIds?: number[];
188
+ error?: string;
189
+ }
190
+
191
+ /**
192
+ * RAW body of GET /api/v2/public/promo-codes/validate/{eventId}/{code}.
193
+ *
194
+ * The live endpoint reports the discount magnitude as `discount` and the code
195
+ * as `name` — NOT `amount`/`code`. validatePromoCode normalises this into the
196
+ * client-facing PromoValidationResponse (amount/code) so callers read one shape.
197
+ * The legacy `amount`/`code` keys stay optional here so an older payload (or a
198
+ * mocked one) still maps through the same fallback. Verified 2026-07-11 against
199
+ * two live promos (seeded GUARD20PCT + pre-existing EARLY20).
200
+ */
201
+ export interface RawPromoValidationBody {
202
+ valid?: boolean;
203
+ revealHiddenTickets?: boolean;
204
+ revealTicketIds?: number[];
205
+ provideDiscount?: boolean;
206
+ discountType?: 'percentage' | 'fixed';
207
+ /** Discount magnitude — real server field. */
208
+ discount?: number;
209
+ /** Legacy/mocked magnitude field — fallback only. */
210
+ amount?: number;
211
+ /** Promo code — real server field. */
212
+ name?: string;
213
+ /** Legacy/mocked code field — fallback only. */
214
+ code?: string;
215
+ appliesToAllTickets?: boolean;
216
+ applyToTicketIds?: number[];
217
+ isGlobal?: boolean;
218
+ ticketLimit?: number;
219
+ error?: string;
220
+ }
221
+
222
+ export interface HasPromoCodesResponse {
223
+ hasPromoCodes: boolean;
224
+ }
225
+
226
+ // ============================================================================
227
+ // Orders
228
+ // ============================================================================
229
+
230
+ export interface Order {
231
+ uuid: string;
232
+ id?: number;
233
+ customerEmail: string;
234
+ customerFirstName?: string;
235
+ customerLastName?: string;
236
+ status: string;
237
+ totalAmount: number;
238
+ subtotal?: number;
239
+ serviceFeesAmount: number;
240
+ taxAmount: number;
241
+ discount?: number;
242
+ paymentIntentId?: string;
243
+ paymentMethod?: string;
244
+ purchasedTickets: PurchasedTicket[];
245
+ createdAt?: string;
246
+ updatedAt?: string;
247
+ }
248
+
249
+ export interface PurchasedTicket {
250
+ uuid: string;
251
+ id?: number;
252
+ ticketNumber?: string;
253
+ orderId?: string | number;
254
+ attendeeFirstName?: string;
255
+ attendeeLastName?: string;
256
+ attendeeEmail?: string;
257
+ ticketName: string;
258
+ ticketTypeId?: number;
259
+ purchasePrice: number;
260
+ status?: string;
261
+ checkedIn?: boolean;
262
+ checkedInAt?: string;
263
+ }
264
+
265
+ // ============================================================================
266
+ // Events
267
+ // ============================================================================
268
+
269
+ export interface Event {
270
+ eventID: number;
271
+ id?: number;
272
+ name: string;
273
+ title?: string;
274
+ slug?: string;
275
+ description?: string;
276
+ date: string;
277
+ startDateTime?: string;
278
+ endDateTime?: string;
279
+ doorsOpenTime?: string;
280
+ timezone?: string;
281
+ venueId?: number;
282
+ venueName?: string;
283
+ venueAddress?: string;
284
+ location?: string;
285
+ imageUrl?: string;
286
+ imageURL?: string;
287
+ status?: string;
288
+ isPublished?: boolean;
289
+ isCancelled?: boolean;
290
+ availableTickets?: AvailableTicket[];
291
+ ticketsAvailable?: number;
292
+ ticketsSold?: number;
293
+ minPrice?: number;
294
+ maxPrice?: number;
295
+ ctaText?: string;
296
+ ctaState?: 'available' | 'sold_out' | 'coming_soon' | 'ended';
297
+ showPerformers?: boolean;
298
+ eventSeriesId?: number;
299
+ seriesInstanceNumber?: number;
300
+ // ── Passthrough seams (events convergence) ─────────────────────────────
301
+ // Carried verbatim from the wire by parseEvent so view-shape projections
302
+ // (utils/event-transform.js, public-calendar-flow/transform.ts) compose the
303
+ // canonical instead of reading wire fields directly. The object-valued
304
+ // fields are intentionally raw (see composition note in
305
+ // api/transformers/event.ts) — their consumers own the wire variance.
306
+ timeZone?: string;
307
+ /**
308
+ * The operator's sales dial — effective on public payloads (MIC-2455).
309
+ * `computeCtaState` refuses on `paused` / `ended` / `sold_out` and leaves
310
+ * `not_started` to the sale-window logic.
311
+ */
312
+ salesStatus?: string;
313
+ eventSummary?: string;
314
+ password?: string;
315
+ hasPassword?: boolean;
316
+ disclaimer?: string;
317
+ ticketType?: number;
318
+ eventTicketingType?: number;
319
+ ageRestriction?: number;
320
+ displayAgeRestriction?: boolean;
321
+ displayStartTime?: boolean;
322
+ displayEndTime?: boolean;
323
+ displayDoorsTime?: boolean;
324
+ collectionId?: number;
325
+ ticketsRemaining?: number;
326
+ ticketsTotal?: number;
327
+ hasHiddenTickets?: boolean;
328
+ stage?: { name?: string } | null;
329
+ stageName?: string;
330
+ // Widget-filter dimensions on the public month feeds (saved-widget engine):
331
+ // stage/category/performer/collection data the embed adapts onto the SC
332
+ // FilterableEvent view (lib/widget-filters.js).
333
+ stageId?: number;
334
+ eventCategoryTypes?: number[] | null;
335
+ publicPerformers?: Array<{
336
+ id?: number;
337
+ displayName?: string;
338
+ image?: string;
339
+ }> | null;
340
+ collectionIds?: number[] | null;
341
+ venue?: Record<string, any>;
342
+ performers?: Record<string, any>[];
343
+ faqs?: Record<string, any>[];
344
+ showtimes?: Record<string, any>[];
345
+ purchasedTickets?: Record<string, any>[];
346
+ }
347
+
348
+ export interface AvailableTicket {
349
+ id: number;
350
+ name: string;
351
+ description?: string;
352
+ price: number;
353
+ quantity: number;
354
+ quantitySold?: number;
355
+ quantityAvailable?: number;
356
+ minPerOrder?: number;
357
+ maxPerOrder?: number;
358
+ saleStartDate?: string;
359
+ saleEndDate?: string;
360
+ isHidden?: boolean;
361
+ revealWithPromoCode?: boolean;
362
+ ticketType?: number; // 0 = GA, 1 = assigned
363
+ sectionId?: number;
364
+ sortOrder?: number;
365
+ }
366
+
367
+ export interface EventPerformersResponse {
368
+ performers: Performer[];
369
+ showPerformers: boolean;
370
+ }
371
+
372
+ export interface Performer {
373
+ id: number;
374
+ displayName: string;
375
+ avatar?: string;
376
+ order?: number;
377
+ }
378
+
379
+ // ============================================================================
380
+ // Venues
381
+ // ============================================================================
382
+
383
+ export interface Venue {
384
+ id: number;
385
+ name: string;
386
+ slug?: string;
387
+ address?: string;
388
+ googleLocationNameCache?: string;
389
+ city?: string;
390
+ state?: string;
391
+ zipCode?: string;
392
+ country?: string;
393
+ timezone?: string;
394
+ logoUrl?: string;
395
+ serviceFeePercentage?: number;
396
+ serviceFeeCents?: number;
397
+ /** True = the venue absorbs the service fee; false/absent = the buyer pays it. */
398
+ absorbServiceFee?: boolean;
399
+ taxPercentage?: number;
400
+ organizationId?: number;
401
+ }
402
+
403
+ // ============================================================================
404
+ // Series
405
+ // ============================================================================
406
+
407
+ export interface SeriesOccurrence {
408
+ eventId: EventId;
409
+ date: string;
410
+ startDateTime: string;
411
+ endDateTime?: string;
412
+ instanceNumber: number;
413
+ status: string;
414
+ ticketsAvailable?: number;
415
+ ctaState?: string;
416
+ }
417
+
418
+ export interface SeriesOccurrencesResponse {
419
+ seriesId: SeriesId;
420
+ occurrences: SeriesOccurrence[];
421
+ }
422
+
423
+ // ============================================================================
424
+ // Series Page
425
+ // ============================================================================
426
+
427
+ export interface SeriesPageData {
428
+ id: number;
429
+ title: string;
430
+ description: string;
431
+ eventSummary?: string;
432
+ image?: string;
433
+ timeZone: string;
434
+ venue: {
435
+ id: number;
436
+ name: string;
437
+ location?: string;
438
+ googleLocationNameCache?: string;
439
+ faq?: Array<{ question: string; answer: string }> | string;
440
+ disclaimer?: string;
441
+ };
442
+ performers: Array<{
443
+ displayName: string;
444
+ firstName?: string;
445
+ lastName?: string;
446
+ profileImage?: string;
447
+ }>;
448
+ occurrences: Array<{
449
+ id: number;
450
+ slug: string;
451
+ startDateTime: string;
452
+ endDateTime: string;
453
+ ctaState: {
454
+ text: string;
455
+ disabled: boolean;
456
+ reason?: string;
457
+ };
458
+ /**
459
+ * The occurrence's OWN location. The backend emits this ONLY when the
460
+ * occurrence has been moved off the series master venue/stage, so
461
+ * presence === "this showtime is not where the rest of the series is".
462
+ * Absent for every ordinary occurrence.
463
+ */
464
+ venue?: {
465
+ id: number;
466
+ name: string;
467
+ address?: string;
468
+ city?: string;
469
+ state?: string;
470
+ googleLocationNameCache?: string;
471
+ stageName?: string;
472
+ };
473
+ }>;
474
+ }
475
+
476
+ // ============================================================================
477
+ // Entity resolution (type-agnostic public id → concrete entity)
478
+ // ============================================================================
479
+
480
+ /**
481
+ * Response shape of `/api/v2/public/resolve/{id}`. The embed deep-link is
482
+ * type-agnostic (`#{id}-{slug}`), so the backend resolves which kind of entity
483
+ * the id points at; `data` is the corresponding event / series / collection
484
+ * payload (same shape the dedicated fetchers return for that type).
485
+ */
486
+ export interface ResolvedEntity {
487
+ type: 'event' | 'series' | 'collection';
488
+ id: string | number;
489
+ title?: string;
490
+ data: any;
491
+ }
492
+
493
+ // ============================================================================
494
+ // Public Collection
495
+ // ============================================================================
496
+
497
+ export interface PublicCollectionData {
498
+ id: number;
499
+ collectionTitle: string;
500
+ summary?: string;
501
+ description?: string;
502
+ coverImage?: string;
503
+ events: Array<{
504
+ id: number;
505
+ title: string;
506
+ slug?: string;
507
+ startDateTime?: string;
508
+ endDateTime?: string;
509
+ image?: string;
510
+ status?: string;
511
+ venue?: { name: string };
512
+ minPrice?: number;
513
+ }>;
514
+ // Password-gate variant: a protected collection responds with a gate signal
515
+ // instead of the collection body (either `passwordRequired: true` or an
516
+ // `error.code === 'PASSWORD_REQUIRED'`). Modelled here as optional wire
517
+ // fields so the view can read them without an `as any` escape.
518
+ passwordRequired?: boolean;
519
+ error?: { code?: string };
520
+ }
521
+
522
+ // ============================================================================
523
+ // API Configuration
524
+ // ============================================================================
525
+
526
+ export interface ApiConfig {
527
+ baseUrl?: string;
528
+ timeout?: number;
529
+ /** Number of additional attempts after the first try (default: 2 = 3 total tries). */
530
+ retries?: number;
531
+ /** Base delay before the first retry; doubles each attempt (default: 500ms). */
532
+ retryDelay?: number;
533
+ onError?: (_error: Error) => void;
534
+ }
535
+
536
+ // ============================================================================
537
+ // Generic API Response
538
+ // ============================================================================
539
+
540
+ export interface ApiResponse<T> {
541
+ success: boolean;
542
+ data?: T;
543
+ error?: string;
544
+ statusCode?: number;
545
+ }