@opexa/portal-components 0.0.1113 → 0.0.1115

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.
@@ -4,8 +4,4 @@ export interface DigitainContainerProps {
4
4
  params: DigitainBootstrapperParams;
5
5
  fallbackBackgroundImage?: ImageProps['src'] | null;
6
6
  }
7
- /**
8
- * Assumes `bootstrapper.min.js` is already included by `DigitainLauncher` (any auth state).
9
- * Watches for `window.Bootstrapper`, then boots. MutationObserver + rAF backstops onLoad timing.
10
- */
11
7
  export declare function DigitainContainer(props: DigitainContainerProps): import("react/jsx-runtime").JSX.Element;
@@ -1,108 +1,61 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import isMobile from 'is-mobile';
4
4
  import { useRouter } from 'next/navigation';
5
- import { useCallback, useEffect, useRef, useState } from 'react';
5
+ import { useEffect, useState } from 'react';
6
+ import { twJoin } from 'tailwind-merge';
6
7
  import { useSessionQuery } from '../../client/hooks/useSessionQuery.js';
7
8
  import { toaster } from '../../client/utils/toaster.js';
8
9
  import { DigitainLoadingView } from './DigitainLoadingView.js';
9
- const SCRIPT_FAIL_TIMEOUT_MS = 20_000;
10
- /**
11
- * Assumes `bootstrapper.min.js` is already included by `DigitainLauncher` (any auth state).
12
- * Watches for `window.Bootstrapper`, then boots. MutationObserver + rAF backstops onLoad timing.
13
- */
14
10
  export function DigitainContainer(props) {
15
11
  const session = useSessionQuery();
16
- const router = useRouter();
17
- const paramsRef = useRef(props.params);
18
- paramsRef.current = props.params;
19
- const bootedRef = useRef(false);
20
- const waitingForScriptRef = useRef(true);
21
- const [isBooting, setIsBooting] = useState(true);
12
+ const [isLoading, setLoading] = useState(false);
22
13
  const [progress, setProgress] = useState(0);
23
14
  const [stepIndex, setStepIndex] = useState(0);
24
- const failLoad = useCallback((description) => {
25
- if (bootedRef.current)
26
- return;
27
- bootedRef.current = true;
28
- waitingForScriptRef.current = false;
29
- setIsBooting(false);
30
- toaster.error({ title: 'Error', description });
31
- }, []);
32
- const runBoot = useCallback(() => {
33
- if (bootedRef.current)
34
- return true;
35
- const B = window.Bootstrapper;
36
- if (!B)
37
- return false;
38
- bootedRef.current = true;
39
- waitingForScriptRef.current = false;
40
- setStepIndex(1);
41
- setProgress(88);
42
- void B.boot(paramsRef.current, {
43
- name: isMobile() ? 'Mobile' : 'AsianView',
44
- })
45
- .catch(() => {
46
- /* partner lib may reject; still transition UI */
47
- })
48
- .finally(() => {
49
- setStepIndex(2);
50
- setProgress(100);
51
- });
52
- window.setTimeout(() => setIsBooting(false), 1000);
53
- return true;
54
- }, []);
15
+ const router = useRouter();
55
16
  useEffect(() => {
56
17
  if (session.data?.status === 'authenticated') {
57
18
  router.refresh();
58
19
  }
59
20
  }, [session.data?.status, router]);
60
- // Smooth 0% → 45% only while the script is still missing (max ~8s of ramp for UX)
61
- useEffect(() => {
62
- const t0 = performance.now();
63
- let raf;
64
- const tick = () => {
65
- if (!waitingForScriptRef.current)
66
- return;
67
- const elapsed = performance.now() - t0;
68
- const cap = 45;
69
- setProgress((prev) => Math.max(prev, Math.min(cap, (elapsed / 8000) * cap)));
70
- raf = requestAnimationFrame(tick);
71
- };
72
- raf = requestAnimationFrame(tick);
73
- return () => cancelAnimationFrame(raf);
74
- }, []);
75
- // Throttled MutationObserver: re-check for `window.Bootstrapper` on DOM changes (e.g. script tag injection order).
76
21
  useEffect(() => {
77
- let raf = null;
78
- const tryBoot = () => {
79
- if (raf != null)
80
- return;
81
- raf = requestAnimationFrame(() => {
82
- raf = null;
83
- if (runBoot()) {
84
- observer.disconnect();
85
- }
86
- });
87
- };
88
- // Synchronous first try (e.g. script already cached in another tab)
89
- tryBoot();
90
- const observer = new MutationObserver(tryBoot);
91
- observer.observe(document.documentElement, { childList: true, subtree: true });
92
- return () => {
93
- observer.disconnect();
94
- if (raf != null)
95
- cancelAnimationFrame(raf);
96
- };
97
- }, [runBoot]);
98
- // Fails if the script never becomes bootable
99
- useEffect(() => {
100
- const t = window.setTimeout(() => {
101
- if (bootedRef.current)
102
- return;
103
- failLoad('Sportsbook failed to load in time. Please refresh or try again later.');
104
- }, SCRIPT_FAIL_TIMEOUT_MS);
105
- return () => clearTimeout(t);
106
- }, [failLoad]);
107
- return (_jsxs("div", { className: "w-full min-h-[calc(100dvh-4rem)]", children: [isBooting ? (_jsx(DigitainLoadingView, { fallbackBackgroundImage: props.fallbackBackgroundImage, loadingProgress: progress, loadingActiveStepIndex: stepIndex })) : null, _jsx("div", { id: "digitain-container", className: isBooting ? 'hidden' : 'w-full min-h-[calc(100dvh-4rem)]' })] }));
22
+ setLoading(true);
23
+ setProgress(10);
24
+ setStepIndex(0);
25
+ let attempts = 0;
26
+ const maxAttempts = 5;
27
+ function checkAndBoot() {
28
+ if (typeof window !== 'undefined' && window.Bootstrapper) {
29
+ console.log('Bootstrapper found, booting with params:', props.params);
30
+ setStepIndex(1);
31
+ setProgress(88);
32
+ window.Bootstrapper.boot(props.params, {
33
+ name: isMobile() ? 'Mobile' : 'AsianView',
34
+ }).then(() => {
35
+ console.log('Sportsbook booted!');
36
+ });
37
+ setTimeout(() => {
38
+ setStepIndex(2);
39
+ setProgress(100);
40
+ setLoading(false);
41
+ }, 1000);
42
+ clearInterval(intervalId);
43
+ }
44
+ else if (++attempts >= maxAttempts) {
45
+ setLoading(false);
46
+ toaster.error({
47
+ title: 'Error',
48
+ description: 'Sportsbook failed to load. Please try again later.',
49
+ });
50
+ console.warn('Bootstrapper did not load in time.');
51
+ clearInterval(intervalId);
52
+ }
53
+ else {
54
+ setProgress(Math.min(45, 10 + attempts * 8));
55
+ }
56
+ }
57
+ const intervalId = setInterval(checkAndBoot, 500);
58
+ return () => clearInterval(intervalId);
59
+ }, [props.params]);
60
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: twJoin(!isLoading && 'hidden'), children: _jsx(DigitainLoadingView, { fallbackBackgroundImage: props.fallbackBackgroundImage, loadingProgress: progress, loadingActiveStepIndex: stepIndex }) }), _jsx("div", { id: "digitain-container", className: twJoin(isLoading && 'hidden') })] }));
108
61
  }
