@necrolab/dashboard 0.5.21 → 0.5.23

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,568 @@
1
+ import BaseRenderer from "../base-renderer.js";
2
+ import * as TMUtils from "./tm-utils.js";
3
+ import * as utils from "../utils.js";
4
+ import { getEventData, getSeatData, fetchBackgroundImage } from "./requests.js";
5
+
6
+ class TMRenderer extends BaseRenderer {
7
+ constructor(eventId, options = {}) {
8
+ // Validate event ID
9
+ if (!options.country && !TMUtils.checkIfEventValid(eventId)) {
10
+ throw new Error("Invalid eventId!");
11
+ }
12
+
13
+ super(eventId, options);
14
+
15
+ this.config = this.getDefaultConfig();
16
+ this.highlighted = [];
17
+ this.pricemap = false;
18
+ this.pricemapData = null;
19
+ this._shapeToName = {};
20
+ }
21
+
22
+ setHighlightedSeats(placeIds, color = null) {
23
+ if (!this.highlighted) {
24
+ this.highlighted = [];
25
+ }
26
+
27
+ const seatsToAdd = (placeIds || []).map((id) => ({
28
+ id,
29
+ color: color || this.config.highlightedSeatColor,
30
+ }));
31
+
32
+ this.highlighted.push(...seatsToAdd);
33
+
34
+ if (this.config.renderRowBlocks && this.highlighted.length !== 0) {
35
+ this.config.renderRowBlocks = false;
36
+ }
37
+ return this;
38
+ }
39
+
40
+ setCustomConfig(config) {
41
+ this.config = {
42
+ ...this.config,
43
+ ...config,
44
+ };
45
+ if (this.config.renderRowBlocks && this.highlighted.length !== 0) {
46
+ this.config.renderRowBlocks = false;
47
+ }
48
+ return this;
49
+ }
50
+
51
+ async render() {
52
+ if (!this.storage) {
53
+ this.logger.Error("Cannot render without storage");
54
+ return false;
55
+ }
56
+
57
+ try {
58
+ const { svg, bgImg } = await this._render();
59
+ if (!svg) {
60
+ this.logger.Error(`Couldn't render ${this.eventId}`);
61
+ return false;
62
+ }
63
+ this.svg = svg;
64
+ this.bgImg = bgImg;
65
+ return true;
66
+ } catch (e) {
67
+ if (this.config.logFullError) {
68
+ this.logger.Error("Full error details:", e);
69
+ }
70
+ this.logger.Error(`Couldn't render ${this.eventId} ${e.message}`);
71
+ return false;
72
+ }
73
+ }
74
+
75
+ async renderSeatmap(priceData) {
76
+ this.pricemap = true;
77
+ this.pricemapData = priceData;
78
+ this.config.dontRenderSeats = true;
79
+ return this.render();
80
+ }
81
+
82
+ async _render() {
83
+ let data = await this.storage.getItem(`renderer.eventData.${this.eventId}`);
84
+ if (!data || !data?.pages?.[0]?.segments) {
85
+ data = await this.getEventData();
86
+ if (!data) {
87
+ this.logger.Error("Couldn't get event data");
88
+ return {};
89
+ }
90
+ await this.storage.setItem(`renderer.eventData.${this.eventId}`, data);
91
+ }
92
+
93
+ if (!data?.pages?.[0]?.images) {
94
+ this.logger.Error("Could not get event data");
95
+ return {};
96
+ }
97
+
98
+ this.bgImg = `https://mapsapi.tmol.io/maps/geometry/image/${
99
+ data.pages[0].images.find((i) => i.url.includes("/")).url
100
+ }?removeFilters=ISM_Shadow`;
101
+ const labels = data?.pages?.[0]?.segments
102
+ .map((seg) => {
103
+ return {
104
+ shape: seg.id,
105
+ name: seg.name,
106
+ labels: seg.shapes[0]?.labels,
107
+ generalAdmission: seg.shapes[0]?.mode === "GA_STANDING",
108
+ };
109
+ })
110
+ .flat(1);
111
+
112
+ // seats data - used cached if possible
113
+ let seats = await this.storage.getItem(`renderer.seatData.${this.eventId}`);
114
+ if (!seats) {
115
+ const seatData = await this.getSeatData();
116
+ seats = TMUtils.parseSeatData(seatData);
117
+ await this.storage.setItem(`renderer.seatData.${this.eventId}`, seats);
118
+ }
119
+
120
+ if (typeof seats === "string" && seats.startsWith("[")) seats = JSON.parse(seats);
121
+
122
+ // availablility data
123
+ let avSeats = [];
124
+ let availablilityData = {};
125
+ let stock = [];
126
+
127
+ stock?.forEach((s) => {
128
+ s?.seats?.forEach((seat) => avSeats.push(seat.placeId));
129
+ if (!availablilityData[s.section]) availablilityData[s.section] = 0;
130
+ if (s.generalAdmission) availablilityData[s.section] += s.available;
131
+ else availablilityData[s.section]++;
132
+ });
133
+
134
+ let usedSections = [];
135
+ const highlightedIds = this.highlighted.map((h) => h.id);
136
+ const highlightedSections = this.highlighted.map((h) => h.id.split("/")[0]);
137
+
138
+ seats = seats.map((seat) => {
139
+ const secIdentifier = seat.rowId?.split(":")?.[0] || seat.section;
140
+
141
+ if (highlightedSections.includes(seat.section) && !usedSections.includes(secIdentifier)) {
142
+ usedSections.push(secIdentifier);
143
+ }
144
+
145
+ if (highlightedIds.includes(seat.id) && !usedSections.includes(secIdentifier)) {
146
+ usedSections.push(secIdentifier);
147
+ }
148
+
149
+ const highlightedSeat = this.highlighted.find(
150
+ (h) => h.id === seat.id || h.id === `${seat.section}/${seat.row}/${seat.seat}`,
151
+ );
152
+
153
+ return {
154
+ ...seat,
155
+ available: avSeats.includes(seat.id),
156
+ highlighted: !!highlightedSeat,
157
+ highlightColor: highlightedSeat?.color || this.config.highlightedSeatColor,
158
+ };
159
+ });
160
+
161
+ const svg = this._makeSVG(data, labels, seats, this.bgImg, usedSections);
162
+
163
+ return {
164
+ svg,
165
+ bgImg: this.bgImg,
166
+ };
167
+ }
168
+
169
+ _renderPriceLegend(priceData) {
170
+ priceData = [
171
+ {
172
+ priceFrom: 65,
173
+ priceTo: 130,
174
+ color: "dd0000",
175
+ },
176
+ {
177
+ priceFrom: 167,
178
+ priceTo: 275,
179
+ color: "aa0",
180
+ },
181
+
182
+ {
183
+ priceFrom: 310,
184
+ priceTo: 450,
185
+ color: "00ff00",
186
+ },
187
+
188
+ {
189
+ priceFrom: 555,
190
+ priceTo: 778,
191
+ color: "00ff00",
192
+ },
193
+ ];
194
+
195
+ const legendBody = priceData
196
+ .map((price, i) => {
197
+ return (
198
+ utils.createElement("circle", {
199
+ cx: 10,
200
+ cy: (i + 1) * 20 - 3,
201
+ r: 6,
202
+ fill: `#${price.color}`,
203
+ }) +
204
+ utils.createElement(
205
+ "text",
206
+ {
207
+ style: "font-size:10px;font-family: sans-serif;opacity:0.8;font-weight:500",
208
+ x: 20,
209
+ y: (i + 1) * 20,
210
+ },
211
+ utils.createElement("tspan", {}, `$${price.priceFrom}-${price.priceTo}`),
212
+ )
213
+ );
214
+ })
215
+ .join("");
216
+
217
+ return utils.createElement(
218
+ "g",
219
+ { transform: "scale(15) translate(0 0)" },
220
+ utils.createElement("rect", {
221
+ width: 100,
222
+ height: (priceData.length + 1) * 20 - 10,
223
+ rx: 5,
224
+ fill: "#ffffffbb",
225
+ }) + legendBody,
226
+ );
227
+ }
228
+
229
+ _renderPolygons(segments, usedSections) {
230
+ // make polygons
231
+ let svg = `<g>`;
232
+
233
+ if (this.pricemap) {
234
+ this.minSection = { min: 100000 };
235
+ this.maxSection = { max: 0 };
236
+ Object.values(this.pricemapData.stubhub).forEach((sec) => {
237
+ sec.min = TMUtils.getPrice(sec.min);
238
+ sec.max = TMUtils.getPrice(sec.max);
239
+ if (sec.min < this.minSection.min) this.minSection = sec;
240
+ if (sec.max > this.maxSection.max) this.maxSection = sec;
241
+ });
242
+ }
243
+
244
+ segments?.forEach((segment) => {
245
+ let secIdentifier = segment.id;
246
+
247
+ let color = "#fff";
248
+ if (segment.generalAdmission) color = this.config.nonAvGAColor;
249
+
250
+ const highlightedSection = this.highlighted.find((h) => h.id === segment.id || h.id === segment.name);
251
+
252
+ if (highlightedSection) {
253
+ color = highlightedSection.color || this.config.GAColor;
254
+ } else if (
255
+ !segment.generalAdmission &&
256
+ this.config.dontRenderUnusedSections &&
257
+ !usedSections.includes(secIdentifier)
258
+ ) {
259
+ color = this.config.grayedOutSection;
260
+ }
261
+
262
+ if (this.config.dontRenderSeats) color = this.config.grayedOutSection;
263
+ if (this.pricemap) {
264
+ const stub =
265
+ this.pricemapData.stubhub?.[this._shapeToName?.[segment.id]] ||
266
+ this.pricemapData.stubhub?.[segment.name];
267
+ if (stub) {
268
+ color = utils.manipulateHexColor(this.minSection.min / stub.min, "#00ff00", "#ff0000");
269
+ }
270
+ }
271
+
272
+ const attrs = {
273
+ d: segment?.shapes[0]?.path,
274
+ fill: color,
275
+ stroke: "rgba(200, 210, 220, 0.6)",
276
+ "stroke-width": "1.2"
277
+ };
278
+
279
+ // Use white stroke for special modes
280
+ if (this.config.dontRenderUnusedSections || this.config.dontRenderSeats) {
281
+ attrs.stroke = "white";
282
+ attrs["stroke-width"] = "2";
283
+ }
284
+ if (this.config.includeDetailedAttributes) {
285
+ attrs.shape = segment.id;
286
+ attrs.name = segment.name;
287
+ if (segment.generalAdmission) attrs.generalAdmission = true;
288
+ }
289
+
290
+ svg += utils.createElement("path", attrs);
291
+ });
292
+ svg += `</g>`;
293
+ return svg;
294
+ }
295
+
296
+ _renderSeatDots(seats, usedSections) {
297
+ let svg = `<g>`;
298
+
299
+ let addedLast = [];
300
+ seats?.forEach((seat) => {
301
+ let color = seat.available ? this.config.seatColor : this.config.nonAvSeatColor;
302
+ if (seat.highlighted) {
303
+ color = seat.highlightColor || this.config.highlightedSeatColor;
304
+ seat.r = seat.r + this.config.increaseHighlightedSeatSize;
305
+ }
306
+
307
+ if (
308
+ (this.config.dontRenderSeats || this.config.dontRenderUnusedSections) &&
309
+ !usedSections.includes(seat.rowId?.split(":")?.[0] || seat.section)
310
+ )
311
+ return;
312
+
313
+ const attrs = {
314
+ cx: seat.x,
315
+ cy: seat.y,
316
+ r: seat.r,
317
+ fill: color,
318
+ };
319
+
320
+ if (this.config.includeDetailedAttributes) {
321
+ attrs.section = seat.section;
322
+ attrs.row = seat.row;
323
+ attrs.rowId = seat.rowId;
324
+ attrs.seat = seat.seat;
325
+ attrs.seatId = seat.seatId;
326
+ }
327
+
328
+ const renderDot =
329
+ ((this.config.dontRenderSeats || this.config.dontRenderUnusedSections) && !seat.highlighted) ||
330
+ !(this.config.dontRenderSeats || this.config.dontRenderUnusedSections) ||
331
+ !this.config.seatsAsPins;
332
+
333
+ if (renderDot) svg += utils.createElement("circle", attrs);
334
+ else {
335
+ if (!seat.highlighted) return;
336
+
337
+ const scaleBy = seat.r * 5;
338
+ const size = (scaleBy - seat.r) * 1.5;
339
+
340
+ attrs.x = seat.x - seat.r * 1.8;
341
+ attrs.y = seat.y - seat.r * 1.8;
342
+ attrs.width = size;
343
+ attrs.height = size;
344
+ attrs.viewBox = ["-10", "-10", scaleBy / 2, scaleBy / 2].join(" ");
345
+
346
+ addedLast.push(utils.createElement("svg", attrs, utils.pinIcon));
347
+ }
348
+ });
349
+ svg += addedLast.join("\n");
350
+ svg += `</g>`;
351
+ return svg;
352
+ }
353
+
354
+ _renderRowBlocks(seats, usedSections, shapeToName) {
355
+ let svg = `<g>`;
356
+
357
+ const rows = {};
358
+ seats.forEach((s) => {
359
+ if (
360
+ (this.config.dontRenderSeats || this.config.dontRenderUnusedSections) &&
361
+ !usedSections.includes(s.rowId?.split(":")?.[0] || s.section)
362
+ )
363
+ return;
364
+ if (!rows[s.rowId]) rows[s.rowId] = [];
365
+ rows[s.rowId].push(s);
366
+ });
367
+
368
+ Object.values(rows).forEach((rowSeats) => {
369
+ const s = rowSeats[0];
370
+ const start = `${s.x} ${s.y}`;
371
+
372
+ let color = this.config.nonAvSeatColor;
373
+
374
+ let path = "";
375
+ for (let i = 1; i < rowSeats.length; i++) {
376
+ const curr = rowSeats[i];
377
+ const last = rowSeats[i - 1];
378
+ path += `l ${Math.round(curr.x - last.x)} ${Math.round(curr.y - last.y)} `;
379
+ }
380
+
381
+ const attrs = {
382
+ "transform": `translate(${start})`,
383
+ "fill": "transparent",
384
+ "id": s.rowId,
385
+ "d": `M 0 0 ${path}`,
386
+ "stroke": "rgba(200, 210, 220, 0.8)",
387
+ "stroke-width": s.r * 2,
388
+ };
389
+
390
+ if (this.config.includeDetailedAttributes) {
391
+ attrs.section = s.section;
392
+ attrs.row = s.row;
393
+ attrs.sectionName = shapeToName[s.rowId.split(":")[0]].trim();
394
+ }
395
+ svg += utils.createElement("path", attrs);
396
+ });
397
+
398
+ svg += "</g>";
399
+ return svg;
400
+ }
401
+
402
+ _renderLabels(labels, data) {
403
+ let svg = `<g>`;
404
+ const styles = TMUtils.parseStyles(data?.pages?.[0]?.labelStyles);
405
+
406
+ labels?.forEach((rawLabel) => {
407
+ const GA = rawLabel.generalAdmission;
408
+ rawLabel.labels.forEach((label) => {
409
+ const rotate = `${label.angle * -1}, ${label.x}, ${label.y}`;
410
+
411
+ const attrs = {
412
+ transform: `rotate(${rotate})`,
413
+ };
414
+
415
+ const style = [
416
+ `font-family: ${this.config.font}`,
417
+ `opacity: ${GA ? this.config.textOpacityOnGA : this.config.textOpacity}`,
418
+ ];
419
+
420
+ if (this.config.useBig) style.push("font-weight: 500");
421
+ if (!label.size) style.push(`font-size: ${styles[label.styleId].size}`);
422
+ else style.push(`font-size: ${label.size}`);
423
+ attrs.style = style.join(";");
424
+
425
+ const textElements = label.text
426
+ .split("\r")
427
+ .map((word, i) => {
428
+ const tspanAttrs = {
429
+ "text-anchor": "middle",
430
+ "x": label.x,
431
+ "y": label.y,
432
+ "dy": `${i + 1}em`,
433
+ "font-size": this.config.fontSize,
434
+ "fill": GA ? this.config.labelColorOnGA : this.config.labelColor,
435
+ "section": this.config.includeDetailedAttributes ? rawLabel.name : null,
436
+ "shape": this.config.includeDetailedAttributes ? rawLabel.shape : null,
437
+ };
438
+ return utils.createElement("tspan", tspanAttrs, word);
439
+ })
440
+ .join("\n");
441
+
442
+ svg += utils.createElement("text", attrs, textElements);
443
+ });
444
+ });
445
+
446
+ svg += "</g>";
447
+
448
+ return svg;
449
+ }
450
+
451
+ _makeSVG(data, labels, seats, bgImg, usedSections) {
452
+ let svgBody = "";
453
+ const { width, height } = data.pages[0];
454
+
455
+ const viewbox = `0 0 ${width} ${height}`;
456
+ const attrs = {
457
+ "style": `background-image: url(&quot;${bgImg}&quot;)`,
458
+ "viewBox": viewbox,
459
+ "stroke-width": this.config.dontRenderSeats || this.config.dontRenderUnusedSections ? "10.0" : null,
460
+ };
461
+
462
+ svgBody += this._renderPolygons(data?.pages?.[0]?.segments, usedSections);
463
+
464
+ // make seats
465
+ this._shapeToName = {};
466
+ labels.forEach((l) => {
467
+ this._shapeToName[l.shape] = l.labels[0].text.replaceAll("\r", " ");
468
+ });
469
+
470
+ if (!this.config.dontRenderSeats) {
471
+ if (this.config.renderRowBlocks) svgBody += this._renderRowBlocks(seats, usedSections, this._shapeToName);
472
+ else svgBody += this._renderSeatDots(seats, usedSections);
473
+ }
474
+
475
+ // make labels
476
+ svgBody += this._renderLabels(labels, data);
477
+
478
+ if (this.pricemap) {
479
+ svgBody += this._renderPriceLegend({});
480
+ }
481
+
482
+ return utils.createElement("svg", attrs, svgBody);
483
+ }
484
+
485
+ async outputToSVG(path) {
486
+ if (!this.svg || !this.fs) {
487
+ this.logger.Error("No SVG data available or fs not initialized");
488
+ return this;
489
+ }
490
+ this.fs.writeFileSync(path, this.svg);
491
+ return this;
492
+ }
493
+
494
+ async _fetchSaveBgImage() {
495
+ const bgImgData = await this.fetchBackgroundImage();
496
+ const background = await this.sharp(bgImgData).png().resize(this.config.outputWidth).toBuffer();
497
+ await this.storage.setItem(`renderer.background.${this.eventId}`, background);
498
+ return background;
499
+ }
500
+
501
+ async _png() {
502
+ let background = await this.storage.getItem(`renderer.background.${this.eventId}`);
503
+ if (!background) {
504
+ try {
505
+ background = await this._fetchSaveBgImage();
506
+ } catch (e) {
507
+ this.logger.Error(e.message);
508
+ }
509
+ } else if (background) {
510
+ if (typeof background === "string" && background.startsWith("{")) background = JSON.parse(background);
511
+ background = Buffer.from(background);
512
+ const bgData = await this.sharp(background).metadata();
513
+ if (bgData.width !== this.config.outputWidth) {
514
+ this.config.outputWidth = bgData.width;
515
+ }
516
+ }
517
+
518
+ const svgBuffer = Buffer.from(this.svg);
519
+ const png = await this.sharp(svgBuffer).png().resize(this.config.outputWidth).toBuffer();
520
+
521
+ if (background) {
522
+ const foreground = await this.sharp(png).toBuffer();
523
+ return this.sharp(background).composite([
524
+ {
525
+ input: foreground,
526
+ gravity: "southeast",
527
+ },
528
+ ]);
529
+ } else return this.sharp(png);
530
+ }
531
+
532
+ async outputToPNG(filePath) {
533
+ if (!this.svg || !this.sharp) {
534
+ this.logger.Error("No SVG data available or sharp not initialized");
535
+ return this;
536
+ }
537
+ const s = await this._png();
538
+ await s.toFile(filePath);
539
+ return this;
540
+ }
541
+
542
+ async outputToPNGBuffer() {
543
+ if (!this.svg || !this.sharp) {
544
+ this.logger.Error("No SVG data available or sharp not initialized");
545
+ return Buffer.from([0x00]);
546
+ }
547
+ const s = await this._png();
548
+ return s.toBuffer();
549
+ }
550
+
551
+ // Request methods
552
+ async getEventData() {
553
+ return await getEventData(this.eventId, this.options.country, this.options.proxy, this.logger);
554
+ }
555
+
556
+ async getSeatData() {
557
+ return await getSeatData(this.eventId, this.options.country, this.options.proxy, this.logger);
558
+ }
559
+
560
+ async fetchBackgroundImage() {
561
+ if (!this.bgImg) {
562
+ throw new Error("Background image URL not available");
563
+ }
564
+ return await fetchBackgroundImage(this.bgImg, this.options.proxy, this.logger);
565
+ }
566
+ }
567
+
568
+ export default TMRenderer;
@@ -0,0 +1,47 @@
1
+ import { getJsonDataWithHeaders, getBinaryDataWithHeaders } from "../request-utils.js";
2
+
3
+ function buildTMEventDataUrl(eventId, country = null) {
4
+ const excluded = ["USA", "Canada", "United Kingdom", "Ireland", "New Zealand", "Australia"];
5
+
6
+ let url = `https://mapsapi.tmol.io/maps/geometry/3/event/${eventId}?systemId=HOST&useHostGrids=true&app=CCP&sectionLevel=true`;
7
+
8
+ if (country && !excluded.includes(country)) {
9
+ url = `https://mapsapi.tmol.io/maps/geometry/3/event/${eventId}?systemId=MFX&useHostGrids=true&app=CCP&sectionLevel=true&domain=${country}&app=PRD1741_ICCP-FE`;
10
+ }
11
+
12
+ return url;
13
+ }
14
+
15
+ function buildTMSeatDataUrl(eventId, country = null) {
16
+ const excluded = ["USA", "Canada", "United Kingdom", "Ireland", "New Zealand", "Australia"];
17
+
18
+ let url = `https://mapsapi.tmol.io/maps/geometry/3/event/${eventId}/placeDetailNoKeys?useHostGrids=true&app=CCP&sectionLevel=true&systemId=HOST`;
19
+
20
+ if (country && !excluded.includes(country)) {
21
+ url = `https://mapsapi.tmol.io/maps/geometry/3/event/${eventId}/placeDetailNoKeys?useHostGrids=true&app=CCP&sectionLevel=true&systemId=MFX&domain=${country}&app=PRD1741_ICCP-FE`;
22
+ }
23
+
24
+ return url;
25
+ }
26
+
27
+ async function getEventData(eventId, country, proxy, logger) {
28
+ const url = buildTMEventDataUrl(eventId, country);
29
+ return await getJsonDataWithHeaders(url, {}, proxy, 0, logger);
30
+ }
31
+
32
+ async function getSeatData(eventId, country, proxy, logger) {
33
+ const url = buildTMSeatDataUrl(eventId, country);
34
+ return await getJsonDataWithHeaders(url, {}, proxy, 0, logger);
35
+ }
36
+
37
+ async function fetchBackgroundImage(bgImgUrl, proxy, logger) {
38
+ return await getBinaryDataWithHeaders(bgImgUrl, {}, proxy, 0, logger);
39
+ }
40
+
41
+ export {
42
+ getEventData,
43
+ getSeatData,
44
+ fetchBackgroundImage,
45
+ buildTMEventDataUrl,
46
+ buildTMSeatDataUrl,
47
+ };
@@ -0,0 +1,40 @@
1
+ const parseStyles = (styles) => {
2
+ let mapping = {};
3
+ for (let style of styles || []) {
4
+ const css = `color: #${style.color}; font-size: ${style.size}px; `;
5
+ mapping[style.id] = style;
6
+ mapping[style.id].css = css;
7
+ }
8
+ return mapping;
9
+ };
10
+
11
+ const getDotsOfSegment = (seg, allSeats, r = 0, outer = null) => {
12
+ if (seg.placeSize) r = seg.placeSize;
13
+ if (seg.generalAdmission || !seg) return;
14
+ if (seg.segments) {
15
+ seg.segments.forEach((s) => getDotsOfSegment(s, allSeats, r, seg));
16
+ return;
17
+ }
18
+ seg?.placesNoKeys?.forEach((s) => {
19
+ allSeats.push({
20
+ id: s[0],
21
+ seat: s[1],
22
+ x: s[2],
23
+ y: s[3],
24
+ row: seg.name,
25
+ rowId: seg.id,
26
+ r: r / 2,
27
+ section: outer?.name,
28
+ });
29
+ });
30
+ };
31
+
32
+ const parseSeatData = (seatData) => {
33
+ let allSeats = [];
34
+ seatData?.pages?.[0]?.segments?.forEach((seg) => getDotsOfSegment(seg, allSeats));
35
+ return allSeats;
36
+ };
37
+
38
+ export { parseStyles, parseSeatData };
39
+ export const getPrice = (p) => parseInt(p.replaceAll("$", "").replaceAll(",", ".").replaceAll(".", ""));
40
+ export const checkIfEventValid = (eventId) => /^\w{16}$/.test(eventId);