@opexa/portal-components 0.0.78 → 0.0.79

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 (28) hide show
  1. package/dist/client/hooks/index.d.ts +1 -0
  2. package/dist/client/hooks/index.js +1 -0
  3. package/dist/client/hooks/useChunk.d.ts +8 -0
  4. package/dist/client/hooks/useChunk.js +26 -0
  5. package/dist/components/DepositWithdrawal/Deposit/OnlineBankDeposit/OnlineBankDepositContext.d.ts +2 -2
  6. package/dist/components/DepositWithdrawal/Deposit/OnlineBankDeposit/useOnlineBankDeposit.d.ts +1 -1
  7. package/dist/components/DepositWithdrawal/Deposit/QRPHDeposit/QRPHDepositContext.d.ts +2 -2
  8. package/dist/components/DepositWithdrawal/Deposit/QRPHDeposit/useQRPHDeposit.d.ts +1 -1
  9. package/dist/components/GameProviders/GameProviders.d.ts +3 -24
  10. package/dist/components/GameProviders/GameProviders.js +9 -41
  11. package/dist/components/GameProviders/GameProvidersCarousel.d.ts +29 -0
  12. package/dist/components/GameProviders/GameProvidersCarousel.js +47 -0
  13. package/dist/components/GameProviders/GameProvidersList.d.ts +25 -0
  14. package/dist/components/GameProviders/GameProvidersList.js +25 -0
  15. package/dist/components/Games/Game.d.ts +6 -1
  16. package/dist/components/Games/Game.js +7 -3
  17. package/dist/components/Games/Games.d.ts +1 -5
  18. package/dist/components/Games/GamesCarousel.client.js +10 -3
  19. package/dist/components/Games/GamesCarousel.d.ts +7 -2
  20. package/dist/components/Games/GamesContext.d.ts +4 -0
  21. package/dist/components/Games/GamesContext.js +3 -0
  22. package/dist/components/Games/GamesList.client.js +10 -4
  23. package/dist/components/Games/GamesList.d.ts +7 -2
  24. package/dist/components/Promos/Promos.d.ts +3 -14
  25. package/dist/components/Promos/PromosCarousel.d.ts +13 -2
  26. package/dist/components/Promos/PromosGrid.d.ts +11 -2
  27. package/dist/constants/gameProviders.js +1 -1
  28. package/package.json +1 -1
@@ -10,6 +10,7 @@ export * from './useCashbackBonusQuery';
10
10
  export * from './useCashbackQuery';
11
11
  export * from './useCashbacksQuery';
12
12
  export * from './useCheckInDailyCheckInQuestMutation';
13
+ export * from './useChunk';
13
14
  export * from './useClaimCashbackBonusMutation';
14
15
  export * from './useClaimSpotBonusMutation';
15
16
  export * from './useControllableState';
@@ -10,6 +10,7 @@ export * from './useCashbackBonusQuery.js';
10
10
  export * from './useCashbackQuery.js';
11
11
  export * from './useCashbacksQuery.js';
12
12
  export * from './useCheckInDailyCheckInQuestMutation.js';
13
+ export * from './useChunk.js';
13
14
  export * from './useClaimCashbackBonusMutation.js';
14
15
  export * from './useClaimSpotBonusMutation.js';
15
16
  export * from './useControllableState.js';
@@ -0,0 +1,8 @@
1
+ export declare function useChunk<T>(items: T[], size: number): {
2
+ totalItems: number;
3
+ currentItems: T[];
4
+ loadedItems: T[];
5
+ totalLoadedItems: number;
6
+ hasNext: boolean;
7
+ next: () => void;
8
+ };
@@ -0,0 +1,26 @@
1
+ import { chunk } from 'lodash-es';
2
+ import { useState } from 'react';
3
+ export function useChunk(items, size) {
4
+ const chunks = chunk(items, size);
5
+ const [currentIndex, setCurrentIndex] = useState(0);
6
+ const currentItems = chunks.at(currentIndex) ?? [];
7
+ const loadedItems = (() => {
8
+ return chunks.filter((_, index) => index <= currentIndex).flat();
9
+ })();
10
+ const next = () => {
11
+ if (currentIndex >= chunks.length - 1)
12
+ return;
13
+ setCurrentIndex((prevIndex) => prevIndex + 1);
14
+ };
15
+ const totalItems = items.length;
16
+ const totalLoadedItems = loadedItems.length;
17
+ const hasNext = currentIndex < chunks.length - 1;
18
+ return {
19
+ totalItems,
20
+ currentItems,
21
+ loadedItems,
22
+ totalLoadedItems,
23
+ hasNext,
24
+ next,
25
+ };
26
+ }
@@ -1,5 +1,5 @@
1
1
  export declare const OnlineBankDepositContext: import("react").Context<{
2
- view: "vca" | "form";
2
+ view: "form" | "vca";
3
3
  status: "waiting" | "processing" | "failed" | "verification-waiting" | "verification-processing" | "verification-failed" | "verification-success";
4
4
  verify: () => void;
5
5
  reset: () => void;
@@ -9,7 +9,7 @@ export declare const OnlineBankDepositContext: import("react").Context<{
9
9
  promo?: string | null;
10
10
  }) => void;