@@ -2,8 +2,8 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
2
2
  import Script from 'next/script';
3
3
  import { getSession } from '../../server/services/getSession.js';
4
4
  import { Protected } from '../Protected/index.js';
5
- import { DigitainAuthWall } from './DigitainAuthWall.js';
6
5
  import { DigitainContainer } from './DigitainContainer.js';
6
+ import { Fallback } from './Fallback.js';
7
7
  import { defaultDigitainParams, getDigitainLaunchToken } from './utils.js';
8
8
  export async function DigitainLauncher(props) {
9
9
  const session = await getSession();
@@ -15,5 +15,5 @@ export async function DigitainLauncher(props) {
15
15
  params.server = props.hostname;
16
16
  }
17
17
  const finalizedParams = { ...params };
18
- return (_jsxs(_Fragment, { children: [_jsx(Script, { id: "digitain-bootstrapper-script", src: `//${props.hostname}/js/partner/bootstrapper.min.js`, strategy: "afterInteractive" }), _jsx(Protected, { fallback: _jsx(DigitainAuthWall, { signInUrl: props.signInUrl, fallbackBackgroundImage: props.fallbackBackgroundImage }), children: _jsx(DigitainContainer, { params: finalizedParams, fallbackBackgroundImage: props.fallbackBackgroundImage }) })] }));
18
+ return (_jsxs(_Fragment, { children: [_jsx(Script, { src: `//${props.hostname}/js/partner/bootstrapper.min.js` }), _jsx(Protected, { fallback: _jsx(Fallback, { type: "fallback", signInUrl: props.signInUrl, fallbackBackgroundImage: props.fallbackBackgroundImage }), children: _jsx(DigitainContainer, { params: finalizedParams, fallbackBackgroundImage: props.fallbackBackgroundImage }) })] }));
19
19
  }
