@getmicdrop/venue-calendar 3.3.1 → 3.3.2
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.
- package/dist/{VenueCalendar-Xppig0q_.js → VenueCalendar-BMSfRl2d.js} +10 -13
- package/dist/{VenueCalendar-Xppig0q_.js.map → VenueCalendar-BMSfRl2d.js.map} +1 -1
- package/dist/api/api.cjs +2 -0
- package/dist/api/api.cjs.map +1 -0
- package/dist/api/api.mjs +787 -0
- package/dist/api/api.mjs.map +1 -0
- package/dist/api/client.d.ts +46 -0
- package/dist/api/events.d.ts +102 -0
- package/dist/api/index.d.ts +38 -0
- package/dist/api/orders.d.ts +104 -0
- package/dist/api/promo.d.ts +45 -0
- package/dist/api/transformers/event.d.ts +86 -0
- package/dist/api/transformers/index.d.ts +9 -0
- package/dist/api/transformers/order.d.ts +105 -0
- package/dist/api/transformers/venue.d.ts +48 -0
- package/dist/api/venues.d.ts +33 -0
- package/dist/{index-BjErG0CG.js → index-CoJaem3n.js} +2 -2
- package/dist/{index-BjErG0CG.js.map → index-CoJaem3n.js.map} +1 -1
- package/dist/venue-calendar.css +1 -1
- package/dist/venue-calendar.es.js +1 -1
- package/dist/venue-calendar.iife.js +4 -4
- package/dist/venue-calendar.iife.js.map +1 -1
- package/dist/venue-calendar.umd.js +4 -4
- package/dist/venue-calendar.umd.js.map +1 -1
- package/package.json +96 -94
- package/src/lib/api/client.ts +0 -210
- package/src/lib/api/events.ts +0 -358
- package/src/lib/api/index.ts +0 -182
- package/src/lib/api/orders.ts +0 -390
- package/src/lib/api/promo.ts +0 -164
- package/src/lib/api/transformers/event.ts +0 -248
- package/src/lib/api/transformers/index.ts +0 -29
- package/src/lib/api/transformers/order.ts +0 -207
- package/src/lib/api/transformers/venue.ts +0 -118
- package/src/lib/api/venues.ts +0 -100
- package/src/lib/utils/api.js +0 -790
- package/src/lib/utils/api.test.js +0 -1284
- package/src/lib/utils/constants.js +0 -8
- package/src/lib/utils/constants.test.js +0 -39
- package/src/lib/utils/datetime.js +0 -266
- package/src/lib/utils/datetime.test.js +0 -340
- package/src/lib/utils/event-transform.js +0 -464
- package/src/lib/utils/event-transform.test.js +0 -413
- package/src/lib/utils/logger.js +0 -105
- package/src/lib/utils/timezone.js +0 -109
- package/src/lib/utils/timezone.test.js +0 -222
- package/src/lib/utils/utils.js +0 -806
- package/src/lib/utils/utils.test.js +0 -959
- /package/{src/lib/api/types.ts → dist/api/types.d.ts} +0 -0
|
@@ -1,413 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
getCDNImageUrl,
|
|
4
|
-
getEventImageUrl,
|
|
5
|
-
calculateTicketStatus,
|
|
6
|
-
filterPublicTickets,
|
|
7
|
-
calculateScarcity,
|
|
8
|
-
findLowestPrice,
|
|
9
|
-
formatInTimezone,
|
|
10
|
-
formatEventTimeRange,
|
|
11
|
-
getDateParts,
|
|
12
|
-
TransformMode,
|
|
13
|
-
transformEvent,
|
|
14
|
-
transformEventData,
|
|
15
|
-
} from './event-transform.js';
|
|
16
|
-
|
|
17
|
-
describe('event-transform.js', () => {
|
|
18
|
-
describe('getCDNImageUrl', () => {
|
|
19
|
-
it('prepends CDN URL to relative paths', () => {
|
|
20
|
-
const result = getCDNImageUrl('/images/poster.jpg');
|
|
21
|
-
expect(result).toBe('https://moxy.sfo3.digitaloceanspaces.com/images/poster.jpg');
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('returns absolute URLs unchanged', () => {
|
|
25
|
-
const url = 'https://example.com/image.jpg';
|
|
26
|
-
expect(getCDNImageUrl(url)).toBe(url);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('returns empty string for falsy input', () => {
|
|
30
|
-
expect(getCDNImageUrl('')).toBe('');
|
|
31
|
-
expect(getCDNImageUrl(null)).toBe('');
|
|
32
|
-
expect(getCDNImageUrl(undefined)).toBe('');
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
describe('getEventImageUrl', () => {
|
|
37
|
-
it('uses image field first', () => {
|
|
38
|
-
const event = { image: '/poster.jpg' };
|
|
39
|
-
const result = getEventImageUrl(event);
|
|
40
|
-
expect(result).toContain('/poster.jpg');
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('falls back to coverImage', () => {
|
|
44
|
-
const event = { coverImage: '/cover.jpg' };
|
|
45
|
-
const result = getEventImageUrl(event);
|
|
46
|
-
expect(result).toContain('/cover.jpg');
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('falls back to imageUrl', () => {
|
|
50
|
-
const event = { imageUrl: '/url.jpg' };
|
|
51
|
-
const result = getEventImageUrl(event);
|
|
52
|
-
expect(result).toContain('/url.jpg');
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('falls back to image_url', () => {
|
|
56
|
-
const event = { image_url: '/snake_case.jpg' };
|
|
57
|
-
const result = getEventImageUrl(event);
|
|
58
|
-
expect(result).toContain('/snake_case.jpg');
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('returns placeholder for no image', () => {
|
|
62
|
-
const result = getEventImageUrl({});
|
|
63
|
-
// Uses data URI SVG placeholder to avoid third-party dependencies
|
|
64
|
-
expect(result).toContain('data:image/svg+xml');
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
describe('calculateTicketStatus', () => {
|
|
69
|
-
beforeEach(() => {
|
|
70
|
-
vi.useFakeTimers();
|
|
71
|
-
vi.setSystemTime(new Date('2024-06-15T12:00:00Z'));
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
afterEach(() => {
|
|
75
|
-
vi.useRealTimers();
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('returns not_started for future sale start date', () => {
|
|
79
|
-
const ticket = {
|
|
80
|
-
saleStartDate: '2024-07-01T00:00:00Z',
|
|
81
|
-
remainingCapacity: 100,
|
|
82
|
-
};
|
|
83
|
-
expect(calculateTicketStatus(ticket)).toBe('not_started');
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it('returns ended for past sale end date', () => {
|
|
87
|
-
const ticket = {
|
|
88
|
-
saleEndDate: '2024-06-01T00:00:00Z',
|
|
89
|
-
remainingCapacity: 100,
|
|
90
|
-
};
|
|
91
|
-
expect(calculateTicketStatus(ticket)).toBe('ended');
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('returns sold_out when remainingCapacity is 0', () => {
|
|
95
|
-
const ticket = { remainingCapacity: 0, totalCapacity: 100 };
|
|
96
|
-
expect(calculateTicketStatus(ticket)).toBe('sold_out');
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('returns sold_out when quantityRemaining is 0', () => {
|
|
100
|
-
const ticket = { quantityRemaining: 0, quantity: 50 };
|
|
101
|
-
expect(calculateTicketStatus(ticket)).toBe('sold_out');
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it('returns low when less than 10% remaining', () => {
|
|
105
|
-
const ticket = { remainingCapacity: 5, totalCapacity: 100 };
|
|
106
|
-
expect(calculateTicketStatus(ticket)).toBe('low');
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('returns available when 10%+ remaining', () => {
|
|
110
|
-
const ticket = { remainingCapacity: 50, totalCapacity: 100 };
|
|
111
|
-
expect(calculateTicketStatus(ticket)).toBe('available');
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it('handles ticket with only quantity field', () => {
|
|
115
|
-
expect(calculateTicketStatus({ quantity: 0 })).toBe('sold_out');
|
|
116
|
-
expect(calculateTicketStatus({ quantity: 50 })).toBe('available');
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
describe('filterPublicTickets', () => {
|
|
121
|
-
const tickets = [
|
|
122
|
-
{ id: 1, visibility: 0, salesChannel: 1 },
|
|
123
|
-
{ id: 2, visibility: 0, salesChannel: 2 }, // door only
|
|
124
|
-
{ id: 3, visibility: 1, salesChannel: 1 }, // hidden
|
|
125
|
-
{ id: 4, visibility: 0, salesChannel: 1 },
|
|
126
|
-
];
|
|
127
|
-
|
|
128
|
-
it('returns empty array for non-array input', () => {
|
|
129
|
-
expect(filterPublicTickets(null)).toEqual([]);
|
|
130
|
-
expect(filterPublicTickets(undefined)).toEqual([]);
|
|
131
|
-
expect(filterPublicTickets('not array')).toEqual([]);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it('returns all tickets with no options', () => {
|
|
135
|
-
expect(filterPublicTickets(tickets)).toHaveLength(4);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it('excludes door-only tickets when excludeDoorOnly is true', () => {
|
|
139
|
-
const result = filterPublicTickets(tickets, { excludeDoorOnly: true });
|
|
140
|
-
expect(result).toHaveLength(3);
|
|
141
|
-
expect(result.every(t => t.salesChannel !== 2)).toBe(true);
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
it('includes only public visibility tickets when publicOnly is true', () => {
|
|
145
|
-
const result = filterPublicTickets(tickets, { publicOnly: true });
|
|
146
|
-
expect(result).toHaveLength(3);
|
|
147
|
-
expect(result.every(t => t.visibility === 0)).toBe(true);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it('combines filters', () => {
|
|
151
|
-
const result = filterPublicTickets(tickets, { excludeDoorOnly: true, publicOnly: true });
|
|
152
|
-
expect(result).toHaveLength(2);
|
|
153
|
-
expect(result.map(t => t.id)).toEqual([1, 4]);
|
|
154
|
-
});
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
describe('calculateScarcity', () => {
|
|
158
|
-
it('returns zeros for empty/invalid input', () => {
|
|
159
|
-
expect(calculateScarcity([])).toEqual({ totalRemaining: 0, totalCapacity: 0, isSoldOut: false });
|
|
160
|
-
expect(calculateScarcity(null)).toEqual({ totalRemaining: 0, totalCapacity: 0, isSoldOut: false });
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
it('sums remaining and capacity across tickets', () => {
|
|
164
|
-
const tickets = [
|
|
165
|
-
{ remainingCapacity: 10, totalCapacity: 50 },
|
|
166
|
-
{ remainingCapacity: 20, totalCapacity: 100 },
|
|
167
|
-
];
|
|
168
|
-
const result = calculateScarcity(tickets);
|
|
169
|
-
expect(result.totalRemaining).toBe(30);
|
|
170
|
-
expect(result.totalCapacity).toBe(150);
|
|
171
|
-
expect(result.isSoldOut).toBe(false);
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
it('detects sold out when total remaining is 0', () => {
|
|
175
|
-
const tickets = [
|
|
176
|
-
{ remainingCapacity: 0, totalCapacity: 50 },
|
|
177
|
-
{ remainingCapacity: 0, totalCapacity: 100 },
|
|
178
|
-
];
|
|
179
|
-
const result = calculateScarcity(tickets);
|
|
180
|
-
expect(result.isSoldOut).toBe(true);
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
it('handles quantityRemaining fallback', () => {
|
|
184
|
-
const tickets = [{ quantityRemaining: 15, quantity: 30 }];
|
|
185
|
-
const result = calculateScarcity(tickets);
|
|
186
|
-
expect(result.totalRemaining).toBe(15);
|
|
187
|
-
expect(result.totalCapacity).toBe(30);
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
it('handles quantity-only fallback', () => {
|
|
191
|
-
const tickets = [{ quantity: 25 }];
|
|
192
|
-
const result = calculateScarcity(tickets);
|
|
193
|
-
expect(result.totalRemaining).toBe(25);
|
|
194
|
-
expect(result.totalCapacity).toBe(25);
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
describe('findLowestPrice', () => {
|
|
199
|
-
it('returns 0 for empty/invalid input', () => {
|
|
200
|
-
expect(findLowestPrice([])).toBe(0);
|
|
201
|
-
expect(findLowestPrice(null)).toBe(0);
|
|
202
|
-
expect(findLowestPrice(undefined)).toBe(0);
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
it('finds lowest price among tickets', () => {
|
|
206
|
-
const tickets = [{ price: 50 }, { price: 20 }, { price: 35 }];
|
|
207
|
-
expect(findLowestPrice(tickets)).toBe(20);
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
it('excludes zero prices (free tickets)', () => {
|
|
211
|
-
const tickets = [{ price: 0 }, { price: 25 }, { price: 15 }];
|
|
212
|
-
expect(findLowestPrice(tickets)).toBe(15);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
it('supports ticketPrice field', () => {
|
|
216
|
-
const tickets = [{ ticketPrice: 30 }, { ticketPrice: 10 }];
|
|
217
|
-
expect(findLowestPrice(tickets)).toBe(10);
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
it('returns 0 when all tickets are free', () => {
|
|
221
|
-
const tickets = [{ price: 0 }, { price: 0 }];
|
|
222
|
-
expect(findLowestPrice(tickets)).toBe(0);
|
|
223
|
-
});
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
describe('formatInTimezone', () => {
|
|
227
|
-
it('formats date in specified timezone', () => {
|
|
228
|
-
const result = formatInTimezone('2024-01-15T19:00:00Z', 'UTC', {
|
|
229
|
-
hour: 'numeric',
|
|
230
|
-
minute: '2-digit',
|
|
231
|
-
hour12: true,
|
|
232
|
-
});
|
|
233
|
-
expect(result).toBe('7:00 PM');
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
it('returns empty string for invalid date', () => {
|
|
237
|
-
expect(formatInTimezone('invalid', 'UTC', {})).toBe('');
|
|
238
|
-
expect(formatInTimezone(null, 'UTC', {})).toBe('');
|
|
239
|
-
expect(formatInTimezone('', 'UTC', {})).toBe('');
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
describe('formatEventTimeRange', () => {
|
|
244
|
-
it('formats time range', () => {
|
|
245
|
-
const event = {
|
|
246
|
-
startDateTime: '2024-01-15T19:00:00Z',
|
|
247
|
-
endDateTime: '2024-01-15T22:00:00Z',
|
|
248
|
-
};
|
|
249
|
-
const result = formatEventTimeRange(event, 'UTC');
|
|
250
|
-
expect(result).toBe('7:00 PM - 10:00 PM');
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
it('returns just start time if no end', () => {
|
|
254
|
-
const event = { startDateTime: '2024-01-15T19:00:00Z' };
|
|
255
|
-
const result = formatEventTimeRange(event, 'UTC');
|
|
256
|
-
expect(result).toBe('7:00 PM');
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
it('returns empty for missing start', () => {
|
|
260
|
-
const result = formatEventTimeRange({}, 'UTC');
|
|
261
|
-
expect(result).toBe('');
|
|
262
|
-
});
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
describe('getDateParts', () => {
|
|
266
|
-
it('returns date parts correctly', () => {
|
|
267
|
-
const result = getDateParts('2024-01-15T12:00:00Z', 'UTC');
|
|
268
|
-
expect(result.day).toBe('Mon');
|
|
269
|
-
expect(result.month).toBe('Jan');
|
|
270
|
-
expect(result.dateOfMonth).toBe('15');
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
it('returns empty/fallback strings for invalid date', () => {
|
|
274
|
-
const result = getDateParts('invalid', 'UTC');
|
|
275
|
-
expect(result.day).toBe('');
|
|
276
|
-
expect(result.month).toBe('');
|
|
277
|
-
// Note: dateOfMonth returns '0' for invalid dates per implementation
|
|
278
|
-
expect(result.dateOfMonth).toBe('0');
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
it('returns empty strings for null', () => {
|
|
282
|
-
const result = getDateParts(null, 'UTC');
|
|
283
|
-
expect(result.day).toBe('');
|
|
284
|
-
expect(result.month).toBe('');
|
|
285
|
-
expect(result.dateOfMonth).toBe('');
|
|
286
|
-
});
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
describe('TransformMode', () => {
|
|
290
|
-
it('has BROWSE and DETAIL modes', () => {
|
|
291
|
-
expect(TransformMode.BROWSE).toBe('browse');
|
|
292
|
-
expect(TransformMode.DETAIL).toBe('detail');
|
|
293
|
-
});
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
describe('transformEvent', () => {
|
|
297
|
-
const mockEvent = {
|
|
298
|
-
id: 123,
|
|
299
|
-
ID: 123,
|
|
300
|
-
title: 'Test Event',
|
|
301
|
-
description: 'A test event',
|
|
302
|
-
startDateTime: '2024-06-15T19:00:00Z',
|
|
303
|
-
endDateTime: '2024-06-15T22:00:00Z',
|
|
304
|
-
timeZone: 'America/New_York',
|
|
305
|
-
image: '/poster.jpg',
|
|
306
|
-
status: 'On Sale',
|
|
307
|
-
venueId: 456,
|
|
308
|
-
availableTickets: [
|
|
309
|
-
{ ID: 1, price: 25, remainingCapacity: 50, totalCapacity: 100, visibility: 0, salesChannel: 1 },
|
|
310
|
-
{ ID: 2, price: 15, remainingCapacity: 10, totalCapacity: 50, visibility: 0, salesChannel: 1 },
|
|
311
|
-
],
|
|
312
|
-
venue: { googleLocationNameCache: '123 Main St' },
|
|
313
|
-
eventSummary: 'Event summary text',
|
|
314
|
-
performers: [{ name: 'Test Performer' }],
|
|
315
|
-
};
|
|
316
|
-
|
|
317
|
-
describe('BROWSE mode', () => {
|
|
318
|
-
it('returns minimal data for browse mode', () => {
|
|
319
|
-
const result = transformEvent(mockEvent, { mode: TransformMode.BROWSE });
|
|
320
|
-
expect(result.id).toBe(123);
|
|
321
|
-
expect(result.name).toBe('Test Event');
|
|
322
|
-
expect(result.description).toBe('A test event');
|
|
323
|
-
expect(result.status).toBe('On Sale');
|
|
324
|
-
expect(result.venueId).toBe(456);
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
it('calculates scarcity for browse mode', () => {
|
|
328
|
-
const result = transformEvent(mockEvent, { mode: TransformMode.BROWSE });
|
|
329
|
-
expect(result.ticketsRemaining).toBe(60);
|
|
330
|
-
expect(result.ticketsTotal).toBe(150);
|
|
331
|
-
expect(result.isSoldOut).toBe(false);
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
it('formats timeline', () => {
|
|
335
|
-
const result = transformEvent(mockEvent, { mode: TransformMode.BROWSE });
|
|
336
|
-
expect(result.timeline).toContain('PM');
|
|
337
|
-
});
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
describe('DETAIL mode', () => {
|
|
341
|
-
it('returns full data for detail mode', () => {
|
|
342
|
-
const result = transformEvent(mockEvent, { mode: TransformMode.DETAIL });
|
|
343
|
-
expect(result.id).toBe(123);
|
|
344
|
-
expect(result.name).toBe('Test Event');
|
|
345
|
-
expect(result.venue).toEqual(mockEvent.venue);
|
|
346
|
-
expect(result.performers).toEqual(mockEvent.performers);
|
|
347
|
-
expect(result.eventSummary).toBe('Event summary text');
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
it('calculates lowest price', () => {
|
|
351
|
-
const result = transformEvent(mockEvent, { mode: TransformMode.DETAIL });
|
|
352
|
-
expect(result.price).toBe(15);
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
it('includes ticket data with status', () => {
|
|
356
|
-
const result = transformEvent(mockEvent, { mode: TransformMode.DETAIL });
|
|
357
|
-
expect(result.availableTickets).toHaveLength(2);
|
|
358
|
-
expect(result.availableTickets[0].status).toBeDefined();
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
it('includes date parts', () => {
|
|
362
|
-
const result = transformEvent(mockEvent, { mode: TransformMode.DETAIL });
|
|
363
|
-
expect(result.day).toBeDefined();
|
|
364
|
-
expect(result.month).toBeDefined();
|
|
365
|
-
expect(result.dateOfMonth).toBeDefined();
|
|
366
|
-
});
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
describe('null handling', () => {
|
|
370
|
-
it('returns empty browse event for null', () => {
|
|
371
|
-
const result = transformEvent(null, { mode: TransformMode.BROWSE });
|
|
372
|
-
expect(result.id).toBeNull();
|
|
373
|
-
expect(result.name).toBe('');
|
|
374
|
-
expect(result.status).toBe('unavailable');
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
it('returns empty detail event for null', () => {
|
|
378
|
-
const result = transformEvent(null, { mode: TransformMode.DETAIL });
|
|
379
|
-
expect(result.id).toBeNull();
|
|
380
|
-
expect(result.name).toBe('');
|
|
381
|
-
expect(result.availableTickets).toEqual([]);
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
it('defaults to DETAIL mode', () => {
|
|
385
|
-
const result = transformEvent(null);
|
|
386
|
-
expect(result.availableTickets).toEqual([]);
|
|
387
|
-
});
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
describe('field fallbacks', () => {
|
|
391
|
-
it('uses name if title missing', () => {
|
|
392
|
-
const event = { name: 'Event Name' };
|
|
393
|
-
const result = transformEvent(event, { mode: TransformMode.BROWSE });
|
|
394
|
-
expect(result.name).toBe('Event Name');
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
it('uses ID if id missing', () => {
|
|
398
|
-
const event = { ID: 999 };
|
|
399
|
-
const result = transformEvent(event, { mode: TransformMode.BROWSE });
|
|
400
|
-
expect(result.id).toBe(999);
|
|
401
|
-
});
|
|
402
|
-
});
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
describe('transformEventData (deprecated)', () => {
|
|
406
|
-
it('calls transformEvent with BROWSE mode', () => {
|
|
407
|
-
const event = { id: 1, title: 'Test' };
|
|
408
|
-
const result = transformEventData(event);
|
|
409
|
-
expect(result.id).toBe(1);
|
|
410
|
-
expect(result.name).toBe('Test');
|
|
411
|
-
});
|
|
412
|
-
});
|
|
413
|
-
});
|
package/src/lib/utils/logger.js
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Centralized logger utility for MicDrop applications
|
|
3
|
-
* Can be disabled in production or configured for different log levels
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @typedef {'debug' | 'info' | 'warn' | 'error'} LogLevel
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* @typedef {Object} LoggerConfig
|
|
12
|
-
* @property {boolean} enabled
|
|
13
|
-
* @property {LogLevel} level
|
|
14
|
-
* @property {string} prefix
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
/** @type {Record<LogLevel, number>} */
|
|
18
|
-
const LOG_LEVELS = {
|
|
19
|
-
debug: 0,
|
|
20
|
-
info: 1,
|
|
21
|
-
warn: 2,
|
|
22
|
-
error: 3,
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
/** @type {LoggerConfig} */
|
|
26
|
-
let config = {
|
|
27
|
-
enabled: typeof window !== 'undefined' && import.meta.env?.DEV !== false,
|
|
28
|
-
level: 'debug',
|
|
29
|
-
prefix: '[MicDrop]',
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Configure the logger
|
|
34
|
-
* @param {Partial<LoggerConfig>} options
|
|
35
|
-
*/
|
|
36
|
-
export function configureLogger(options) {
|
|
37
|
-
config = { ...config, ...options };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Check if a log level should be logged
|
|
42
|
-
* @param {LogLevel} level
|
|
43
|
-
* @returns {boolean}
|
|
44
|
-
*/
|
|
45
|
-
function shouldLog(level) {
|
|
46
|
-
return config.enabled && LOG_LEVELS[level] >= LOG_LEVELS[config.level];
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Format a log message
|
|
51
|
-
* @param {LogLevel} level
|
|
52
|
-
* @param {string} message
|
|
53
|
-
* @returns {string}
|
|
54
|
-
*/
|
|
55
|
-
function formatMessage(level, message) {
|
|
56
|
-
return `${config.prefix} [${level.toUpperCase()}] ${message}`;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export const logger = {
|
|
60
|
-
/**
|
|
61
|
-
* Log debug message
|
|
62
|
-
* @param {string} message
|
|
63
|
-
* @param {...unknown} args
|
|
64
|
-
*/
|
|
65
|
-
debug: (message, ...args) => {
|
|
66
|
-
if (shouldLog('debug')) {
|
|
67
|
-
console.log(formatMessage('debug', message), ...args);
|
|
68
|
-
}
|
|
69
|
-
},
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Log info message
|
|
73
|
-
* @param {string} message
|
|
74
|
-
* @param {...unknown} args
|
|
75
|
-
*/
|
|
76
|
-
info: (message, ...args) => {
|
|
77
|
-
if (shouldLog('info')) {
|
|
78
|
-
console.info(formatMessage('info', message), ...args);
|
|
79
|
-
}
|
|
80
|
-
},
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Log warning message
|
|
84
|
-
* @param {string} message
|
|
85
|
-
* @param {...unknown} args
|
|
86
|
-
*/
|
|
87
|
-
warn: (message, ...args) => {
|
|
88
|
-
if (shouldLog('warn')) {
|
|
89
|
-
console.warn(formatMessage('warn', message), ...args);
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Log error message
|
|
95
|
-
* @param {string} message
|
|
96
|
-
* @param {...unknown} args
|
|
97
|
-
*/
|
|
98
|
-
error: (message, ...args) => {
|
|
99
|
-
if (shouldLog('error')) {
|
|
100
|
-
console.error(formatMessage('error', message), ...args);
|
|
101
|
-
}
|
|
102
|
-
},
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
export default logger;
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Timezone Utilities
|
|
3
|
-
*
|
|
4
|
-
* Provides timezone normalization and validation for venue calendar.
|
|
5
|
-
* Handles legacy timezone formats and normalizes to IANA timezone IDs.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/** Default timezone when none specified */
|
|
9
|
-
export const DEFAULT_TIMEZONE = 'UTC';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Map of legacy timezone values to IANA timezone IDs.
|
|
13
|
-
* Used to normalize various timezone formats from the API.
|
|
14
|
-
*/
|
|
15
|
-
export const LEGACY_TIMEZONE_MAP = {
|
|
16
|
-
// US timezones - common abbreviations
|
|
17
|
-
'PST': 'America/Los_Angeles',
|
|
18
|
-
'PDT': 'America/Los_Angeles',
|
|
19
|
-
'MST': 'America/Denver',
|
|
20
|
-
'MDT': 'America/Denver',
|
|
21
|
-
'CST': 'America/Chicago',
|
|
22
|
-
'CDT': 'America/Chicago',
|
|
23
|
-
'EST': 'America/New_York',
|
|
24
|
-
'EDT': 'America/New_York',
|
|
25
|
-
'HST': 'Pacific/Honolulu',
|
|
26
|
-
'AKST': 'America/Anchorage',
|
|
27
|
-
'AKDT': 'America/Anchorage',
|
|
28
|
-
|
|
29
|
-
// Common city names
|
|
30
|
-
'Pacific': 'America/Los_Angeles',
|
|
31
|
-
'Mountain': 'America/Denver',
|
|
32
|
-
'Central': 'America/Chicago',
|
|
33
|
-
'Eastern': 'America/New_York',
|
|
34
|
-
|
|
35
|
-
// UTC variants
|
|
36
|
-
'UTC': 'UTC',
|
|
37
|
-
'GMT': 'UTC',
|
|
38
|
-
'Z': 'UTC',
|
|
39
|
-
|
|
40
|
-
// Numeric offsets (common ones)
|
|
41
|
-
'-08:00': 'America/Los_Angeles',
|
|
42
|
-
'-07:00': 'America/Denver',
|
|
43
|
-
'-06:00': 'America/Chicago',
|
|
44
|
-
'-05:00': 'America/New_York',
|
|
45
|
-
'-04:00': 'America/New_York', // EDT
|
|
46
|
-
'+00:00': 'UTC',
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Check if a timezone string is valid IANA timezone.
|
|
51
|
-
* @param {string} tz - Timezone string to validate
|
|
52
|
-
* @returns {boolean}
|
|
53
|
-
*/
|
|
54
|
-
export function isValidTimezone(tz) {
|
|
55
|
-
if (!tz || typeof tz !== 'string') return false;
|
|
56
|
-
try {
|
|
57
|
-
Intl.DateTimeFormat(undefined, { timeZone: tz });
|
|
58
|
-
return true;
|
|
59
|
-
} catch {
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export const isValidIANATimezone = isValidTimezone;
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Normalize a timezone value to IANA format.
|
|
68
|
-
* Handles legacy abbreviations, city names, and UTC offsets.
|
|
69
|
-
*
|
|
70
|
-
* @param {string} tz - Timezone string (may be legacy format)
|
|
71
|
-
* @returns {string} IANA timezone ID (or 'UTC' if invalid)
|
|
72
|
-
*
|
|
73
|
-
* @example
|
|
74
|
-
* getIANATimezone('PST') // 'America/Los_Angeles'
|
|
75
|
-
* getIANATimezone('America/New_York') // 'America/New_York'
|
|
76
|
-
* getIANATimezone('Eastern') // 'America/New_York'
|
|
77
|
-
* getIANATimezone(null) // 'UTC'
|
|
78
|
-
*/
|
|
79
|
-
export function getIANATimezone(tz) {
|
|
80
|
-
// Handle null/undefined/empty
|
|
81
|
-
if (!tz || typeof tz !== 'string') {
|
|
82
|
-
return DEFAULT_TIMEZONE;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const trimmed = tz.trim();
|
|
86
|
-
|
|
87
|
-
// Check legacy map FIRST (before isValidTimezone)
|
|
88
|
-
// This ensures that offset strings like '-08:00' get converted to IANA timezones
|
|
89
|
-
// rather than being returned as-is (since Intl.DateTimeFormat accepts offsets)
|
|
90
|
-
|
|
91
|
-
// Check legacy map (case-insensitive for abbreviations)
|
|
92
|
-
const upper = trimmed.toUpperCase();
|
|
93
|
-
if (LEGACY_TIMEZONE_MAP[upper]) {
|
|
94
|
-
return LEGACY_TIMEZONE_MAP[upper];
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Check legacy map with original case (for city names and offsets)
|
|
98
|
-
if (LEGACY_TIMEZONE_MAP[trimmed]) {
|
|
99
|
-
return LEGACY_TIMEZONE_MAP[trimmed];
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Already valid IANA timezone (check after legacy map)
|
|
103
|
-
if (isValidTimezone(trimmed)) {
|
|
104
|
-
return trimmed;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Default to UTC for unknown timezones
|
|
108
|
-
return DEFAULT_TIMEZONE;
|
|
109
|
-
}
|