11
11
  }>, useOnlineBankDepositContext: () => {
12
- view: "vca" | "form";
12
+ view: "form" | "vca";
13
13
  status: "waiting" | "processing" | "failed" | "verification-waiting" | "verification-processing" | "verification-failed" | "verification-success";
14
14
  verify: () => void;
15
15
  reset: () => void;
@@ -1,7 +1,7 @@
1
1
  import type { Deposit } from '@opexa/portal-sdk-core';
2
2
  export type UseOnlineBankDepositReturn = ReturnType<typeof useOnlineBankDeposit>;
3
3
  export declare function useOnlineBankDeposit(): {
4
- view: "vca" | "form";
4
+ view: "form" | "vca";
5
5
  status: "waiting" | "processing" | "failed" | "verification-waiting" | "verification-processing" | "verification-failed" | "verification-success";
6
6
  verify: () => void;
7
7
  reset: () => void;
@@ -1,5 +1,5 @@
1
1
  export declare const QRPHDepositContext: import("react").Context<{
2
- view: "qrCode" | "form";
2
+ view: "form" | "qrCode";
3
3
  status: "waiting" | "processing" | "failed" | "verification-waiting" | "verification-processing" | "verification-failed" | "verification-success";
4
4
  verify: () => void;
5
5
  reset: () => void;
@@ -9,7 +9,7 @@ export declare const QRPHDepositContext: import("react").Context<{
9
9
  promo?: string | null;
10
10
  }) => void;
11
11
  }>, useQRPHDepositContext: () => {
12
- view: "qrCode" | "form";
12
+ view: "form" | "qrCode";
13
13
  status: "waiting" | "processing" | "failed" | "verification-waiting" | "verification-processing" | "verification-failed" | "verification-success";
14
14
  verify: () => void;
15
15
  reset: () => void;
@@ -1,7 +1,7 @@
1
1
  import type { Deposit } from '@opexa/portal-sdk-core';
2
2
  export type UseQRPHDepositReturn = ReturnType<typeof useQRPHDeposit>;
3
3
  export declare function useQRPHDeposit(): {
4
- view: "qrCode" | "form";
4
+ view: "form" | "qrCode";
5
5
  status: "waiting" | "processing" | "failed" | "verification-waiting" | "verification-processing" | "verification-failed" | "verification-success";
6
6
  verify: () => void;
7
7
  reset: () => void;
@@ -1,25 +1,4 @@
1
- import type { GameProvider } from '@opexa/portal-sdk-core';
2
- import type { GameProviderData } from '../../types';
3
- export interface GameProvidersProps {
4
- gameProviders: GameProvider[];
5
- heading?: string;
6
- /**
7
- * @default '/providers'
8
- */
9
- viewAllUrl?: string;
10
- /**
11
- * @default '/providers/<slug>'
12
- * @example
13
- * ```ts
14
- * '/bingo/:slug' // '/bingo/jili'
15
- * '/bingo/:id' // '/bingo/JILI'
16
- * // or using a function
17
- * ({slug}) => `/bingo/${slug}`
18
- * ```
19
- */
20
- viewGamesUrl?: string | ((gameProvider: GameProviderData) => string);
21
- className?: string;
22
- gameProviderContainerClass?: string;
23
- gameProviderImageClass?: string;
24
- }
1
+ import { type GameProvidersCarouselProps } from './GameProvidersCarousel';
2
+ import { type GameProvidersListProps } from './GameProvidersList';
3
+ export type GameProvidersProps = GameProvidersCarouselProps | GameProvidersListProps;
25
4
  export declare function GameProviders(props: GameProvidersProps): import("react/jsx-runtime").JSX.Element;
@@ -1,43 +1,11 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import useEmblaCarousel from 'embla-carousel-react';
4
- import Image from 'next/image';
5
- import Link from 'next/link';
6
- import { useCallback, useEffect, useState } from 'react';
7
- import { twMerge } from 'tailwind-merge';
8
- import { GAME_PROVIDER_DATA } from '../../constants/index.js';
9
- import { ArrowLeftIcon, ArrowRightIcon, ChevronRightIcon } from '../../icons/index.js';
10
- import { Button } from '../../ui/index.js';
11
- import { callIfFn } from '../../utils/index.js';
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { GameProvidersCarousel, } from './GameProvidersCarousel.js';
3
+ import { GameProvidersList, } from './GameProvidersList.js';
12
4
  export function GameProviders(props) {
13
- const [emblaRef, emblaApi] = useEmblaCarousel({
14
- align: 'start',
15
- slidesToScroll: 1,
16
- });
17
- const [prevBtnDisabled, setPrevBtnDisabled] = useState(true);
18
- const [nextBtnDisabled, setNextBtnDisabled] = useState(true);
19
- const onPrevButtonClick = useCallback(() => emblaApi?.scrollPrev(), [emblaApi]);
20
- const onNextButtonClick = useCallback(async () => {
21
- emblaApi?.scrollNext();
22
- }, [emblaApi]);
23
- const onSelect = useCallback((emblaApi) => {
24
- setPrevBtnDisabled(!emblaApi?.canScrollPrev());
25
- setNextBtnDisabled(!emblaApi?.canScrollNext());
26
- }, []);
27
- useEffect(() => {
28
- if (!emblaApi)
29
- return;
30
- onSelect(emblaApi);
31
- emblaApi.on('reInit', onSelect).on('select', onSelect);
32
- }, [emblaApi, onSelect]);
33
- const viewAllUrl = props.viewAllUrl ?? '/providers';
34
- const viewGamesUrl = (data) => {
35
- return props.viewGamesUrl
36
- ? callIfFn(props.viewGamesUrl, data)
37
- .replace(':id', data.id)
38
- .replace(':slug', data.slug)
39
- : `/providers/${data.slug}`;
40
- };
41
- const gameProviders = props.gameProviders.map((provider) => GAME_PROVIDER_DATA[provider]);
42
- return (_jsxs("div", { className: props.className, children: [_jsxs("div", { className: "flex items-center", children: [_jsx("h2", { className: "text-lg font-semibold", children: props.heading ?? 'Providers' }), _jsx("div", { className: "grow" }), _jsxs("div", { className: "flex items-center justify-center gap-xl", children: [_jsxs(Link, { href: viewAllUrl, className: "text-button-tertiary-fg flex gap-sm text-sm font-semibold", children: ["See All", _jsx(ChevronRightIcon, { className: "size-5 lg:hidden" })] }), _jsxs("div", { className: "hidden lg:flex", children: [_jsx(Button, { variant: "outline", colorScheme: "gray", className: "rounded-r-none border-r-0", onClick: onPrevButtonClick, disabled: prevBtnDisabled, "aria-label": "Previous", children: _jsx(ArrowLeftIcon, { className: "size-5" }) }), _jsx(Button, { variant: "outline", colorScheme: "gray", className: "rounded-l-none", onClick: onNextButtonClick, disabled: nextBtnDisabled, "aria-label": "Next", children: _jsx(ArrowRightIcon, { className: "size-5" }) })] })] })] }), _jsx("div", { ref: emblaRef, className: "relative mt-lg lg:overflow-hidden", children: _jsx("div", { className: "grid auto-cols-[calc((100%-(0.375rem*2))/3)] grid-flow-col grid-rows-1 gap-sm lg:auto-cols-[calc((100%-(0.5rem*5))/6)] lg:gap-md", children: gameProviders.map((provider) => (_jsx(Link, { href: viewGamesUrl(provider), className: twMerge('bg-gray-800 flex h-full w-full items-center overflow-hidden rounded-md lg:h-[5.75rem]', props.gameProviderContainerClass), "aria-label": `View ${provider.name} games`, children: _jsx(Image, { src: provider.logo, alt: "", width: 300, height: 150, className: twMerge('mx-auto h-auto w-full', props.gameProviderImageClass) }) }, provider.id))) }) })] }));
5
+ if (props.layout === 'list') {
6
+ return _jsx(GameProvidersList, { ...props });
7
+ }
8
+ else {
9
+ return _jsx(GameProvidersCarousel, { ...props });
10
+ }
43
11
  }
@@ -0,0 +1,29 @@
1
+ import type { GameProvider } from '@opexa/portal-sdk-core';
2
+ import type { GameProviderData } from '../../types';
3
+ export interface ClassNameEntries {
4
+ root?: string;
5
+ thumbnailRoot?: string;
6
+ thumbnailImage?: string;
7
+ }
8
+ export interface GameProvidersCarouselProps {
9
+ layout: 'carousel';
10
+ gameProviders: GameProvider[];
11
+ heading?: string;
12
+ /**
13
+ * @default '/providers'
14
+ */
15
+ viewAllUrl?: string;
16
+ /**
17
+ * @default '/providers/<slug>'
18
+ * @example
19
+ * ```ts
20
+ * '/bingo/:slug' // '/bingo/jili'
21
+ * '/bingo/:id' // '/bingo/JILI'
22
+ * // or using a function
23
+ * ({slug}) => `/bingo/${slug}`
24
+ * ```
25
+ */
26
+ viewGamesUrl?: string | ((gameProvider: GameProviderData) => string);
27
+ className?: string | ClassNameEntries;
28
+ }
29
+ export declare function GameProvidersCarousel(props: GameProvidersCarouselProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,47 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import useEmblaCarousel from 'embla-carousel-react';
4
+ import { isString } from 'lodash-es';
5
+ import Image from 'next/image';
6
+ import Link from 'next/link';
7
+ import { useCallback, useEffect, useState } from 'react';
8
+ import { twMerge } from 'tailwind-merge';
9
+ import { GAME_PROVIDER_DATA } from '../../constants/index.js';
10
+ import { ArrowLeftIcon, ArrowRightIcon, ChevronRightIcon } from '../../icons/index.js';
11
+ import { Button } from '../../ui/index.js';
12
+ import { callIfFn } from '../../utils/index.js';
13
+ export function GameProvidersCarousel(props) {
14
+ const [emblaRef, emblaApi] = useEmblaCarousel({
15
+ align: 'start',
16
+ slidesToScroll: 1,
17
+ });
18
+ const [prevBtnDisabled, setPrevBtnDisabled] = useState(true);
19
+ const [nextBtnDisabled, setNextBtnDisabled] = useState(true);
20
+ const onPrevButtonClick = useCallback(() => emblaApi?.scrollPrev(), [emblaApi]);
21
+ const onNextButtonClick = useCallback(async () => {
22
+ emblaApi?.scrollNext();
23
+ }, [emblaApi]);
24
+ const onSelect = useCallback((emblaApi) => {
25
+ setPrevBtnDisabled(!emblaApi?.canScrollPrev());
26
+ setNextBtnDisabled(!emblaApi?.canScrollNext());
27
+ }, []);
28
+ useEffect(() => {
29
+ if (!emblaApi)
30
+ return;
31
+ onSelect(emblaApi);
32
+ emblaApi.on('reInit', onSelect).on('select', onSelect);
33
+ }, [emblaApi, onSelect]);
34
+ const viewAllUrl = props.viewAllUrl ?? '/providers';
35
+ const viewGamesUrl = (data) => {
36
+ return props.viewGamesUrl
37
+ ? callIfFn(props.viewGamesUrl, data)
38
+ .replace(':id', data.id)
39
+ .replace(':slug', data.slug)
40
+ : `/providers/${data.slug}`;
41
+ };
42
+ const gameProviders = props.gameProviders.map((provider) => GAME_PROVIDER_DATA[provider]);
43
+ const classNames = isString(props.className)
44
+ ? { root: props.className }
45
+ : (props.className ?? {});
46
+ return (_jsxs("div", { className: classNames.root, children: [_jsxs("div", { className: "flex items-center", children: [_jsx("h2", { className: "text-lg font-semibold", children: props.heading ?? 'Providers' }), _jsx("div", { className: "grow" }), _jsxs("div", { className: "flex items-center justify-center gap-xl", children: [_jsxs(Link, { href: viewAllUrl, className: "text-button-tertiary-fg flex gap-sm text-sm font-semibold", children: ["See All", _jsx(ChevronRightIcon, { className: "size-5 lg:hidden" })] }), _jsxs("div", { className: "hidden lg:flex", children: [_jsx(Button, { variant: "outline", colorScheme: "gray", className: "rounded-r-none border-r-0", onClick: onPrevButtonClick, disabled: prevBtnDisabled, "aria-label": "Previous", children: _jsx(ArrowLeftIcon, { className: "size-5" }) }), _jsx(Button, { variant: "outline", colorScheme: "gray", className: "rounded-l-none", onClick: onNextButtonClick, disabled: nextBtnDisabled, "aria-label": "Next", children: _jsx(ArrowRightIcon, { className: "size-5" }) })] })] })] }), _jsx("div", { ref: emblaRef, className: "relative mt-lg lg:overflow-hidden", children: _jsx("div", { className: "grid auto-cols-[calc((100%-(0.375rem*2))/3)] grid-flow-col grid-rows-1 gap-sm lg:auto-cols-[calc((100%-(0.5rem*5))/6)] lg:gap-md", children: gameProviders.map((provider) => (_jsx(Link, { href: viewGamesUrl(provider), className: twMerge('bg-gray-800 flex h-full w-full items-center overflow-hidden rounded-md lg:h-[5.75rem]', classNames.thumbnailRoot), "aria-label": `View ${provider.name} games`, children: _jsx(Image, { src: provider.logo, alt: "", width: 300, height: 150, className: twMerge('mx-auto h-auto w-full', classNames.thumbnailImage) }) }, provider.id))) }) })] }));
47
+ }
@@ -0,0 +1,25 @@
1
+ import type { GameProvider } from '@opexa/portal-sdk-core';
2
+ import type { GameProviderData } from '../../types';
3
+ export interface ClassNameEntries {
4
+ root?: string;
5
+ thumbnailRoot?: string;
6
+ thumbnailImage?: string;
7
+ }
8
+ export interface GameProvidersListProps {
9
+ layout: 'list';
10
+ gameProviders: GameProvider[];
11
+ heading?: string;
12
+ /**
13
+ * @default '/providers/<slug>'
14
+ * @example
15
+ * ```ts
16
+ * '/bingo/:slug' // '/bingo/jili'
17
+ * '/bingo/:id' // '/bingo/JILI'
18
+ * // or using a function
19
+ * ({slug}) => `/bingo/${slug}`
20
+ * ```
21
+ */
22
+ viewGamesUrl?: string | ((gameProvider: GameProviderData) => string);
23
+ className?: string | ClassNameEntries;
24
+ }
25
+ export declare function GameProvidersList(props: GameProvidersListProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,25 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { isString, noop } from 'lodash-es';
4
+ import Image from 'next/image';
5
+ import Link from 'next/link';
6
+ import { twMerge } from 'tailwind-merge';
7
+ import { useChunk } from '../../client/hooks/index.js';
8
+ import { GAME_PROVIDER_DATA } from '../../constants/index.js';
9
+ import { Button, Progress } from '../../ui/index.js';
10
+ import { callIfFn } from '../../utils/index.js';
11
+ export function GameProvidersList(props) {
12
+ const viewGamesUrl = (data) => {
13
+ return props.viewGamesUrl
14
+ ? callIfFn(props.viewGamesUrl, data)
15
+ .replace(':id', data.id)
16
+ .replace(':slug', data.slug)
17
+ : `/providers/${data.slug}`;
18
+ };
19
+ const gameProviders = props.gameProviders.map((provider) => GAME_PROVIDER_DATA[provider]);
20
+ const classNames = isString(props.className)
21
+ ? { root: props.className }
22
+ : (props.className ?? {});
23
+ const chunked = useChunk(gameProviders, 18);
24
+ return (_jsxs("div", { className: classNames.root, children: [_jsx("h2", { className: "text-lg font-semibold", children: props.heading ?? 'Providers' }), _jsx("div", { className: "mt-lg grid grid-cols-3 gap-1.5 lg:grid-cols-6 lg:gap-2.5", children: chunked.loadedItems.map((provider) => (_jsx(Link, { href: viewGamesUrl(provider), className: twMerge('bg-gray-800 flex h-full w-full items-center overflow-hidden rounded-md lg:h-[5.75rem]', classNames.thumbnailRoot), "aria-label": `View ${provider.name} games`, children: _jsx(Image, { src: provider.logo, alt: "", width: 300, height: 150, className: twMerge('mx-auto h-auto w-full', classNames.thumbnailImage) }) }, provider.id))) }), _jsxs("div", { className: "mt-2xl flex flex-col items-center lg:mt-3xl", children: [_jsx(Progress.Root, { min: 0, max: chunked.totalItems, value: chunked.totalLoadedItems, onValueChange: noop, className: "w-[12.5rem]", children: _jsx(Progress.Track, { className: "bg-bg-tertiary", children: _jsx(Progress.Range, {}) }) }), _jsx("p", { className: "text-button-tertiary-fg mt-md text-sm", children: `Displaying ${chunked.totalLoadedItems} of ${chunked.totalItems}` }), chunked.hasNext && (_jsx(Button, { size: "sm", variant: "outline", fullWidth: false, onClick: chunked.next, className: "mt-lg", children: "Load More" }))] })] }));
25
+ }
@@ -1,5 +1,10 @@
1
+ interface ClassNameEntries {
2
+ root?: string;
3
+ title?: string;
4
+ }
1
5
  export interface GameProps {
2
6
  badge?: 'top' | 'new' | 'popular';
3
- gameNameClass?: string;
7
+ className?: string | ClassNameEntries;
4
8
  }
5
9
  export declare function Game(props: GameProps): import("react/jsx-runtime").JSX.Element;
10
+ export {};
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { isString } from 'lodash-es';
2
3
  import Image from 'next/image';
3
4
  import { twMerge } from 'tailwind-merge';
4
5
  import { useFavoriteGamesQuery, useMarkGameAsFavoriteMutation, useSessionQuery, useUnmarkGameAsFavoriteMutation, } from '../../client/hooks/index.js';
@@ -7,7 +8,7 @@ import { GameLaunchTrigger } from '../GameLaunch/index.js';
7
8
  import { BadgeNew } from './BadgeNew.js';
8
9
  import { BadgePopular } from './BadgePopular.js';
9
10
  import { BadgeTop } from './BadgeTop.js';
10
- import { useGameContext } from './GameContext.js';
11
+ import { useGameContext } from './GamesContext.js';
11
12
  export function Game(props) {
12
13
  const game = useGameContext();
13
14
  const sessionQuery = useSessionQuery();
@@ -17,7 +18,10 @@ export function Game(props) {
17
18
  const markGameAsFavoriteMutation = useMarkGameAsFavoriteMutation();
18
19
  const unmarkGameAsFavoriteMutation = useUnmarkGameAsFavoriteMutation();
19
20
  const markedAsFavorite = favoriteGames.some((favoriteGame) => favoriteGame.id === game.id);
20
- return (_jsxs(GameLaunchTrigger, { game: game, className: "animate-scale-in relative block w-full shadow-sm", children: [props.badge === 'new' && (_jsx(BadgeNew, { className: "absolute left-0 top-0 size-[3.75rem]" })), props.badge === 'top' && (_jsx(BadgeTop, { className: "absolute left-0 top-0 size-[3.75rem]" })), props.badge === 'popular' && (_jsx(BadgePopular, { className: "absolute left-0 top-0 size-[3.75rem]" })), session?.status === 'authenticated' && (_jsx("div", { role: "button", onClick: (e) => {
21
+ const classNames = isString(props.className)
22
+ ? { root: props.className }
23
+ : (props.className ?? {});
24
+ return (_jsxs(GameLaunchTrigger, { game: game, className: twMerge('animate-scale-in relative block w-full shadow-sm', classNames.root), children: [props.badge === 'new' && (_jsx(BadgeNew, { className: "absolute left-0 top-0 size-[3.75rem]" })), props.badge === 'top' && (_jsx(BadgeTop, { className: "absolute left-0 top-0 size-[3.75rem]" })), props.badge === 'popular' && (_jsx(BadgePopular, { className: "absolute left-0 top-0 size-[3.75rem]" })), session?.status === 'authenticated' && (_jsx("div", { role: "button", onClick: (e) => {
21
25
  e.preventDefault();
22
26
  e.stopPropagation();
23
27
  if (markGameAsFavoriteMutation.isPending)
@@ -33,5 +37,5 @@ export function Game(props) {
33
37
  }, "aria-disabled": markGameAsFavoriteMutation.isPending ||
34
38
  unmarkGameAsFavoriteMutation.isPending, className: "rounded-xs absolute right-2 top-2 flex size-9 items-center justify-center bg-black/65 blur-[0.5px]", children: _jsx(Star01Icon, { className: twMerge('size-5', markedAsFavorite
35
39
  ? 'fill-yellow-400 text-yellow-400'
36
- : 'text-white') }) })), _jsx(Image, { src: game.image, alt: "", width: 200, height: 200, priority: true, className: "aspect-square w-full rounded-t-md object-cover" }), _jsx("span", { className: twMerge('bg-bg-tertiary text-text-primary-brand block w-full truncate rounded-b-md px-2 py-3.5 text-center text-xs font-semibold', props.gameNameClass), children: game.name })] }));
40
+ : 'text-white') }) })), _jsx(Image, { src: game.image, alt: "", width: 200, height: 200, priority: true, className: "aspect-square w-full rounded-t-md object-cover" }), _jsx("span", { className: twMerge('bg-bg-tertiary text-text-primary-brand block w-full truncate rounded-b-md px-2 py-3.5 text-center text-xs font-semibold', classNames.title), children: game.name })] }));
37
41
  }
@@ -1,8 +1,4 @@
1
1
  import { type GamesCarouselProps } from './GamesCarousel';
2
2
  import { type GamesListProps } from './GamesList';
3
- export type GamesProps = ({
4
- layout: 'list';
5
- } & GamesListProps) | ({
6
- layout?: 'carousel';
7
- } & GamesCarouselProps);
3
+ export type GamesProps = GamesListProps | GamesCarouselProps;
8
4
  export declare function Games(props: GamesProps): Promise<import("react/jsx-runtime").JSX.Element>;
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import useEmblaCarousel, {} from 'embla-carousel-react';
4
+ import { isString } from 'lodash-es';
4
5
  import Link from 'next/link';
5
6
  import { useCallback, useEffect, useState } from 'react';
6
7
  import { twMerge } from 'tailwind-merge';
@@ -9,7 +10,7 @@ import { ArrowLeftIcon, ArrowRightIcon, ChevronRightIcon, GamingPad01Icon, Spinn
9
10
  import { Button } from '../../ui/index.js';
10
11
  import { Empty } from '../Empty.js';
11
12
  import { Game } from './Game.js';
12
- import { GameContext } from './GameContext.js';
13
+ import { GameContext } from './GamesContext.js';
13
14
  export function GamesCarousel__client(props) {
14
15
  const numOfRows = props.numOfRows ?? 1;
15
16
  const gamesQuery = useGamesQuery({
@@ -51,11 +52,17 @@ export function GamesCarousel__client(props) {
51
52
  onSelect(emblaApi);
52
53
  emblaApi.on('reInit', onSelect).on('select', onSelect);
53
54
  }, [emblaApi, onSelect]);
54
- return (_jsxs("div", { className: props.className, children: [_jsxs("div", { className: "flex items-center", children: [_jsx("h2", { className: "text-lg font-semibold", children: props.heading ?? 'Games' }), _jsx("div", { className: "grow" }), _jsxs("div", { className: "flex items-center justify-center gap-xl", children: [_jsxs(Link, { href: props.viewAllUrl ?? '/games', className: "text-button-tertiary-fg flex gap-sm text-sm font-semibold", children: ["See All", _jsx(ChevronRightIcon, { className: "size-5 lg:hidden" })] }), _jsxs("div", { className: "hidden lg:flex", children: [_jsx(Button, { variant: "outline", colorScheme: "gray", className: "rounded-r-none border-r-0", onClick: onPrevButtonClick, disabled: prevBtnDisabled, "aria-label": "Previous", children: _jsx(ArrowLeftIcon, { className: "size-5" }) }), _jsx(Button, { variant: "outline", colorScheme: "gray", className: "rounded-l-none", onClick: onNextButtonClick, disabled: nextBtnDisabled && !gamesQuery.hasNextPage
55
+ const classNames = isString(props.className)
56
+ ? { root: props.className }
57
+ : (props.className ?? {});
58
+ return (_jsxs("div", { className: classNames.root, children: [_jsxs("div", { className: "flex items-center", children: [_jsx("h2", { className: "text-lg font-semibold", children: props.heading ?? 'Games' }), _jsx("div", { className: "grow" }), _jsxs("div", { className: "flex items-center justify-center gap-xl", children: [_jsxs(Link, { href: props.viewAllUrl ?? '/games', className: "text-button-tertiary-fg flex gap-sm text-sm font-semibold", children: ["See All", _jsx(ChevronRightIcon, { className: "size-5 lg:hidden" })] }), _jsxs("div", { className: "hidden lg:flex", children: [_jsx(Button, { variant: "outline", colorScheme: "gray", className: "rounded-r-none border-r-0", onClick: onPrevButtonClick, disabled: prevBtnDisabled, "aria-label": "Previous", children: _jsx(ArrowLeftIcon, { className: "size-5" }) }), _jsx(Button, { variant: "outline", colorScheme: "gray", className: "rounded-l-none", onClick: onNextButtonClick, disabled: nextBtnDisabled && !gamesQuery.hasNextPage
55
59
  ? true
56
60
  : gamesQuery.isFetchingNextPage, "aria-label": "Next", children: gamesQuery.isFetchingNextPage ? (_jsx(SpinnerIcon, { className: "size-5" })) : (_jsx(ArrowRightIcon, { className: "size-5" })) })] })] })] }), games.length <= 0 && (_jsx(Empty, { icon: GamingPad01Icon, title: "No games", message: "No game is currently available. Please check back later", className: "mt-lg" })), games.length >= 1 && (_jsx("div", { ref: emblaRef, className: "relative mt-lg lg:overflow-hidden", children: _jsx("div", { className: twMerge('grid auto-cols-[calc((100%-(0.375rem*2))/3)] grid-flow-col gap-sm lg:auto-cols-[calc((100%-(0.5rem*5))/6)] lg:gap-md', numOfRows === 3
57
61
  ? 'grid-rows-3'
58
62
  : numOfRows === 2
59
63
  ? 'grid-rows-2'
60
- : 'grid-rows-1'), children: games.map((game) => (_jsx(GameContext, { value: game, children: _jsx(Game, { badge: props.badge, gameNameClass: props.gameNameClass }) }, game.id))) }) }))] }));
64
+ : 'grid-rows-1'), children: games.map((game) => (_jsx(GameContext, { value: game, children: _jsx(Game, { badge: props.badge, className: {
65
+ root: classNames.thumbnailRoot,
66
+ title: classNames.thumbnailTitle,
67
+ } }) }, game.id))) }) }))] }));
61
68
  }
@@ -1,5 +1,11 @@
1
1
  import type { GamesInput__Next } from '@opexa/portal-sdk-core';
2
+ export interface ClassNameEntries {
3
+ root?: string;
4
+ thumbnailRoot?: string;
5
+ thumbnailTitle?: string;
6
+ }
2
7
  export interface GamesCarouselProps {
8
+ layout: 'carousel';
3
9
  /** @default "Games" */
4
10
  heading?: string;
5
11
  /**
@@ -13,7 +19,6 @@ export interface GamesCarouselProps {
13
19
  filter?: GamesInput__Next['filter'];
14
20
  search?: string;
15
21
  sort?: GamesInput__Next['sort'];
16
- className?: string;
17
- gameNameClass?: string;
22
+ className?: string | ClassNameEntries;
18
23
  }
19
24
  export declare function GamesCarousel(props: GamesCarouselProps): Promise<import("react/jsx-runtime").JSX.Element>;
@@ -0,0 +1,4 @@
1
+ import type { Game } from '@opexa/portal-sdk-core';
2
+ import type { GamesProps } from './Games';
3
+ export declare const GamesPropsContext: import("react").Context<GamesProps>, useGamesPropsContext: () => GamesProps;
4
+ export declare const GameContext: import("react").Context<Game>, useGameContext: () => Game;
@@ -0,0 +1,3 @@
1
+ import { createContext } from '../../client/utils/index.js';
2
+ export const [GamesPropsContext, useGamesPropsContext] = createContext();
3
+ export const [GameContext, useGameContext] = createContext();
@@ -1,12 +1,12 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { noop } from 'lodash-es';
3
+ import { isString, noop } from 'lodash-es';
4
4
  import { useGamesQuery, useInfiniteQueryHelper } from '../../client/hooks/index.js';
5
5
  import { GamingPad01Icon, SpinnerIcon } from '../../icons/index.js';
6
6
  import { Button, Progress } from '../../ui/index.js';
7
7
  import { Empty } from '../Empty.js';
8
8
  import { Game } from './Game.js';
9
- import { GameContext } from './GameContext.js';
9
+ import { GameContext } from './GamesContext.js';
10
10
  export function GamesList__client(props) {
11
11
  const { loading, availableRows, totalRows, totalAvailableRows, hasNextPage, next, } = useInfiniteQueryHelper(useGamesQuery, {
12
12
  first: props.first ?? 24,
@@ -14,7 +14,13 @@ export function GamesList__client(props) {
14
14
  search: props.search,
15
15
  sort: props.sort,
16
16
  });
17
- return (_jsxs("div", { className: props.className, children: [_jsx("h2", { className: "text-lg font-semibold", children: props.heading ?? 'Games' }), totalRows <= 0 && (_jsx(Empty, { icon: loading ? SpinnerIcon : GamingPad01Icon, title: loading ? 'Just a moment' : 'No games', message: loading
17
+ const classNames = isString(props.className)
18
+ ? { root: props.className }
19
+ : (props.className ?? {});
20
+ return (_jsxs("div", { className: classNames.root, children: [_jsx("h2", { className: "text-lg font-semibold", children: props.heading ?? 'Games' }), totalRows <= 0 && (_jsx(Empty, { icon: loading ? SpinnerIcon : GamingPad01Icon, title: loading ? 'Just a moment' : 'No games', message: loading
18
21
  ? 'Fetching latest games...'
19
- : 'No game is currently available. Please check back later', className: "mt-lg" })), totalRows >= 1 && (_jsxs(_Fragment, { children: [_jsx("div", { className: "mt-lg grid grid-cols-3 gap-1.5 lg:grid-cols-6 lg:gap-2.5", children: availableRows.map((game) => (_jsx(GameContext, { value: game, children: _jsx(Game, { badge: props.badge, gameNameClass: props.gameNameClass }) }, game.id))) }), _jsxs("div", { className: "mt-3xl flex flex-col items-center", children: [_jsx(Progress.Root, { min: 0, max: totalRows, value: totalAvailableRows, onValueChange: noop, className: "w-[12.5rem]", children: _jsx(Progress.Track, { className: "bg-bg-tertiary", children: _jsx(Progress.Range, {}) }) }), _jsx("p", { className: "text-button-tertiary-fg mt-md text-sm", children: `Displaying ${totalAvailableRows} of ${totalRows}` }), hasNextPage && (_jsx(Button, { size: "sm", variant: "outline", fullWidth: false, onClick: next, className: "mt-lg", children: "Load More" }))] })] }))] }));
22
+ : 'No game is currently available. Please check back later', className: "mt-lg" })), totalRows >= 1 && (_jsxs(_Fragment, { children: [_jsx("div", { className: "mt-lg grid grid-cols-3 gap-1.5 lg:grid-cols-6 lg:gap-2.5", children: availableRows.map((game) => (_jsx(GameContext, { value: game, children: _jsx(Game, { badge: props.badge, className: {
23
+ root: classNames.thumbnailRoot,
24
+ title: classNames.thumbnailTitle,
25
+ } }) }, game.id))) }), _jsxs("div", { className: "mt-2xl flex flex-col items-center lg:mt-3xl", children: [_jsx(Progress.Root, { min: 0, max: totalRows, value: totalAvailableRows, onValueChange: noop, className: "w-[12.5rem]", children: _jsx(Progress.Track, { className: "bg-bg-tertiary", children: _jsx(Progress.Range, {}) }) }), _jsx("p", { className: "text-button-tertiary-fg mt-md text-sm", children: `Displaying ${totalAvailableRows} of ${totalRows}` }), hasNextPage && (_jsx(Button, { size: "sm", variant: "outline", fullWidth: false, onClick: next, className: "mt-lg", children: "Load More" }))] })] }))] }));
20
26
  }
@@ -1,5 +1,11 @@
1
1
  import type { GamesInput__Next } from '@opexa/portal-sdk-core';
2
+ export interface ClassNameEntries {
3
+ root?: string;
4
+ thumbnailRoot?: string;
5
+ thumbnailTitle?: string;
6
+ }
2
7
  export interface GamesListProps {
8
+ layout: 'list';
3
9
  badge?: 'top' | 'new' | 'popular';
4
10
  /** @default 24 */
5
11
  first?: number;
@@ -8,7 +14,6 @@ export interface GamesListProps {
8
14
  search?: string;
9
15
  /** @default "Games"*/
10
16
  heading?: string;
11
- className?: string;
12
- gameNameClass?: string;
17
+ className?: string | ClassNameEntries;
13
18
  }
14
19
  export declare function GamesList(props: GamesListProps): Promise<import("react/jsx-runtime").JSX.Element>;
@@ -1,15 +1,4 @@
1
- export interface PromosProps {
2
- /** @default "Promotions" */
3
- heading?: string;
4
- /** @default "/promos" */
5
- viewAllUrl?: string;
6
- /** @default "/promos" */
7
- viewPromoDetailsUrl?: string;
8
- /** @default "/promos/cashback" */
9
- viewCashbackDetailsUrl?: string;
10
- /** @default 1 */
11
- className?: string;
12
- /** @default 'carousel' */
13
- layout?: 'carousel' | 'grid';
14
- }
1
+ import { type PromosCarouselProps } from './PromosCarousel';
2
+ import { type PromosGridProps } from './PromosGrid';
3
+ export type PromosProps = PromosCarouselProps | PromosGridProps;
15
4
  export declare function Promos(props: PromosProps): Promise<import("react/jsx-runtime").JSX.Element>;
@@ -1,2 +1,13 @@
1
- import type { PromosProps } from './Promos';
2
- export declare function PromosCarousel(props: PromosProps): import("react/jsx-runtime").JSX.Element;
1
+ export interface PromosCarouselProps {
2
+ layout: 'carousel';
3
+ /** @default "Promotions" */
4
+ heading?: string;
5
+ /** @default "/promos" */
6
+ viewAllUrl?: string;
7
+ /** @default "/promos" */
8
+ viewPromoDetailsUrl?: string;
9
+ /** @default "/promos/cashback" */
10
+ viewCashbackDetailsUrl?: string;
11
+ className?: string;
12
+ }
13
+ export declare function PromosCarousel(props: PromosCarouselProps): import("react/jsx-runtime").JSX.Element;
@@ -1,2 +1,11 @@
1
- import type { PromosProps } from './Promos';
2
- export declare function PromosGrid(props: PromosProps): import("react/jsx-runtime").JSX.Element;
1
+ export interface PromosGridProps {
2
+ layout: 'grid';
3
+ /** @default "Promotions" */
4
+ heading?: string;
5
+ /** @default "/promos" */
6
+ viewPromoDetailsUrl?: string;
7
+ /** @default "/promos/cashback" */
8
+ viewCashbackDetailsUrl?: string;
9
+ className?: string;
10
+ }
11
+ export declare function PromosGrid(props: PromosGridProps): import("react/jsx-runtime").JSX.Element;
@@ -101,7 +101,7 @@ export const GAME_PROVIDER_DATA = {
101
101
  id: 'ETENGJUE',
102
102
  name: 'Etengjue',
103
103
  slug: 'etengjue',
104
- logo: '' /* FIXME */,
104
+ logo: '',
105
105
  },
106
106
  EVOLUTION: {
107
107
  id: 'EVOLUTION',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opexa/portal-components",
3
- "version": "0.0.78",
3
+ "version": "0.0.79",
4
4
  "exports": {
5
5
  ".": {
6
6
  "types": "./dist/components/index.d.ts",