@circlesquare/embedding-next 1.0.1 → 2.1.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,88 @@
1
+ import { CsSport } from './types.js';
2
+ export const DEFAULT_CS_BASE_URL = 'https://circlesquare.app';
3
+ const WIDGET_PATHS = {
4
+ courts: '/embedded/court-availability',
5
+ events: '/embedded/events',
6
+ };
7
+ const SUPPORTED_LANGUAGES = ['en', 'de'];
8
+ /* The page matches a sport's key without spaces, `_` and `-` and in any
9
+ case (the clubs domain's sport.model.ts), and drops one it does not know. */
10
+ const normaliseSportKey = (value) => value.replace(/[\s_-]/g, '').toLowerCase();
11
+ const KNOWN_SPORTS = Object.values(CsSport).map(normaliseSportKey);
12
+ /** Each theme option by the parameter the framed page reads. */
13
+ const THEME_PARAMETERS = {
14
+ appearance: 'appearance',
15
+ surfaceColor: 'surface',
16
+ textColor: 'text',
17
+ accentColor: 'accent',
18
+ accentTextColor: 'accentText',
19
+ eventColor: 'event',
20
+ borderRadius: 'radius',
21
+ fontFamily: 'font',
22
+ };
23
+ const COLOUR_OPTIONS = ['surfaceColor', 'textColor', 'accentColor', 'accentTextColor', 'eventColor'];
24
+ const getBaseUrl = (config) => config.csBaseUrl || DEFAULT_CS_BASE_URL;
25
+ /* A plain JavaScript caller can pass any string as the language. */
26
+ const isSupportedLanguage = (language) => SUPPORTED_LANGUAGES.includes(language.toLowerCase());
27
+ /* A configured language goes in front of the path, which is how the pages
28
+ are served per language; without one, or with one the pages do not
29
+ have, the page negotiates the language from the visitor. */
30
+ const getLocalizedPath = (path, { language }) => language && isSupportedLanguage(language) ? `/${language.toLowerCase()}${path}` : path;
31
+ const joinList = (values) => (values ?? []).map(String).join(',');
32
+ /**
33
+ * The frame's address. `csEmbedded` is sent as the loader sends it; the framed page knows it is
34
+ * framed from its path.
35
+ */
36
+ export function getFrameUrl({ widget, containerId, config, }) {
37
+ const url = new URL(getLocalizedPath(WIDGET_PATHS[widget], config), getBaseUrl(config));
38
+ const append = (name, value) => {
39
+ if (value)
40
+ url.searchParams.append(name, value);
41
+ };
42
+ append('containerId', containerId);
43
+ append('csEmbedded', 'true');
44
+ append('hostId', config.hostId);
45
+ append('clubId', config.merchantId);
46
+ append('eventTypes', joinList(config.eventTypes));
47
+ append('sports', joinList(config.sports));
48
+ append('sportIds', joinList(config.sportIds));
49
+ append('background', config.backgroundColor);
50
+ for (const [option, parameter] of Object.entries(THEME_PARAMETERS)) {
51
+ const value = config[option];
52
+ if (value !== undefined && value !== null)
53
+ append(parameter, String(value));
54
+ }
55
+ return url;
56
+ }
57
+ /**
58
+ * Where the fallback notice sends a visitor: the club's page on the tab the widget stands for, or
59
+ * the events on CircleSquare for an organizer's list.
60
+ */
61
+ export function getFallbackUrl({ widget, config }) {
62
+ const path = config.merchantId ? `/clubs/${encodeURIComponent(config.merchantId)}` : '/events';
63
+ const url = new URL(getLocalizedPath(path, config), getBaseUrl(config));
64
+ if (config.merchantId)
65
+ url.searchParams.set('tab', widget === 'courts' ? 'booking' : 'events');
66
+ return url;
67
+ }
68
+ /**
69
+ * What the host asked for that the frame will not show, as one console warning, or null. The page
70
+ * leaves out a colour it cannot read and keeps the kit's own; the host's browser says so here,
71
+ * before the host wonders why the club's colour does not show. `isColour` is the browser's check.
72
+ */
73
+ export function getConfigWarning(config, { isColour }) {
74
+ const warnings = COLOUR_OPTIONS.flatMap((option) => {
75
+ const value = config[option];
76
+ return value && !isColour(value) ? [`${option} "${value}" is not a colour, leaving the widget's own`] : [];
77
+ });
78
+ /* A key the types allow is always known; this is for a plain JavaScript caller or a cast. */
79
+ for (const sport of config.sports ?? []) {
80
+ if (!KNOWN_SPORTS.includes(normaliseSportKey(String(sport)))) {
81
+ warnings.push(`sport "${sport}" is not one of ${Object.values(CsSport).join(', ')}, leaving it out`);
82
+ }
83
+ }
84
+ if (config.language && !isSupportedLanguage(config.language)) {
85
+ warnings.push(`unsupported language "${config.language}", falling back to automatic selection`);
86
+ }
87
+ return warnings.length ? warnings.map((warning) => `[CircleSquare] ${warning}`).join('\n') : null;
88
+ }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
- export * from './types';
2
- export * from './CsEmbeddingConfigProvider';
3
- export * from './CsEvents';
4
- export * from './CsCourtAvailability';
1
+ export * from './types.js';
2
+ export { CircleSquareEmbeddingConfigProvider, useCircleSquareEmbeddingConfig, type CircleSquareEmbeddingConfigProviderProps, } from './embedding-config.js';
3
+ export { CSEvents, type CSEventsProps } from './cs-events.js';
4
+ export { CSCourtAvailability, type CSCourtAvailabilityProps } from './cs-court-availability.js';
5
+ export { DEFAULT_TIMEOUT_MS } from './embedded-frame.js';
6
+ export { DEFAULT_CS_BASE_URL } from './frame-url.js';
package/dist/index.js CHANGED
@@ -1,4 +1,6 @@
1
- export * from './types';
2
- export * from './CsEmbeddingConfigProvider';
3
- export * from './CsEvents';
4
- export * from './CsCourtAvailability';
1
+ export * from './types.js';
2
+ export { CircleSquareEmbeddingConfigProvider, useCircleSquareEmbeddingConfig, } from './embedding-config.js';
3
+ export { CSEvents } from './cs-events.js';
4
+ export { CSCourtAvailability } from './cs-court-availability.js';
5
+ export { DEFAULT_TIMEOUT_MS } from './embedded-frame.js';
6
+ export { DEFAULT_CS_BASE_URL } from './frame-url.js';
package/dist/types.d.ts CHANGED
@@ -1,24 +1,71 @@
1
+ /** The kinds of event the list can be limited to, as the API spells them. */
1
2
  export declare enum CsEventType {
2
- KnockOutTournament = "KNOCKOUT_TOURNAMENT",
3
+ OpenPlay = "OPEN_PLAY",
4
+ Training = "TRAINING",
5
+ League = "LEAGUE",
6
+ Tournament = "TOURNAMENT",
3
7
  RoundRobinTournament = "ROUND_ROBIN_TOURNAMENT",
4
8
  AmericanoTournament = "AMERICANO_TOURNAMENT",
5
9
  Americano = "AMERICANO",
6
- OpenPlay = "OPEN_PLAY",
7
- Training = "TRAINING",
8
- Other = "OTHER",
9
- League = "LEAGUE"
10
+ LevelingSession = "LEVELING_SESSION",
11
+ Other = "OTHER"
12
+ }
13
+ /** The sports a widget can be limited to, by the keys the framed page reads. */
14
+ export declare enum CsSport {
15
+ Tennis = "TENNIS",
16
+ Padel = "PADEL",
17
+ Pickleball = "PICKLEBALL",
18
+ BeachVolleyball = "BEACH_VOLLEYBALL",
19
+ Football = "FOOTBALL",
20
+ Badminton = "BADMINTON",
21
+ Squash = "SQUASH"
22
+ }
23
+ export type CsLanguage = 'en' | 'de';
24
+ export type CsAppearance = 'light' | 'dark';
25
+ /** The typefaces the framed page serves itself; a frame cannot use the fonts of the page around it. */
26
+ export type CsFontFamily = 'Barlow' | 'DM Sans' | 'Inter' | 'Lato' | 'Manrope' | 'Montserrat' | 'Nunito' | 'Open Sans' | 'Oswald' | 'Poppins' | 'Roboto';
27
+ /** The club's look. Every colour is any CSS colour: hex, a colour function or a name. */
28
+ export interface CsEmbeddingTheme {
29
+ /** The look the rest follows: `dark` for a dark site. Light by default. */
30
+ appearance?: CsAppearance;
31
+ /** The widget's own surface, such as the court grid's card and a popover. */
32
+ surfaceColor?: string;
33
+ /** The text; secondary text, lines and the unavailable cells are mixed from it. */
34
+ textColor?: string;
35
+ /** The club's colour: your booking, the chosen slot, the main button, focus. */
36
+ accentColor?: string;
37
+ /** The text on the accent; without it, black or white, whichever reads on the accent. */
38
+ accentTextColor?: string;
39
+ /** The events in the court grid. */
40
+ eventColor?: string;
41
+ /** A card's corner: a number of pixels or a CSS length in `px` or `rem`. */
42
+ borderRadius?: number | string;
43
+ fontFamily?: CsFontFamily;
10
44
  }
