@getmicdrop/venue-calendar 2.0.0 → 3.2.0

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.
@@ -0,0 +1,289 @@
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
+ // ============================================================================
9
+ // Payment & Orders
10
+ // ============================================================================
11
+
12
+ export interface PaymentIntentRequest {
13
+ IP?: string;
14
+ productQuantities: Record<string | number, number>;
15
+ }
16
+
17
+ export interface PaymentIntentResponse {
18
+ client_secret: string;
19
+ amount: number;
20
+ amount_total: number;
21
+ tax_amount_exclusive?: number;
22
+ service_fee?: number;
23
+ discount?: number;
24
+ gift_card_amount?: number;
25
+ gift_card_code?: string;
26
+ stripe_publishable_key?: string;
27
+ }
28
+
29
+ export interface CompleteReservationResponse {
30
+ success: boolean;
31
+ message?: string;
32
+ error?: string;
33
+ }
34
+
35
+ export interface CancelReservationResponse {
36
+ success: boolean;
37
+ message?: string;
38
+ error?: string;
39
+ }
40
+
41
+ export interface CreateOrderRequest {
42
+ eventId: string | number;
43
+ promoCode?: string;
44
+ }
45
+
46
+ export interface CreateOrderResponse {
47
+ uuid: string;
48
+ }
49
+
50
+ export interface ValidatePaymentRequest {
51
+ id: string;
52
+ paymentIntentId: string;
53
+ tickets: Record<string | number, number>;
54
+ firstName: string;
55
+ lastName: string;
56
+ email: string;
57
+ paymentMethod: string;
58
+ mailingList?: boolean;
59
+ saleType?: string;
60
+ attendees?: AttendeeInfo[];
61
+ }
62
+
63
+ export interface AttendeeInfo {
64
+ ticketId: string | number;
65
+ firstName: string;
66
+ lastName: string;
67
+ email: string;
68
+ }
69
+
70
+ export interface ValidatePaymentResponse {
71
+ success: boolean;
72
+ status: string;
73
+ orderUUID?: string;
74
+ error?: string;
75
+ }
76
+
77
+ // ============================================================================
78
+ // Session Management
79
+ // ============================================================================
80
+
81
+ export interface ExtendSessionRequest {
82
+ orderUuid: string;
83
+ }
84
+
85
+ export interface ExtendSessionResponse {
86
+ success: boolean;
87
+ newExpiryTime?: string;
88
+ remainingExtensions?: number;
89
+ error?: string;
90
+ }
91
+
92
+ export interface SessionStatus {
93
+ expiresAt?: string;
94
+ extensionCount?: number;
95
+ remainingExtensions?: number;
96
+ canExtend?: boolean;
97
+ reservationCount?: number;
98
+ error?: string;
99
+ }
100
+
101
+ // ============================================================================
102
+ // Promo Codes
103
+ // ============================================================================
104
+
105
+ export interface PromoValidationResponse {
106
+ valid: boolean;
107
+ revealHiddenTickets?: boolean;
108
+ revealTicketIds?: number[];
109
+ provideDiscount?: boolean;
110
+ discountType?: 'percentage' | 'fixed';
111
+ amount?: number;
112
+ code?: string;
113
+ error?: string;
114
+ }
115
+
116
+ export interface HasPromoCodesResponse {
117
+ hasPromoCodes: boolean;
118
+ }
119
+
120
+ // ============================================================================
121
+ // Orders
122
+ // ============================================================================
123
+
124
+ export interface Order {
125
+ uuid: string;
126
+ id?: number;
127
+ customerEmail: string;
128
+ customerFirstName?: string;
129
+ customerLastName?: string;
130
+ status: string;
131
+ totalAmount: number;
132
+ subtotal?: number;
133
+ serviceFeesAmount: number;
134
+ taxAmount: number;
135
+ discount?: number;
136
+ paymentIntentId?: string;
137
+ paymentMethod?: string;
138
+ purchasedTickets: PurchasedTicket[];
139
+ createdAt?: string;
140
+ updatedAt?: string;
141
+ }
142
+
143
+ export interface PurchasedTicket {
144
+ uuid: string;
145
+ id?: number;
146
+ ticketNumber?: string;
147
+ orderId?: string | number;
148
+ attendeeFirstName?: string;
149
+ attendeeLastName?: string;
150
+ attendeeEmail?: string;
151
+ ticketName: string;
152
+ ticketTypeId?: number;
153
+ purchasePrice: number;
154
+ status?: string;
155
+ checkedIn?: boolean;
156
+ checkedInAt?: string;
157
+ }
158
+
159
+ // ============================================================================
160
+ // Events
161
+ // ============================================================================
162
+
163
+ export interface Event {
164
+ eventID: number;
165
+ id?: number;
166
+ name: string;
167
+ title?: string;
168
+ slug?: string;
169
+ description?: string;
170
+ date: string;
171
+ startDateTime?: string;
172
+ endDateTime?: string;
173
+ doorsOpenTime?: string;
174
+ timezone?: string;
175
+ venueId?: number;
176
+ venueName?: string;
177
+ venueAddress?: string;
178
+ location?: string;
179
+ imageUrl?: string;
180
+ imageURL?: string;
181
+ status?: string;
182
+ isPublished?: boolean;
183
+ isCancelled?: boolean;
184
+ availableTickets?: AvailableTicket[];
185
+ ticketsAvailable?: number;
186
+ ticketsSold?: number;
187
+ minPrice?: number;
188
+ maxPrice?: number;
189
+ ctaText?: string;
190
+ ctaState?: 'available' | 'sold_out' | 'coming_soon' | 'ended';
191
+ showPerformers?: boolean;
192
+ eventSeriesId?: number;
193
+ seriesInstanceNumber?: number;
194
+ }
195
+
196
+ export interface AvailableTicket {
197
+ id: number;
198
+ name: string;
199
+ description?: string;
200
+ price: number;
201
+ quantity: number;
202
+ quantitySold?: number;
203
+ quantityAvailable?: number;
204
+ minPerOrder?: number;
205
+ maxPerOrder?: number;
206
+ saleStartDate?: string;
207
+ saleEndDate?: string;
208
+ isHidden?: boolean;
209
+ revealWithPromoCode?: boolean;
210
+ ticketType?: number; // 0 = GA, 1 = assigned
211
+ sectionId?: number;
212
+ sortOrder?: number;
213
+ }
214
+
215
+ export interface EventPerformersResponse {
216
+ performers: Performer[];
217
+ showPerformers: boolean;
218
+ }
219
+
220
+ export interface Performer {
221
+ id: number;
222
+ stageName: string;
223
+ displayName?: string;
224
+ avatar?: string;
225
+ order?: number;
226
+ }
227
+
228
+ // ============================================================================
229
+ // Venues
230
+ // ============================================================================
231
+
232
+ export interface Venue {
233
+ id: number;
234
+ name: string;
235
+ slug?: string;
236
+ address?: string;
237
+ googleLocationNameCache?: string;
238
+ city?: string;
239
+ state?: string;
240
+ zipCode?: string;
241
+ country?: string;
242
+ timezone?: string;
243
+ logoUrl?: string;
244
+ serviceFeePercentage?: number;
245
+ serviceFeeCents?: number;
246
+ taxPercentage?: number;
247
+ organizationId?: number;
248
+ }
249
+
250
+ // ============================================================================
251
+ // Series
252
+ // ============================================================================
253
+
254
+ export interface SeriesOccurrence {
255
+ eventId: number;
256
+ date: string;
257
+ startDateTime: string;
258
+ endDateTime?: string;
259
+ instanceNumber: number;
260
+ status: string;
261
+ ticketsAvailable?: number;
262
+ ctaState?: string;
263
+ }
264
+
265
+ export interface SeriesOccurrencesResponse {
266
+ seriesId: number;
267
+ occurrences: SeriesOccurrence[];
268
+ }
269
+
270
+ // ============================================================================
271
+ // API Configuration
272
+ // ============================================================================
273
+
274
+ export interface ApiConfig {
275
+ baseUrl?: string;
276
+ timeout?: number;
277
+ onError?: (error: Error) => void;
278
+ }
279
+
280
+ // ============================================================================
281
+ // Generic API Response
282
+ // ============================================================================
283
+
284
+ export interface ApiResponse<T> {
285
+ success: boolean;
286
+ data?: T;
287
+ error?: string;
288
+ statusCode?: number;
289
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Venues API
3
+ *
4
+ * Functions for fetching venue data including
5
+ * service fees and tax configuration.
6
+ */
7
+
8
+ import { logger } from '../utils/logger.js';
9
+ import { getPublicBaseUrl, getLegacyPublicUrl, simpleFetch } from './client.js';
10
+ import type { Venue } from './types.js';
11
+
12
+ /**
13
+ * Get venue details (public, no auth required)
14
+ *
15
+ * Fetches venue information including service fees and taxes
16
+ * needed for checkout calculations.
17
+ *
18
+ * @param venueId - The venue ID
19
+ * @returns Venue details or null on error
20
+ */
21
+ export async function getVenue(venueId: string | number): Promise<Venue | null> {
22
+ try {
23
+ if (!venueId) {
24
+ logger.warn('getVenue called without venueId');
25
+ return null;
26
+ }
27
+
28
+ // Uses /api/public/getVenue/:id (same as micdrop-frontend)
29
+ const response = await fetch(`${getLegacyPublicUrl()}/getVenue/${venueId}`);
30
+
31
+ if (!response.ok) {
32
+ logger.error(`Failed to fetch venue: ${response.status}`);
33
+ return null;
34
+ }
35
+
36
+ return response.json();
37
+ } catch (error) {
38
+ logger.error('Error fetching venue:', error);
39
+ return null;
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Get venue service fee configuration
45
+ *
46
+ * Returns just the fee-related fields for checkout calculations.
47
+ *
48
+ * @param venueId - The venue ID
49
+ * @returns Fee configuration or null on error
50
+ */
51
+ export async function getVenueFees(
52
+ venueId: string | number
53
+ ): Promise<{
54
+ serviceFeePercentage: number;
55
+ serviceFeeCents: number;
56
+ taxPercentage: number;
57
+ } | null> {
58
+ const venue = await getVenue(venueId);
59
+
60
+ if (!venue) {
61
+ return null;
62
+ }
63
+
64
+ return {
65
+ serviceFeePercentage: venue.serviceFeePercentage ?? 0,
66
+ serviceFeeCents: venue.serviceFeeCents ?? 0,
67
+ taxPercentage: venue.taxPercentage ?? 0,
68
+ };
69
+ }
70
+
71
+ /**
72
+ * Get venue by slug
73
+ *
74
+ * Fetches venue using its URL-friendly slug.
75
+ *
76
+ * @param slug - The venue slug
77
+ * @returns Venue details or null on error
78
+ */
79
+ export async function getVenueBySlug(slug: string): Promise<Venue | null> {
80
+ try {
81
+ if (!slug) {
82
+ logger.warn('getVenueBySlug called without slug');
83
+ return null;
84
+ }
85
+
86
+ const encodedSlug = encodeURIComponent(slug);
87
+ // Uses /api/public/venue/:slug (same as micdrop-frontend)
88
+ const response = await fetch(`${getLegacyPublicUrl()}/venue/${encodedSlug}`);
89
+
90
+ if (!response.ok) {
91
+ logger.error(`Failed to fetch venue by slug: ${response.status}`);
92
+ return null;
93
+ }
94
+
95
+ return response.json();
96
+ } catch (error) {
97
+ logger.error('Error fetching venue by slug:', error);
98
+ return null;
99
+ }
100
+ }
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Theme Configuration for Embeddable Calendar Widget
3
+ *
4
+ * This module provides comprehensive theming options for the calendar widget.
5
+ * All colors use HSL format for easy manipulation.
6
+ *
7
+ * Usage:
8
+ * 1. Import and call applyTheme() with your custom theme
9
+ * 2. Or set CSS custom properties directly on the widget container
10
+ *
11
+ * Example:
12
+ * ```js
13
+ * import { applyTheme, themes } from '@getmicdrop/venue-calendar/theme';
14
+ * applyTheme(themes.dark);
15
+ *
16
+ * // Or custom theme:
17
+ * applyTheme({
18
+ * brandPrimary: '270 76% 60%', // Purple
19
+ * textPrimary: '0 0% 100%',
20
+ * bgPrimary: '0 0% 10%',
21
+ * });
22
+ * ```
23
+ */
24
+
25
+ // Default light theme
26
+ export const lightTheme = {
27
+ // Brand colors
28
+ brandPrimary: '217 91% 60%', // #2563EB - Blue
29
+
30
+ // Text colors
31
+ textPrimary: '0 0% 0%', // Black
32
+ textSecondary: '0 0% 40%', // Dark Gray
33
+ textTertiary: '0 0% 60%', // Gray
34
+
35
+ // Background colors
36
+ bgPrimary: '0 0% 100%', // White
37
+ bgSecondary: '0 0% 98%', // Light Gray
38
+ bgQuaternary: '0 0% 96%', // Very Light Gray
39
+
40
+ // Border/Stroke colors
41
+ strokePrimary: '0 0% 80%', // Medium Gray
42
+ strokeSecondary: '0 0% 90%', // Light Gray
43
+
44
+ // Status colors
45
+ statusOnSale: '217 91% 60%', // Blue
46
+ statusSellingFast: '38 92% 50%', // Orange
47
+ statusSoldOut: '0 84% 60%', // Red
48
+
49
+ // Calendar specific
50
+ todayBg: '217 91% 97%', // Light blue
51
+ todayText: '217 91% 50%', // Blue
52
+ eventDotOnSale: '217 91% 60%',
53
+ eventDotSellingFast: '38 92% 50%',
54
+ eventDotSoldOut: '0 84% 60%',
55
+
56
+ // Interaction states
57
+ hoverBg: '0 0% 96%',
58
+ focusRing: '217 91% 60%',
59
+ };
60
+
61
+ // Dark theme
62
+ export const darkTheme = {
63
+ // Brand colors
64
+ brandPrimary: '217 91% 65%', // Lighter blue for dark mode
65
+
66
+ // Text colors
67
+ textPrimary: '0 0% 98%', // Near white
68
+ textSecondary: '0 0% 70%', // Light gray
69
+ textTertiary: '0 0% 50%', // Gray
70
+
71
+ // Background colors
72
+ bgPrimary: '0 0% 9%', // Very dark gray
73
+ bgSecondary: '0 0% 13%', // Dark gray
74
+ bgQuaternary: '0 0% 17%', // Slightly lighter
75
+
76
+ // Border/Stroke colors
77
+ strokePrimary: '0 0% 30%',
78
+ strokeSecondary: '0 0% 20%',
79
+
80
+ // Status colors
81
+ statusOnSale: '217 91% 65%',
82
+ statusSellingFast: '38 92% 55%',
83
+ statusSoldOut: '0 84% 65%',
84
+
85
+ // Calendar specific
86
+ todayBg: '217 50% 20%',
87
+ todayText: '217 91% 70%',
88
+ eventDotOnSale: '217 91% 65%',
89
+ eventDotSellingFast: '38 92% 55%',
90
+ eventDotSoldOut: '0 84% 65%',
91
+
92
+ // Interaction states
93
+ hoverBg: '0 0% 20%',
94
+ focusRing: '217 91% 65%',
95
+ };
96
+
97
+ // High contrast theme for accessibility
98
+ export const highContrastTheme = {
99
+ brandPrimary: '217 100% 50%',
100
+ textPrimary: '0 0% 0%',
101
+ textSecondary: '0 0% 20%',
102
+ textTertiary: '0 0% 30%',
103
+ bgPrimary: '0 0% 100%',
104
+ bgSecondary: '0 0% 95%',
105
+ bgQuaternary: '0 0% 90%',
106
+ strokePrimary: '0 0% 0%',
107
+ strokeSecondary: '0 0% 30%',
108
+ statusOnSale: '217 100% 40%',
109
+ statusSellingFast: '30 100% 40%',
110
+ statusSoldOut: '0 100% 40%',
111
+ todayBg: '217 100% 90%',
112
+ todayText: '217 100% 30%',
113
+ eventDotOnSale: '217 100% 40%',
114
+ eventDotSellingFast: '30 100% 40%',
115
+ eventDotSoldOut: '0 100% 40%',
116
+ hoverBg: '217 100% 95%',
117
+ focusRing: '217 100% 40%',
118
+ };
119
+
120
+ // Preset themes collection
121
+ export const themes = {
122
+ light: lightTheme,
123
+ dark: darkTheme,
124
+ highContrast: highContrastTheme,
125
+ };
126
+
127
+ /**
128
+ * Apply a theme to the calendar widget
129
+ * @param {Object} theme - Theme object with HSL color values
130
+ * @param {HTMLElement} container - Optional container element (defaults to :root)
131
+ */
132
+ export function applyTheme(theme, container = document.documentElement) {
133
+ const cssVarMap = {
134
+ brandPrimary: '--Brand-Primary',
135
+ textPrimary: '--Text-Primary',
136
+ textSecondary: '--Text-Secondary',
137
+ textTertiary: '--Text-Tartiary', // Note: keeping original typo for compatibility
138
+ bgPrimary: '--BG-Primary',
139
+ bgSecondary: '--BG-Secondary',
140
+ bgQuaternary: '--BG-Quaternary',
141
+ strokePrimary: '--Stroke-Primary',
142
+ strokeSecondary: '--Stroke-Secondary',
143
+ statusOnSale: '--Status-OnSale',
144
+ statusSellingFast: '--Status-SellingFast',
145
+ statusSoldOut: '--Status-SoldOut',
146
+ todayBg: '--Today-BG',
147
+ todayText: '--Today-Text',
148
+ eventDotOnSale: '--EventDot-OnSale',
149
+ eventDotSellingFast: '--EventDot-SellingFast',
150
+ eventDotSoldOut: '--EventDot-SoldOut',
151
+ hoverBg: '--Hover-BG',
152
+ focusRing: '--Focus-Ring',
153
+ };
154
+
155
+ Object.entries(theme).forEach(([key, value]) => {
156
+ const cssVar = cssVarMap[key];
157
+ if (cssVar) {
158
+ container.style.setProperty(cssVar, value);
159
+ }
160
+ });
161
+ }
162
+
163
+ /**
164
+ * Generate CSS string for embedding
165
+ * @param {Object} theme - Theme object
166
+ * @returns {string} CSS string
167
+ */
168
+ export function generateThemeCSS(theme) {
169
+ const cssVarMap = {
170
+ brandPrimary: '--Brand-Primary',
171
+ textPrimary: '--Text-Primary',
172
+ textSecondary: '--Text-Secondary',
173
+ textTertiary: '--Text-Tartiary',
174
+ bgPrimary: '--BG-Primary',
175
+ bgSecondary: '--BG-Secondary',
176
+ bgQuaternary: '--BG-Quaternary',
177
+ strokePrimary: '--Stroke-Primary',
178
+ strokeSecondary: '--Stroke-Secondary',
179
+ statusOnSale: '--Status-OnSale',
180
+ statusSellingFast: '--Status-SellingFast',
181
+ statusSoldOut: '--Status-SoldOut',
182
+ todayBg: '--Today-BG',
183
+ todayText: '--Today-Text',
184
+ eventDotOnSale: '--EventDot-OnSale',
185
+ eventDotSellingFast: '--EventDot-SellingFast',
186
+ eventDotSoldOut: '--EventDot-SoldOut',
187
+ hoverBg: '--Hover-BG',
188
+ focusRing: '--Focus-Ring',
189
+ };
190
+
191
+ const vars = Object.entries(theme)
192
+ .map(([key, value]) => {
193
+ const cssVar = cssVarMap[key];
194
+ return cssVar ? ` ${cssVar}: ${value};` : null;
195
+ })
196
+ .filter(Boolean)
197
+ .join('\n');
198
+
199
+ return `:root {\n${vars}\n}`;
200
+ }
201
+
202
+ export default {
203
+ themes,
204
+ lightTheme,
205
+ darkTheme,
206
+ highContrastTheme,
207
+ applyTheme,
208
+ generateThemeCSS,
209
+ };