@@ -2,5 +2,4 @@ export interface DigitainLoadingPanelProps {
2
2
  progress: number;
3
3
  activeStepIndex: number;
4
4
  }
5
- /** Centered copy + progress only—no card shell; meant to sit on a dark overlay. */
6
5
  export declare function DigitainLoadingPanel({ progress, activeStepIndex, }: DigitainLoadingPanelProps): import("react/jsx-runtime").JSX.Element;
@@ -2,14 +2,17 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { twMerge } from 'tailwind-merge';
4
4
  import { Progress } from '../../ui/Progress/index.js';
5
- const STEPS = ['Initializing', 'Loading', 'Opening Sportsbook'];
6
- /** Centered copy + progress only—no card shell; meant to sit on a dark overlay. */
5
+ const STEPS = [
6
+ 'Connecting to server',
7
+ 'Setting up your session',
8
+ 'Launching Sportsbook',
9
+ ];
7
10
  export function DigitainLoadingPanel({ progress, activeStepIndex, }) {
8
11
  const clamped = Math.min(100, Math.max(0, progress));
9
12
  const current = Math.min(STEPS.length - 1, Math.max(0, activeStepIndex));
10
- return (_jsxs("div", { className: "w-full max-w-[22rem] mx-auto space-y-8 text-center", role: "status", "aria-live": "polite", "aria-busy": "true", "aria-label": "Loading sportsbook", children: [_jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "font-semibold text-lg text-white drop-shadow-sm sm:text-xl", children: "Sportsbook" }), _jsx("p", { className: "text-sm text-white/80", children: "Please wait while we load the sportsbook." })] }), _jsx("ol", { className: "list-none space-y-2 pl-0 text-left text-sm sm:text-base", children: STEPS.map((label, i) => {
13
+ return (_jsxs("div", { className: "mx-auto w-full max-w-[22rem] space-y-8 text-center", role: "status", "aria-live": "polite", "aria-busy": "true", "aria-label": "Loading sportsbook", children: [_jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "font-semibold text-lg text-white drop-shadow-sm sm:text-xl", children: "Sportsbook" }), _jsx("p", { className: "text-sm text-white/80", children: "Please wait while we load the sportsbook." })] }), _jsx("ol", { className: "list-none space-y-2 pl-0 text-left text-sm sm:text-base", children: STEPS.map((label, i) => {
11
14
  const isCurrent = i === current;
12
15
  const isDone = i < current;
13
- return (_jsxs("li", { className: twMerge(isCurrent && 'font-medium text-button-tertiary-fg', !isCurrent && isDone && 'text-white/90', !isCurrent && !isDone && 'text-white/45'), children: [_jsxs("span", { className: "tabular-nums", children: [i + 1, "."] }), " ", label, isCurrent && (_jsx("span", { className: "text-button-tertiary-fg", children: " \u2014 in progress" }))] }, label));
14
- }) }), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-3 text-white/70 text-xs sm:text-sm", children: [_jsx("span", { children: "Progress" }), _jsxs("span", { className: "font-medium tabular-nums text-white", children: [Math.round(clamped), "%"] })] }), _jsx(Progress.Root, { min: 0, max: 100, value: clamped, variant: "solid", className: "w-full", "aria-label": "Loading progress", children: _jsx(Progress.Track, { className: "h-2 !rounded-full bg-white/20", children: _jsx(Progress.Range, { className: "!bg-button-tertiary-fg" }) }) })] })] }));
16
+ return (_jsxs("li", { className: twMerge(isCurrent && 'font-medium text-button-tertiary-fg', !isCurrent && isDone && 'text-white/90', !isCurrent && !isDone && 'text-white/45'), children: [_jsxs("span", { className: "tabular-nums", children: [i + 1, "."] }), " ", label, isCurrent && (_jsx("span", { className: "text-button-tertiary-fg", children: " - in progress" }))] }, label));
17
+ }) }), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-3 text-white/70 text-xs sm:text-sm", children: [_jsx("span", { children: "Progress" }), _jsxs("span", { className: "font-medium text-white tabular-nums", children: [Math.round(clamped), "%"] })] }), _jsx(Progress.Root, { min: 0, max: 100, value: clamped, variant: "solid", className: "w-full", "aria-label": "Loading progress", children: _jsx(Progress.Track, { className: "!rounded-full h-2 bg-white/20", children: _jsx(Progress.Range, { className: "!bg-button-tertiary-fg" }) }) })] })] }));
15
18
  }