11
- export interface CsEmbeddingBaseConfig {
45
+ export interface CsEmbeddingBaseConfig extends CsEmbeddingTheme {
46
+ /** The club. */
12
47
  merchantId?: string;
48
+ /** The page behind the widget, any CSS colour; the widget's surface by default. */
13
49
  backgroundColor?: string;
14
- language?: 'en' | 'de';
50
+ /** Without one, the framed page picks the visitor's language. */
51
+ language?: CsLanguage;
52
+ /** Where the framed pages are served: `https://circlesquare.app` by default. */
15
53
  csBaseUrl?: string;
16
54
  }
17
55
  export interface CsEventsConfig extends CsEmbeddingBaseConfig {
56
+ /** The organizer whose events are listed; replaces `merchantId` for an organizer's own list. */
18
57
  hostId?: string;
19
58
  eventTypes?: CsEventType[];
59
+ /** The sports whose events are listed; every sport by default. */
60
+ sports?: CsSport[];
61
+ /** @deprecated Name the sports with `sports` instead. The page still reads the ids beside it. */
62
+ sportIds?: number[];
20
63
  }
21
64
  export interface CsCourtAvailabilityConfig extends CsEmbeddingBaseConfig {
65
+ /** The sports the court grid offers; every sport of the club by default. */
66
+ sports?: CsSport[];
67
+ /** @deprecated Name the sports with `sports` instead. The page still reads the ids beside it. */
68
+ sportIds?: number[];
22
69
  }
23
70
  export interface CircleSquareEmbeddingConfigContextType {
24
71
  config: CsEmbeddingBaseConfig;
package/dist/types.js CHANGED
@@ -1,11 +1,29 @@
1
+ /* WHAT A CLUB'S SITE PASSES TO THE FRAMES. The option names are the loader's
2
+ (apps/web/public/assets/scripts/embed.js), so a club that moves between
3
+ the loader and this package keeps its configuration. The framed page reads
4
+ them as apps/web/src/lib/embed.ts and lib/embed-theme.ts describe, and
5
+ leaves out a value it cannot read. */
6
+ /** The kinds of event the list can be limited to, as the API spells them. */
1
7
  export var CsEventType;
2
8
  (function (CsEventType) {
3
- CsEventType["KnockOutTournament"] = "KNOCKOUT_TOURNAMENT";
9
+ CsEventType["OpenPlay"] = "OPEN_PLAY";
10
+ CsEventType["Training"] = "TRAINING";
11
+ CsEventType["League"] = "LEAGUE";
12
+ CsEventType["Tournament"] = "TOURNAMENT";
4
13
  CsEventType["RoundRobinTournament"] = "ROUND_ROBIN_TOURNAMENT";
5
14
  CsEventType["AmericanoTournament"] = "AMERICANO_TOURNAMENT";
6
15
  CsEventType["Americano"] = "AMERICANO";
7
- CsEventType["OpenPlay"] = "OPEN_PLAY";
8
- CsEventType["Training"] = "TRAINING";
16
+ CsEventType["LevelingSession"] = "LEVELING_SESSION";
9
17
  CsEventType["Other"] = "OTHER";
10
- CsEventType["League"] = "LEAGUE";
11
18
  })(CsEventType || (CsEventType = {}));
19
+ /** The sports a widget can be limited to, by the keys the framed page reads. */
20
+ export var CsSport;
21
+ (function (CsSport) {
22
+ CsSport["Tennis"] = "TENNIS";
23
+ CsSport["Padel"] = "PADEL";
24
+ CsSport["Pickleball"] = "PICKLEBALL";
25
+ CsSport["BeachVolleyball"] = "BEACH_VOLLEYBALL";
26
+ CsSport["Football"] = "FOOTBALL";
27
+ CsSport["Badminton"] = "BADMINTON";
28
+ CsSport["Squash"] = "SQUASH";
29
+ })(CsSport || (CsSport = {}));
package/package.json CHANGED
@@ -1,28 +1,7 @@
1
1
  {
2
2
  "name": "@circlesquare/embedding-next",
3
- "private": false,
4
- "version": "1.0.1",
5
- "description": "Next.js components for embedding your CircleSquare events and court availability",
6
- "main": "dist/index.js",
7
- "types": "dist/index.d.ts",
8
- "scripts": {
9
- "build": "tsc",
10
- "prepare": "npm run build"
11
- },
12
- "peerDependencies": {
13
- "next": ">=13.0.0",
14
- "react": ">=18.0.0",
15
- "react-dom": ">=18.0.0"
16
- },
17
- "devDependencies": {
18
- "@types/node": "^20.0.0",
19
- "@types/react": "^18.0.0",
20
- "@types/react-dom": "^18.0.0",
21
- "typescript": "^5.0.0"
22
- },
23
- "files": [
24
- "dist"
25
- ],
3
+ "version": "2.1.0",
4
+ "description": "React components for Next.js that embed a club's CircleSquare events and court availability, in the club's look",
26
5
  "keywords": [
27
6
  "circlesquare",
28
7
  "embedding",
@@ -30,5 +9,41 @@
30
9
  "react"
31
10
  ],
32
11
  "author": "CircleSquare",
33
- "license": "MIT"
34
- }
12
+ "license": "MIT",
13
+ "type": "module",
14
+ "sideEffects": false,
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.js"
19
+ }
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "peerDependencies": {
28
+ "react": ">=18.0.0"
29
+ },
30
+ "devDependencies": {
31
+ "@circlesquare/config": "0.0.0",
32
+ "@testing-library/react": "^16.3.3",
33
+ "@types/react": "~19.2.4",
34
+ "eslint": "^10.10.0",
35
+ "jsdom": "^30.1.1",
36
+ "react": "19.2.3",
37
+ "react-dom": "19.2.3",
38
+ "typescript": "~6.0.3",
39
+ "vitest": "^5.0.0"
40
+ },
41
+ "scripts": {
42
+ "typecheck": "tsc -p tsconfig.json",
43
+ "lint": "eslint .",
44
+ "test": "vitest run",
45
+ "build": "rm -rf dist && tsc -p tsconfig.build.json"
46
+ },
47
+ "main": "./dist/index.js",
48
+ "types": "./dist/index.d.ts"
49
+ }
@@ -1,9 +0,0 @@
1
- import React from 'react';
2
- import { CsCourtAvailabilityConfig } from './types';
3
- interface CSCourtAvailabilityProps extends CsCourtAvailabilityConfig {
4
- className?: string;
5
- fallback?: React.ReactNode;
6
- timeoutMs?: number;
7
- }
8
- export declare const CSCourtAvailability: React.FC<CSCourtAvailabilityProps>;
9
- export {};
@@ -1,80 +0,0 @@
1
- 'use client';
2
- import React, { useEffect, useId, useState, useRef } from 'react';
3
- import { useCircleSquareEmbeddingConfig } from './CsEmbeddingConfigProvider';
4
- import { getBaseUrlWithLanguage } from './utils';
5
- export var CSCourtAvailability = function (_a) {
6
- var merchantId = _a.merchantId, backgroundColor = _a.backgroundColor, csBaseUrl = _a.csBaseUrl, language = _a.language, className = _a.className, fallback = _a.fallback, _b = _a.timeoutMs, timeoutMs = _b === void 0 ? 3000 : _b;
7
- var containerId = useId();
8
- var context = useCircleSquareEmbeddingConfig();
9
- var _c = useState(), iframeUrl = _c[0], setIframeUrl = _c[1];
10
- var _d = useState(), height = _d[0], setHeight = _d[1];
11
- var _e = useState('loading'), status = _e[0], setStatus = _e[1];
12
- var timerRef = useRef();
13
- useEffect(function () {
14
- var finalConfig = {
15
- merchantId: merchantId || (context === null || context === void 0 ? void 0 : context.merchantId),
16
- backgroundColor: backgroundColor || (context === null || context === void 0 ? void 0 : context.backgroundColor),
17
- csBaseUrl: csBaseUrl || (context === null || context === void 0 ? void 0 : context.csBaseUrl) || 'https://circlesquare.app',
18
- };
19
- if (!finalConfig.merchantId) {
20
- console.error('[CircleSquare] merchantId is required');
21
- setStatus('error');
22
- return;
23
- }
24
- var url = new URL("".concat(getBaseUrlWithLanguage(finalConfig.csBaseUrl, language || (context === null || context === void 0 ? void 0 : context.language)), "/embedded/court-availability"));
25
- url.searchParams.append('clubId', finalConfig.merchantId);
26
- url.searchParams.append('containerId', containerId);
27
- if (finalConfig.backgroundColor) {
28
- url.searchParams.append('background', finalConfig.backgroundColor);
29
- }
30
- setIframeUrl(url.toString());
31
- // start our timeout
32
- timerRef.current = window.setTimeout(function () {
33
- if (status === 'loading') {
34
- console.warn('[CircleSquare] iframe didn’t respond in time');
35
- setStatus('error');
36
- }
37
- }, timeoutMs);
38
- // message listener
39
- var handleMessage = function (event) {
40
- if (event.origin !== finalConfig.csBaseUrl)
41
- return;
42
- if (event.data.type === 'setHeight' && event.data.containerId === containerId) {
43
- clearTimeout(timerRef.current);
44
- setHeight(event.data.height);
45
- setStatus('ready');
46
- }
47
- };
48
- window.addEventListener('message', handleMessage);
49
- return function () {
50
- clearTimeout(timerRef.current);
51
- window.removeEventListener('message', handleMessage);
52
- };
53
- }, [
54
- merchantId,
55
- backgroundColor,
56
- csBaseUrl,
57
- context === null || context === void 0 ? void 0 : context.merchantId,
58
- context === null || context === void 0 ? void 0 : context.backgroundColor,
59
- context === null || context === void 0 ? void 0 : context.csBaseUrl,
60
- containerId,
61
- timeoutMs,
62
- ]);
63
- if (status === 'error' || !iframeUrl) {
64
- return <div className={className}>{fallback ? fallback : <Fallback csBaseUrl={csBaseUrl || 'https://circlesquare.app'} merchantId={merchantId || (context === null || context === void 0 ? void 0 : context.merchantId)} backgroundColor={backgroundColor || (context === null || context === void 0 ? void 0 : context.backgroundColor)} language={language || (context === null || context === void 0 ? void 0 : context.language)}/>}</div>;
65
- }
66
- return (<iframe className={className} src={iframeUrl} style={{ width: '100%', border: 'none', height: height }} onError={function () { return setStatus('error'); }}/>);
67
- };
68
- function Fallback(_a) {
69
- var csBaseUrl = _a.csBaseUrl, merchantId = _a.merchantId, backgroundColor = _a.backgroundColor, language = _a.language;
70
- var title = language === 'de' ? 'Verfügbarkeit kann nicht angezeigt werden' : 'Unable to display court availability';
71
- var description = language === 'de' ? 'Bitte klicken unten, um einen Platz direkt über CircleSquare zu buchen.' : 'Please click the button below to book a court via CircleSquare directly.';
72
- var buttonText = language === 'de' ? 'Platz über CircleSquare buchen' : 'Book a court via CircleSquare';
73
- return <div style={{ width: '100%', minHeight: 200, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', backgroundColor: backgroundColor, padding: '2rem' }}>
74
- <p style={{ marginBottom: '0.5rem', fontSize: '1.2rem', fontWeight: 'bold', color: '#000' }}>{title}</p>
75
- <p style={{ marginBottom: '1rem', color: '#000' }}>{description}</p>
76
- <a href={"".concat(getBaseUrlWithLanguage(csBaseUrl, language), "/clubs/").concat(merchantId)} style={{ display: 'block', width: 'fit-content', padding: '10px 20px', backgroundColor: '#007A70', color: '#fff', borderRadius: '5px', textDecoration: 'none', fontWeight: 'semibold', cursor: 'pointer' }}>
77
- <span>{buttonText}</span>
78
- </a>
79
- </div>;
80
- }
@@ -1,12 +0,0 @@
1
- import React from 'react';
2
- import { CsEmbeddingBaseConfig } from './types';
3
- export declare const useCircleSquareEmbeddingConfig: () => CsEmbeddingBaseConfig | null;
4
- interface CircleSquareEmbeddingConfigProviderProps {
5
- children: React.ReactNode;
6
- merchantId: string;
7
- backgroundColor?: string;
8
- csBaseUrl?: string;
9
- language?: 'en' | 'de';
10
- }
11
- export declare const CircleSquareEmbeddingConfigProvider: React.FC<CircleSquareEmbeddingConfigProviderProps>;
12
- export {};
@@ -1,22 +0,0 @@
1
- 'use client';
2
- import React, { createContext, useContext } from 'react';
3
- var CircleSquareEmbeddingConfigContext = createContext(null);
4
- export var useCircleSquareEmbeddingConfig = function () {
5
- var context = useContext(CircleSquareEmbeddingConfigContext);
6
- if (!context) {
7
- return null;
8
- }
9
- return context.config;
10
- };
11
- export var CircleSquareEmbeddingConfigProvider = function (_a) {
12
- var children = _a.children, merchantId = _a.merchantId, backgroundColor = _a.backgroundColor, _b = _a.csBaseUrl, csBaseUrl = _b === void 0 ? 'https://circlesquare.app' : _b, language = _a.language;
13
- var config = {
14
- merchantId: merchantId,
15
- backgroundColor: backgroundColor,
16
- csBaseUrl: csBaseUrl,
17
- language: language,
18
- };
19
- return (<CircleSquareEmbeddingConfigContext.Provider value={{ config: config }}>
20
- {children}
21
- </CircleSquareEmbeddingConfigContext.Provider>);
22
- };
@@ -1,11 +0,0 @@
1
- import React from 'react';
2
- import { CsEventsConfig } from './types';
3
- interface CSEventsProps extends CsEventsConfig {
4
- className?: string;
5
- /** Render this instead of the iframe if something goes wrong */
6
- fallback?: React.ReactNode;
7
- /** How long (ms) to wait for a height postMessage before erroring */
8
- timeoutMs?: number;
9
- }
10
- export declare const CSEvents: React.FC<CSEventsProps>;
11
- export {};
package/dist/CsEvents.jsx DELETED
@@ -1,94 +0,0 @@
1
- 'use client';
2
- import React, { useEffect, useId, useState, useRef } from 'react';
3
- import { useCircleSquareEmbeddingConfig } from './CsEmbeddingConfigProvider';
4
- import { getBaseUrlWithLanguage } from './utils';
5
- export var CSEvents = function (_a) {
6
- var merchantId = _a.merchantId, hostId = _a.hostId, backgroundColor = _a.backgroundColor, csBaseUrl = _a.csBaseUrl, eventTypes = _a.eventTypes, className = _a.className, language = _a.language, fallback = _a.fallback, _b = _a.timeoutMs, timeoutMs = _b === void 0 ? 3000 : _b;
7
- var containerId = useId();
8
- var context = useCircleSquareEmbeddingConfig();
9
- // Computed iframe URL (or undefined if missing merchantId)
10
- var _c = useState(), iframeUrl = _c[0], setIframeUrl = _c[1];
11
- // Measured height from postMessage
12
- var _d = useState(), height = _d[0], setHeight = _d[1];
13
- // 'loading' → 'ready' → 'error'
14
- var _e = useState('loading'), status = _e[0], setStatus = _e[1];
15
- var timerRef = useRef();
16
- useEffect(function () {
17
- var finalConfig = {
18
- merchantId: merchantId || (context === null || context === void 0 ? void 0 : context.merchantId),
19
- hostId: hostId,
20
- backgroundColor: backgroundColor || (context === null || context === void 0 ? void 0 : context.backgroundColor),
21
- csBaseUrl: csBaseUrl || (context === null || context === void 0 ? void 0 : context.csBaseUrl) || 'https://circlesquare.app',
22
- };
23
- if (!finalConfig.merchantId) {
24
- console.error('[CircleSquare] merchantId is required for events');
25
- setStatus('error');
26
- return;
27
- }
28
- // Build URL
29
- var url = new URL("".concat(getBaseUrlWithLanguage(finalConfig.csBaseUrl, language || (context === null || context === void 0 ? void 0 : context.language)), "/embedded/events"));
30
- url.searchParams.append('clubId', finalConfig.merchantId);
31
- url.searchParams.append('containerId', containerId);
32
- if (finalConfig.hostId) {
33
- url.searchParams.append('hostId', finalConfig.hostId);
34
- }
35
- if (eventTypes) {
36
- url.searchParams.append('eventTypes', eventTypes.join(','));
37
- }
38
- if (finalConfig.backgroundColor) {
39
- url.searchParams.append('background', finalConfig.backgroundColor);
40
- }
41
- setIframeUrl(url.toString());
42
- // Kick off a timeout to catch silent failures/ad-blockers
43
- timerRef.current = window.setTimeout(function () {
44
- if (status === 'loading') {
45
- console.warn('[CircleSquare] events iframe did not respond in time');
46
- setStatus('error');
47
- }
48
- }, timeoutMs);
49
- // Listen for height postMessage
50
- var handleMessage = function (event) {
51
- if (event.origin !== finalConfig.csBaseUrl)
52
- return;
53
- if (event.data.type === 'setHeight' && event.data.containerId === containerId) {
54
- clearTimeout(timerRef.current);
55
- setHeight(event.data.height);
56
- setStatus('ready');
57
- }
58
- };
59
- window.addEventListener('message', handleMessage);
60
- return function () {
61
- clearTimeout(timerRef.current);
62
- window.removeEventListener('message', handleMessage);
63
- };
64
- }, [
65
- merchantId,
66
- hostId,
67
- backgroundColor,
68
- csBaseUrl,
69
- eventTypes,
70
- context === null || context === void 0 ? void 0 : context.merchantId,
71
- context === null || context === void 0 ? void 0 : context.backgroundColor,
72
- context === null || context === void 0 ? void 0 : context.csBaseUrl,
73
- containerId,
74
- timeoutMs,
75
- status,
76
- ]);
77
- if (status === 'error' || !iframeUrl) {
78
- return <div className={className}>{fallback ? fallback : <Fallback csBaseUrl={csBaseUrl || 'https://circlesquare.app'} merchantId={merchantId || (context === null || context === void 0 ? void 0 : context.merchantId)} backgroundColor={backgroundColor || (context === null || context === void 0 ? void 0 : context.backgroundColor)} language={language || (context === null || context === void 0 ? void 0 : context.language)}/>}</div>;
79
- }
80
- return (<iframe className={className} src={iframeUrl} style={{ width: '100%', border: 'none', height: height }} onError={function () { return setStatus('error'); }}/>);
81
- };
82
- function Fallback(_a) {
83
- var csBaseUrl = _a.csBaseUrl, merchantId = _a.merchantId, backgroundColor = _a.backgroundColor, language = _a.language;
84
- var title = language === 'de' ? 'Events können nicht angezeigt werden' : 'Unable to display events';
85
- var description = language === 'de' ? 'Bitte klicken unten, um alle unsere Events bei CircleSquare anzuzeigen.' : 'Please click the button below to view all our events on CircleSquare.';
86
- var buttonText = language === 'de' ? 'Events bei CircleSquare anzeigen' : 'View events on CircleSquare';
87
- return <div style={{ width: '100%', minHeight: 200, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', backgroundColor: backgroundColor, padding: '2rem' }}>
88
- <p style={{ marginBottom: '0.5rem', fontSize: '1.2rem', fontWeight: 'bold', color: '#000' }}>{title}</p>
89
- <p style={{ marginBottom: '1rem', color: '#000' }}>{description}</p>
90
- <a href={"".concat(getBaseUrlWithLanguage(csBaseUrl, language), "/clubs/").concat(merchantId)} style={{ display: 'block', width: 'fit-content', padding: '10px 20px', backgroundColor: '#007A70', color: '#fff', borderRadius: '5px', textDecoration: 'none', fontWeight: 'semibold', cursor: 'pointer' }}>
91
- <span>{buttonText}</span>
92
- </a>
93
- </div>;
94
- }