@opexa/portal-components 0.0.123 → 0.0.125

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 (70) hide show
  1. package/dist/components/BetRecords/BetRecords.client.d.ts +13 -0
  2. package/dist/components/BetRecords/BetRecords.client.js +9 -1
  3. package/dist/components/DepositWithdrawal/Deposit/OnlineBankDeposit/OnlineBankDepositContext.d.ts +2 -2
  4. package/dist/components/DepositWithdrawal/Deposit/OnlineBankDeposit/useOnlineBankDeposit.d.ts +1 -1
  5. package/dist/components/DepositWithdrawal/Deposit/QRPHDeposit/QRPHDepositContext.d.ts +2 -2
  6. package/dist/components/DepositWithdrawal/Deposit/QRPHDeposit/useQRPHDeposit.d.ts +1 -1
  7. package/dist/components/FavoriteGames/FavoriteGames.client.d.ts +7 -1
  8. package/dist/components/FavoriteGames/FavoriteGames.client.js +24 -5
  9. package/dist/components/ForgotPassword/ForgotPasswordForm.js +14 -7
  10. package/dist/components/GameLaunch/GameLaunch.lazy.js +25 -2
  11. package/dist/components/Games/Game.js +25 -2
  12. package/dist/components/Jackpots/JackpotsList/JackpotsList.d.ts +12 -1
  13. package/dist/components/Jackpots/JackpotsList/JackpotsListItemGameProviders.js +14 -7
  14. package/dist/components/KYC/CaptureSelfie.js +5 -3
  15. package/dist/components/SignIn/MobileNumberSignIn.js +14 -6
  16. package/dist/components/SignIn/NameAndPasswordSignIn.js +1 -1
  17. package/dist/components/SignUp/SignUpForm.js +21 -10
  18. package/dist/components/Tournaments/TournamentsList/TournamentsList.d.ts +12 -1
  19. package/dist/components/Tournaments/TournamentsList/TournamentsListItemGameProviders.js +11 -1
  20. package/dist/components/TransactionRecords/DepositRecordsTable.js +29 -17
  21. package/dist/components/TransactionRecords/TableFilters.d.ts +5 -1
  22. package/dist/components/TransactionRecords/TableFilters.js +105 -31
  23. package/dist/components/TransactionRecords/TransactionRecordsTable.js +21 -9
  24. package/dist/components/TransactionRecords/WithdrawalRecordsTable.js +29 -17
  25. package/dist/services/queries.d.ts +5 -5
  26. package/dist/services/queries.js +13 -18
  27. package/dist/services/trigger.js +2 -2
  28. package/dist/services/wallet.d.ts +7 -6
  29. package/dist/services/wallet.js +14 -12
  30. package/dist/ui/AlertDialog/AlertDialog.d.ts +121 -121
  31. package/dist/ui/AlertDialog/alertDialog.recipe.d.ts +11 -11
  32. package/dist/ui/Avatar/Avatar.d.ts +9 -9
  33. package/dist/ui/Avatar/avatar.recipe.d.ts +3 -3
  34. package/dist/ui/Badge/Badge.d.ts +12 -12
  35. package/dist/ui/Badge/badge.anatomy.d.ts +1 -1
  36. package/dist/ui/Badge/badge.recipe.d.ts +3 -3
  37. package/dist/ui/Clipboard/Clipboard.d.ts +18 -18
  38. package/dist/ui/Clipboard/clipboard.recipe.d.ts +3 -3
  39. package/dist/ui/Collapsible/Collapsible.d.ts +32 -32
  40. package/dist/ui/Collapsible/collapsible.recipe.d.ts +8 -8
  41. package/dist/ui/Combobox/Combobox.d.ts +42 -42
  42. package/dist/ui/Combobox/combobox.recipe.d.ts +3 -3
  43. package/dist/ui/DatePicker/DatePicker.d.ts +72 -72
  44. package/dist/ui/DatePicker/datePicker.recipe.d.ts +3 -3
  45. package/dist/ui/Dialog/Dialog.d.ts +121 -121
  46. package/dist/ui/Dialog/dialog.recipe.d.ts +11 -11
  47. package/dist/ui/Drawer/Drawer.d.ts +33 -33
  48. package/dist/ui/Drawer/drawer.recipe.d.ts +3 -3
  49. package/dist/ui/Field/Field.d.ts +21 -21
  50. package/dist/ui/Field/field.recipe.d.ts +3 -3
  51. package/dist/ui/Menu/Menu.d.ts +252 -252
  52. package/dist/ui/Menu/menu.recipe.d.ts +14 -14
  53. package/dist/ui/NumberInput/NumberInput.d.ts +24 -24
  54. package/dist/ui/NumberInput/numberInput.recipe.d.ts +3 -3
  55. package/dist/ui/Popover/Popover.d.ts +121 -121
  56. package/dist/ui/Popover/popover.recipe.d.ts +11 -11
  57. package/dist/ui/Progress/Progress.d.ts +27 -27
  58. package/dist/ui/Progress/progress.recipe.d.ts +3 -3
  59. package/dist/ui/QrCode/QrCode.d.ts +25 -25
  60. package/dist/ui/QrCode/qrCode.recipe.d.ts +5 -5
  61. package/dist/ui/SegmentGroup/SegmentGroup.d.ts +18 -18
  62. package/dist/ui/SegmentGroup/segmentGroup.recipe.d.ts +3 -3
  63. package/dist/ui/Select/Select.d.ts +45 -45
  64. package/dist/ui/Select/select.recipe.d.ts +3 -3
  65. package/dist/ui/Table/Table.d.ts +21 -21
  66. package/dist/ui/Table/table.anatomy.d.ts +1 -1
  67. package/dist/ui/Table/table.recipe.d.ts +3 -3
  68. package/dist/ui/Tabs/Tabs.d.ts +15 -15
  69. package/dist/ui/Tabs/tabs.recipe.d.ts +3 -3
  70. package/package.json +1 -1
@@ -2,6 +2,19 @@ import type { GameProvider, GameType } from '../../types';
2
2
  export interface BetRecordsProps {
3
3
  gameTypes: GameType[];
4
4
  gameProviders: GameProvider[];
5
+ /**
6
+ * Game providers grouped by game type.
7
+ * This is used to filter the game providers based on the selected game type.
8
+ * If not provided, all game providers will be shown regardless of the game type.
9
+ * @example
10
+ * ```ts
11
+ * gameProvidersGroup={{
12
+ * SLOTS: ['JILI', 'PGSOFT', 'FACHAI'],
13
+ * SPORTS: ['BTI', 'DIGITAIN']
14
+ * }}
15
+ * ```
16
+ */
17
+ gameProvidersGroup?: Partial<Record<GameType, GameProvider[]>>;
5
18
  className?: string;
6
19
  }
7
20
  export declare function BetRecords__client(props: BetRecordsProps): import("react/jsx-runtime").JSX.Element;
@@ -40,8 +40,16 @@ export function BetRecords__client(props) {
40
40
  type: null,
41
41
  });
42
42
  const localeInfo = useLocaleInfo();
43
+ const providersByGroup = gameProviders.filter((o) => {
44
+ if (!state.type)
45
+ return true;
46
+ const l = props.gameProvidersGroup?.[state.type];
47
+ if (!l?.length)
48
+ return true;
49
+ return l.includes(o.id);
50
+ });
43
51
  const providerCollection = createListCollection({
44
- items: gameProviders,
52
+ items: providersByGroup,
45
53
  itemToValue: (item) => item.id,
46
54
  itemToString: (item) => item.name,
47
55
  });