@@ -4,7 +4,4 @@ export interface DigitainLoadingViewProps {
4
4
  loadingProgress: number;
5
5
  loadingActiveStepIndex: number;
6
6
  }
7
- /**
8
- * Dimmed full-area overlay (below the app header) with optional hero image behind it.
9
- */
10
7
  export declare function DigitainLoadingView(props: DigitainLoadingViewProps): import("react/jsx-runtime").JSX.Element;
@@ -2,9 +2,6 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import Image from 'next/image';
4
4
  import { DigitainLoadingPanel } from './DigitainLoadingPanel.js';
5
- /**
6
- * Dimmed full-area overlay (below the app header) with optional hero image behind it.
7
- */
8
5
  export function DigitainLoadingView(props) {
9
- return (_jsxs("div", { className: "w-full min-h-[calc(100dvh-4rem)]", children: [props.fallbackBackgroundImage ? (_jsx("div", { className: "relative min-h-[calc(100dvh-4rem)] w-full overflow-hidden rounded-xl", children: _jsx(Image, { src: props.fallbackBackgroundImage, alt: "", fill: true, className: "object-cover", sizes: "100vw" }) })) : null, _jsx("div", { className: "fixed inset-x-0 top-16 bottom-0 z-[1000] flex min-h-0 items-center justify-center overflow-y-auto overscroll-contain bg-black/65 px-4 py-6 backdrop-blur-md sm:px-10 sm:py-10", children: _jsx("div", { className: "my-auto w-full max-w-[22rem] py-2 pb-[max(1.5rem,env(safe-area-inset-bottom))]", children: _jsx(DigitainLoadingPanel, { progress: props.loadingProgress, activeStepIndex: props.loadingActiveStepIndex }) }) })] }));
6
+ return (_jsxs("div", { className: "min-h-[calc(100dvh-4rem)] w-full", children: [props.fallbackBackgroundImage ? (_jsx("div", { className: "relative min-h-[calc(100dvh-4rem)] w-full overflow-hidden rounded-xl", children: _jsx(Image, { src: props.fallbackBackgroundImage, alt: "", fill: true, className: "object-cover", sizes: "100vw" }) })) : null, _jsx("div", { className: "fixed inset-x-0 top-16 bottom-0 z-[1000] flex min-h-0 items-center justify-center overflow-y-auto overscroll-contain bg-black/65 px-4 py-6 backdrop-blur-md sm:px-10 sm:py-10", children: _jsx("div", { className: "my-auto w-full max-w-[22rem] py-2 pb-[max(1.5rem,env(safe-area-inset-bottom))]", children: _jsx(DigitainLoadingPanel, { progress: props.loadingProgress, activeStepIndex: props.loadingActiveStepIndex }) }) })] }));
10
7
  }
