iticket-seatingplan-dev 1.5.6 → 1.5.7

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.
@@ -1,786 +0,0 @@
1
- import "./styles/index.css";
2
- import "./styles/pos.css";
3
- import "./styles/flexi.css";
4
-
5
- import React, { useLayoutEffect, useRef } from "react";
6
- import { useEffect, useState } from "react";
7
- import Map from "./Map";
8
- import * as L from "leaflet";
9
- import { MapContainer } from "react-leaflet";
10
- import {
11
- statuses,
12
- statusColors,
13
- calculateCenterOfMap,
14
- bookingModes,
15
- modes,
16
- } from "../utils";
17
- import { createPortal } from "react-dom";
18
- import {
19
- BookingMode,
20
- ChosenSeat,
21
- CallbackEvent,
22
- Price,
23
- Seat,
24
- SeatData,
25
- } from "../utils/types";
26
- import PricingPopup from "./PricingPopup";
27
-
28
- interface Props {
29
- eventId: number;
30
- eventVenueId: number;
31
- sessionId: string;
32
- showingId: number;
33
- areaId: number;
34
- priceSectionIds?: number[] | null;
35
- price?: number;
36
- quantity?: number;
37
- callbackFunction?: (event: CallbackEvent) => void;
38
- apiKey: string;
39
- baseUrl: string;
40
- countryCode: string;
41
- connectedShowings?: number[] | null;
42
- areaName: string;
43
- promoCode?: string;
44
- bookingMode?: BookingMode;
45
- }
46
-
47
- const SeatingPlan = ({
48
- eventId,
49
- eventVenueId,
50
- sessionId,
51
- showingId,
52
- areaId,
53
- priceSectionIds,
54
- price,
55
- quantity,
56
- callbackFunction,
57
- apiKey,
58
- baseUrl,
59
- countryCode,
60
- connectedShowings,
61
- areaName,
62
- promoCode,
63
- bookingMode = bookingModes.SHOPSITE,
64
- }: Props) => {
65
- const [initialiseMessage, setInitialiseMessage] = useState(
66
- "Initialising seating plan..."
67
- );
68
- const [isReloading, setIsReloading] = useState(false);
69
- const [position, setPosition] = useState(null);
70
- const [height, setHeight] = useState(0);
71
- const [width, setWidth] = useState(0);
72
- const [seats, setSeats] = useState<SeatData | undefined>();
73
- const [error, setError] = useState(null);
74
- const [bookedSeats, setBookedSeats] = useState([]);
75
- const [chosenSeat, setChosenSeat] = useState(null);
76
- const [area, setArea] = useState(null);
77
- const [ticketPopupOpen, setTicketPopupOpen] = useState(false);
78
- const [bounds, setBounds] = useState(null);
79
- const [isFullScreen, setIsFullScreen] = useState(false);
80
- const [mounted, setMounted] = React.useState(false);
81
- const [mode, setMode] = useState(modes.SINGLE);
82
- const [pricingPopupOpen, setPricingPopupOpen] = useState(false);
83
- const [selectedSeats, setSelectedSeats] = useState([]);
84
- const mapRef = useRef(null);
85
- const canMultiSelect = bookingMode === bookingModes.POS;
86
- const apiUrl = `${baseUrl}/legacy/${countryCode}/shop/events/${eventId}/${eventVenueId}/showings/${showingId}/tickets/allocated/${areaId}`;
87
-
88
- const setMap = (map: L.Map) => {
89
- mapRef.current = map;
90
- };
91
-
92
- useLayoutEffect(() => setMounted(true), []);
93
-
94
- const addTicketToCart = (s: Seat, priceage: Price, cs?: ChosenSeat) => {
95
- const updatedSeats = { ...seats };
96
- const seatIndex = updatedSeats.seats.findIndex(
97
- (seat) => seat.ssId === s.ssId
98
- );
99
-
100
- if (seatIndex !== -1) {
101
- updatedSeats.seats[seatIndex].loading = true;
102
- setSeats(updatedSeats);
103
- }
104
-
105
- if (
106
- (s.s !== statuses.UNSOLD && s.s !== statuses.USER_PENDING) ||
107
- (bookedSeats.length >= quantity && s.s === statuses.UNSOLD)
108
- ) {
109
- if (bookedSeats.length >= quantity) {
110
- const event = {
111
- type: "error",
112
- details: {
113
- message: "Maximum ticket booking quantity specified reached.",
114
- },
115
- };
116
- callbackFunction?.(event);
117
- }
118
- if (seatIndex !== -1) {
119
- updatedSeats.seats[seatIndex].loading = false;
120
- setSeats(updatedSeats);
121
- }
122
- return;
123
- }
124
-
125
- if (priceage.is_available === false) {
126
- const event = {
127
- type: "error",
128
- details: {
129
- message: "Allocation exhausted, please try another area or price.",
130
- },
131
- };
132
- callbackFunction?.(event);
133
- if (seatIndex !== -1) {
134
- updatedSeats.seats[seatIndex].loading = false;
135
- setSeats(updatedSeats);
136
- }
137
- return;
138
- }
139
-
140
- mapRef.current?.target?.closePopup();
141
-
142
- fetch(apiUrl, {
143
- method: "POST",
144
- body: JSON.stringify({
145
- priceAgeId: priceage.paId,
146
- seatId: s.ssId,
147
- price: priceage.p,
148
- connectedShowings: connectedShowings,
149
- }),
150
- headers: {
151
- "content-type": "application/json",
152
- "basket-key": sessionId,
153
- },
154
- })
155
- .then((response) => {
156
- if (response?.status === 403) {
157
- if (seatIndex !== -1) {
158
- updatedSeats.seats[seatIndex] = {
159
- ...s,
160
- loading: false,
161
- s: statuses.SOLD,
162
- };
163
- setSeats(updatedSeats);
164
- }
165
-
166
- const event = {
167
- type: "error",
168
- details: {
169
- error: {
170
- code: 403,
171
- message: `The seat you're trying to book is no longer available.`,
172
- },
173
- },
174
- };
175
- callbackFunction?.(event);
176
- } else if (response?.status === 400) {
177
- if (seatIndex !== -1) {
178
- updatedSeats.seats[seatIndex] = {
179
- ...s,
180
- loading: false,
181
- s: statuses.SOLD,
182
- };
183
- setSeats(updatedSeats);
184
- }
185
-
186
- const event = {
187
- type: "error",
188
- details: {
189
- error: {
190
- code: 403,
191
- message: `Oops, seems like you're trying to book tickets that are incompatible with other tickets in your cart.`,
192
- },
193
- },
194
- };
195
- callbackFunction?.(event);
196
- } else if (response?.status === 429) {
197
- if (seatIndex !== -1) {
198
- updatedSeats.seats[seatIndex].loading = false;
199
- setSeats(updatedSeats);
200
- }
201
-
202
- const event = {
203
- type: "error",
204
- details: {
205
- error: {
206
- code: 429,
207
- message: `Someone else selected this first. Please try again`,
208
- },
209
- },
210
- };
211
- callbackFunction?.(event);
212
- } else {
213
- if (seatIndex !== -1) {
214
- updatedSeats.seats[seatIndex] = {
215
- ...s,
216
- s: statuses.USER_PENDING,
217
- bookedPrice: price,
218
- loading: false,
219
- };
220
- setSeats(updatedSeats);
221
- }
222
-
223
- setBookedSeats((prev) => [
224
- ...prev,
225
- {
226
- ssId: s.ssId,
227
- r: s.r,
228
- c: s.c,
229
- showingId: showingId,
230
- pId: priceage.paId,
231
- p: priceage.p,
232
- paName: priceage.paName,
233
- },
234
- ]);
235
-
236
- const event = {
237
- type: "cart-change-add",
238
- details: [
239
- ...bookedSeats,
240
- {
241
- ssId: s.ssId,
242
- r: s.r,
243
- c: s.c,
244
- showingId: showingId,
245
- pId: priceage.paId,
246
- p: priceage.p,
247
- paName: priceage.paName,
248
- },
249
- ],
250
- };
251
- callbackFunction?.(event);
252
- }
253
- })
254
- .catch(() => {
255
- if (seatIndex !== -1) {
256
- updatedSeats.seats[seatIndex].loading = false;
257
- setSeats(updatedSeats);
258
- }
259
- const event = {
260
- type: "error",
261
- details: {
262
- error: {
263
- code: 500,
264
- message: "Oops! Something went wrong. Please try again.",
265
- },
266
- },
267
- };
268
- callbackFunction?.(event);
269
- })
270
- .finally(() => {
271
- initialFetch(true);
272
- });
273
- };
274
-
275
- const removeTicketFromCart = (s: Seat, e: L.LeafletMouseEvent) => {
276
- const updatedSeats = { ...seats };
277
- const seatIndex = updatedSeats.seats.findIndex(
278
- (seat) => seat.ssId === s.ssId
279
- );
280
-
281
- if (seatIndex !== -1) {
282
- updatedSeats.seats[seatIndex].loading = true;
283
- setSeats(updatedSeats);
284
- }
285
-
286
- fetch(`${apiUrl}/seat/${s.ssId}`, {
287
- method: "DELETE",
288
- headers: {
289
- "basket-key": sessionId,
290
- },
291
- })
292
- .then(() => {
293
- if (seatIndex !== -1) {
294
- updatedSeats.seats[seatIndex] = {
295
- ...s,
296
- loading: false,
297
- s: statuses.UNSOLD,
298
- bookedPrice: null,
299
- };
300
- setSeats(updatedSeats);
301
- }
302
-
303
- setBookedSeats((prev) => prev.filter((bs) => bs.ssId !== s.ssId));
304
-
305
- const event = {
306
- type: "cart-change-remove",
307
- details: bookedSeats.filter((bs) => bs.ssId === s.ssId),
308
- };
309
- callbackFunction?.(event);
310
- })
311
- .catch(() => {
312
- if (seatIndex !== -1) {
313
- updatedSeats.seats[seatIndex].loading = false;
314
- setSeats(updatedSeats);
315
- }
316
- e.target.setStyle({ fillColor: statusColors.booked });
317
-
318
- const event = {
319
- type: "error",
320
- details: {
321
- error: {
322
- code: 500,
323
- message: "Oops! Something went wrong. Please try again.",
324
- },
325
- },
326
- };
327
- callbackFunction?.(event);
328
- })
329
- .finally(() => {
330
- initialFetch(true);
331
- });
332
- };
333
-
334
- const batchAddTicketsToCart = async (
335
- seatsToBook: { seat: Seat; priceage: Price }[]
336
- ) => {
337
- if (!canMultiSelect || seatsToBook.length === 0) {
338
- return;
339
- }
340
-
341
- const updatedSeats = { ...seats };
342
- const seatIds = seatsToBook.map((s) => s.seat.ssId);
343
-
344
- updatedSeats.seats.forEach((s) => {
345
- if (seatIds.includes(s.ssId)) {
346
- s.loading = true;
347
- }
348
- });
349
- setSeats(updatedSeats);
350
-
351
- await new Promise((resolve) => setTimeout(resolve, 1000));
352
-
353
- setBookedSeats((prev) => [
354
- ...prev,
355
- ...seatsToBook.map(({ seat, priceage }) => ({
356
- ssId: seat.ssId,
357
- r: seat.r,
358
- c: seat.c,
359
- showingId: showingId,
360
- pId: priceage.paId,
361
- p: priceage.p,
362
- paName: priceage.paName,
363
- })),
364
- ]);
365
-
366
- updatedSeats.seats.forEach((s) => {
367
- if (seatIds.includes(s.ssId)) {
368
- s.s = statuses.USER_PENDING;
369
- s.bookedPrice = price;
370
- s.loading = false;
371
- }
372
- });
373
- setSeats(updatedSeats);
374
- };
375
-
376
- const batchRemoveTicketsFromCart = async (seatsToRemove: Seat[]) => {
377
- if (!canMultiSelect || seatsToRemove.length === 0) {
378
- return;
379
- }
380
-
381
- const updatedSeats = { ...seats };
382
- const seatIds = seatsToRemove.map((s) => s.ssId);
383
-
384
- updatedSeats.seats.forEach((s) => {
385
- if (seatIds.includes(s.ssId)) {
386
- s.loading = true;
387
- }
388
- });
389
- setSeats(updatedSeats);
390
-
391
- await new Promise((resolve) => setTimeout(resolve, 1000));
392
-
393
- setBookedSeats((prev) => [
394
- ...prev.filter((v) => !seatIds.includes(v.ssId)),
395
- ]);
396
-
397
- updatedSeats.seats.forEach((s) => {
398
- if (seatIds.includes(s.ssId)) {
399
- s.loading = false;
400
- s.s = statuses.UNSOLD;
401
- s.bookedPrice = null;
402
- }
403
- });
404
- setSeats(updatedSeats);
405
- };
406
-
407
- const handleClickSeat = (e: L.LeafletMouseEvent, s: Seat) => {
408
- setChosenSeat({ circle: e, seat: s });
409
-
410
- if (
411
- (s.s !== statuses.UNSOLD && s.s !== statuses.USER_PENDING) ||
412
- (bookedSeats.length >= quantity && s.s === statuses.UNSOLD)
413
- ) {
414
- if (bookedSeats.length >= quantity) {
415
- const event = {
416
- type: "error",
417
- details: {
418
- message: "Maximum ticket booking quantity specified reached.",
419
- },
420
- };
421
- callbackFunction?.(event);
422
- }
423
- return;
424
- }
425
-
426
- if (s.s === statuses.UNSOLD) {
427
- const availablePrices = seats.pricing.filter(
428
- (price) =>
429
- (!priceSectionIds || priceSectionIds.includes(price.psId)) &&
430
- s.psId === price.psId
431
- );
432
-
433
- const isSingleFlexi =
434
- availablePrices.length === 1 &&
435
- !!availablePrices[0].pMax &&
436
- !!availablePrices[0].pMin &&
437
- availablePrices[0].pMax > availablePrices[0].pMin;
438
-
439
- if (availablePrices.length === 1 && !isSingleFlexi) {
440
- addTicketToCart(s, availablePrices[0], { circle: e, seat: s });
441
- }
442
- } else {
443
- removeTicketFromCart(s, e);
444
- }
445
- };
446
-
447
- const initialFetch = (isReload = false) => {
448
- if (isReload) {
449
- setIsReloading(true);
450
- }
451
- setInitialiseMessage("Initialising seating plan...");
452
- fetch(
453
- // `http://localhost:3155/api/legacy/shop/events/${eventId}/${eventVenueId}/showings/${showingId}/tickets/allocated/${areaId}`,
454
- `${apiUrl}${promoCode ? `?promo=${promoCode}` : ""}`,
455
- {
456
- headers: {
457
- "basket-key": sessionId,
458
- Authorization: `Bearer ${apiKey}`,
459
- },
460
- }
461
- )
462
- .then(async (response) => {
463
- const data = await response.json();
464
- data.retryAfter = response.headers.get("Retry-After");
465
- return data;
466
- })
467
- .then((data) => {
468
- if (data.statusCode === 429) {
469
- setInitialiseMessage(
470
- `Looks like it's busy. Stand by. Trying again in ${
471
- data.retryAfter
472
- } second${data.retryAfter > 1 ? "s" : ""}.`
473
- );
474
- return setTimeout(initialFetch, data.retryAfter * 1000);
475
- }
476
-
477
- if (data.statusCode === 403) {
478
- if (!sessionId || data.message === "Invalid Session id.") {
479
- throw new Error(
480
- "Looks like your cart has expired. Please refresh the page to try again."
481
- );
482
- } else {
483
- throw new Error("Invalid API Key or unauthorized URL");
484
- }
485
- }
486
-
487
- const img = new Image();
488
- img.src = data.background;
489
-
490
- setBookedSeats(
491
- data.seats
492
- .filter(
493
- (s) => s.s === statuses.USER_PENDING && s.bookedPrice === price
494
- )
495
- .map((s) => ({
496
- ssId: s.ssId,
497
- r: s.r,
498
- c: s.c,
499
- showingId: showingId,
500
- p: s.p, //price || s.defaultPrice,
501
- pId: s.pId,
502
- paName: s.paName,
503
- }))
504
- );
505
-
506
- img.onload = () => {
507
- const imgSouthwest = [0, 0];
508
- const imgNortheast = [img.height * 0.015, img.width * 0.015];
509
-
510
- setBounds([imgSouthwest, imgNortheast]);
511
- setPosition([img.height * 0.005, img.width * 0.005]);
512
- setHeight(img.height * 0.5);
513
- setWidth(img.width * 0.5);
514
- setSeats(data);
515
- };
516
-
517
- setIsReloading(false);
518
- })
519
- .catch((error) => {
520
- // console.log(error);
521
- setError({
522
- response: {
523
- data: {
524
- message:
525
- error?.message ?? "Something went wrong. Please try again.",
526
- },
527
- },
528
- });
529
-
530
- setIsReloading(false);
531
- });
532
- };
533
-
534
- useEffect(() => {
535
- if (!error && areaId !== area) {
536
- setArea(areaId);
537
- initialFetch();
538
- setPosition(null);
539
- }
540
- }, [areaId]);
541
-
542
- useEffect(() => {
543
- if (ticketPopupOpen) {
544
- chosenSeat?.circle.target.setStyle({ fillOpacity: 0 });
545
- }
546
- }, [ticketPopupOpen, chosenSeat]);
547
-
548
- // TEST
549
- // useEffect(() => console.log("Booked: ", bookedSeats), [bookedSeats]);
550
-
551
- // render the map using ReactDOM.createPortal to avoid z-index issues
552
- // in full screen mode, render the map in the body, otherwise render in the seating-plan-root div
553
- const mapContainer =
554
- mounted &&
555
- (isFullScreen
556
- ? document?.body
557
- : document?.getElementById("seating-plan-root"));
558
-
559
- const backdropContainer = mounted && document?.body;
560
-
561
- const groupedSelectedSeats: {
562
- [key: number]: { seats: Seat[]; priceage?: Price };
563
- } = selectedSeats.reduce((acc, curr) => {
564
- if (curr.psId) {
565
- const priceages = seats.pricing.filter(
566
- (price) =>
567
- (!priceSectionIds || priceSectionIds.includes(price.psId)) &&
568
- price.psId === curr.psId
569
- );
570
- const availablePriceages = priceages.filter(
571
- (p) => p.is_available !== false
572
- );
573
- if (!acc[curr.psId]) {
574
- acc[curr.psId] = {
575
- seats: [],
576
- priceage:
577
- availablePriceages.length > 0 ? availablePriceages[0] : undefined,
578
- };
579
- }
580
- acc[curr.psId].seats.push(curr);
581
- }
582
- return acc;
583
- }, {} as { [key: number]: { seats: Seat[]; priceage?: Price } });
584
-
585
- const cancelPriceSelect = () => {
586
- setPricingPopupOpen(false);
587
- setSelectedSeats([]);
588
- };
589
-
590
- // return seating plan
591
- return (
592
- <>
593
- <div className="seating-plan-root" id="seating-plan-root">
594
- {error ? (
595
- <div className="loading">
596
- <h1>OOPS!</h1>
597
- <div>{error.response.data.message}</div>
598
- </div>
599
- ) : position && area === areaId ? (
600
- <>
601
- {backdropContainer &&
602
- createPortal(
603
- <div
604
- className={`seating-plan-backdrop ${
605
- isFullScreen ? "full-screen" : ""
606
- }`}
607
- onClick={() => setIsFullScreen(false)}
608
- />,
609
- backdropContainer
610
- )}
611
- {mapContainer &&
612
- createPortal(
613
- <div
614
- className={`seating-plan-container ${
615
- isFullScreen ? "full-screen" : ""
616
- }`}
617
- data-mode={mode}
618
- >
619
- {pricingPopupOpen && canMultiSelect && (
620
- <PricingPopup
621
- cancelPriceSelect={cancelPriceSelect}
622
- groupedSelectedSeats={groupedSelectedSeats}
623
- priceSectionIds={priceSectionIds}
624
- pricing={seats.pricing}
625
- batchAddTicketsToCart={batchAddTicketsToCart}
626
- />
627
- )}
628
- <MapContainer
629
- zoomSnap={0}
630
- zoomDelta={1}
631
- center={calculateCenterOfMap(height, seats.seats)}
632
- zoom={undefined}
633
- zoomControl={true}
634
- scrollWheelZoom={true}
635
- bounds={bounds}
636
- // @ts-ignore
637
- whenReady={setMap}
638
- >
639
- <Map
640
- seats={seats}
641
- height={height}
642
- width={width}
643
- bounds={bounds}
644
- handleClickSeat={handleClickSeat}
645
- ticketPopupOpen={ticketPopupOpen}
646
- setTicketPopupOpen={setTicketPopupOpen}
647
- chosenSeat={chosenSeat}
648
- bookedSeats={bookedSeats}
649
- addTicketToCart={addTicketToCart}
650
- batchRemoveTicketsFromCart={batchRemoveTicketsFromCart}
651
- isReloading={isReloading}
652
- initialFetch={initialFetch}
653
- isFullScreen={isFullScreen}
654
- setIsFullScreen={setIsFullScreen}
655
- priceSectionIds={priceSectionIds}
656
- areaName={areaName}
657
- price={price}
658
- canMultiSelect={canMultiSelect}
659
- mode={mode}
660
- setMode={setMode}
661
- pricingPopupOpen={pricingPopupOpen}
662
- setPricingPopupOpen={setPricingPopupOpen}
663
- selectedSeats={selectedSeats}
664
- setSelectedSeats={setSelectedSeats}
665
- />
666
- {/* <GeoJSON attribution="powered by &copy; iTICKET" data={}/> */}
667
- </MapContainer>
668
- </div>,
669
- mapContainer
670
- )}
671
- </>
672
- ) : (
673
- <div className="loading">
674
- <div>
675
- {/* <img
676
- src="https://iticketseatingplan.blob.core.windows.net/embed/static/media/spinner.gif"
677
- alt="loading"
678
- /> */}
679
- <svg
680
- width="105"
681
- height="105"
682
- viewBox="0 0 105 105"
683
- xmlns="http://www.w3.org/2000/svg"
684
- fill="#2ecc71"
685
- >
686
- <circle cx="12.5" cy="12.5" r="12.5">
687
- <animate
688
- attributeName="fill-opacity"
689
- begin="0s"
690
- dur="1s"
691
- values="1;.2;1"
692
- calcMode="linear"
693
- repeatCount="indefinite"
694
- />
695
- </circle>
696
- <circle cx="12.5" cy="52.5" r="12.5" fillOpacity=".5">
697
- <animate
698
- attributeName="fill-opacity"
699
- begin="100ms"
700
- dur="1s"
701
- values="1;.2;1"
702
- calcMode="linear"
703
- repeatCount="indefinite"
704
- />
705
- </circle>
706
- <circle cx="52.5" cy="12.5" r="12.5">
707
- <animate
708
- attributeName="fill-opacity"
709
- begin="300ms"
710
- dur="1s"
711
- values="1;.2;1"
712
- calcMode="linear"
713
- repeatCount="indefinite"
714
- />
715
- </circle>
716
- <circle cx="52.5" cy="52.5" r="12.5">
717
- <animate
718
- attributeName="fill-opacity"
719
- begin="600ms"
720
- dur="1s"
721
- values="1;.2;1"
722
- calcMode="linear"
723
- repeatCount="indefinite"
724
- />
725
- </circle>
726
- <circle cx="92.5" cy="12.5" r="12.5">
727
- <animate
728
- attributeName="fill-opacity"
729
- begin="800ms"
730
- dur="1s"
731
- values="1;.2;1"
732
- calcMode="linear"
733
- repeatCount="indefinite"
734
- />
735
- </circle>
736
- <circle cx="92.5" cy="52.5" r="12.5">
737
- <animate
738
- attributeName="fill-opacity"
739
- begin="400ms"
740
- dur="1s"
741
- values="1;.2;1"
742
- calcMode="linear"
743
- repeatCount="indefinite"
744
- />
745
- </circle>
746
- <circle cx="12.5" cy="92.5" r="12.5">
747
- <animate
748
- attributeName="fill-opacity"
749
- begin="700ms"
750
- dur="1s"
751
- values="1;.2;1"
752
- calcMode="linear"
753
- repeatCount="indefinite"
754
- />
755
- </circle>
756
- <circle cx="52.5" cy="92.5" r="12.5">
757
- <animate
758
- attributeName="fill-opacity"
759
- begin="500ms"
760
- dur="1s"
761
- values="1;.2;1"
762
- calcMode="linear"
763
- repeatCount="indefinite"
764
- />
765
- </circle>
766
- <circle cx="92.5" cy="92.5" r="12.5">
767
- <animate
768
- attributeName="fill-opacity"
769
- begin="200ms"
770
- dur="1s"
771
- values="1;.2;1"
772
- calcMode="linear"
773
- repeatCount="indefinite"
774
- />
775
- </circle>
776
- </svg>
777
- </div>
778
- <div className="initialise-message">{initialiseMessage}</div>
779
- </div>
780
- )}
781
- </div>
782
- </>
783
- );
784
- };
785
-
786
- export default SeatingPlan;