@@ -1,5 +1,5 @@
1
1
  export declare const OnlineBankDepositContext: import("react").Context<{
2
- view: "form" | "vca";
2
+ view: "vca" | "form";
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: "form" | "vca";
12
+ view: "vca" | "form";
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 '../../../../types';
2
2
  export type UseOnlineBankDepositReturn = ReturnType<typeof useOnlineBankDeposit>;
3
3
  export declare function useOnlineBankDeposit(): {
4
- view: "form" | "vca";
4
+ view: "vca" | "form";
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: "form" | "qrCode";
2
+ view: "qrCode" | "form";
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: "form" | "qrCode";
12
+ view: "qrCode" | "form";
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 '../../../../types';
2
2
  export type UseQRPHDepositReturn = ReturnType<typeof useQRPHDeposit>;
3
3
  export declare function useQRPHDeposit(): {
4
- view: "form" | "qrCode";
4
+ view: "qrCode" | "form";
5
5
  status: "waiting" | "processing" | "failed" | "verification-waiting" | "verification-processing" | "verification-failed" | "verification-success";
6
6
  verify: () => void;
7
7
  reset: () => void;
@@ -1,6 +1,12 @@
1
+ interface ClassNameEntries {
2
+ root?: string;
3
+ thumbnailRoot?: string;
4
+ thumbnailTitle?: string;
5
+ }
1
6
  export interface FavoriteGamesProps {
2
- className?: string;
7
+ className?: string | ClassNameEntries;
3
8
  /** @default 'Favorite Games' */
4
9
  heading?: string;
5
10
  }
6
11
  export declare function FavoriteGames__client(props: FavoriteGamesProps): import("react/jsx-runtime").JSX.Element | null;
12
+ export {};
@@ -1,12 +1,14 @@
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 Image from 'next/image';
5
6
  import { useCallback, useEffect, useState } from 'react';
6
7
  import { twMerge } from 'tailwind-merge';
7
8
  import { useFavoriteGamesQuery } from '../../client/hooks/useFavoriteGamesQuery.js';
8
9
  import { useSessionQuery } from '../../client/hooks/useSessionQuery.js';
9
10
  import { useUnmarkGameAsFavoriteMutation } from '../../client/hooks/useUnmarkGameAsFavoriteMutation.js';
11
+ import { toaster } from '../../client/utils/toaster.js';
10
12
  import { ArrowLeftIcon } from '../../icons/ArrowLeftIcon.js';
11
13
  import { ArrowRightIcon } from '../../icons/ArrowRightIcon.js';
12
14
  import { Star01Icon } from '../../icons/Star01Icon.js';
@@ -38,13 +40,30 @@ export function FavoriteGames__client(props) {
38
40
  const games = gamesQuery.data ?? [];
39
41
  if (games.length <= 0)
40
42
  return null;
41
- return (_jsxs("div", { className: props.className, children: [_jsxs("div", { className: "flex items-center", children: [_jsx("h2", { className: "text-lg font-semibold", children: props.heading ?? 'Favorite Games' }), _jsx("div", { className: "grow" }), _jsx("div", { className: "flex items-center justify-center gap-xl", children: _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: twMerge('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: games.map((game) => (_jsx(Item, { game: game }, game.id))) }) })] }));
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 ?? 'Favorite Games' }), _jsx("div", { className: "grow" }), _jsx("div", { className: "flex items-center justify-center gap-xl", children: _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: twMerge('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: games.map((game) => (_jsx(Item, { game: game, className: {
47
+ thumbnailRoot: classNames.thumbnailRoot,
48
+ thumbnailTitle: classNames.thumbnailTitle,
49
+ } }, game.id))) }) })] }));
42
50
  }
43
- function Item({ game }) {
51
+ function Item({ game, className, }) {
44
52
  const sessionQuery = useSessionQuery();
45
53
  const session = sessionQuery.data;
46
- const unmarkGameAsFavoriteMutation = useUnmarkGameAsFavoriteMutation();
47
- return (_jsxs(GameLaunchTrigger, { game: game, className: "relative block w-full shadow-sm", children: [session?.status === 'authenticated' && (_jsx("div", { role: "button", onClick: (e) => {
54
+ const unmarkGameAsFavoriteMutation = useUnmarkGameAsFavoriteMutation({
55
+ onSuccess() {
56
+ toaster.create({
57
+ description: 'Game removed from favorites',
58
+ });
59
+ },
60
+ onError() {
61
+ toaster.create({
62
+ description: 'Failed to remove game from favorites',
63
+ });
64
+ },
65
+ });
66
+ return (_jsxs(GameLaunchTrigger, { game: game, className: twMerge('relative block w-full shadow-sm', className?.thumbnailRoot), children: [session?.status === 'authenticated' && (_jsx("div", { role: "button", onClick: (e) => {
48
67
  e.preventDefault();
49
68
  e.stopPropagation();
50
69
  if (unmarkGameAsFavoriteMutation.isPending)
@@ -53,5 +72,5 @@ function Item({ game }) {
53
72
  }, "aria-disabled": 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: "fill-yellow-400 text-yellow-400 size-5" }) })), _jsx(Image, { src: getGameImageUrl({
54
73
  id: game.id,
55
74
  provider: game.provider,
56
- }), alt: "", width: 200, height: 200, priority: true, className: "aspect-square w-full rounded-t-md object-cover" }), _jsx("span", { className: "bg-bg-tertiary text-text-primary-brand block w-full truncate rounded-b-md px-2 py-2.5 text-center text-xs font-semibold", children: game.name })] }));
75
+ }), 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', className?.thumbnailTitle), children: game.name })] }));
57
76
  }
@@ -70,7 +70,14 @@ export function ForgotPasswordForm() {
70
70
  }
71
71
  });
72
72
  const Step2Definition = z.object({
73
- verificationCode: z.array(z.string().min(1).max(1).trim()).min(6).max(6),
73
+ verificationCode: z.array(z.string()).superRefine((val, ctx) => {
74
+ if (val.length !== 6 || val.some((v) => v.length !== 1)) {
75
+ ctx.addIssue({
76
+ code: z.ZodIssueCode.custom,
77
+ message: 'Please Enter your 6 digits verification code',
78
+ });
79
+ }
80
+ }),
74
81
  });
75
82
  const step1Form = useForm({
76
83
  mode: 'all',
@@ -110,17 +117,17 @@ export function ForgotPasswordForm() {
110
117
  }
111
118
  }), children: [_jsxs(Field.Root, { invalid: !!step1Form.formState.errors.mobileNumber, children: [_jsx(Field.Label, { children: "Mobile Number" }), _jsxs("div", { className: "relative", children: [_jsxs("div", { className: "absolute left-3.5 top-1/2 flex shrink-0 -translate-y-1/2 items-center gap-md", children: [_jsx(localeInfo.country.flag, { className: "size-5" }), _jsx("span", { className: "text-text-placeholder", children: localeInfo.mobileNumber.areaCode })] }), _jsx(Field.Input, { style: {
112
119
  paddingLeft: `calc(2.75rem + ${localeInfo.mobileNumber.areaCode.length}ch)`,
113
- }, ...step1Form.register('mobileNumber') })] }), _jsx(Field.ErrorText, { children: step1Form.formState.errors.mobileNumber?.message })] }), _jsx(Field.Root, { invalid: !!step1Form.formState.errors.newPassword, className: "mt-xl", children: _jsxs(PasswordInput.Root, { children: [_jsx(PasswordInput.Label, { children: "New Password" }), _jsxs(PasswordInput.Control, { children: [_jsx(PasswordInput.Input, { placeholder: "Enter your new password", ...step1Form.register('newPassword') }), _jsx(PasswordInput.VisibilityTrigger, { children: _jsx(PasswordInput.Indicator, { fallback: _jsx(EyeOffIcon, {}), asChild: true, children: _jsx(EyeIcon, {}) }) })] }), _jsx(Field.ErrorText, { children: step1Form.formState.errors.newPassword?.message })] }) }), _jsx(Field.Root, { invalid: !!step1Form.formState.errors.confirmPassword, className: "mt-xl", children: _jsxs(PasswordInput.Root, { children: [_jsx(PasswordInput.Label, { children: "Confirm Password" }), _jsxs(PasswordInput.Control, { children: [_jsx(PasswordInput.Input, { placeholder: "Confirm new password", ...step1Form.register('confirmPassword') }), _jsx(PasswordInput.VisibilityTrigger, { children: _jsx(PasswordInput.Indicator, { fallback: _jsx(EyeOffIcon, {}), asChild: true, children: _jsx(EyeIcon, {}) }) })] }), _jsx(Field.ErrorText, { children: step1Form.formState.errors.confirmPassword?.message })] }) }), _jsx(Button, { type: "submit", className: "mt-3xl", disabled: step1Form.formState.isSubmitting, children: "Continue" })] })] })), step === 2 && (_jsxs(_Fragment, { children: [_jsx("h2", { className: "mt-xl text-center text-lg font-semibold", children: "Check your Phone" }), _jsxs("p", { className: "text-text-secondary-700 mt-xs text-center text-sm", children: ["We\u2019ve sent a verification code to your mobile number", ' ', _jsx("span", { className: "font-semibold", children: mobileNumberParser.format(step1Form.getValues('mobileNumber')) }), ' ', "via text"] }), _jsxs("form", { ref: form2Ref, className: "mt-5", onSubmit: step2Form.handleSubmit(async ({ verificationCode }) => {
120
+ }, ...step1Form.register('mobileNumber') })] }), _jsx(Field.ErrorText, { children: step1Form.formState.errors.mobileNumber?.message })] }), _jsx(Field.Root, { invalid: !!step1Form.formState.errors.newPassword, className: "mt-xl", children: _jsxs(PasswordInput.Root, { children: [_jsx(PasswordInput.Label, { children: "New Password" }), _jsxs(PasswordInput.Control, { children: [_jsx(PasswordInput.Input, { placeholder: "Enter your new password", ...step1Form.register('newPassword') }), _jsx(PasswordInput.VisibilityTrigger, { children: _jsx(PasswordInput.Indicator, { fallback: _jsx(EyeOffIcon, {}), asChild: true, children: _jsx(EyeIcon, {}) }) })] }), _jsx(Field.ErrorText, { children: step1Form.formState.errors.newPassword?.message })] }) }), _jsx(Field.Root, { invalid: !!step1Form.formState.errors.confirmPassword, className: "mt-xl", children: _jsxs(PasswordInput.Root, { children: [_jsx(PasswordInput.Label, { children: "Confirm Password" }), _jsxs(PasswordInput.Control, { children: [_jsx(PasswordInput.Input, { placeholder: "Confirm new password", ...step1Form.register('confirmPassword') }), _jsx(PasswordInput.VisibilityTrigger, { children: _jsx(PasswordInput.Indicator, { fallback: _jsx(EyeOffIcon, {}), asChild: true, children: _jsx(EyeIcon, {}) }) })] }), _jsx(Field.ErrorText, { children: step1Form.formState.errors.confirmPassword?.message })] }) }), _jsx(Button, { type: "submit", className: "mt-3xl", disabled: step1Form.formState.isSubmitting, children: "Continue" })] })] })), step === 2 && (_jsxs(_Fragment, { children: [_jsx("h2", { className: "mt-xl text-center text-lg font-semibold", children: "Check your Phone" }), _jsxs("p", { className: "text-text-secondary-700 mt-xs text-center text-sm", children: ["We\u2019ve sent a verification code to your mobile number", ' ', _jsx("span", { className: "font-semibold", children: mobileNumberParser.format(step1Form.getValues('mobileNumber')) }), ' ', "via text"] }), _jsxs("form", { ref: form2Ref, className: "mt-5", autoComplete: "off", onSubmit: step2Form.handleSubmit(async ({ verificationCode }) => {
114
121
  resetPasswordMutation.mutate({
115
122
  mobileNumber: mobileNumberParser.format(step1Form.getValues('mobileNumber')),
116
123
  newPassword: step1Form.getValues('newPassword'),
117
124
  verificationCode: verificationCode.join(''),
118
125
  });
119
- }), children: [_jsx(Controller, { name: "verificationCode", control: step2Form.control, render: (o) => (_jsxs(PinInput.Root, { placeholder: "0", invalid: o.fieldState.invalid, value: o.field.value, onValueChange: (details) => {
120
- o.field.onChange(details.value);
121
- }, onValueComplete: () => {
122
- form2Ref.current?.requestSubmit();
123
- }, children: [_jsxs(PinInput.Control, { className: "grid-cols-[1fr_1fr_1fr_auto_1fr_1fr_1fr] items-center gap-md", children: [_jsx(PinInput.Input, { index: 0 }), _jsx(PinInput.Input, { index: 1 }), _jsx(PinInput.Input, { index: 2 }), _jsx("span", { className: "text-text-placeholder-subtle text-2xl font-medium", children: "\u2013" }), _jsx(PinInput.Input, { index: 3 }), _jsx(PinInput.Input, { index: 4 }), _jsx(PinInput.Input, { index: 5 })] }), _jsx(PinInput.HiddenInput, {})] })) }), _jsx(Button, { type: "submit", className: "mt-4xl", disabled: resetPasswordMutation.isPending, children: "Verify" }), _jsxs("div", { className: "mt-3 flex w-full items-center justify-center gap-xs text-sm", children: [_jsx("span", { className: "text-text-secondary-700", children: "Didn't recieve the code?" }), _jsx("button", { type: "button", className: "text-button-secondary-fg font-semibold disabled:cursor-not-allowed disabled:opacity-75", disabled: cooldown.cooling, onClick: async () => {
126
+ }), children: [_jsx(Controller, { name: "verificationCode", control: step2Form.control, render: (o) => (_jsxs(Field.Root, { invalid: o.fieldState.invalid, children: [_jsxs(PinInput.Root, { placeholder: "0", value: o.field.value, onValueChange: (details) => {
127
+ o.field.onChange(details.value);
128
+ }, onValueComplete: () => {
129
+ form2Ref.current?.requestSubmit();
130
+ }, children: [_jsxs(PinInput.Control, { className: "grid-cols-[1fr_1fr_1fr_auto_1fr_1fr_1fr] items-center gap-md", children: [_jsx(PinInput.Input, { index: 0 }), _jsx(PinInput.Input, { index: 1 }), _jsx(PinInput.Input, { index: 2 }), _jsx("span", { className: "text-text-placeholder-subtle text-2xl font-medium", children: "\u2013" }), _jsx(PinInput.Input, { index: 3 }), _jsx(PinInput.Input, { index: 4 }), _jsx(PinInput.Input, { index: 5 })] }), _jsx(PinInput.HiddenInput, {})] }), _jsx(Field.ErrorText, { children: o.formState.errors.verificationCode?.message })] })) }), _jsx(Button, { type: "submit", className: "mt-4xl", disabled: resetPasswordMutation.isPending, children: "Verify" }), _jsxs("div", { className: "mt-3 flex w-full items-center justify-center gap-xs text-sm", children: [_jsx("span", { className: "text-text-secondary-700", children: "Didn't recieve the code?" }), _jsx("button", { type: "button", className: "text-button-secondary-fg font-semibold disabled:cursor-not-allowed disabled:opacity-75", disabled: cooldown.cooling, onClick: async () => {
124
131
  await sendVerificationCodeMutation.mutateAsync({
125
132
  channel: 'SMS',
126
133
  recipient: mobileNumberParser.format(step1Form.getValues('mobileNumber')),
@@ -9,6 +9,7 @@ import { useFavoriteGamesQuery } from '../../client/hooks/useFavoriteGamesQuery.
9
9
  import { useGlobalStore } from '../../client/hooks/useGlobalStore.js';
10
10
  import { useMarkGameAsFavoriteMutation } from '../../client/hooks/useMarkGameAsFavoriteMutation.js';
11
11
  import { useUnmarkGameAsFavoriteMutation } from '../../client/hooks/useUnmarkGameAsFavoriteMutation.js';
12
+ import { toaster } from '../../client/utils/toaster.js';
12
13
  import { AlertCircleIcon } from '../../icons/AlertCircleIcon.js';
13
14
  import { ChevronDownIcon } from '../../icons/ChevronDownIcon.js';
14
15
  import { ChevronLeftIcon } from '../../icons/ChevronLeftIcon.js';
@@ -46,8 +47,30 @@ export function GameLaunch() {
46
47
  const favoriteGamesQuery = useFavoriteGamesQuery();
47
48
  const favoriteGames = favoriteGamesQuery.data ?? [];
48
49
  const endGameSessionMutation = useEndGameSessionMutation();
49
- const markGameAsFavoriteMutation = useMarkGameAsFavoriteMutation();
50
- const unmarkGameAsFavoriteMutation = useUnmarkGameAsFavoriteMutation();
50
+ const markGameAsFavoriteMutation = useMarkGameAsFavoriteMutation({
51
+ onSuccess() {
52
+ toaster.create({
53
+ description: 'Game added to favorites',
54
+ });
55
+ },
56
+ onError() {
57
+ toaster.create({
58
+ description: 'Failed to add game to favorites',
59
+ });
60
+ },
61
+ });
62
+ const unmarkGameAsFavoriteMutation = useUnmarkGameAsFavoriteMutation({
63
+ onSuccess() {
64
+ toaster.create({
65
+ description: 'Game removed from favorites',
66
+ });
67
+ },
68
+ onError() {
69
+ toaster.create({
70
+ description: 'Failed to remove game from favorites',
71
+ });
72
+ },
73
+ });
51
74
  const markedAsFavorite = favoriteGames.some((favoriteGame) => {
52
75
  return !globalStore.gameLaunch.details.game
53
76
  ? false
@@ -6,6 +6,7 @@ import { useFavoriteGamesQuery } from '../../client/hooks/useFavoriteGamesQuery.
6
6
  import { useMarkGameAsFavoriteMutation } from '../../client/hooks/useMarkGameAsFavoriteMutation.js';
7
7
  import { useSessionQuery } from '../../client/hooks/useSessionQuery.js';
8
8
  import { useUnmarkGameAsFavoriteMutation } from '../../client/hooks/useUnmarkGameAsFavoriteMutation.js';
9
+ import { toaster } from '../../client/utils/toaster.js';
9
10
  import { Star01Icon } from '../../icons/Star01Icon.js';
10
11
  import { getGameImageUrl } from '../../utils/getGameImageUrl.js';
11
12
  import { GameLaunchTrigger } from '../GameLaunch/index.js';
@@ -19,8 +20,30 @@ export function Game(props) {
19
20
  const session = sessionQuery.data;
20
21
  const favoriteGamesQuery = useFavoriteGamesQuery();
21
22
  const favoriteGames = favoriteGamesQuery.data ?? [];
22
- const markGameAsFavoriteMutation = useMarkGameAsFavoriteMutation();
23
- const unmarkGameAsFavoriteMutation = useUnmarkGameAsFavoriteMutation();
23
+ const markGameAsFavoriteMutation = useMarkGameAsFavoriteMutation({
24
+ onSuccess() {
25
+ toaster.create({
26
+ description: 'Game added to favorites',
27
+ });
28
+ },
29
+ onError() {
30
+ toaster.create({
31
+ description: 'Failed to add game to favorites',
32
+ });
33
+ },
34
+ });
35
+ const unmarkGameAsFavoriteMutation = useUnmarkGameAsFavoriteMutation({
36
+ onSuccess() {
37
+ toaster.create({
38
+ description: 'Game removed from favorites',
39
+ });
40
+ },
41
+ onError() {
42
+ toaster.create({
43
+ description: 'Failed to remove game from favorites',
44
+ });
45
+ },
46
+ });
24
47
  const markedAsFavorite = favoriteGames.some((favoriteGame) => favoriteGame.id === game.reference);
25
48
  const classNames = isString(props.className)
26
49
  ? { root: props.className }
@@ -1,6 +1,6 @@
1
1
  import { type ImageProps } from 'next/image';
2
2
  import type { CSSProperties } from 'react';
3
- import type { GameProvider } from '../../../types';
3
+ import type { GameProvider, GameProviderData } from '../../../types';
4
4
  interface ClassNameEntries {
5
5
  root?: string;
6
6
  itemRoot?: string;
@@ -19,6 +19,17 @@ export interface JackpotsListProps {
19
19
  style?: CSSProperties | StyleEntries;
20
20
  gameProviders: GameProvider[];
21
21
  gameProviderImages?: Partial<Record<GameProvider, ImageProps['src']>>;
22
+ /**
23
+ * @default '/providers/<slug>'
24
+ * @example
25
+ * ```ts
26
+ * '/bingo/:slug' // '/bingo/jili'
27
+ * '/bingo/:id' // '/bingo/JILI'
28
+ * // or using a function
29
+ * ({slug}) => `/bingo/${slug}`
30
+ * ```
31
+ */
32
+ viewGamesUrl?: string | ((gameProvider: GameProviderData) => string);
22
33
  animate?: boolean;
23
34
  }
24
35
  export declare function JackpotsList(props: JackpotsListProps): import("react/jsx-runtime").JSX.Element | null;
@@ -8,9 +8,20 @@ import { twMerge } from 'tailwind-merge';
8
8
  import { ArrowLeftIcon } from '../../../icons/ArrowLeftIcon.js';
9
9
  import { ArrowRightIcon } from '../../../icons/ArrowRightIcon.js';
10
10
  import { Button } from '../../../ui/Button/index.js';
11
+ import { callIfFn } from '../../../utils/callIfFn.js';
11
12
  import { useJackpotsListPropsContext } from './JackpotsListContext.js';
12
13
  export function JackpotsListItemGameProviders({ gameProviders, }) {
13
- const props = useJackpotsListPropsContext();
14
+ const jackpotsProps = useJackpotsListPropsContext();
15
+ const viewGamesUrl = (data) => {
16
+ return jackpotsProps.viewGamesUrl
17
+ ? callIfFn(jackpotsProps.viewGamesUrl, data)
18
+ .replace(':id', data.id)
19
+ .replace(':slug', data.slug)
20
+ : `/providers/${data.slug}`;
21
+ };
22
+ const classNames = isString(jackpotsProps.className)
23
+ ? { root: jackpotsProps.className }
24
+ : (jackpotsProps.className ?? {});
14
25
  const [emblaRef, emblaApi] = useEmblaCarousel({
15
26
  align: 'start',
16
27
  slidesToScroll: 1,
@@ -38,10 +49,6 @@ export function JackpotsListItemGameProviders({ gameProviders, }) {
38
49
  emblaApi.on('select', onSelect);
39
50
  emblaApi.on('reInit', onSelect);
40
51
  }, [emblaApi, onSelect]);
41
- const jackpotsProps = useJackpotsListPropsContext();
42
- const classNames = isString(props.className)
43
- ? { root: props.className }
44
- : (props.className ?? {});
45
- return (_jsxs("div", { className: "p-4 pb-8", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("div", { className: "text-text-primary-900 text-lg font-semibold", children: "Jackpot Game Providers" }), _jsxs("div", { className: "hidden lg:flex", children: [_jsx(Button, { disabled: !canScrollPrev, onClick: scrollPrev, variant: "outline", colorScheme: "gray", className: "rounded-r-none border-r-0", "aria-label": "Previous", children: _jsx(ArrowLeftIcon, { className: "size-5" }) }), _jsxs(Button, { disabled: !canScrollNext, onClick: scrollNext, variant: "outline", colorScheme: "gray", className: "rounded-l-none", "aria-label": "Next", children: [_jsx("span", { className: "sr-only", children: "Next" }), _jsx(ArrowRightIcon, { className: "size-5" })] })] })] }), _jsx("div", { className: "relative mt-3 overflow-hidden", ref: emblaRef, children: _jsx("div", { className: "flex gap-3.5", children: gameProviders.map((provider, i) => (_jsx(Link, { href: `/${provider.slug}`, className: twMerge('bg-gray-800 lg:bg-bg-primary-900 min-w-27.5 rounded-md', classNames.providerThumbnailRoot), children: _jsx(Image, { src: jackpotsProps.gameProviderImages?.[provider.id] ??
46
- provider.logo, alt: "provider", className: twMerge('size-full', classNames.providerThumbnailImage), width: 300, height: 150 }) }, i))) }) })] }));
52
+ return (_jsxs("div", { className: "p-4 pb-8", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("div", { className: "text-text-primary-900 text-lg font-semibold", children: "Jackpot Game Providers" }), _jsxs("div", { className: "hidden lg:flex", children: [_jsx(Button, { disabled: !canScrollPrev, onClick: scrollPrev, variant: "outline", colorScheme: "gray", className: "rounded-r-none border-r-0", "aria-label": "Previous", children: _jsx(ArrowLeftIcon, { className: "size-5" }) }), _jsxs(Button, { disabled: !canScrollNext, onClick: scrollNext, variant: "outline", colorScheme: "gray", className: "rounded-l-none", "aria-label": "Next", children: [_jsx("span", { className: "sr-only", children: "Next" }), _jsx(ArrowRightIcon, { className: "size-5" })] })] })] }), _jsx("div", { className: "relative mt-3 overflow-hidden", ref: emblaRef, children: _jsx("div", { className: "flex gap-3.5", children: gameProviders.map((provider) => (_jsx(Link, { href: viewGamesUrl(provider), className: twMerge('bg-gray-800 lg:bg-bg-primary-900 min-w-27.5 rounded-md', classNames.providerThumbnailRoot), children: _jsx(Image, { src: jackpotsProps.gameProviderImages?.[provider.id] ??
53
+ provider.logo, alt: "provider", className: twMerge('size-full', classNames.providerThumbnailImage), width: 300, height: 150 }) }, provider.slug))) }) })] }));
47
54
  }
@@ -8,6 +8,7 @@ import { Camera01Icon } from '../../icons/Camera01Icon.js';
8
8
  import { XIcon } from '../../icons/XIcon.js';
9
9
  import { Button } from '../../ui/Button/index.js';
10
10
  import { Dialog } from '../../ui/Dialog/index.js';
11
+ import { onMobileDevice } from '../../utils/onMobileDevice.js';
11
12
  import { useKYCContext } from './KYCContext.js';
12
13
  import { loadFaceApiModels } from './loadModels.js';
13
14
  import PersonOverlayDesktop from './PersonOverlayDesktop.js';
@@ -15,6 +16,7 @@ import { dataURLtoBlob, resizeFile } from './utils.js';
15
16
  export function CaptureSelfie() {
16
17
  const kyc = useKYCContext();
17
18
  const { mutate, isPending } = useUploadImageFileMutation();
19
+ const mobileDevice = onMobileDevice();
18
20
  const [modelsLoaded, setModelsLoaded] = useState(false);
19
21
  const [videoStream, setVideoStream] = useState(null);
20
22
  const [cameraError, setCameraError] = useState(null);
@@ -40,8 +42,8 @@ export function CaptureSelfie() {
40
42
  setCaptureInProgress(true);
41
43
  const stream = await navigator.mediaDevices.getUserMedia({
42
44
  video: {
43
- facingMode: 'environment',
44
- aspectRatio: 16 / 9,
45
+ facingMode: mobileDevice ? 'user' : 'environment',
46
+ aspectRatio: mobileDevice ? 9 / 16 : 16 / 9,
45
47
  width: { ideal: 1920 },
46
48
  height: { ideal: 1080 },
47
49
  },
@@ -232,5 +234,5 @@ export function CaptureSelfie() {
232
234
  return (_jsxs("div", { className: "w-full p-4 lg:w-[640px] lg:px-12 lg:py-4xl", children: [_jsxs("button", { type: "button", className: "flex items-center gap-lg lg:absolute lg:right-5 lg:top-5 lg:p-md", onClick: () => {
233
235
  stopCamera();
234
236
  kyc.setCapturing(null);
235
- }, children: [_jsx(XIcon, { className: "text-text-senary-300 hidden size-5 lg:block" }), _jsx(ArrowLeftIcon, { className: "text-text-senary-300 size-5 lg:hidden" }), _jsx("span", { className: "text-lg font-semibold lg:hidden", children: "Back" })] }), _jsx(Dialog.Title, { className: "mt-6 text-center text-lg font-semibold lg:mt-0", children: "Take a Selfie Holding Your ID" }), _jsx(Dialog.Description, { className: "text-text-tertiary-600 mx-auto mt-sm max-w-[19rem] text-center text-sm lg:max-w-full", children: "Make sure your face and ID are clearly visible, with no glare or blur." }), _jsx("div", { id: "parent", className: "border-border-disabled mt-6 aspect-video rounded-xl border bg-black lg:mt-8", children: captureInProgress ? (_jsxs("div", { className: "relative flex h-fit w-full flex-col items-center justify-center", children: [_jsx("video", { ref: videoRef, autoPlay: true, muted: true, className: "h-[60vh] w-full rounded-lg object-cover md:aspect-video md:h-[333px]", width: "100%", height: "100%" }), _jsx("canvas", { ref: faceCanvasRef, className: "absolute inset-0 h-[60vh] w-full object-cover md:aspect-video md:h-[333px] md:object-contain", width: "100%", height: "100%" }), isFaceCentered && (_jsx("span", { className: "absolute inset-x-0 bottom-4 flex items-center justify-center text-5xl font-bold text-[#713B12CC]", children: countdown })), !isFaceCentered && (_jsx("span", { className: "absolute inset-x-0 bottom-12 mx-auto w-1/2 text-center text-xs text-white md:bottom-3", children: "Please center your face in the camera view. Make sure your ID is clearly visible next to your face." })), _jsx(PersonOverlayDesktop, { isFaceCentered: isFaceCentered, width: videoRef.current?.clientWidth || 543, height: videoRef.current?.clientHeight || 333 })] })) : (selectedImage && (_jsx("img", { src: selectedImage, alt: "Captured Photo", className: "h-full w-full rounded-lg object-cover md:aspect-video md:h-[333px]" }))) }), selectedImage && (_jsxs("div", { className: "flex flex-col items-center justify-center gap-2 md:flex-row", children: [_jsxs(Button, { className: "disabled:bg-bg-disabled disabled:text-text-disabled mx-auto mt-4 px-xl disabled:opacity-100", size: "sm", onClick: retakePhoto, type: "button", children: ["Rekate Photo", _jsx(Camera01Icon, { className: "size-5" })] }), _jsxs(Button, { className: "disabled:bg-bg-disabled disabled:text-text-disabled mx-auto mt-4 px-xl disabled:opacity-100", size: "sm", onClick: usePhoto, type: "button", disabled: isPending, children: ["Use Photo", _jsx(Camera01Icon, { className: "size-5" })] })] })), !selectedImage && (_jsxs(Button, { className: "mx-auto mt-6 px-xl lg:mt-8", onClick: startCamera, disabled: !modelsLoaded, type: "button", children: ["Start Camera", _jsx(Camera01Icon, { className: "size-5" })] }))] }));
237
+ }, children: [_jsx(XIcon, { className: "text-text-senary-300 hidden size-5 lg:block" }), _jsx(ArrowLeftIcon, { className: "text-text-senary-300 size-5 lg:hidden" }), _jsx("span", { className: "text-lg font-semibold lg:hidden", children: "Back" })] }), _jsx(Dialog.Title, { className: "mt-6 text-center text-lg font-semibold lg:mt-0", children: "Take a Selfie Holding Your ID" }), _jsx(Dialog.Description, { className: "text-text-tertiary-600 mx-auto mt-sm max-w-[19rem] text-center text-sm lg:max-w-full", children: "Make sure your face and ID are clearly visible, with no glare or blur." }), _jsx("div", { id: "parent", className: "border-border-disabled mt-6 aspect-video rounded-xl border bg-black lg:mt-8", children: captureInProgress ? (_jsxs("div", { className: "relative flex h-fit w-full flex-col items-center justify-center", children: [_jsx("video", { ref: videoRef, autoPlay: true, muted: true, className: "h-[60vh] w-full rounded-lg object-cover md:aspect-video md:h-[333px]", width: "100%", height: "100%" }), _jsx("canvas", { ref: faceCanvasRef, className: "absolute inset-0 h-[60vh] w-full object-cover md:aspect-video md:h-[333px] md:object-contain", width: "100%", height: "100%" }), isFaceCentered && (_jsx("span", { className: "absolute inset-x-0 bottom-8 flex items-center justify-center text-5xl font-bold text-[#713B12CC] md:bottom-4", children: countdown })), !isFaceCentered && (_jsx("span", { className: "absolute inset-x-0 bottom-12 mx-auto w-1/2 text-center text-xs text-white md:bottom-3", children: "Please center your face in the camera view. Make sure your ID is clearly visible next to your face." })), _jsx(PersonOverlayDesktop, { isFaceCentered: isFaceCentered, width: videoRef.current?.clientWidth || 543, height: videoRef.current?.clientHeight || 333 })] })) : (selectedImage && (_jsx("img", { src: selectedImage, alt: "Captured Photo", className: "h-full w-full rounded-lg object-cover md:aspect-video md:h-[333px]" }))) }), selectedImage && (_jsxs("div", { className: "flex flex-col items-center justify-center gap-2 md:flex-row", children: [_jsxs(Button, { className: "disabled:bg-bg-disabled disabled:text-text-disabled mx-auto mt-4 px-xl disabled:opacity-100", size: "sm", onClick: retakePhoto, type: "button", children: ["Rekate Photo", _jsx(Camera01Icon, { className: "size-5" })] }), _jsxs(Button, { className: "disabled:bg-bg-disabled disabled:text-text-disabled mx-auto mt-4 px-xl disabled:opacity-100", size: "sm", onClick: usePhoto, type: "button", disabled: isPending, children: ["Use Photo", _jsx(Camera01Icon, { className: "size-5" })] })] })), !selectedImage && (_jsxs(Button, { className: "mx-auto mt-6 px-xl lg:mt-8", onClick: startCamera, disabled: !modelsLoaded, type: "button", children: ["Start Camera", _jsx(Camera01Icon, { className: "size-5" })] }))] }));
236
238
  }
@@ -53,7 +53,14 @@ export function MobileNumberSignIn() {
53
53
  }),
54
54
  });
55
55
  const Step2Definition = z.object({
56
- verificationCode: z.array(z.string().min(1).max(1).trim()).min(6).max(6),
56
+ verificationCode: z.array(z.string()).superRefine((val, ctx) => {
57
+ if (val.length !== 6 || val.some((v) => v.length !== 1)) {
58
+ ctx.addIssue({
59
+ code: z.ZodIssueCode.custom,
60
+ message: 'Please Enter your 6 digits verification code',
61
+ });
62
+ }
63
+ }),
57
64
  });
58
65
  const step1Form = useForm({
59
66
  resolver: zodResolver(Step1Definition),
@@ -119,11 +126,12 @@ export function MobileNumberSignIn() {
119
126
  description: 'Invalid Verification Code',
120
127
  });
121
128
  }
122
- }), children: [_jsx(Controller, { name: "verificationCode", control: step2Form.control, render: (o) => (_jsxs(PinInput.Root, { placeholder: "0", invalid: o.fieldState.invalid, value: o.field.value, onValueChange: (details) => {
123
- o.field.onChange(details.value);
124
- }, otp: true, onValueComplete: () => {
125
- form2Ref.current?.requestSubmit();
126
- }, children: [_jsxs(PinInput.Control, { className: "grid-cols-[1fr_1fr_1fr_auto_1fr_1fr_1fr] items-center gap-md", children: [_jsx(PinInput.Input, { index: 0 }), _jsx(PinInput.Input, { index: 1 }), _jsx(PinInput.Input, { index: 2 }), _jsx("span", { className: "text-text-placeholder-subtle text-2xl font-medium", children: "\u2013" }), _jsx(PinInput.Input, { index: 3 }), _jsx(PinInput.Input, { index: 4 }), _jsx(PinInput.Input, { index: 5 })] }), _jsx(PinInput.HiddenInput, {})] })) }), _jsx(Button, { type: "submit", className: "mt-4xl", disabled: step2Form.formState.isSubmitting, children: "Verify" }), _jsxs("div", { className: "mt-3 flex w-full items-center justify-center gap-xs text-sm", children: [_jsx("span", { className: "text-text-secondary-700", children: "Didn't recieve the code?" }), _jsx("button", { type: "button", className: "text-button-secondary-fg font-semibold", disabled: cooldown.cooling, onClick: async () => {
129
+ }), children: [_jsx(Controller, { name: "verificationCode", control: step2Form.control, render: (o) => (_jsxs(Field.Root, { invalid: o.fieldState.invalid, children: [_jsxs(PinInput.Root, { placeholder: "0", value: o.field.value, onValueChange: (details) => {
130
+ o.field.onChange(details.value);
131
+ o.field.onBlur();
132
+ }, otp: true, onValueComplete: () => {
133
+ form2Ref.current?.requestSubmit();
134
+ }, children: [_jsxs(PinInput.Control, { className: "grid-cols-[1fr_1fr_1fr_auto_1fr_1fr_1fr] items-center gap-md", children: [_jsx(PinInput.Input, { index: 0 }), _jsx(PinInput.Input, { index: 1 }), _jsx(PinInput.Input, { index: 2 }), _jsx("span", { className: "text-text-placeholder-subtle text-2xl font-medium", children: "\u2013" }), _jsx(PinInput.Input, { index: 3 }), _jsx(PinInput.Input, { index: 4 }), _jsx(PinInput.Input, { index: 5 })] }), _jsx(PinInput.HiddenInput, {})] }), _jsx(Field.ErrorText, { children: o.fieldState.error?.message })] })) }), _jsx(Button, { type: "submit", className: "mt-4xl", disabled: step2Form.formState.isSubmitting, children: "Verify" }), _jsxs("div", { className: "mt-3 flex w-full items-center justify-center gap-xs text-sm", children: [_jsx("span", { className: "text-text-secondary-700", children: "Didn't recieve the code?" }), _jsx("button", { type: "button", className: "text-button-secondary-fg font-semibold", disabled: cooldown.cooling, onClick: async () => {
127
135
  await sendVerificationCodeMutation.mutateAsync({
128
136
  channel: 'SMS',
129
137
  recipient: mobileNumberParser.format(step1Form.getValues('mobileNumber')),
@@ -119,7 +119,7 @@ export function NameAndPasswordSignIn() {
119
119
  signInMutation.reset();
120
120
  form.setValue('secretAnswer', '');
121
121
  },
122
- }) }), _jsx(Field.ErrorText, { children: form.formState.errors.name?.message })] }), _jsx(Field.Root, { disabled: !!signInMutation.data, invalid: !!form.formState.errors.name, className: "mt-xl", children: _jsxs(PasswordInput.Root, { children: [_jsx(PasswordInput.Label, { children: "Password" }), _jsxs("div", { children: [_jsxs(PasswordInput.Control, { children: [_jsx(PasswordInput.Input, { placeholder: "Enter your password", ...form.register('password') }), _jsx(PasswordInput.VisibilityTrigger, { children: _jsx(PasswordInput.Indicator, { fallback: _jsx(EyeOffIcon, {}), asChild: true, children: _jsx(EyeIcon, {}) }) })] }), _jsx(Field.ErrorText, { children: form.formState.errors.password?.message })] }), _jsx("div", { className: "mt-2 flex justify-end", children: _jsx("button", { type: "button", tabIndex: -1, className: "text-button-tertiary-fg text-sm font-semibold", onClick: () => {
122
+ }) }), _jsx(Field.ErrorText, { children: form.formState.errors.name?.message })] }), _jsx(Field.Root, { disabled: !!signInMutation.data, invalid: !!form.formState.errors.password, className: "mt-xl", children: _jsxs(PasswordInput.Root, { children: [_jsx(PasswordInput.Label, { children: "Password" }), _jsxs("div", { children: [_jsxs(PasswordInput.Control, { children: [_jsx(PasswordInput.Input, { placeholder: "Enter your password", ...form.register('password') }), _jsx(PasswordInput.VisibilityTrigger, { children: _jsx(PasswordInput.Indicator, { fallback: _jsx(EyeOffIcon, {}), asChild: true, children: _jsx(EyeIcon, {}) }) })] }), _jsx(Field.ErrorText, { children: form.formState.errors.password?.message })] }), _jsx("div", { className: "mt-2 flex justify-end", children: _jsx("button", { type: "button", tabIndex: -1, className: "text-button-tertiary-fg text-sm font-semibold", onClick: () => {
123
123
  globalStore.forgotPassword.setOpen(true);
124
124
  dialog.setOpen(false);
125
125
  }, children: "Forgot Password" }) })] }) }), signInMutation.data && (_jsxs(Field.Root, { invalid: !!form.formState.errors.secretAnswer, className: "mt-xl", children: [_jsx(Field.Label, { children: secretQuestion }), _jsx(Field.Input, { placeholder: "Answer the security question", ...form.register('secretAnswer') }), _jsx(Field.ErrorText, { children: form.formState.errors.secretAnswer?.message })] })), _jsx(Button, { type: "submit", className: "mt-3xl", disabled: signInMutation.isPending || authenticateMutation.isPending, children: "Log In" }), _jsx(Controller, { control: form.control, name: "termsAccepted", render: (o) => (_jsxs(Field.Root, { invalid: o.fieldState.invalid, className: "mt-xl", children: [_jsxs(Checkbox.Root, { checked: o.field.value, onCheckedChange: (details) => {
@@ -54,7 +54,10 @@ export function SignUpForm() {
54
54
  const localeInfo = useLocaleInfo();
55
55
  const mobileNumberParser = useMobileNumberParser();
56
56
  const Step1Definition = z.object({
57
- mobileNumber: z.string().superRefine((v, ctx) => {
57
+ mobileNumber: z
58
+ .string()
59
+ .min(1, 'Mobile number is required')
60
+ .superRefine((v, ctx) => {
58
61
  if (!mobileNumberParser.validate(v)) {
59
62
  ctx.addIssue({
60
63
  code: z.ZodIssueCode.custom,
@@ -93,7 +96,14 @@ export function SignUpForm() {
93
96
  branchCode: z.string().min(1, 'Branch is required').trim(),
94
97
  });
95
98
  const Step2Definition = z.object({
96
- verificationCode: z.array(z.string().min(1).max(1).trim()).min(6).max(6),
99
+ verificationCode: z.array(z.string()).superRefine((val, ctx) => {
100
+ if (val.length !== 6 || val.some((v) => v.length !== 1)) {
101
+ ctx.addIssue({
102
+ code: z.ZodIssueCode.custom,
103
+ message: 'Please Enter your 6 digits verification code',
104
+ });
105
+ }
106
+ }),
97
107
  });
98
108
  const step1Form = useForm({
99
109
  mode: 'all',
@@ -118,7 +128,7 @@ export function SignUpForm() {
118
128
  const form2Ref = useRef(null);
119
129
  const birthDay = step1Form.watch('birthDay');
120
130
  const branchCode = step1Form.watch('branchCode', '');
121
- return (_jsxs(_Fragment, { children: [step === 1 && (_jsxs(_Fragment, { children: [_jsx("h2", { className: "mt-xl text-center text-lg font-semibold", children: "Create an account" }), _jsx("p", { className: "text-text-secondary-700 mt-xs text-center text-sm", children: "Register instantly and start playing!" }), _jsxs("form", { className: "mt-3xl", onSubmit: step1Form.handleSubmit(async (data) => {
131
+ return (_jsxs(_Fragment, { children: [step === 1 && (_jsxs(_Fragment, { children: [_jsx("h2", { className: "mt-xl text-center text-lg font-semibold", children: "Create an account" }), _jsx("p", { className: "text-text-secondary-700 mt-xs text-center text-sm", children: "Register instantly and start playing!" }), _jsxs("form", { className: "mt-3xl", autoComplete: "off", onSubmit: step1Form.handleSubmit(async (data) => {
122
132
  try {
123
133
  await sendVerificationCodeMutation.mutateAsync({
124
134
  channel: 'SMS',
@@ -145,7 +155,9 @@ export function SignUpForm() {
145
155
  shouldValidate: true,
146
156
  });
147
157
  }
148
- }, children: [_jsx(DatePicker.Label, { children: "Date of Birth" }), _jsxs(DatePicker.Control, { children: [_jsx(DatePicker.Input, {}), _jsx(DatePicker.Trigger, { children: _jsx(CalendarIcon, {}) })] }), _jsx(DatePicker.Positioner, { children: _jsx(DatePicker.Content, { children: _jsx(DatePicker.View, { view: "day", children: _jsx(DatePicker.Context, { children: (api) => (_jsxs(_Fragment, { children: [_jsxs(DatePicker.ViewControl, { children: [_jsx(DatePicker.PrevTrigger, { children: _jsx(ChevronLeftIcon, {}) }), _jsx(DatePicker.ViewTrigger, { children: _jsx(DatePicker.RangeText, {}) }), _jsx(DatePicker.NextTrigger, { children: _jsx(ChevronRightIcon, {}) })] }), _jsxs(DatePicker.Table, { children: [_jsx(DatePicker.TableHead, { children: _jsx(DatePicker.TableRow, { children: api.weekDays.map((weekDay, id) => (_jsx(DatePicker.TableHeader, { children: weekDay.short.substring(0, 2) }, id))) }) }), _jsx(DatePicker.TableBody, { children: api.weeks.map((week, id) => (_jsx(DatePicker.TableRow, { children: week.map((day, id) => (_jsx(DatePicker.TableCell, { value: day, children: _jsx(DatePicker.TableCellTrigger, { children: day.day }) }, id))) }, id))) })] })] })) }) }) }) })] }), _jsx(Field.ErrorText, { children: step1Form.formState.errors.birthDay?.message })] }), _jsxs(Field.Root, { invalid: !!step1Form.formState.errors.branchCode, className: "mt-xl", children: [_jsxs(Select.Root, { value: [branchCode], onValueChange: (details) => {
158
+ }, children: [_jsx(DatePicker.Label, { children: "Date of Birth" }), _jsxs(DatePicker.Control, { children: [_jsx(DatePicker.Input, { onBlur: () => {
159
+ step1Form.trigger('birthDay');
160
+ } }), _jsx(DatePicker.Trigger, { children: _jsx(CalendarIcon, {}) })] }), _jsx(DatePicker.Positioner, { children: _jsx(DatePicker.Content, { children: _jsx(DatePicker.View, { view: "day", children: _jsx(DatePicker.Context, { children: (api) => (_jsxs(_Fragment, { children: [_jsxs(DatePicker.ViewControl, { children: [_jsx(DatePicker.PrevTrigger, { children: _jsx(ChevronLeftIcon, {}) }), _jsx(DatePicker.ViewTrigger, { children: _jsx(DatePicker.RangeText, {}) }), _jsx(DatePicker.NextTrigger, { children: _jsx(ChevronRightIcon, {}) })] }), _jsxs(DatePicker.Table, { children: [_jsx(DatePicker.TableHead, { children: _jsx(DatePicker.TableRow, { children: api.weekDays.map((weekDay, id) => (_jsx(DatePicker.TableHeader, { children: weekDay.short.substring(0, 2) }, id))) }) }), _jsx(DatePicker.TableBody, { children: api.weeks.map((week, id) => (_jsx(DatePicker.TableRow, { children: week.map((day, id) => (_jsx(DatePicker.TableCell, { value: day, children: _jsx(DatePicker.TableCellTrigger, { children: day.day }) }, id))) }, id))) })] })] })) }) }) }) })] }), _jsx(Field.ErrorText, { children: step1Form.formState.errors.birthDay?.message })] }), _jsxs(Field.Root, { invalid: !!step1Form.formState.errors.branchCode, className: "mt-xl", children: [_jsxs(Select.Root, { value: [branchCode], onValueChange: (details) => {
149
161
  step1Form.setValue('branchCode', details.value.at(0) ?? BRANCH_CODES[0].code, {
150
162
  shouldDirty: true,
151
163
  shouldTouch: true,
@@ -213,17 +225,16 @@ export function SignUpForm() {
213
225
  }
214
226
  catch (error) {
215
227
  toaster.error({
216
- title: 'Login Failed',
217
228
  description: error instanceof Error
218
229
  ? error.message
219
230
  : 'Invalid Verification Code',
220
231
  });
221
232
  }
222
- }), children: [_jsx(Controller, { name: "verificationCode", control: step2Form.control, render: (o) => (_jsxs(PinInput.Root, { placeholder: "0", invalid: o.fieldState.invalid, value: o.field.value, onValueChange: (details) => {
223
- o.field.onChange(details.value);
224
- }, onValueComplete: () => {
225
- form2Ref.current?.requestSubmit();
226
- }, children: [_jsxs(PinInput.Control, { className: "grid-cols-[1fr_1fr_1fr_auto_1fr_1fr_1fr] items-center gap-md", children: [_jsx(PinInput.Input, { index: 0 }), _jsx(PinInput.Input, { index: 1 }), _jsx(PinInput.Input, { index: 2 }), _jsx("span", { className: "text-text-placeholder-subtle text-2xl font-medium", children: "\u2013" }), _jsx(PinInput.Input, { index: 3 }), _jsx(PinInput.Input, { index: 4 }), _jsx(PinInput.Input, { index: 5 })] }), _jsx(PinInput.HiddenInput, {})] })) }), _jsx(Button, { type: "submit", className: "mt-4xl", disabled: step2Form.formState.isSubmitting, children: "Verify" }), _jsxs("div", { className: "mt-3 flex w-full items-center justify-center gap-xs text-sm", children: [_jsx("span", { className: "text-text-secondary-700", children: "Didn't recieve the code?" }), _jsx("button", { type: "button", className: "text-button-secondary-fg font-semibold disabled:cursor-not-allowed disabled:opacity-75", disabled: cooldown.cooling, onClick: async () => {
233
+ }), children: [_jsx(Controller, { name: "verificationCode", control: step2Form.control, render: (o) => (_jsxs(Field.Root, { invalid: o.fieldState.invalid, children: [_jsxs(PinInput.Root, { placeholder: "0", value: o.field.value, onValueChange: (details) => {
234
+ o.field.onChange(details.value);
235
+ }, onValueComplete: () => {
236
+ form2Ref.current?.requestSubmit();
237
+ }, children: [_jsxs(PinInput.Control, { className: "grid-cols-[1fr_1fr_1fr_auto_1fr_1fr_1fr] items-center gap-md", children: [_jsx(PinInput.Input, { index: 0 }), _jsx(PinInput.Input, { index: 1 }), _jsx(PinInput.Input, { index: 2 }), _jsx("span", { className: "text-text-placeholder-subtle text-2xl font-medium", children: "\u2013" }), _jsx(PinInput.Input, { index: 3 }), _jsx(PinInput.Input, { index: 4 }), _jsx(PinInput.Input, { index: 5 })] }), _jsx(PinInput.HiddenInput, {})] }), _jsx(Field.ErrorText, { children: o.formState.errors.verificationCode?.message })] })) }), _jsx(Button, { type: "submit", className: "mt-4xl", disabled: step2Form.formState.isSubmitting, children: "Verify" }), _jsxs("div", { className: "mt-3 flex w-full items-center justify-center gap-xs text-sm", children: [_jsx("span", { className: "text-text-secondary-700", children: "Didn't recieve the code?" }), _jsx("button", { type: "button", className: "text-button-secondary-fg font-semibold disabled:cursor-not-allowed disabled:opacity-75", disabled: cooldown.cooling, onClick: async () => {
227
238
  await sendVerificationCodeMutation.mutateAsync({
228
239
  channel: 'SMS',
229
240
  recipient: mobileNumberParser.format(step1Form.getValues('mobileNumber')),
@@ -1,5 +1,5 @@
1
1
  import type { CSSProperties } from 'react';
2
- import type { GameProvider } from '../../../types';
2
+ import type { GameProvider, GameProviderData } from '../../../types';
3
3
  interface ClassNameEntries {
4
4
  root?: string;
5
5
  itemRoot?: string;
@@ -21,6 +21,17 @@ export interface TournamentsListProps {
21
21
  className?: string | ClassNameEntries;
22
22
  style?: CSSProperties | StyleEntries;
23
23
  gameProviders: GameProvider[];
24
+ /**
25
+ * @default '/providers/<slug>'
26
+ * @example
27
+ * ```ts
28
+ * '/bingo/:slug' // '/bingo/jili'
29
+ * '/bingo/:id' // '/bingo/JILI'
30
+ * // or using a function
31
+ * ({slug}) => `/bingo/${slug}`
32
+ * ```
33
+ */
34
+ viewGamesUrl?: string | ((gameProvider: GameProviderData) => string);
24
35
  }
25
36
  export declare function TournamentsList({ style, className, ...props }: TournamentsListProps): import("react/jsx-runtime").JSX.Element;
26
37
  export {};
@@ -6,7 +6,17 @@ import { useCallback, useEffect, useState } from 'react';
6
6
  import { ArrowLeftIcon } from '../../../icons/ArrowLeftIcon.js';
7
7
  import { ArrowRightIcon } from '../../../icons/ArrowRightIcon.js';
8
8
  import { Button } from '../../../ui/Button/index.js';
9
+ import { callIfFn } from '../../../utils/callIfFn.js';
10
+ import { useTournamentsListPropsContext } from './TournamentsListContext.js';
9
11
  export function TournamentsListItemGameProviders({ gameProviders, }) {
12
+ const tournamentsProps = useTournamentsListPropsContext();
13
+ const viewGamesUrl = (data) => {
14
+ return tournamentsProps.viewGamesUrl
15
+ ? callIfFn(tournamentsProps.viewGamesUrl, data)
16
+ .replace(':id', data.id)
17
+ .replace(':slug', data.slug)
18
+ : `/providers/${data.slug}`;
19
+ };
10
20
  const [emblaRef, emblaApi] = useEmblaCarousel({
11
21
  align: 'start',
12
22
  slidesToScroll: 1,
@@ -34,5 +44,5 @@ export function TournamentsListItemGameProviders({ gameProviders, }) {
34
44
  emblaApi.on('select', onSelect);
35
45
  emblaApi.on('reInit', onSelect);
36
46
  }, [emblaApi, onSelect]);
37
- return (_jsxs("div", { className: "p-4 pb-8", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("div", { className: "text-text-primary-900 text-lg font-semibold", children: "Tournament Game Providers" }), _jsxs("div", { className: "hidden lg:flex", children: [_jsx(Button, { disabled: !canScrollPrev, onClick: scrollPrev, variant: "outline", colorScheme: "gray", className: "rounded-r-none border-r-0", "aria-label": "Previous", children: _jsx(ArrowLeftIcon, { className: "size-5" }) }), _jsxs(Button, { disabled: !canScrollNext, onClick: scrollNext, variant: "outline", colorScheme: "gray", className: "rounded-l-none", "aria-label": "Next", children: [_jsx("span", { className: "sr-only", children: "Next" }), _jsx(ArrowRightIcon, { className: "size-5" })] })] })] }), _jsx("div", { className: "relative mt-3 overflow-hidden", ref: emblaRef, children: _jsx("div", { className: "flex gap-3.5", children: gameProviders.map((provider, i) => (_jsx(Link, { href: `/${provider.slug}`, className: "bg-gray-800 lg:bg-bg-primary-900 min-w-27.5 rounded-md", children: _jsx(Image, { src: provider.logo, alt: "provider", className: "size-full", width: 300, height: 150 }) }, i))) }) })] }));
47
+ return (_jsxs("div", { className: "p-4 pb-8", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("div", { className: "text-text-primary-900 text-lg font-semibold", children: "Tournament Game Providers" }), _jsxs("div", { className: "hidden lg:flex", children: [_jsx(Button, { disabled: !canScrollPrev, onClick: scrollPrev, variant: "outline", colorScheme: "gray", className: "rounded-r-none border-r-0", "aria-label": "Previous", children: _jsx(ArrowLeftIcon, { className: "size-5" }) }), _jsxs(Button, { disabled: !canScrollNext, onClick: scrollNext, variant: "outline", colorScheme: "gray", className: "rounded-l-none", "aria-label": "Next", children: [_jsx("span", { className: "sr-only", children: "Next" }), _jsx(ArrowRightIcon, { className: "size-5" })] })] })] }), _jsx("div", { className: "relative mt-3 overflow-hidden", ref: emblaRef, children: _jsx("div", { className: "flex gap-3.5", children: gameProviders.map((provider) => (_jsx(Link, { href: viewGamesUrl(provider), className: "bg-gray-800 lg:bg-bg-primary-900 min-w-27.5 rounded-md", children: _jsx(Image, { src: provider.logo, alt: "provider", className: "size-full", width: 300, height: 150 }) }, provider.slug))) }) })] }));
38
48
  }