@opexa/portal-components 0.0.963 → 0.0.965
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.
- package/dist/lib/components/GamesSearch/GamesSearch.js +8 -5
- package/dist/lib/components/Search/Search.lazy.js +8 -5
- package/dist/lib/components/UpdateMobilePhoneNumber/UpdateMobilePhoneNumber.d.ts +1 -0
- package/dist/lib/components/UpdateMobilePhoneNumber/UpdateMobilePhoneNumber.js +152 -0
- package/dist/lib/components/UpdateMobilePhoneNumber/index.d.ts +1 -0
- package/dist/lib/components/UpdateMobilePhoneNumber/index.js +1 -0
- package/package.json +1 -1
|
@@ -8,6 +8,7 @@ import { twMerge } from 'tailwind-merge';
|
|
|
8
8
|
import { useBypassKycChecker, } from '../../client/hooks/useBypassKycChecker.js';
|
|
9
9
|
import { useGamesQuery } from '../../client/hooks/useGamesQuery.js';
|
|
10
10
|
import { SearchLgIcon } from '../../icons/SearchLgIcon.js';
|
|
11
|
+
import RainbowballImg from '../../images/rainbow-ball-online.webp';
|
|
11
12
|
import { Button } from '../../ui/Button/index.js';
|
|
12
13
|
import { Combobox } from '../../ui/Combobox/index.js';
|
|
13
14
|
import { Portal } from '../../ui/Portal/index.js';
|
|
@@ -60,11 +61,13 @@ export function GamesSearch(props) {
|
|
|
60
61
|
}, children: "Clear" }));
|
|
61
62
|
} })] }), _jsx(Portal, { children: _jsx(Combobox.Context, { children: (api) => (_jsxs(_Fragment, { children: [_jsx(Presence, { present: api.open, children: _jsx("div", { className: "fixed inset-0 z-40 bg-black/50 backdrop-blur-sm", onClick: () => api.setOpen(false) }) }), _jsx(Combobox.Positioner, { children: searchInput.trim().length > 0 && (_jsx(Combobox.Content, { className: "z-50 max-h-[33.25rem] overflow-y-auto p-0", children: showMinLengthWarning ? (_jsx(Alert, { message: "Search requires at least 3 characters." })) : (_jsxs(_Fragment, { children: [games.length <= 0 && (_jsx(Alert, { message: "No results found" })), games.length > 0 && (_jsxs("div", { className: "p-xl", children: [_jsx(Combobox.Context, { children: (api) => (_jsx("div", { className: twMerge('grid grid-cols-3 gap-1.5 lg:grid-cols-9 lg:gap-3.5', classNames.gameSearchResult), children: games.map((game) => (_jsxs(GameLaunchTrigger, { bypassKycCheck: isBypass, game: game, onClick: () => {
|
|
62
63
|
api.setOpen(false);
|
|
63
|
-
}, className: twMerge('md:hover:-translate-y-1 relative flex h-full w-full flex-col shadow-sm transition-transform duration-200', classNames.thumbnailRoot), children: [_jsx(Image, { src:
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
}, className: twMerge('md:hover:-translate-y-1 relative flex h-full w-full flex-col shadow-sm transition-transform duration-200', classNames.thumbnailRoot), children: [_jsx(Image, { src: game.name === 'Rainbow Ball'
|
|
65
|
+
? RainbowballImg
|
|
66
|
+
: getGameImageUrl({
|
|
67
|
+
reference: game.reference,
|
|
68
|
+
provider: game.provider,
|
|
69
|
+
image: game.image,
|
|
70
|
+
}), alt: "", width: 200, height: 200, loading: "lazy", unoptimized: true, className: "aspect-square w-full rounded-t-md object-cover" }), _jsx("span", { className: twMerge('flex w-full flex-1 items-center justify-center break-words rounded-b-md bg-bg-tertiary px-2 py-2.5 text-center font-semibold text-text-primary-brand text-xs', classNames.thumbnailTitle), children: fixMojibake(game.name) })] }, game.id))) })) }), _jsx(Presence, { present: gamesQuery.hasNextPage, children: _jsx(Button, { variant: "outline", className: twMerge('mx-auto mt-4xl w-fit', classNames.loadMoreButton), onClick: () => gamesQuery.fetchNextPage(), children: "Load More" }) })] }))] })) })) })] })) }) })] }) }));
|
|
68
71
|
}
|
|
69
72
|
function Alert({ message }) {
|
|
70
73
|
return (_jsxs("div", { className: "py-lg", role: "alert", "aria-live": "polite", children: [_jsx("div", { className: "mx-auto flex size-12 items-center justify-center rounded-lg border border-border-primary bg-bg-secondary shadow-xs", children: _jsx(SearchLgIcon, { className: "size-6 text-text-secondary-700" }) }), _jsx("p", { className: "mt-4 text-center text-text-secondary-700", children: message })] }));
|
|
@@ -14,6 +14,7 @@ import { useGlobalStore } from '../../client/hooks/useGlobalStore.js';
|
|
|
14
14
|
import { GAME_PROVIDER_DATA } from '../../constants/index.js';
|
|
15
15
|
import { SearchLgIcon } from '../../icons/SearchLgIcon.js';
|
|
16
16
|
import { XIcon } from '../../icons/XIcon.js';
|
|
17
|
+
import RainbowballImg from '../../images/rainbow-ball-online.webp';
|
|
17
18
|
import { Button } from '../../ui/Button/index.js';
|
|
18
19
|
import { Dialog } from '../../ui/Dialog/index.js';
|
|
19
20
|
import { Field } from '../../ui/Field/index.js';
|
|
@@ -86,11 +87,13 @@ export function Search(props) {
|
|
|
86
87
|
}, children: _jsx(Image, { src: props.gameProviderImages?.[provider.id] ??
|
|
87
88
|
provider.logo, alt: "", width: 100, height: 50, className: twMerge('mx-auto h-auto w-full', classNames.providerThumbnailImage) }) }, provider.id))) })] })), games.length > 0 && (_jsxs(_Fragment, { children: [_jsx("h2", { className: "font-semibold text-lg", children: "Games" }), _jsx("div", { className: twMerge('mt-3.5 grid grid-cols-3 gap-1.5 lg:grid-cols-9 lg:gap-3.5', classNames.gameSearchResult), children: games.map((game) => (_jsxs(GameLaunchTrigger, { bypassKycCheck: isBypass, game: game, className: twMerge('block w-full shadow-sm', classNames.gameThumbnailRoot), onClick: () => {
|
|
88
89
|
globalStore.search.setOpen(false);
|
|
89
|
-
}, children: [_jsx(Image, { src:
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
}, children: [_jsx(Image, { src: game.name === 'Rainbow Ball'
|
|
91
|
+
? RainbowballImg
|
|
92
|
+
: getGameImageUrl({
|
|
93
|
+
reference: game.reference,
|
|
94
|
+
provider: game.provider,
|
|
95
|
+
image: game.image,
|
|
96
|
+
}), alt: "", width: 200, height: 200, loading: "lazy", unoptimized: true, className: "aspect-square w-full rounded-t-md object-cover" }), _jsx("span", { className: twMerge('block w-full rounded-b-md bg-bg-tertiary px-2 py-2.5 text-center font-semibold text-text-primary-brand text-xs', props.variant !== '88play' && 'truncate', classNames.gameThumbnailTitle), children: fixMojibake(game.name) })] }, game.id))) })] })), _jsx(Presence, { present: gamesQuery.hasNextPage, children: _jsx(Button, { variant: "outline", className: twMerge('mx-auto mt-12 w-fit', classNames.loadMoreButton), onClick: () => gamesQuery.fetchNextPage(), children: "Load More" }) })] }))] })) }) })] }) })] }) }));
|
|
94
97
|
}
|
|
95
98
|
function DebouncedInput(props) {
|
|
96
99
|
const [value, setValue] = useControllableState({
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function UpdateMobilePhoneNumber(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
4
|
+
import Image from 'next/image';
|
|
5
|
+
import { useEffect, useRef, useState } from 'react';
|
|
6
|
+
import { Controller, useForm } from 'react-hook-form';
|
|
7
|
+
import z from 'zod';
|
|
8
|
+
import { useAccountQuery } from '../../client/hooks/useAccountQuery.js';
|
|
9
|
+
import { useCooldown } from '../../client/hooks/useCooldown.js';
|
|
10
|
+
import { useLocaleInfo } from '../../client/hooks/useLocaleInfo.js';
|
|
11
|
+
import { useMobileNumberParser } from '../../client/hooks/useMobileNumberParser.js';
|
|
12
|
+
import { useSendVerificationCodeMutation } from '../../client/hooks/useSendVerificationCodeMutation.js';
|
|
13
|
+
import { useUpdateMobileNumber } from '../../client/hooks/useUpdateMobileNumber.js';
|
|
14
|
+
import { toaster } from '../../client/utils/toaster.js';
|
|
15
|
+
import { ArrowLeftIcon } from '../../icons/ArrowLeftIcon.js';
|
|
16
|
+
import inplayLogo from '../../images/inplay-logo.png';
|
|
17
|
+
import lightBg from '../../images/light-bg.png';
|
|
18
|
+
import { Button } from '../../ui/Button/index.js';
|
|
19
|
+
import { Dialog } from '../../ui/Dialog/index.js';
|
|
20
|
+
import { Field } from '../../ui/Field/index.js';
|
|
21
|
+
import { PinInput } from '../../ui/PinInput/index.js';
|
|
22
|
+
import { Portal } from '../../ui/Portal/index.js';
|
|
23
|
+
export function UpdateMobilePhoneNumber() {
|
|
24
|
+
const accountQuery = useAccountQuery();
|
|
25
|
+
const account = accountQuery.data;
|
|
26
|
+
const isAccountLoading = accountQuery.isLoading;
|
|
27
|
+
const hasMobileNumber = !!account?.mobileNumber;
|
|
28
|
+
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
|
29
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: We only want to open the dialog when the account data loads, not on every hasMobileNumber change
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (!isAccountLoading && !!account && !hasMobileNumber) {
|
|
32
|
+
setIsDialogOpen(true);
|
|
33
|
+
}
|
|
34
|
+
else if (!isAccountLoading && !!account && hasMobileNumber) {
|
|
35
|
+
setIsDialogOpen(false);
|
|
36
|
+
}
|
|
37
|
+
}, [isAccountLoading, account?.mobileNumber]);
|
|
38
|
+
const [step, setStep] = useState(1);
|
|
39
|
+
const sendVerificationCodeMutation = useSendVerificationCodeMutation({
|
|
40
|
+
onSuccess: () => {
|
|
41
|
+
setStep(2);
|
|
42
|
+
cooldown.start();
|
|
43
|
+
},
|
|
44
|
+
onError: (err) => {
|
|
45
|
+
toaster.error({
|
|
46
|
+
title: 'Sign In Failed',
|
|
47
|
+
description: err.message,
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
const updateMobileNumberMutation = useUpdateMobileNumber({
|
|
52
|
+
onSuccess: async () => {
|
|
53
|
+
step1Form.reset();
|
|
54
|
+
step2Form.reset();
|
|
55
|
+
setStep(1);
|
|
56
|
+
toaster.success({
|
|
57
|
+
title: 'Verification Successful',
|
|
58
|
+
description: 'Your mobile number has been verified.',
|
|
59
|
+
});
|
|
60
|
+
setIsDialogOpen(false);
|
|
61
|
+
console.log('mobile number updated successfully');
|
|
62
|
+
},
|
|
63
|
+
onError: (err) => {
|
|
64
|
+
console.log('error updating mobile number');
|
|
65
|
+
const errorMessage = err.message === 'Internal Server Error'
|
|
66
|
+
? `mobile number ${mobileNumberParser.format(step1Form.getValues('mobileNumber'))} is not available`
|
|
67
|
+
: err.message;
|
|
68
|
+
toaster.error({
|
|
69
|
+
title: 'Sign In Failed',
|
|
70
|
+
description: errorMessage,
|
|
71
|
+
});
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
const localeInfo = useLocaleInfo();
|
|
75
|
+
const mobileNumberParser = useMobileNumberParser();
|
|
76
|
+
const Step1Definition = z.object({
|
|
77
|
+
mobileNumber: z
|
|
78
|
+
.string()
|
|
79
|
+
.min(1, 'Mobile number is required')
|
|
80
|
+
.superRefine((v, ctx) => {
|
|
81
|
+
if (!mobileNumberParser.validate(v)) {
|
|
82
|
+
ctx.addIssue({
|
|
83
|
+
code: z.ZodIssueCode.custom,
|
|
84
|
+
message: 'Invalid mobile number',
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}),
|
|
88
|
+
});
|
|
89
|
+
const Step2Definition = z.object({
|
|
90
|
+
verificationCode: z.array(z.string()).superRefine((val, ctx) => {
|
|
91
|
+
if (val.length !== 6 || val.some((v) => v.length !== 1)) {
|
|
92
|
+
ctx.addIssue({
|
|
93
|
+
code: z.ZodIssueCode.custom,
|
|
94
|
+
message: 'Please enter your 6-digit verification code.',
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}),
|
|
98
|
+
});
|
|
99
|
+
const step1Form = useForm({
|
|
100
|
+
resolver: zodResolver(Step1Definition),
|
|
101
|
+
defaultValues: {
|
|
102
|
+
mobileNumber: '',
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
const step2Form = useForm({
|
|
106
|
+
resolver: zodResolver(Step2Definition),
|
|
107
|
+
defaultValues: {
|
|
108
|
+
verificationCode: Array.from({ length: 6 }).fill(''),
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
const cooldown = useCooldown({
|
|
112
|
+
max: 60,
|
|
113
|
+
duration: 1000 * 60,
|
|
114
|
+
});
|
|
115
|
+
const formRef = useRef(null);
|
|
116
|
+
return (_jsx(Dialog.Root, { open: isDialogOpen, lazyMount: true, unmountOnExit: true, closeOnEscape: false, closeOnInteractOutside: false, children: _jsxs(Portal, { children: [_jsx(Dialog.Backdrop, {}), _jsx(Dialog.Positioner, { className: "flex items-center", children: _jsxs(Dialog.Content, { className: "flex w-[375px] flex-col items-center space-y-4 rounded-xl bg-[#111827] p-6 text-center", style: {
|
|
117
|
+
backgroundImage: `url(${lightBg.src})`,
|
|
118
|
+
}, children: [_jsx(Image, { src: inplayLogo, alt: "inplay logo", width: 82, height: 34, className: "h-auto w-[82px]" }), _jsxs("div", { children: [step === 1 && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "font-bold text-sm", children: ["Get ", _jsx("span", { className: "text-[#F05127]", children: "\u20B150 Bonus" }), " when you verify your account and play."] }), _jsxs("form", { className: "mt-3xl", onSubmit: step1Form.handleSubmit(async (data) => {
|
|
119
|
+
sendVerificationCodeMutation.mutateAsync({
|
|
120
|
+
channel: 'SMS',
|
|
121
|
+
recipient: mobileNumberParser.format(data.mobileNumber),
|
|
122
|
+
});
|
|
123
|
+
}), children: [_jsxs(Field.Root, { invalid: !!step1Form.formState.errors.mobileNumber, className: "text-left", children: [_jsx(Field.Label, { children: "Mobile Number" }), _jsxs("div", { className: "relative", children: [_jsx("div", { className: "-translate-y-1/2 absolute top-1/2 left-3.5 flex shrink-0 items-center gap-md", children: _jsx("span", { className: "text-text-placeholder", children: localeInfo.mobileNumber.areaCode }) }), _jsx(Field.Input, { style: {
|
|
124
|
+
paddingLeft: `calc(1.25rem + ${localeInfo.mobileNumber.areaCode.length}ch)`,
|
|
125
|
+
}, ...step1Form.register('mobileNumber') })] }), _jsx(Field.ErrorText, { children: step1Form.formState.errors.mobileNumber?.message })] }), _jsx(Button, { type: "submit", className: "mt-3xl", disabled: step1Form.formState.isSubmitting, children: "Send Code" })] })] })), step === 2 && (_jsxs(_Fragment, { children: [_jsx("h2", { className: "mt-xl text-center font-semibold text-lg", children: "Check your Phone" }), _jsxs("p", { className: "mt-xs text-center text-sm text-text-secondary-700", children: ["We\u2019ve sent a verification code to your mobile number", ' ', _jsx("span", { className: "font-semibold text-[#F05127]", children: mobileNumberParser.format(step1Form.getValues('mobileNumber')) }), ' ', "via text"] }), _jsxs("form", { ref: formRef, className: "mt-5", onSubmit: step2Form.handleSubmit(async ({ verificationCode }) => {
|
|
126
|
+
updateMobileNumberMutation.mutateAsync({
|
|
127
|
+
mobileNumber: mobileNumberParser.format(step1Form.getValues('mobileNumber')),
|
|
128
|
+
verificationCode: verificationCode.join(''),
|
|
129
|
+
});
|
|
130
|
+
}), children: [_jsx(Controller, { name: "verificationCode", control: step2Form.control, render: (o) => (_jsxs(Field.Root, { invalid: o.fieldState.invalid, children: [_jsxs(PinInput.Root, { placeholder: "0", onKeyDown: (e) => {
|
|
131
|
+
if (e.key === 'Backspace') {
|
|
132
|
+
step2Form.reset();
|
|
133
|
+
}
|
|
134
|
+
}, value: o.field.value, onValueChange: (details) => {
|
|
135
|
+
o.field.onChange(details.value);
|
|
136
|
+
o.field.onBlur();
|
|
137
|
+
}, otp: true, onValueComplete: () => {
|
|
138
|
+
formRef.current?.requestSubmit();
|
|
139
|
+
}, blurOnComplete: true, readOnly: step2Form.formState.isSubmitting, type: "numeric", 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: "font-medium text-2xl text-text-placeholder-subtle", 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-4 flex w-full items-center justify-center gap-xs text-xs", children: [_jsx("span", { className: "text-[#9CA3AF]", children: "Didn't receive the code?" }), _jsx("button", { type: "button", className: "font-semibold text-[#C084FC]", disabled: cooldown.cooling, onClick: async () => {
|
|
140
|
+
await sendVerificationCodeMutation.mutateAsync({
|
|
141
|
+
channel: 'SMS',
|
|
142
|
+
recipient: mobileNumberParser.format(step1Form.getValues('mobileNumber')),
|
|
143
|
+
});
|
|
144
|
+
cooldown.start();
|
|
145
|
+
}, children: cooldown.cooling
|
|
146
|
+
? `Resend in ${cooldown.countdown}s`
|
|
147
|
+
: 'Resend' })] }), _jsx("button", { type: "button", className: "absolute top-0 left-6 mx-auto mt-3xl flex h-8 w-8 items-center gap-1 rounded-full bg-[#1f2638] font-semibold text-sm text-text-tertiary-600", onClick: () => {
|
|
148
|
+
setStep(1);
|
|
149
|
+
step2Form.reset();
|
|
150
|
+
cooldown.stop();
|
|
151
|
+
}, children: _jsx(ArrowLeftIcon, { className: "mx-auto size-5" }) })] })] }))] })] }) })] }) }));
|
|
152
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './UpdateMobilePhoneNumber';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './UpdateMobilePhoneNumber.js';
|