@eventlook/sdk 1.7.6 → 1.7.8

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 (48) hide show
  1. package/.gitlab-ci.yml +51 -0
  2. package/README.md +7 -0
  3. package/dist/cjs/index.js +25692 -16
  4. package/dist/cjs/index.js.map +1 -1
  5. package/dist/esm/index.js +25674 -12
  6. package/dist/esm/index.js.map +1 -1
  7. package/dist/types/components/RichText.d.ts +7 -0
  8. package/dist/types/context/SeatPickerContext.d.ts +13 -0
  9. package/dist/types/embed.d.ts +49 -0
  10. package/dist/types/locales/cs.d.ts +1 -0
  11. package/dist/types/locales/en.d.ts +1 -0
  12. package/dist/types/locales/es.d.ts +1 -0
  13. package/dist/types/locales/pl.d.ts +1 -0
  14. package/dist/types/locales/sk.d.ts +1 -0
  15. package/dist/types/locales/uk.d.ts +1 -0
  16. package/dist/types/utils/render-rich-text.d.ts +2 -0
  17. package/dist/types/utils/types/event.type.d.ts +1 -0
  18. package/package.json +4 -2
  19. package/rollup.embed.config.mjs +74 -0
  20. package/src/components/RichText.tsx +33 -0
  21. package/src/context/SeatPickerContext.tsx +35 -0
  22. package/src/embed.tsx +243 -0
  23. package/src/form/Payment.tsx +6 -1
  24. package/src/form/PaymentOverviewBox.tsx +35 -19
  25. package/src/form/PaymentPending.tsx +7 -24
  26. package/src/form/ReleaseWithMerchandise.tsx +8 -1
  27. package/src/form/TicketForm.tsx +204 -164
  28. package/src/form/services/index.tsx +1 -0
  29. package/src/form/tickets/TicketSelectionMap.tsx +240 -73
  30. package/src/index.ts +4 -0
  31. package/src/locales/cs.tsx +1 -0
  32. package/src/locales/en.tsx +1 -0
  33. package/src/locales/es.tsx +1 -0
  34. package/src/locales/pl.tsx +1 -0
  35. package/src/locales/sk.tsx +1 -0
  36. package/src/locales/uk.tsx +1 -0
  37. package/src/react-dom-client.d.ts +11 -0
  38. package/src/utils/axios.ts +3 -0
  39. package/src/utils/render-rich-text.ts +100 -0
  40. package/src/utils/types/event.type.ts +1 -0
  41. package/dist/cjs/index-Dh-sV_wk.js +0 -41946
  42. package/dist/cjs/index-Dh-sV_wk.js.map +0 -1
  43. package/dist/cjs/index.umd-CJ-ZDtw8.js +0 -13397
  44. package/dist/cjs/index.umd-CJ-ZDtw8.js.map +0 -1
  45. package/dist/esm/index-DPHu9Sfs.js +0 -41925
  46. package/dist/esm/index-DPHu9Sfs.js.map +0 -1
  47. package/dist/esm/index.umd-DPIwz_aJ.js +0 -13395
  48. package/dist/esm/index.umd-DPIwz_aJ.js.map +0 -1
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ type Props = {
3
+ content: string | null | undefined;
4
+ className?: string;
5
+ };
6
+ declare const RichText: React.FC<Props>;
7
+ export default RichText;
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ type DeselectFn = (locationId: string) => void;
3
+ interface SeatPickerContextValue {
4
+ /** The mounted picker registers its deselect handler here (null on unmount). */
5
+ registerDeselect: (fn: DeselectFn | null) => void;
6
+ /** Ask the picker to deselect one selection for a locationId (cart remove button). */
7
+ deselect: DeselectFn;
8
+ }
9
+ export declare const SeatPickerProvider: React.FC<{
10
+ children: React.ReactNode;
11
+ }>;
12
+ export declare const useSeatPicker: () => SeatPickerContextValue;
13
+ export {};
@@ -0,0 +1,49 @@
1
+ import { Root } from 'react-dom/client';
2
+ import type { IContent } from '@utils/types/global.type';
3
+ interface ThemeOpts {
4
+ /** 'light' (default) or 'dark'. */
5
+ mode?: 'light' | 'dark';
6
+ /** Brand color — buttons, step markers, accents. */
7
+ primaryColor?: string;
8
+ /** Optional secondary accent color. */
9
+ secondaryColor?: string;
10
+ /** CSS font-family for the whole form, e.g. "Georgia, serif". */
11
+ fontFamily?: string;
12
+ /** Page background behind the form. */
13
+ background?: string;
14
+ /** Card / input surface background. */
15
+ paper?: string;
16
+ /** Corner radius in px for buttons, cards and inputs. */
17
+ radius?: number;
18
+ }
19
+ export interface MountOptions {
20
+ event: string;
21
+ apiUrl?: string;
22
+ seatingIframeUrl?: string;
23
+ lang?: string;
24
+ packetaApiKey?: string;
25
+ stickyHeaderTop?: number;
26
+ theme?: ThemeOpts;
27
+ callbacks?: {
28
+ homepage: () => void;
29
+ tickets: () => void;
30
+ detail: () => void;
31
+ };
32
+ links?: {
33
+ termsAndConditions: string;
34
+ gdpr: string;
35
+ };
36
+ /** Preselect a ticket release/type by id. */
37
+ selectedReleaseId?: number;
38
+ /** Content overrides — hide birth-date/gender fields, custom confirmation copy/image. */
39
+ content?: IContent;
40
+ /** Show combined T&C for these company ids (multi-organizer events). */
41
+ termsAndConditionsCompanies?: string[];
42
+ /** Auto-scroll to the top of the form after each step change. */
43
+ autoscrollAfterViewChange?: boolean;
44
+ /** Paydroid (Sobit / TapyGo) cashless API key. */
45
+ paydroidApiKey?: string;
46
+ }
47
+ export declare function mount(target: string | Element, userOpts?: Partial<MountOptions>): Root | undefined;
48
+ export declare function unmount(target: string | Element): void;
49
+ export {};
@@ -9,6 +9,7 @@ declare const cs: {
9
9
  cancel: string;
10
10
  close: string;
11
11
  remove: string;
12
+ price_from: string;
12
13
  pay: string;
13
14
  change: string;
14
15
  free: string;
@@ -9,6 +9,7 @@ declare const en: {
9
9
  cancel: string;
10
10
  close: string;
11
11
  remove: string;
12
+ price_from: string;
12
13
  pay: string;
13
14
  change: string;
14
15
  free: string;
@@ -9,6 +9,7 @@ declare const es: {
9
9
  cancel: string;
10
10
  close: string;
11
11
  remove: string;
12
+ price_from: string;
12
13
  pay: string;
13
14
  change: string;
14
15
  free: string;
@@ -9,6 +9,7 @@ declare const pl: {
9
9
  cancel: string;
10
10
  close: string;
11
11
  remove: string;
12
+ price_from: string;
12
13
  pay: string;
13
14
  change: string;
14
15
  free: string;
@@ -9,6 +9,7 @@ declare const sk: {
9
9
  cancel: string;
10
10
  close: string;
11
11
  remove: string;
12
+ price_from: string;
12
13
  pay: string;
13
14
  change: string;
14
15
  free: string;
@@ -9,6 +9,7 @@ declare const uk: {
9
9
  cancel: string;
10
10
  close: string;
11
11
  remove: string;
12
+ price_from: string;
12
13
  pay: string;
13
14
  change: string;
14
15
  free: string;
@@ -0,0 +1,2 @@
1
+ export declare function renderRichTextToHtml(content: string | null | undefined): string;
2
+ export declare function isEmptyRichText(content: string | null | undefined): boolean;
@@ -21,6 +21,7 @@ export interface IEvent extends IBase {
21
21
  releaseEndDate: string;
22
22
  place: IPlace;
23
23
  description: string;
24
+ purchaseInstructions: string | null;
24
25
  image: IFile;
25
26
  verified: boolean;
26
27
  ageLimit: number | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventlook/sdk",
3
- "version": "1.7.6",
3
+ "version": "1.7.8",
4
4
  "main": "dist/cjs/index.js",
5
5
  "module": "dist/esm/index.js",
6
6
  "types": "dist/types/index.d.ts",
@@ -14,6 +14,7 @@
14
14
  },
15
15
  "scripts": {
16
16
  "build": "rollup -c && tsc -p tsconfig.build.json",
17
+ "build:embed": "rollup -c rollup.embed.config.mjs",
17
18
  "prepare": "yarn run build",
18
19
  "push": "yalc push --force",
19
20
  "push:initial": "yarn build && yalc push --force",
@@ -54,8 +55,9 @@
54
55
  "@rollup/plugin-json": "^6.1.0",
55
56
  "@rollup/plugin-node-resolve": "^16.0.3",
56
57
  "@rollup/plugin-replace": "^6.0.3",
58
+ "@rollup/plugin-terser": "^0.4.4",
57
59
  "@rollup/plugin-typescript": "^12.3.0",
58
- "@seat-picker/seat-picker-sdk": "^1.2.0",
60
+ "@seat-picker/seat-picker-sdk": "^2.0.0",
59
61
  "@stripe/react-stripe-js": "^6.6.0",
60
62
  "@stripe/stripe-js": "^9.7.0",
61
63
  "@types/js-cookie": "^3.0.6",
@@ -0,0 +1,74 @@
1
+ /*
2
+ * Standalone browser build of the SDK — a single self-contained IIFE that bundles
3
+ * React, MUI, Emotion and the seat-picker SDK, exposing window.Eventlook.mount().
4
+ * Hosted unversioned by Eventlook so third-party sites never pin a version.
5
+ *
6
+ * yarn build:embed -> dist/embed/eventlook.js
7
+ */
8
+ import typescript from '@rollup/plugin-typescript';
9
+ import resolve from '@rollup/plugin-node-resolve';
10
+ import commonjs from '@rollup/plugin-commonjs';
11
+ import json from '@rollup/plugin-json';
12
+ import replace from '@rollup/plugin-replace';
13
+ import alias from '@rollup/plugin-alias';
14
+ import terser from '@rollup/plugin-terser';
15
+
16
+ export default {
17
+ input: './src/embed.tsx',
18
+ output: {
19
+ file: 'dist/embed/eventlook.js',
20
+ format: 'iife',
21
+ name: 'EventlookEmbedBundle',
22
+ sourcemap: true,
23
+ inlineDynamicImports: true,
24
+ // Shim Node globals some deps reference, without a bundler define step.
25
+ intro:
26
+ 'var process = (typeof process !== "undefined") ? process : { env: { NODE_ENV: "production" } };' +
27
+ 'var global = (typeof global !== "undefined") ? global : (typeof globalThis !== "undefined" ? globalThis : window);',
28
+ },
29
+ onwarn(warning, warn) {
30
+ // Bundling a large app surfaces benign warnings — keep the log readable.
31
+ if (
32
+ warning.code === 'MODULE_LEVEL_DIRECTIVE' ||
33
+ warning.code === 'THIS_IS_UNDEFINED' ||
34
+ warning.code === 'CIRCULAR_DEPENDENCY' ||
35
+ warning.code === 'SOURCEMAP_ERROR'
36
+ ) {
37
+ return;
38
+ }
39
+ warn(warning);
40
+ },
41
+ plugins: [
42
+ alias({
43
+ entries: [
44
+ { find: 'lottie-web', replacement: 'lottie-web/build/player/lottie_light.js' },
45
+ ],
46
+ }),
47
+ replace({
48
+ preventAssignment: true,
49
+ values: {
50
+ 'process.env.NODE_ENV': JSON.stringify('production'),
51
+ 'require("form-data")': '{}',
52
+ "require('form-data')": '{}',
53
+ 'require("zlib")': '{}',
54
+ "require('zlib')": '{}',
55
+ },
56
+ }),
57
+ json({ compact: true, preferConst: true, namedExports: true }),
58
+ resolve({
59
+ browser: true,
60
+ preferBuiltins: false,
61
+ moduleDirectories: ['node_modules'],
62
+ extensions: ['.mjs', '.js', '.jsx', '.ts', '.tsx', '.json'],
63
+ }),
64
+ commonjs(),
65
+ typescript({
66
+ tsconfig: './tsconfig.json',
67
+ noEmitOnError: false,
68
+ compilerOptions: { declaration: false, declarationMap: false, sourceMap: true },
69
+ }),
70
+ terser(),
71
+ ],
72
+ // Bundle everything — the whole point of the hosted script is zero deps on the host.
73
+ external: [],
74
+ };
@@ -0,0 +1,33 @@
1
+ import React from 'react';
2
+ import { styled } from '@mui/material/styles';
3
+ import { renderRichTextToHtml } from '@utils/render-rich-text';
4
+
5
+ // Renders sanitized rich text (ProseMirror JSON or legacy HTML). Empty
6
+ // paragraphs are emitted as literal `<p></p>` and given height via CSS,
7
+ // matching frontend/src/components/RichText.tsx. See render-rich-text.ts.
8
+ const RootStyle = styled('div')(({ theme }) => ({
9
+ '& p': {
10
+ margin: 0,
11
+ },
12
+ '& p:empty': {
13
+ minHeight: '1em',
14
+ },
15
+ '& a': {
16
+ color: theme.palette.primary.main,
17
+ textDecoration: 'underline',
18
+ },
19
+ }));
20
+
21
+ type Props = {
22
+ content: string | null | undefined;
23
+ className?: string;
24
+ };
25
+
26
+ const RichText: React.FC<Props> = ({ content, className }) => (
27
+ <RootStyle
28
+ className={className}
29
+ dangerouslySetInnerHTML={{ __html: renderRichTextToHtml(content) }}
30
+ />
31
+ );
32
+
33
+ export default RichText;
@@ -0,0 +1,35 @@
1
+ import React, { createContext, useCallback, useContext, useRef } from 'react';
2
+
3
+ type DeselectFn = (locationId: string) => void;
4
+
5
+ interface SeatPickerContextValue {
6
+ /** The mounted picker registers its deselect handler here (null on unmount). */
7
+ registerDeselect: (fn: DeselectFn | null) => void;
8
+ /** Ask the picker to deselect one selection for a locationId (cart remove button). */
9
+ deselect: DeselectFn;
10
+ }
11
+
12
+ const SeatPickerContext = createContext<SeatPickerContextValue>({
13
+ registerDeselect: () => {},
14
+ deselect: () => {},
15
+ });
16
+
17
+ export const SeatPickerProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
18
+ const deselectRef = useRef<DeselectFn | null>(null);
19
+
20
+ const registerDeselect = useCallback((fn: DeselectFn | null) => {
21
+ deselectRef.current = fn;
22
+ }, []);
23
+
24
+ const deselect = useCallback((locationId: string) => {
25
+ deselectRef.current?.(locationId);
26
+ }, []);
27
+
28
+ return (
29
+ <SeatPickerContext.Provider value={{ registerDeselect, deselect }}>
30
+ {children}
31
+ </SeatPickerContext.Provider>
32
+ );
33
+ };
34
+
35
+ export const useSeatPicker = () => useContext(SeatPickerContext);
package/src/embed.tsx ADDED
@@ -0,0 +1,243 @@
1
+ /*
2
+ * Standalone browser entry for the Eventlook SDK.
3
+ *
4
+ * This is the same in-page OrderFormSdk, packaged as a self-contained <script>
5
+ * (React + MUI + everything bundled) so a third-party site can drop it in ONCE
6
+ * and never pin/upgrade a version — Eventlook redeploys the hosted file and every
7
+ * site gets it. It renders in-page (not in an iframe), so the seat picker overlays
8
+ * the real viewport and mobile seating works correctly.
9
+ *
10
+ * Declarative:
11
+ * <div data-eventlook-event="my-event-slug"></div>
12
+ * <script async src="https://cdn.eventlook.cz/eventlook.js"></script>
13
+ *
14
+ * Imperative:
15
+ * Eventlook.mount('#tickets', {
16
+ * event: 'my-event-slug',
17
+ * theme: { primaryColor: '#9b1d43' },
18
+ * callbacks: { detail: () => location.assign('/program') },
19
+ * });
20
+ */
21
+ import React, { useMemo, useState } from 'react';
22
+ import { createRoot, Root } from 'react-dom/client';
23
+ import { ThemeProvider, createTheme, ScopedCssBaseline, Snackbar, Alert } from '@mui/material';
24
+ import { OrderFormSdk } from './index';
25
+ import { Languages } from '@utils/data/language';
26
+ import type { IContent } from '@utils/types/global.type';
27
+
28
+ type SnackVariant = 'success' | 'error';
29
+
30
+ interface ThemeOpts {
31
+ /** 'light' (default) or 'dark'. */
32
+ mode?: 'light' | 'dark';
33
+ /** Brand color — buttons, step markers, accents. */
34
+ primaryColor?: string;
35
+ /** Optional secondary accent color. */
36
+ secondaryColor?: string;
37
+ /** CSS font-family for the whole form, e.g. "Georgia, serif". */
38
+ fontFamily?: string;
39
+ /** Page background behind the form. */
40
+ background?: string;
41
+ /** Card / input surface background. */
42
+ paper?: string;
43
+ /** Corner radius in px for buttons, cards and inputs. */
44
+ radius?: number;
45
+ }
46
+
47
+ export interface MountOptions {
48
+ event: string;
49
+ apiUrl?: string;
50
+ seatingIframeUrl?: string;
51
+ lang?: string;
52
+ packetaApiKey?: string;
53
+ stickyHeaderTop?: number;
54
+ theme?: ThemeOpts;
55
+ callbacks?: { homepage: () => void; tickets: () => void; detail: () => void };
56
+ links?: { termsAndConditions: string; gdpr: string };
57
+ /** Preselect a ticket release/type by id. */
58
+ selectedReleaseId?: number;
59
+ /** Content overrides — hide birth-date/gender fields, custom confirmation copy/image. */
60
+ content?: IContent;
61
+ /** Show combined T&C for these company ids (multi-organizer events). */
62
+ termsAndConditionsCompanies?: string[];
63
+ /** Auto-scroll to the top of the form after each step change. */
64
+ autoscrollAfterViewChange?: boolean;
65
+ /** Paydroid (Sobit / TapyGo) cashless API key. */
66
+ paydroidApiKey?: string;
67
+ }
68
+
69
+ const DEFAULTS = {
70
+ apiUrl: 'https://api.eventlook.cz',
71
+ seatingIframeUrl: 'https://iframe.seatily.com',
72
+ lang: 'cs',
73
+ termsAndConditions: 'https://www.eventlook.cz/obchodni-podminky/',
74
+ gdpr: 'https://www.eventlook.cz/gdpr/',
75
+ };
76
+
77
+ const noop = () => {};
78
+
79
+ const EmbedApp: React.FC<{ opts: MountOptions }> = ({ opts }) => {
80
+ const [snack, setSnack] = useState<{ text: string; variant: SnackVariant } | null>(null);
81
+
82
+ const theme = useMemo(() => {
83
+ const t = opts.theme || {};
84
+ return createTheme({
85
+ palette: {
86
+ mode: t.mode || 'light',
87
+ ...(t.primaryColor ? { primary: { main: t.primaryColor } } : {}),
88
+ ...(t.secondaryColor ? { secondary: { main: t.secondaryColor } } : {}),
89
+ ...(t.background || t.paper
90
+ ? {
91
+ background: {
92
+ ...(t.background ? { default: t.background } : {}),
93
+ ...(t.paper ? { paper: t.paper } : {}),
94
+ },
95
+ }
96
+ : {}),
97
+ },
98
+ ...(t.fontFamily ? { typography: { fontFamily: t.fontFamily } } : {}),
99
+ ...(t.radius != null ? { shape: { borderRadius: t.radius } } : {}),
100
+ });
101
+ // eslint-disable-next-line react-hooks/exhaustive-deps
102
+ }, []);
103
+
104
+ return (
105
+ <ThemeProvider theme={theme}>
106
+ <ScopedCssBaseline>
107
+ <OrderFormSdk
108
+ eventSlug={opts.event}
109
+ apiUrl={opts.apiUrl || DEFAULTS.apiUrl}
110
+ seatingIframeUrl={opts.seatingIframeUrl || DEFAULTS.seatingIframeUrl}
111
+ lang={(opts.lang || DEFAULTS.lang) as Languages}
112
+ selectedReleaseId={opts.selectedReleaseId}
113
+ content={opts.content}
114
+ callbacks={opts.callbacks || { homepage: noop, tickets: noop, detail: noop }}
115
+ links={
116
+ opts.links || {
117
+ termsAndConditions: DEFAULTS.termsAndConditions,
118
+ gdpr: DEFAULTS.gdpr,
119
+ }
120
+ }
121
+ options={{
122
+ packetaApiKey: opts.packetaApiKey,
123
+ stickyHeaderTop: opts.stickyHeaderTop ?? 0,
124
+ termsAndConditionsCompanies: opts.termsAndConditionsCompanies,
125
+ autoscrollAfterViewChange: opts.autoscrollAfterViewChange,
126
+ paydroidApiKey: opts.paydroidApiKey,
127
+ // In-page render (not an iframe) — the seat picker overlays the real
128
+ // viewport, so mobile seating works.
129
+ isIframe: false,
130
+ }}
131
+ slots={{
132
+ showSnackbar: (text, o) => setSnack({ text, variant: o?.variant || 'success' }),
133
+ }}
134
+ />
135
+ <Snackbar
136
+ open={!!snack}
137
+ autoHideDuration={5000}
138
+ onClose={() => setSnack(null)}
139
+ anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
140
+ >
141
+ {snack ? (
142
+ <Alert severity={snack.variant} variant="filled" onClose={() => setSnack(null)}>
143
+ {snack.text}
144
+ </Alert>
145
+ ) : (
146
+ <span />
147
+ )}
148
+ </Snackbar>
149
+ </ScopedCssBaseline>
150
+ </ThemeProvider>
151
+ );
152
+ };
153
+
154
+ const roots = new WeakMap<Element, Root>();
155
+
156
+ function readAttrTheme(el: Element): ThemeOpts | undefined {
157
+ const mode = el.getAttribute('data-eventlook-mode');
158
+ const primary = el.getAttribute('data-eventlook-primary');
159
+ const secondary = el.getAttribute('data-eventlook-secondary');
160
+ const font = el.getAttribute('data-eventlook-font');
161
+ const background = el.getAttribute('data-eventlook-background');
162
+ const paper = el.getAttribute('data-eventlook-paper');
163
+ const radius = el.getAttribute('data-eventlook-radius');
164
+ if (!mode && !primary && !secondary && !font && !background && !paper && !radius) {
165
+ return undefined;
166
+ }
167
+ return {
168
+ mode: mode === 'dark' ? 'dark' : mode === 'light' ? 'light' : undefined,
169
+ primaryColor: primary || undefined,
170
+ secondaryColor: secondary || undefined,
171
+ fontFamily: font || undefined,
172
+ background: background || undefined,
173
+ paper: paper || undefined,
174
+ radius: radius != null ? Number(radius) : undefined,
175
+ };
176
+ }
177
+
178
+ export function mount(
179
+ target: string | Element,
180
+ userOpts: Partial<MountOptions> = {}
181
+ ): Root | undefined {
182
+ const el = typeof target === 'string' ? document.querySelector(target) : target;
183
+ if (!el) {
184
+ console.error('[eventlook] mount target not found:', target);
185
+ return;
186
+ }
187
+ const event = userOpts.event || el.getAttribute('data-eventlook-event') || '';
188
+ if (!event) {
189
+ console.error('[eventlook] missing event slug');
190
+ return;
191
+ }
192
+ if (roots.has(el)) return roots.get(el);
193
+
194
+ const opts: MountOptions = {
195
+ event,
196
+ apiUrl: userOpts.apiUrl || el.getAttribute('data-eventlook-api') || DEFAULTS.apiUrl,
197
+ seatingIframeUrl:
198
+ userOpts.seatingIframeUrl ||
199
+ el.getAttribute('data-eventlook-seating') ||
200
+ DEFAULTS.seatingIframeUrl,
201
+ lang: userOpts.lang || el.getAttribute('data-eventlook-lang') || DEFAULTS.lang,
202
+ packetaApiKey: userOpts.packetaApiKey || el.getAttribute('data-eventlook-packeta') || undefined,
203
+ stickyHeaderTop: userOpts.stickyHeaderTop,
204
+ theme: userOpts.theme || readAttrTheme(el),
205
+ callbacks: userOpts.callbacks,
206
+ links: userOpts.links,
207
+ selectedReleaseId: userOpts.selectedReleaseId,
208
+ content: userOpts.content,
209
+ termsAndConditionsCompanies: userOpts.termsAndConditionsCompanies,
210
+ autoscrollAfterViewChange: userOpts.autoscrollAfterViewChange,
211
+ paydroidApiKey: userOpts.paydroidApiKey,
212
+ };
213
+
214
+ const root = createRoot(el);
215
+ roots.set(el, root);
216
+ root.render(<EmbedApp opts={opts} />);
217
+ return root;
218
+ }
219
+
220
+ export function unmount(target: string | Element): void {
221
+ const el = typeof target === 'string' ? document.querySelector(target) : target;
222
+ if (!el) return;
223
+ const root = roots.get(el);
224
+ if (root) {
225
+ root.unmount();
226
+ roots.delete(el);
227
+ }
228
+ }
229
+
230
+ function autoInit() {
231
+ document.querySelectorAll('[data-eventlook-event]').forEach((el) => mount(el));
232
+ }
233
+
234
+ // Expose the global API used by the hosted snippet.
235
+ type EventlookGlobal = { mount: typeof mount; unmount: typeof unmount };
236
+ const w = window as unknown as { Eventlook?: Partial<EventlookGlobal> };
237
+ w.Eventlook = Object.assign(w.Eventlook || {}, { mount, unmount });
238
+
239
+ if (document.readyState === 'loading') {
240
+ document.addEventListener('DOMContentLoaded', autoInit);
241
+ } else {
242
+ autoInit();
243
+ }
@@ -28,7 +28,7 @@ import { ITicketForm, ITicketFormTicket } from '@utils/types/ticket.type';
28
28
  import useGlobal from '@hooks/useGlobal';
29
29
  import { IPromoCode } from '@utils/types/promo-code.type';
30
30
  import { PromoCodeTypes } from '@utils/data/promo-code';
31
- import { throttle } from 'lodash';
31
+ import throttle from 'lodash/throttle';
32
32
  import { Iconify } from '@components';
33
33
  import { IEventProductForm } from '@utils/types/product.type';
34
34
 
@@ -278,6 +278,11 @@ const Payment: React.FC<Props> = ({ event, stripeReady }) => {
278
278
  <PaymentItem
279
279
  key={payment.type}
280
280
  active={!!paymentMethodId && Number(paymentMethodId) === payment.id}
281
+ className={`eventlook-payment-option${
282
+ !!paymentMethodId && Number(paymentMethodId) === payment.id
283
+ ? ' eventlook-payment-option--active'
284
+ : ''
285
+ }`}
281
286
  >
282
287
  <FormControlLabel
283
288
  value={payment.id}