@@ -8,12 +8,12 @@ export function Fallback({ type, signInUrl, fallbackBackgroundImage, }) {
8
8
  const globalStore = useGlobalStore(useShallow((ctx) => ({
9
9
  signIn: ctx.signIn,
10
10
  })));
11
- return (_jsxs("div", { className: "relative h-full w-full", children: [fallbackBackgroundImage && (_jsx(Image, { src: fallbackBackgroundImage, alt: "Background", fill: true, className: "rounded-xl object-cover" })), _jsx("div", { className: "absolute right-0 bottom-safe-area-inset-bottom left-0 h-1/2 rounded-xl bg-gradient-to-b from-[#00000000] to-bg-primary-alt" }), _jsx("div", { className: "absolute right-0 bottom-5 left-0 z-10 m-auto flex max-w-[42.5rem] flex-col items-center justify-center px-4 lg:bottom-15", children: type === 'loading' ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "mb-4 h-10 w-10 animate-spin rounded-full border-4 border-bg-tertiary border-t-button-tertiary-fg" }), _jsx("p", { className: "font-medium text-gray-700 text-lg", children: "Please wait while we load the Sports Book..." })] })) : (_jsx(_Fragment, { children: _jsxs("div", { className: "text-center", children: [_jsxs("div", { className: "mb-8 space-y-3", children: [_jsx("h2", { className: "font-bold text-gray-900 text-xl uppercase lg:text-[40px]", children: "Sports Book Login Required" }), _jsx("p", { className: "text-gray-600 text-xs leading-relaxed lg:text-lg", children: "The Sports Book is our online platform where you can explore real-time betting odds, place bets on a wide range of sports, and track your activity." }), _jsx("p", { className: "text-gray-600 text-xs leading-relaxed lg:text-lg", children: "Access is restricted to authenticated users. Please login to continue and unlock full access to the platform." })] }), _jsx(Button, { className: "mx-auto w-fit", onClick: () => {
12
- if (signInUrl) {
13
- window.location.href = signInUrl;
14
- }
15
- else {
16
- globalStore.signIn.setOpen(true);
17
- }
18
- }, children: "Login" })] }) })) })] }));
11
+ return (_jsxs("div", { className: "relative min-h-[calc(100dvh-4rem)] w-full", children: [fallbackBackgroundImage && (_jsx(Image, { src: fallbackBackgroundImage, alt: "Background", fill: true, className: "rounded-xl object-cover" })), _jsx("div", { className: "absolute right-0 bottom-safe-area-inset-bottom left-0 h-1/2 rounded-xl bg-gradient-to-b from-[#00000000] to-bg-primary-alt" }), _jsx("div", { className: "absolute right-0 bottom-5 left-0 z-10 m-auto flex max-w-[42.5rem] flex-col items-center justify-center px-4 lg:bottom-15", children: type === 'loading' ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "mb-4 h-10 w-10 animate-spin rounded-full border-4 border-bg-tertiary border-t-button-tertiary-fg" }), _jsx("p", { className: "font-medium text-gray-700 text-lg", children: "Please wait while we load the Sports Book..." })] })) : (_jsxs("div", { className: "text-center", children: [_jsxs("div", { className: "mb-8 space-y-3", children: [_jsx("h2", { className: "font-bold text-gray-900 text-xl uppercase lg:text-[40px]", children: "Sports Book Login Required" }), _jsx("p", { className: "text-gray-600 text-xs leading-relaxed lg:text-lg", children: "The Sports Book is our online platform where you can explore real-time betting odds, place bets on a wide range of sports, and track your activity." }), _jsx("p", { className: "text-gray-600 text-xs leading-relaxed lg:text-lg", children: "Access is restricted to authenticated users. Please login to continue and unlock full access to the platform." })] }), _jsx(Button, { className: "mx-auto w-fit", onClick: () => {
12
+ if (signInUrl) {
13
+ window.location.href = signInUrl;
14
+ }
15
+ else {
16
+ globalStore.signIn.setOpen(true);
17
+ }
18
+ }, children: "Login" })] })) })] }));
19
19
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opexa/portal-components",
3
- "version": "0.0.1113",
3
+ "version": "0.0.1115",
4
4
  "exports": {
5
5
  "./ui/*": {
6
6
  "types": "./dist/ui/*/index.d.ts",