@opexa/portal-components 0.0.986 → 0.0.987
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/client/hooks/useGlobalStore.d.ts +0 -1
- package/dist/client/hooks/useGlobalStore.js +0 -13
- package/dist/components/AccountInfo/AccountInfo.js +0 -2
- package/dist/components/KYC/KYCDefault/KYCDefault.d.ts +0 -1
- package/dist/components/KYC/KYCDefault/KYCDefault.js +2 -3
- package/dist/components/KYC/KYCDefault/PersonalInformation.js +0 -7
- package/dist/components/KYC/KYCDefault/useKYCDefault.d.ts +1 -2
- package/dist/components/KYC/KYCDefault/useKYCDefault.js +1 -2
- package/dist/components/KYC/KycOpenOnHomeMount.js +61 -37
- package/dist/components/Messages/MessagesPopup.js +1 -3
- package/dist/components/SignUp/SignUp.d.ts +2 -1
- package/dist/components/SignUp/SignUp.js +7 -0
- package/dist/components/SignUp/SignUpKYC/SignUpKYCForm.js +22 -2
- package/dist/components/SignUp/SignUpLongForm/SignUpLongForm.d.ts +1 -0
- package/dist/components/SignUp/SignUpLongForm/SignUpLongForm.js +466 -0
- package/dist/components/SignUp/SignUpLongForm/SignUpLongForm.lazy.d.ts +18 -0
- package/dist/components/SignUp/SignUpLongForm/SignUpLongForm.lazy.js +26 -0
- package/dist/components/SignUp/SignUpLongForm/SignUpLongFormContext.d.ts +6 -0
- package/dist/components/SignUp/SignUpLongForm/SignUpLongFormContext.js +2 -0
- package/package.json +1 -1
|
@@ -226,19 +226,6 @@ export const useGlobalStore = create()(devtools(subscribeWithSelector((set) => (
|
|
|
226
226
|
},
|
|
227
227
|
'~touched': false,
|
|
228
228
|
},
|
|
229
|
-
kycReview: {
|
|
230
|
-
open: false,
|
|
231
|
-
setOpen(open) {
|
|
232
|
-
set((prev) => ({
|
|
233
|
-
kycReview: {
|
|
234
|
-
...prev.kycReview,
|
|
235
|
-
open,
|
|
236
|
-
'~touched': true,
|
|
237
|
-
},
|
|
238
|
-
}));
|
|
239
|
-
},
|
|
240
|
-
'~touched': false,
|
|
241
|
-
},
|
|
242
229
|
updateMobilePhoneNumber: {
|
|
243
230
|
open: false,
|
|
244
231
|
setOpen(open) {
|
|
@@ -13,7 +13,6 @@ import { z } from 'zod';
|
|
|
13
13
|
import { useAccountQuery } from '../../client/hooks/useAccountQuery.js';
|
|
14
14
|
import { useDisclosure } from '../../client/hooks/useDisclosure.js';
|
|
15
15
|
import { useFacebookClientQuery } from '../../client/hooks/useFacebookClientQuery.js';
|
|
16
|
-
import { useFeatureFlag } from '../../client/hooks/useFeatureFlag.js';
|
|
17
16
|
import { useGoogleClientIdQuery } from '../../client/hooks/useGoogleClientIdQuery.js';
|
|
18
17
|
import { useLocaleInfo } from '../../client/hooks/useLocaleInfo.js';
|
|
19
18
|
import { useMemberVerificationQuery } from '../../client/hooks/useMemberVerificationQuery.js';
|
|
@@ -95,7 +94,6 @@ function ProfileInfo({ avatar }) {
|
|
|
95
94
|
function PersonalInfo(props) {
|
|
96
95
|
const accountQuery = useAccountQuery();
|
|
97
96
|
const account = accountQuery.data;
|
|
98
|
-
const featureFlag = useFeatureFlag();
|
|
99
97
|
const verificationQuery = useMemberVerificationQuery();
|
|
100
98
|
const verification = verificationQuery.data;
|
|
101
99
|
const updateAccountMutation = useUpdateAccountMutation({
|
|
@@ -6,7 +6,6 @@ import { useSessionQuery } from '../../../client/hooks/useSessionQuery.js';
|
|
|
6
6
|
import { Dialog } from '../../../ui/Dialog/index.js';
|
|
7
7
|
import { Portal } from '../../../ui/Portal/index.js';
|
|
8
8
|
import { KYCReminder } from '../KYCReminder.js';
|
|
9
|
-
import { KYCReview } from '../KYCReview.js';
|
|
10
9
|
import { IdentityVerification } from './IdentityVerification.js';
|
|
11
10
|
import { Indicator } from './Indicator.js';
|
|
12
11
|
import { KYCDefaultContext } from './KYCDefaultContext.js';
|
|
@@ -21,7 +20,7 @@ export function KYCDefault(props) {
|
|
|
21
20
|
updateMobilePhoneNumber: ctx.updateMobilePhoneNumber,
|
|
22
21
|
termsOfUse: ctx.termsOfUse,
|
|
23
22
|
})));
|
|
24
|
-
const kyc = useKYCDefault(props?.isSkippable ?? false
|
|
23
|
+
const kyc = useKYCDefault(props?.isSkippable ?? false);
|
|
25
24
|
const { isLoading: sessionLoading } = useSessionQuery();
|
|
26
25
|
const isDialogOpen = globalStore.kyc.open &&
|
|
27
26
|
!globalStore.kycReminder.open &&
|
|
@@ -31,5 +30,5 @@ export function KYCDefault(props) {
|
|
|
31
30
|
!globalStore.termsOfUse.open;
|
|
32
31
|
return (_jsxs(_Fragment, { children: [_jsx(KYCDefaultContext, { value: kyc, children: _jsx(Dialog.Root, { open: isDialogOpen, onOpenChange: (details) => {
|
|
33
32
|
globalStore.kyc.setOpen(details.open);
|
|
34
|
-
}, lazyMount: true, unmountOnExit: true, closeOnEscape: false, closeOnInteractOutside: false, onExitComplete: kyc.reset, children: _jsxs(Portal, { children: [_jsx(Dialog.Backdrop, {}), _jsx(Dialog.Positioner, { className: "fixed top-[57px] overflow-hidden", children: _jsx(Dialog.Content, { className: "mx-auto h-full w-full overflow-y-auto bg-bg-primary-alt md:max-h-[95vh] lg:w-fit", children: _jsxs("div", { className: "mt-[calc(var(--safe-area-inset-top)*3)] flex h-dvh w-full flex-col overflow-y-auto p-3xl sm:h-fit lg:w-[400px]", children: [_jsx(Image, { src: props.logo, alt: "", width: 200, height: 100, className: "mx-auto mb-5 block h-7.5 w-auto", draggable: false }), _jsx(Indicator, {}), kyc.step === 1 && _jsx(IdentityVerification, {}), kyc.step === 2 && _jsx(PersonalInformation, {})] }) }) })] }) }) }), _jsx(KYCReminder, { ...props })
|
|
33
|
+
}, lazyMount: true, unmountOnExit: true, closeOnEscape: false, closeOnInteractOutside: false, onExitComplete: kyc.reset, children: _jsxs(Portal, { children: [_jsx(Dialog.Backdrop, {}), _jsx(Dialog.Positioner, { className: "fixed top-[57px] overflow-hidden", children: _jsx(Dialog.Content, { className: "mx-auto h-full w-full overflow-y-auto bg-bg-primary-alt md:max-h-[95vh] lg:w-fit", children: _jsxs("div", { className: "mt-[calc(var(--safe-area-inset-top)*3)] flex h-dvh w-full flex-col overflow-y-auto p-3xl sm:h-fit lg:w-[400px]", children: [_jsx(Image, { src: props.logo, alt: "", width: 200, height: 100, className: "mx-auto mb-5 block h-7.5 w-auto", draggable: false }), _jsx(Indicator, {}), kyc.step === 1 && _jsx(IdentityVerification, {}), kyc.step === 2 && _jsx(PersonalInformation, {})] }) }) })] }) }) }), _jsx(KYCReminder, { ...props })] }));
|
|
35
34
|
}
|
|
@@ -37,7 +37,6 @@ export function PersonalInformation() {
|
|
|
37
37
|
const globalStore = useGlobalStore(useShallow((ctx) => ({
|
|
38
38
|
kyc: ctx.kyc,
|
|
39
39
|
kycReminder: ctx.kycReminder,
|
|
40
|
-
kycReview: ctx.kycReview,
|
|
41
40
|
})));
|
|
42
41
|
const memberVerificationQuery = useMemberVerificationQuery();
|
|
43
42
|
const memberVerificationId = memberVerificationQuery.data?.id;
|
|
@@ -77,9 +76,6 @@ export function PersonalInformation() {
|
|
|
77
76
|
});
|
|
78
77
|
kyc.setDone(true);
|
|
79
78
|
globalStore.kyc.setOpen(false);
|
|
80
|
-
if (kyc.hasKYCReviewPromo) {
|
|
81
|
-
globalStore.kycReview.setOpen(true);
|
|
82
|
-
}
|
|
83
79
|
toaster.success({
|
|
84
80
|
title: 'Success',
|
|
85
81
|
description: 'Personal information has been set successfully.',
|
|
@@ -95,9 +91,6 @@ export function PersonalInformation() {
|
|
|
95
91
|
onSuccess() {
|
|
96
92
|
kyc.setDone(true);
|
|
97
93
|
globalStore.kyc.setOpen(false);
|
|
98
|
-
if (kyc.hasKYCReviewPromo) {
|
|
99
|
-
globalStore.kycReview.setOpen(true);
|
|
100
|
-
}
|
|
101
94
|
toaster.success({
|
|
102
95
|
title: 'Success',
|
|
103
96
|
description: 'Personal information has been set successfully.',
|
|
@@ -21,7 +21,6 @@ export interface UseKYCDefaultReturn {
|
|
|
21
21
|
setSelfieImageId: (id: string | null) => void;
|
|
22
22
|
reset: () => void;
|
|
23
23
|
isSkippable: boolean;
|
|
24
|
-
hasKYCReviewPromo: boolean;
|
|
25
24
|
}
|
|
26
|
-
export declare function useKYCDefault(isSkippable: boolean
|
|
25
|
+
export declare function useKYCDefault(isSkippable: boolean): UseKYCDefaultReturn;
|
|
27
26
|
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useRef, useState } from 'react';
|
|
2
|
-
export function useKYCDefault(isSkippable
|
|
2
|
+
export function useKYCDefault(isSkippable) {
|
|
3
3
|
const [step, setStep] = useState(1);
|
|
4
4
|
const [done, setDone] = useState(false);
|
|
5
5
|
const [idFrontImageId, setFrontImageId] = useState(null);
|
|
@@ -35,6 +35,5 @@ export function useKYCDefault(isSkippable, hasKYCReviewPromo) {
|
|
|
35
35
|
triggerEvent,
|
|
36
36
|
reset,
|
|
37
37
|
isSkippable,
|
|
38
|
-
hasKYCReviewPromo,
|
|
39
38
|
};
|
|
40
39
|
}
|
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
'use client';
|
|
2
|
+
import { useIsMutating } from '@tanstack/react-query';
|
|
2
3
|
import { useEffect } from 'react';
|
|
3
4
|
import { useAccountQuery } from '../../client/hooks/useAccountQuery.js';
|
|
4
5
|
import { useGlobalStore } from '../../client/hooks/useGlobalStore.js';
|
|
5
6
|
import { useMemberVerificationQuery } from '../../client/hooks/useMemberVerificationQuery.js';
|
|
7
|
+
import { getApproveMemberVerificationQueryKey, getCreateMemberVerificationQueryKey, } from '../../utils/mutationKeys.js';
|
|
6
8
|
export function KycOpenOnHomeMount(props) {
|
|
7
9
|
const setkycReminderOpen = useGlobalStore((s) => s.kycReminder.setOpen);
|
|
8
10
|
const setkycOpen = useGlobalStore((s) => s.kyc.setOpen);
|
|
9
|
-
const
|
|
11
|
+
const isSignUpOpen = useGlobalStore((s) => s.signUp.open);
|
|
12
|
+
const { data: verification, isLoading: verificationLoading, isRefetching: verificationRefetching, } = useMemberVerificationQuery();
|
|
10
13
|
const { data: account, isLoading: accountLoading } = useAccountQuery();
|
|
14
|
+
const isCreatingVerification = useIsMutating({
|
|
15
|
+
mutationKey: getCreateMemberVerificationQueryKey(),
|
|
16
|
+
});
|
|
17
|
+
const isApprovingVerification = useIsMutating({
|
|
18
|
+
mutationKey: getApproveMemberVerificationQueryKey(),
|
|
19
|
+
});
|
|
20
|
+
const isMutating = isCreatingVerification > 0 || isApprovingVerification > 0;
|
|
11
21
|
const isVerificationLocked = account?.status === 'VERIFICATION_LOCKED';
|
|
12
22
|
const isRejected = verification?.status === 'REJECTED';
|
|
13
23
|
const isUnverified = verification === null ||
|
|
@@ -17,62 +27,76 @@ export function KycOpenOnHomeMount(props) {
|
|
|
17
27
|
verification?.sumsubVerified ||
|
|
18
28
|
verification?.status === 'APPROVED' ||
|
|
19
29
|
verification?.status === 'VERIFIED'; //add default value on null return
|
|
30
|
+
const isPending = verification?.status === 'PENDING';
|
|
20
31
|
useEffect(() => {
|
|
21
32
|
// If bypass is enabled, do nothing.
|
|
22
33
|
if (props.bypassKycCheck) {
|
|
23
34
|
return;
|
|
24
35
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
// 2. Handle member verification locked
|
|
37
|
-
if (isVerificationLocked) {
|
|
38
|
-
setkycReminderOpen(true);
|
|
39
|
-
setkycOpen(false);
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
// Handle rejected verification status
|
|
43
|
-
else if (isRejected) {
|
|
44
|
-
if (isFirstVisit) {
|
|
36
|
+
const timeoutId = setTimeout(() => {
|
|
37
|
+
if (!verificationLoading &&
|
|
38
|
+
!accountLoading &&
|
|
39
|
+
!verificationRefetching &&
|
|
40
|
+
!isMutating &&
|
|
41
|
+
!isSignUpOpen) {
|
|
42
|
+
const shouldShowReminder = Boolean(props.isSkippable);
|
|
43
|
+
const hasSeenKycModal = sessionStorage.getItem('hasSeenKycModal');
|
|
44
|
+
const isFirstVisit = !hasSeenKycModal;
|
|
45
|
+
// 1. If KYC is completed or pending, close both
|
|
46
|
+
if (isKycCompleted || isPending) {
|
|
45
47
|
setkycReminderOpen(false);
|
|
46
|
-
setkycOpen(
|
|
47
|
-
|
|
48
|
+
setkycOpen(false);
|
|
49
|
+
if (isKycCompleted) {
|
|
50
|
+
sessionStorage.removeItem('hasSeenKycModal');
|
|
51
|
+
}
|
|
52
|
+
return;
|
|
48
53
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
54
|
+
// 2. Handle member verification locked
|
|
55
|
+
if (isVerificationLocked) {
|
|
56
|
+
setkycReminderOpen(true);
|
|
57
|
+
setkycOpen(false);
|
|
58
|
+
return;
|
|
52
59
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
// Handle rejected verification status
|
|
61
|
+
else if (isRejected) {
|
|
62
|
+
if (isFirstVisit) {
|
|
63
|
+
setkycReminderOpen(false);
|
|
64
|
+
setkycOpen(true);
|
|
65
|
+
sessionStorage.setItem('hasSeenKycModal', 'true');
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
setkycReminderOpen(shouldShowReminder);
|
|
69
|
+
setkycOpen(!shouldShowReminder);
|
|
70
|
+
}
|
|
60
71
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
72
|
+
// Handle unverified verification status
|
|
73
|
+
else if (isUnverified) {
|
|
74
|
+
if (isFirstVisit) {
|
|
75
|
+
setkycReminderOpen(false);
|
|
76
|
+
setkycOpen(true);
|
|
77
|
+
sessionStorage.setItem('hasSeenKycModal', 'true');
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
setkycReminderOpen(shouldShowReminder);
|
|
81
|
+
setkycOpen(!shouldShowReminder);
|
|
82
|
+
}
|
|
64
83
|
}
|
|
65
84
|
}
|
|
66
|
-
}
|
|
85
|
+
}, 1000);
|
|
86
|
+
return () => clearTimeout(timeoutId);
|
|
67
87
|
}, [
|
|
68
88
|
setkycReminderOpen,
|
|
69
89
|
setkycOpen,
|
|
70
90
|
verificationLoading,
|
|
71
91
|
accountLoading,
|
|
92
|
+
verificationRefetching,
|
|
93
|
+
isMutating,
|
|
94
|
+
isSignUpOpen,
|
|
72
95
|
isVerificationLocked,
|
|
73
96
|
isRejected,
|
|
74
97
|
isUnverified,
|
|
75
98
|
isKycCompleted,
|
|
99
|
+
isPending,
|
|
76
100
|
props.isSkippable,
|
|
77
101
|
props.bypassKycCheck,
|
|
78
102
|
]);
|
|
@@ -34,7 +34,6 @@ export function MessagesPopup() {
|
|
|
34
34
|
gameLaunch: ctx.gameLaunch,
|
|
35
35
|
depositWithdrawal: ctx.depositWithdrawal,
|
|
36
36
|
kycReminder: ctx.kycReminder,
|
|
37
|
-
kycReview: ctx.kycReview,
|
|
38
37
|
responsibleGamingReminder: ctx.responsibleGamingReminder,
|
|
39
38
|
signIn: ctx.signIn,
|
|
40
39
|
signUp: ctx.signUp,
|
|
@@ -84,7 +83,6 @@ export function MessagesPopup() {
|
|
|
84
83
|
!globalStore.account__mobile.open &&
|
|
85
84
|
!globalStore.kyc.open &&
|
|
86
85
|
!globalStore.kycReminder.open &&
|
|
87
|
-
!globalStore.kycReview.open &&
|
|
88
86
|
!globalStore.responsibleGamingReminder.open,
|
|
89
87
|
refetchInterval: disclosure.open ? undefined : 1000 * 10,
|
|
90
88
|
});
|
|
@@ -124,7 +122,7 @@ export function MessagesPopup() {
|
|
|
124
122
|
};
|
|
125
123
|
const sessionQuery = useSessionQuery();
|
|
126
124
|
const session = sessionQuery.data;
|
|
127
|
-
const isKycClosed = !globalStore.kyc.open && !globalStore.kycReminder.open
|
|
125
|
+
const isKycClosed = !globalStore.kyc.open && !globalStore.kycReminder.open;
|
|
128
126
|
const isResponsibleGamingReminderClosed = !globalStore.responsibleGamingReminder.open;
|
|
129
127
|
useEffect(() => {
|
|
130
128
|
if (globalStore.gameLaunch.details.status === 'PLAYING')
|
|
@@ -2,6 +2,7 @@ import type { SignUpCrazyWinProps } from './SignUpCrazyWin/SignUpCrazyWin.lazy';
|
|
|
2
2
|
import type { SignUpDefaultProps } from './SignUpDefault/SignUpDefault.lazy';
|
|
3
3
|
import type { SignUpHappyBingoProps } from './SignUpHappyBingo/SignUpHappyBingo.lazy';
|
|
4
4
|
import type { SignUpKYCProps } from './SignUpKYC/SignUpKYC.lazy';
|
|
5
|
+
import type { SignUpLongFormProps } from './SignUpLongForm/SignUpLongForm.lazy';
|
|
5
6
|
import type { SignUpNameAndPasswordProps } from './SignUpNameAndPassword/SignUpNameAndPassword.lazy';
|
|
6
|
-
export type SignUpProps = SignUpDefaultProps | SignUpKYCProps | SignUpNameAndPasswordProps | SignUpCrazyWinProps | SignUpHappyBingoProps;
|
|
7
|
+
export type SignUpProps = SignUpDefaultProps | SignUpKYCProps | SignUpNameAndPasswordProps | SignUpCrazyWinProps | SignUpHappyBingoProps | SignUpLongFormProps;
|
|
7
8
|
export declare function SignUp(props: SignUpProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -23,6 +23,10 @@ const SignUpHappyBingo = dynamic(() => import('./SignUpHappyBingo/SignUpHappyBin
|
|
|
23
23
|
ssr: false,
|
|
24
24
|
loading: () => null,
|
|
25
25
|
});
|
|
26
|
+
const SignUpLongForm = dynamic(() => import('./SignUpLongForm/SignUpLongForm.lazy.js').then((m) => m.default), {
|
|
27
|
+
ssr: false,
|
|
28
|
+
loading: () => null,
|
|
29
|
+
});
|
|
26
30
|
export function SignUp(props) {
|
|
27
31
|
const touched = useGlobalStore(useShallow((ctx) => ctx.signUp['~touched']));
|
|
28
32
|
if (!touched) {
|
|
@@ -40,6 +44,9 @@ export function SignUp(props) {
|
|
|
40
44
|
else if (props.layout === 'happybingo') {
|
|
41
45
|
return _jsx(SignUpHappyBingo, { ...props });
|
|
42
46
|
}
|
|
47
|
+
else if (props.layout === 'long-form') {
|
|
48
|
+
return _jsx(SignUpLongForm, { ...props });
|
|
49
|
+
}
|
|
43
50
|
else {
|
|
44
51
|
return _jsx(SignUpDefault, { ...props });
|
|
45
52
|
}
|
|
@@ -25,6 +25,7 @@ import { useSignUpMutation } from '../../../client/hooks/useSignUpMutation.js';
|
|
|
25
25
|
import { useUpdateAccountMutation } from '../../../client/hooks/useUpdateAccountMutation.js';
|
|
26
26
|
import { useUpdateMemberVerificationMutation } from '../../../client/hooks/useUpdateMemberVerificationMutation.js';
|
|
27
27
|
import { useUploadImageFileMutation } from '../../../client/hooks/useUploadImageFileMutation.js';
|
|
28
|
+
import { getSession } from '../../../client/services/getSession.js';
|
|
28
29
|
import { toaster } from '../../../client/utils/toaster.js';
|
|
29
30
|
import { ArrowLeftIcon } from '../../../icons/ArrowLeftIcon.js';
|
|
30
31
|
import { CalendarIcon } from '../../../icons/CalendarIcon.js';
|
|
@@ -36,6 +37,7 @@ import { EyeIcon } from '../../../icons/EyeIcon.js';
|
|
|
36
37
|
import { EyeOffIcon } from '../../../icons/EyeOffIcon.js';
|
|
37
38
|
import pagcorLogo from '../../../images/pagcor-round-icon.png';
|
|
38
39
|
import responsibleGamingLogo from '../../../images/responsible-gaming-gold.png';
|
|
40
|
+
import { getFile } from '../../../services/file.js';
|
|
39
41
|
import { ObjectType } from '../../../services/ObjectType.js';
|
|
40
42
|
import { Button } from '../../../ui/Button/index.js';
|
|
41
43
|
import { Checkbox } from '../../../ui/Checkbox/index.js';
|
|
@@ -387,12 +389,30 @@ export function SignUpKYCForm() {
|
|
|
387
389
|
}
|
|
388
390
|
const selfieImageId = await uploadImage(selfieFile);
|
|
389
391
|
const frontImageId = await uploadImage(frontFile);
|
|
392
|
+
const session = await getSession();
|
|
393
|
+
const pollImageStatus = (id) => createPoll(async () => {
|
|
394
|
+
const file = await getFile(id, {
|
|
395
|
+
headers: {
|
|
396
|
+
Authorization: `Bearer ${session.token}`,
|
|
397
|
+
},
|
|
398
|
+
});
|
|
399
|
+
return file?.status === 'READY';
|
|
400
|
+
}, {
|
|
401
|
+
until: (ok) => ok,
|
|
402
|
+
interval: 500,
|
|
403
|
+
maxAttempt: 10,
|
|
404
|
+
})();
|
|
405
|
+
const [selfieReady, frontReady] = await Promise.all([
|
|
406
|
+
pollImageStatus(selfieImageId),
|
|
407
|
+
pollImageStatus(frontImageId),
|
|
408
|
+
]);
|
|
409
|
+
if (!selfieReady || !frontReady) {
|
|
410
|
+
throw new Error('Images were not processed in time');
|
|
411
|
+
}
|
|
390
412
|
const data = {
|
|
391
413
|
selfieImage: selfieImageId,
|
|
392
414
|
idFrontImage: frontImageId,
|
|
393
415
|
};
|
|
394
|
-
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
395
|
-
await delay(2000);
|
|
396
416
|
if (!memberId) {
|
|
397
417
|
await createMemberVerification({
|
|
398
418
|
...data,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function SignUpLongForm(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { createListCollection, useDialogContext } from '@ark-ui/react';
|
|
4
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
5
|
+
import { ObjectId } from '@opexa/object-id';
|
|
6
|
+
import { differenceInYears, format } from 'date-fns';
|
|
7
|
+
import { useRouter, useSearchParams } from 'next/navigation';
|
|
8
|
+
import { useRef, useState } from 'react';
|
|
9
|
+
import { Controller, useForm } from 'react-hook-form';
|
|
10
|
+
import { twMerge } from 'tailwind-merge';
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
import { useShallow } from 'zustand/shallow';
|
|
13
|
+
import { useApproveMemberVerification } from '../../../client/hooks/useApproveMemberVerification.js';
|
|
14
|
+
import { useCooldown } from '../../../client/hooks/useCooldown.js';
|
|
15
|
+
import { useCreateMemberVerificationMutation } from '../../../client/hooks/useCreateMemberVerificationMutation.js';
|
|
16
|
+
import { useGlobalStore } from '../../../client/hooks/useGlobalStore.js';
|
|
17
|
+
import { useLocaleInfo } from '../../../client/hooks/useLocaleInfo.js';
|
|
18
|
+
import { useMemberVerificationQuery } from '../../../client/hooks/useMemberVerificationQuery.js';
|
|
19
|
+
import { useMobileNumberParser } from '../../../client/hooks/useMobileNumberParser.js';
|
|
20
|
+
import { useSendVerificationCodeMutation } from '../../../client/hooks/useSendVerificationCodeMutation.js';
|
|
21
|
+
import { useSignInMutation } from '../../../client/hooks/useSignInMutation.js';
|
|
22
|
+
import { useSignUpMutation } from '../../../client/hooks/useSignUpMutation.js';
|
|
23
|
+
import { useUpdateMemberVerificationMutation } from '../../../client/hooks/useUpdateMemberVerificationMutation.js';
|
|
24
|
+
import { useUploadImageFileMutation } from '../../../client/hooks/useUploadImageFileMutation.js';
|
|
25
|
+
import { getSession } from '../../../client/services/getSession.js';
|
|
26
|
+
import { toaster } from '../../../client/utils/toaster.js';
|
|
27
|
+
import { ArrowLeftIcon } from '../../../icons/ArrowLeftIcon.js';
|
|
28
|
+
import { CheckIcon } from '../../../icons/CheckIcon.js';
|
|
29
|
+
import { ChevronDownIcon } from '../../../icons/ChevronDownIcon.js';
|
|
30
|
+
import { getFile } from '../../../services/file.js';
|
|
31
|
+
import { ObjectType } from '../../../services/ObjectType.js';
|
|
32
|
+
import { Button } from '../../../ui/Button/index.js';
|
|
33
|
+
import { Checkbox } from '../../../ui/Checkbox/index.js';
|
|
34
|
+
import { Field } from '../../../ui/Field/index.js';
|
|
35
|
+
import { PinInput } from '../../../ui/PinInput/index.js';
|
|
36
|
+
import { Select } from '../../../ui/Select/index.js';
|
|
37
|
+
import { createPoll } from '../../../utils/createPoll.js';
|
|
38
|
+
import { getQueryClient } from '../../../utils/getQueryClient.js';
|
|
39
|
+
import { getMemberVerificationQueryKey } from '../../../utils/queryKeys.js';
|
|
40
|
+
import DateOfBirthField from '../../DateOfBirthField.js';
|
|
41
|
+
import { IdFrontImageField } from '../SignUpKYC/IdFrontImageField/index.js';
|
|
42
|
+
import { SelfieImageField } from '../SignUpKYC/SelfieImageField/index.js';
|
|
43
|
+
import { useSignUpLongFormPropsContext } from './SignUpLongFormContext.js';
|
|
44
|
+
export default function SignUpLongForm() {
|
|
45
|
+
const props = useSignUpLongFormPropsContext();
|
|
46
|
+
const dialog = useDialogContext();
|
|
47
|
+
const branchCollection = createListCollection({
|
|
48
|
+
items: props.branches ?? [],
|
|
49
|
+
itemToValue: (item) => item.code,
|
|
50
|
+
itemToString: (item) => {
|
|
51
|
+
const name = (item.name ?? '').trim();
|
|
52
|
+
return name ? `${item.code} - ${name}` : item.code;
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
const search = useSearchParams();
|
|
56
|
+
const globalStore = useGlobalStore(useShallow((ctx) => ({
|
|
57
|
+
signIn: ctx.signIn,
|
|
58
|
+
signUp: ctx.signUp,
|
|
59
|
+
kycReminder: ctx.kycReminder,
|
|
60
|
+
termsAndConditions: ctx.termsAndConditions,
|
|
61
|
+
responsibleGaming: ctx.responsibleGaming,
|
|
62
|
+
kyc: ctx.kyc,
|
|
63
|
+
})));
|
|
64
|
+
const [step, setStep] = useState(1);
|
|
65
|
+
const form2LongForm = useRef(null);
|
|
66
|
+
const mobileNumberParser = useMobileNumberParser();
|
|
67
|
+
const signUpMutation = useSignUpMutation();
|
|
68
|
+
const signInMutation = useSignInMutation();
|
|
69
|
+
const approveMemberVerificationMutation = useApproveMemberVerification();
|
|
70
|
+
const sendVerificationCodeMutation = useSendVerificationCodeMutation();
|
|
71
|
+
const { mutate: uploadImageFile } = useUploadImageFileMutation();
|
|
72
|
+
const localeInfo = useLocaleInfo();
|
|
73
|
+
const router = useRouter();
|
|
74
|
+
const fileSchema = z
|
|
75
|
+
.instanceof(File)
|
|
76
|
+
.nullable()
|
|
77
|
+
.refine((file) => !file || file.size > 0, {
|
|
78
|
+
message: 'File is required.',
|
|
79
|
+
})
|
|
80
|
+
.refine((file) => file?.type.startsWith('image/'), {
|
|
81
|
+
message: 'File must be an image.',
|
|
82
|
+
});
|
|
83
|
+
const Step1Definition = z.object({
|
|
84
|
+
mobileNumber: z
|
|
85
|
+
.string()
|
|
86
|
+
.min(1, 'Mobile number is required')
|
|
87
|
+
.superRefine((v, ctx) => {
|
|
88
|
+
if (!mobileNumberParser.validate(v)) {
|
|
89
|
+
ctx.addIssue({
|
|
90
|
+
code: z.ZodIssueCode.custom,
|
|
91
|
+
message: 'Invalid mobile number',
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}),
|
|
95
|
+
fullName: z
|
|
96
|
+
.string()
|
|
97
|
+
.min(8, 'Full name is required')
|
|
98
|
+
.max(30, 'Full name must not be more than 30 characters')
|
|
99
|
+
.trim(),
|
|
100
|
+
birthDay: z
|
|
101
|
+
.date({
|
|
102
|
+
invalid_type_error: 'Date of birth is required',
|
|
103
|
+
required_error: 'Date of birth is required',
|
|
104
|
+
})
|
|
105
|
+
.superRefine((val, ctx) => {
|
|
106
|
+
const now = new Date();
|
|
107
|
+
const age = differenceInYears(now, val);
|
|
108
|
+
if (age < 21) {
|
|
109
|
+
return ctx.addIssue({
|
|
110
|
+
code: z.ZodIssueCode.custom,
|
|
111
|
+
message: 'You must be at least 21 years old',
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}),
|
|
115
|
+
branchCode: (props?.branches ?? []).length > 0
|
|
116
|
+
? z.string().min(1, 'Branch is required').trim()
|
|
117
|
+
: z.string().optional(),
|
|
118
|
+
selfieImage: fileSchema,
|
|
119
|
+
frontImage: fileSchema,
|
|
120
|
+
placeOfBirth: z.string().min(1, 'Place of birth is required').trim(),
|
|
121
|
+
currentAddress: z.string().min(1, 'Current address is required'),
|
|
122
|
+
permanentAddress: z.string().min(1, 'Permanent address is required'),
|
|
123
|
+
nationality: z.string().min(1, 'Nationality is required').trim(),
|
|
124
|
+
natureOfWork: z.string().min(1, 'Nature of work is required').trim(),
|
|
125
|
+
sourceOfIncome: z.string().min(1, 'Source of income is required').trim(),
|
|
126
|
+
termsAccepted: z.boolean().superRefine((v, ctx) => {
|
|
127
|
+
if (!v) {
|
|
128
|
+
ctx.addIssue({
|
|
129
|
+
code: z.ZodIssueCode.custom,
|
|
130
|
+
message: 'You must accept the terms and conditions and the responsible gaming guidelines',
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}),
|
|
134
|
+
});
|
|
135
|
+
const Step2Definition = z.object({
|
|
136
|
+
verificationCode: z.array(z.string()).superRefine((val, ctx) => {
|
|
137
|
+
if (val.length !== 6 || val.some((v) => v.length !== 1)) {
|
|
138
|
+
ctx.addIssue({
|
|
139
|
+
code: z.ZodIssueCode.custom,
|
|
140
|
+
message: 'Please enter your 6-digit verification code.',
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}),
|
|
144
|
+
});
|
|
145
|
+
const step1Form = useForm({
|
|
146
|
+
resolver: zodResolver(Step1Definition),
|
|
147
|
+
defaultValues: {
|
|
148
|
+
mobileNumber: '',
|
|
149
|
+
birthDay: undefined,
|
|
150
|
+
branchCode: '',
|
|
151
|
+
selfieImage: undefined,
|
|
152
|
+
frontImage: undefined,
|
|
153
|
+
placeOfBirth: '',
|
|
154
|
+
currentAddress: '',
|
|
155
|
+
permanentAddress: '',
|
|
156
|
+
nationality: '',
|
|
157
|
+
natureOfWork: '',
|
|
158
|
+
sourceOfIncome: '',
|
|
159
|
+
termsAccepted: false,
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
const step2Form = useForm({
|
|
163
|
+
resolver: zodResolver(Step2Definition),
|
|
164
|
+
defaultValues: {
|
|
165
|
+
verificationCode: Array.from({ length: 6 }).fill(''),
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
const { mutateAsync: createMemberVerification } = useCreateMemberVerificationMutation({
|
|
169
|
+
onError: (error) => {
|
|
170
|
+
toaster.error({
|
|
171
|
+
title: 'Failed to upload ID Front Image & Selfie Image',
|
|
172
|
+
description: error.message,
|
|
173
|
+
});
|
|
174
|
+
},
|
|
175
|
+
onMutate: async (data) => {
|
|
176
|
+
const queryClient = getQueryClient();
|
|
177
|
+
await queryClient.cancelQueries({
|
|
178
|
+
queryKey: getMemberVerificationQueryKey(),
|
|
179
|
+
});
|
|
180
|
+
queryClient.setQueryData(getMemberVerificationQueryKey(), (prev) => {
|
|
181
|
+
if (!prev)
|
|
182
|
+
return prev;
|
|
183
|
+
return {
|
|
184
|
+
...prev,
|
|
185
|
+
status: 'PENDING',
|
|
186
|
+
};
|
|
187
|
+
});
|
|
188
|
+
},
|
|
189
|
+
onSuccess: () => {
|
|
190
|
+
const queryClient = getQueryClient();
|
|
191
|
+
queryClient.invalidateQueries({
|
|
192
|
+
queryKey: getMemberVerificationQueryKey(),
|
|
193
|
+
});
|
|
194
|
+
dialog.setOpen(false);
|
|
195
|
+
step1Form.reset();
|
|
196
|
+
step2Form.reset();
|
|
197
|
+
setStep(1);
|
|
198
|
+
toaster.success({
|
|
199
|
+
title: 'Member Created!',
|
|
200
|
+
description: 'Member created successfully',
|
|
201
|
+
});
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
const { mutateAsync: updateMemberVerification } = useUpdateMemberVerificationMutation({
|
|
205
|
+
onSuccess: () => {
|
|
206
|
+
const queryClient = getQueryClient();
|
|
207
|
+
queryClient.setQueryData(getMemberVerificationQueryKey(), (prev) => {
|
|
208
|
+
if (!prev)
|
|
209
|
+
return prev;
|
|
210
|
+
return {
|
|
211
|
+
...prev,
|
|
212
|
+
status: 'CREATED',
|
|
213
|
+
};
|
|
214
|
+
});
|
|
215
|
+
},
|
|
216
|
+
onError: (error) => {
|
|
217
|
+
toaster.error({
|
|
218
|
+
title: 'Failed to upload ID Front Image & Selfie Image',
|
|
219
|
+
description: error.message,
|
|
220
|
+
});
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
const cooldown = useCooldown({
|
|
224
|
+
max: 60,
|
|
225
|
+
duration: 1000 * 60,
|
|
226
|
+
});
|
|
227
|
+
const birthDay = step1Form.watch('birthDay');
|
|
228
|
+
const branchCode = step1Form.watch('branchCode', '');
|
|
229
|
+
const address = step1Form.watch('currentAddress');
|
|
230
|
+
const permanentAddress = step1Form.watch('permanentAddress');
|
|
231
|
+
const memberVerification = useMemberVerificationQuery();
|
|
232
|
+
const memberId = memberVerification.data?.id;
|
|
233
|
+
return (_jsx(_Fragment, { children: step === 1 ? (_jsxs(_Fragment, { children: [_jsx("h2", { className: "mt-xl text-center font-semibold text-lg", children: "Create an account" }), _jsx("p", { className: "mt-xs text-center text-sm text-text-secondary-700", children: "Register instantly and start playing!" }), _jsxs("form", { className: "mt-3xl pb-3xl", autoComplete: "off", onSubmit: step1Form.handleSubmit(async (data) => {
|
|
234
|
+
try {
|
|
235
|
+
await sendVerificationCodeMutation.mutateAsync({
|
|
236
|
+
channel: 'SMS',
|
|
237
|
+
recipient: mobileNumberParser.format(data.mobileNumber),
|
|
238
|
+
});
|
|
239
|
+
setStep(2);
|
|
240
|
+
cooldown.start();
|
|
241
|
+
}
|
|
242
|
+
catch (e) {
|
|
243
|
+
toaster.error({
|
|
244
|
+
description: e instanceof Error
|
|
245
|
+
? e.message
|
|
246
|
+
: 'Failed to send verification code',
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}), children: [_jsxs(Field.Root, { invalid: !!step1Form.formState.errors.fullName, className: "mt-xl", children: [_jsx(Field.Label, { children: "Full Name" }), _jsx(Field.Input, { placeholder: "Enter full name", ...step1Form.register('fullName') }), _jsx(Field.ErrorText, { children: step1Form.formState.errors.fullName?.message })] }), _jsxs(Field.Root, { invalid: !!step1Form.formState.errors.mobileNumber, className: "mt-xl", children: [_jsx(Field.Label, { children: "Mobile Number" }), _jsxs("div", { className: "relative", children: [_jsxs("div", { className: "-translate-y-1/2 absolute top-1/2 left-3.5 flex shrink-0 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: {
|
|
250
|
+
paddingLeft: `calc(2.75rem + ${localeInfo.mobileNumber.areaCode.length}ch)`,
|
|
251
|
+
}, ...step1Form.register('mobileNumber') })] }), _jsx(Field.ErrorText, { children: step1Form.formState.errors.mobileNumber?.message })] }), _jsxs(Field.Root, { invalid: !!step1Form.formState.errors.birthDay, className: "mt-xl", children: [_jsx(DateOfBirthField, { value: birthDay, onChange: (value) => {
|
|
252
|
+
if (!value)
|
|
253
|
+
return;
|
|
254
|
+
step1Form.setValue('birthDay', value, {
|
|
255
|
+
shouldDirty: true,
|
|
256
|
+
shouldTouch: true,
|
|
257
|
+
shouldValidate: true,
|
|
258
|
+
});
|
|
259
|
+
}, onBlur: () => {
|
|
260
|
+
step1Form.trigger('birthDay');
|
|
261
|
+
} }), _jsx(Field.ErrorText, { children: step1Form.formState.errors.birthDay?.message })] }), props.branches && (_jsxs(Field.Root, { invalid: !!step1Form.formState.errors.branchCode, className: "mt-xl", children: [_jsxs(Select.Root, { value: [branchCode ?? ''], onValueChange: (details) => {
|
|
262
|
+
step1Form.setValue('branchCode', details.value?.[0], {
|
|
263
|
+
shouldDirty: true,
|
|
264
|
+
shouldTouch: true,
|
|
265
|
+
shouldValidate: true,
|
|
266
|
+
});
|
|
267
|
+
}, collection: branchCollection, positioning: {
|
|
268
|
+
sameWidth: true,
|
|
269
|
+
placement: 'bottom',
|
|
270
|
+
}, lazyMount: true, unmountOnExit: true, children: [_jsx(Select.Label, { className: "flex items-center gap-1", children: "Select Nearest Branch Around You" }), _jsxs(Select.Trigger, { children: [_jsx(Select.ValueText, {}), _jsx(Select.Indicator, { asChild: true, children: _jsx(ChevronDownIcon, {}) })] }), _jsx(Select.Positioner, { children: _jsx(Select.Content, { children: branchCollection.items.map((item) => {
|
|
271
|
+
const label = branchCollection.stringifyItem(item) ?? '';
|
|
272
|
+
return (_jsx(Select.Item, { item: item, "aria-disabled": item.disabled, className: twMerge(item.disabled && 'text-border-disabled'), children: _jsx("div", { title: label, className: "line-clamp-1", children: label }) }, item.code));
|
|
273
|
+
}) }) })] }), _jsx(Field.ErrorText, { children: step1Form.formState.errors.branchCode?.message })] })), _jsx(Controller, { control: step1Form.control, name: "frontImage", render: (o) => (_jsxs(Field.Root, { invalid: o.fieldState.invalid, className: "mt-xl", children: [_jsx(Field.Label, { children: "Front of your ID" }), _jsx(IdFrontImageField, { value: o.field.value, onChange: o.field.onChange, onError: (error) => {
|
|
274
|
+
step1Form.setValue('frontImage', null);
|
|
275
|
+
step1Form.setError('frontImage', {
|
|
276
|
+
type: 'validate',
|
|
277
|
+
message: error.message,
|
|
278
|
+
});
|
|
279
|
+
} }), _jsx(Field.ErrorText, { children: o.fieldState.error?.message })] })) }), _jsx(Controller, { control: step1Form.control, name: "selfieImage", render: (o) => (_jsxs(Field.Root, { invalid: o.fieldState.invalid, className: "my-xl", children: [_jsx(Field.Label, { children: "Selfie holding your ID" }), _jsx(SelfieImageField, { value: o.field.value, onChange: o.field.onChange, onError: (error) => {
|
|
280
|
+
step1Form.setValue('selfieImage', null);
|
|
281
|
+
step1Form.setError('selfieImage', {
|
|
282
|
+
type: 'validate',
|
|
283
|
+
message: error.message,
|
|
284
|
+
});
|
|
285
|
+
} }), _jsx(Field.ErrorText, { children: o.fieldState.error?.message })] })) }), _jsxs(Field.Root, { invalid: !!step1Form.formState.errors.permanentAddress, children: [_jsx(Field.Label, { children: "Permanent address" }), _jsx(Field.Input, { placeholder: "Enter your permanent address", ...step1Form.register('permanentAddress') }), _jsx(Field.ErrorText, { children: step1Form.formState.errors.permanentAddress?.message })] }), _jsxs(Field.Root, { className: "mt-2xl", invalid: !!step1Form.formState.errors.permanentAddress, children: [_jsx(Field.Label, { children: "Current address" }), _jsx(Field.Input, { placeholder: "Enter your current address", ...step1Form.register('currentAddress') }), _jsx(Field.ErrorText, { children: step1Form.formState.errors.currentAddress?.message })] }), _jsxs(Checkbox.Root, { className: "mt-md", checked: address !== '' &&
|
|
286
|
+
permanentAddress !== '' &&
|
|
287
|
+
address === permanentAddress, onCheckedChange: (details) => {
|
|
288
|
+
const isActive = address !== '' &&
|
|
289
|
+
permanentAddress !== '' &&
|
|
290
|
+
address === permanentAddress;
|
|
291
|
+
if (details.checked === true) {
|
|
292
|
+
step1Form.setValue('currentAddress', step1Form.getValues('permanentAddress'), {
|
|
293
|
+
shouldDirty: true,
|
|
294
|
+
shouldTouch: true,
|
|
295
|
+
shouldValidate: true,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
else if (isActive && details.checked === false) {
|
|
299
|
+
step1Form.reset({
|
|
300
|
+
...step1Form.getValues(),
|
|
301
|
+
currentAddress: '',
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}, children: [_jsx(Checkbox.Control, { children: _jsx(Checkbox.Indicator, { asChild: true, children: _jsx(CheckIcon, {}) }) }), _jsx(Checkbox.Label, { children: "Use permanent address as current address" }), _jsx(Checkbox.HiddenInput, {})] }), _jsxs(Field.Root, { className: "mt-2xl", invalid: !!step1Form.formState.errors.sourceOfIncome, children: [_jsx(Field.Label, { children: "Source of income" }), _jsx(Field.Input, { placeholder: "Enter your source of income", ...step1Form.register('sourceOfIncome') }), _jsx(Field.ErrorText, { children: step1Form.formState.errors.sourceOfIncome?.message })] }), _jsxs(Field.Root, { className: "mt-2xl", invalid: !!step1Form.formState.errors.natureOfWork, children: [_jsx(Field.Label, { children: "Nature of Work" }), _jsx(Field.Input, { placeholder: "Enter your nature of work", ...step1Form.register('natureOfWork') }), _jsx(Field.ErrorText, { children: step1Form.formState.errors.natureOfWork?.message })] }), _jsxs(Field.Root, { className: "mt-2xl", invalid: !!step1Form.formState.errors.placeOfBirth, children: [_jsx(Field.Label, { children: "Place of birth" }), _jsx(Field.Input, { placeholder: "Enter your place of birth", ...step1Form.register('placeOfBirth') }), _jsx(Field.ErrorText, { children: step1Form.formState.errors.placeOfBirth?.message })] }), _jsxs(Field.Root, { className: "mt-2xl", invalid: !!step1Form.formState.errors.nationality, children: [_jsx(Field.Label, { children: "Nationality" }), _jsx(Field.Input, { placeholder: "Enter your nationality", ...step1Form.register('nationality') }), _jsx(Field.ErrorText, { children: step1Form.formState.errors.nationality?.message })] }), _jsx(Controller, { control: step1Form.control, name: "termsAccepted", render: (o) => (_jsxs(Field.Root, { className: "mt-2xl", invalid: o.fieldState.invalid, children: [_jsxs(Checkbox.Root, { checked: o.field.value, onCheckedChange: (details) => {
|
|
305
|
+
if (details.checked === 'indeterminate')
|
|
306
|
+
return;
|
|
307
|
+
o.field.onChange(details.checked);
|
|
308
|
+
globalStore.termsAndConditions.setAccepted(details.checked);
|
|
309
|
+
globalStore.responsibleGaming.setAccepted(details.checked);
|
|
310
|
+
}, children: [_jsx(Checkbox.Control, { children: _jsx(Checkbox.Indicator, { asChild: true, children: _jsx(CheckIcon, {}) }) }), _jsxs(Checkbox.Label, { children: ["I am at least 21 years of age and I have read and accept the", ' ', _jsx("button", { type: "button", className: "text-brand-400 underline underline-offset-2", onClick: () => {
|
|
311
|
+
if (props.termsAndConditionsUrl) {
|
|
312
|
+
globalStore.signUp.setOpen(false);
|
|
313
|
+
router.push(props.termsAndConditionsUrl);
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
globalStore.termsAndConditions.setOpen(true);
|
|
317
|
+
globalStore.termsAndConditions.setNext('SIGN_UP');
|
|
318
|
+
globalStore.signUp.setOpen(false);
|
|
319
|
+
}
|
|
320
|
+
}, children: "Terms and Conditions" }), ' ', "and", ' ', _jsx("button", { type: "button", className: "text-brand-400 underline underline-offset-2", onClick: () => {
|
|
321
|
+
if (props.responsibleGamingUrl) {
|
|
322
|
+
globalStore.signUp.setOpen(false);
|
|
323
|
+
router.push(props.responsibleGamingUrl);
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
globalStore.responsibleGaming.setOpen(true);
|
|
327
|
+
globalStore.responsibleGaming.setNext('SIGN_UP');
|
|
328
|
+
globalStore.signUp.setOpen(false);
|
|
329
|
+
}
|
|
330
|
+
}, children: "Responsible Gaming" }), ' ', "guidelines."] }), _jsx(Checkbox.HiddenInput, {})] }), _jsx(Field.ErrorText, { className: "ml-6 text-xs", children: o.fieldState.error?.message })] })) }), _jsx(Button, { type: "submit", className: "mt-3xl", disabled: step1Form.formState.isSubmitting, children: "Create Account" })] })] })) : (_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", children: mobileNumberParser.format(step1Form.getValues('mobileNumber')) }), ' ', "via text"] }), _jsxs("form", { ref: form2LongForm, className: "mt-5", onSubmit: step2Form.handleSubmit(async ({ verificationCode }) => {
|
|
331
|
+
const id = ObjectId.generate(ObjectType.MemberAccount).toString();
|
|
332
|
+
const { mobileNumber } = step1Form.getValues();
|
|
333
|
+
await signUpMutation.mutateAsync({
|
|
334
|
+
id,
|
|
335
|
+
mobileNumber: mobileNumberParser.format(mobileNumber),
|
|
336
|
+
verificationCode: verificationCode.join(''),
|
|
337
|
+
referralCode: search.get('referralCode') ?? undefined,
|
|
338
|
+
branchCode: step1Form.getValues('branchCode'),
|
|
339
|
+
birthDay: format(step1Form.getValues('birthDay'), 'yyyy-MM-dd'),
|
|
340
|
+
realName: step1Form.getValues('fullName'),
|
|
341
|
+
});
|
|
342
|
+
const name = mobileNumberParser.format(mobileNumber);
|
|
343
|
+
const password = `${name}${id}`;
|
|
344
|
+
const pollLogin = createPoll(async () => {
|
|
345
|
+
try {
|
|
346
|
+
await signInMutation.mutateAsync({
|
|
347
|
+
type: 'NAME_AND_PASSWORD',
|
|
348
|
+
name,
|
|
349
|
+
password,
|
|
350
|
+
});
|
|
351
|
+
return true;
|
|
352
|
+
}
|
|
353
|
+
catch {
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
}, {
|
|
357
|
+
until: (ok) => ok,
|
|
358
|
+
interval: 1000,
|
|
359
|
+
maxAttempt: 5,
|
|
360
|
+
});
|
|
361
|
+
const ok = await pollLogin();
|
|
362
|
+
if (!ok) {
|
|
363
|
+
globalStore.signIn.setOpen(true);
|
|
364
|
+
globalStore.signUp.setOpen(false);
|
|
365
|
+
toaster.error({
|
|
366
|
+
title: 'Sign Up Failed',
|
|
367
|
+
description: 'Something went wrong. Please try again later',
|
|
368
|
+
});
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
const pollImageUpload = createPoll(async () => {
|
|
372
|
+
const uploadImage = (file) => new Promise((resolve, reject) => {
|
|
373
|
+
if (!file) {
|
|
374
|
+
return reject(new Error('No file provided'));
|
|
375
|
+
}
|
|
376
|
+
uploadImageFile({ file }, {
|
|
377
|
+
onSuccess: (result) => resolve(result),
|
|
378
|
+
onError: (error) => reject(error || new Error('Upload failed')),
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
try {
|
|
382
|
+
const selfieFile = step1Form.getValues('selfieImage');
|
|
383
|
+
const frontFile = step1Form.getValues('frontImage');
|
|
384
|
+
if (!(selfieFile instanceof File) ||
|
|
385
|
+
!(frontFile instanceof File)) {
|
|
386
|
+
throw new Error('Invalid or missing image files');
|
|
387
|
+
}
|
|
388
|
+
const selfieImageId = await uploadImage(selfieFile);
|
|
389
|
+
const frontImageId = await uploadImage(frontFile);
|
|
390
|
+
const session = await getSession();
|
|
391
|
+
const pollImageStatus = (id) => createPoll(async () => {
|
|
392
|
+
const file = await getFile(id, {
|
|
393
|
+
headers: {
|
|
394
|
+
Authorization: `Bearer ${session.token}`,
|
|
395
|
+
},
|
|
396
|
+
});
|
|
397
|
+
return file?.status === 'READY';
|
|
398
|
+
}, {
|
|
399
|
+
until: (ok) => ok,
|
|
400
|
+
interval: 500,
|
|
401
|
+
maxAttempt: 10,
|
|
402
|
+
})();
|
|
403
|
+
const [selfieReady, frontReady] = await Promise.all([
|
|
404
|
+
pollImageStatus(selfieImageId),
|
|
405
|
+
pollImageStatus(frontImageId),
|
|
406
|
+
]);
|
|
407
|
+
if (!selfieReady || !frontReady) {
|
|
408
|
+
throw new Error('Images were not processed in time');
|
|
409
|
+
}
|
|
410
|
+
const data = {
|
|
411
|
+
selfieImage: selfieImageId,
|
|
412
|
+
idFrontImage: frontImageId,
|
|
413
|
+
};
|
|
414
|
+
if (!memberId) {
|
|
415
|
+
const memberVerificationId = ObjectId.generate(ObjectType.MemberVerification).toString();
|
|
416
|
+
await createMemberVerification({
|
|
417
|
+
...data,
|
|
418
|
+
address: step1Form.getValues('currentAddress'),
|
|
419
|
+
nationality: step1Form.getValues('nationality'),
|
|
420
|
+
natureOfWork: step1Form.getValues('natureOfWork'),
|
|
421
|
+
permanentAddress: step1Form.getValues('permanentAddress'),
|
|
422
|
+
placeOfBirth: step1Form.getValues('placeOfBirth'),
|
|
423
|
+
sourceOfIncome: step1Form.getValues('sourceOfIncome'),
|
|
424
|
+
});
|
|
425
|
+
await approveMemberVerificationMutation.mutateAsync(memberVerificationId);
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
await updateMemberVerification({
|
|
429
|
+
id: memberId,
|
|
430
|
+
data,
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
return true;
|
|
434
|
+
}
|
|
435
|
+
catch (error) {
|
|
436
|
+
console.error('Image upload or verification failed:', error);
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
}, {
|
|
440
|
+
until: (ok) => ok,
|
|
441
|
+
maxAttempt: 5,
|
|
442
|
+
interval: 300,
|
|
443
|
+
});
|
|
444
|
+
await pollImageUpload();
|
|
445
|
+
}), 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) => {
|
|
446
|
+
o.field.onChange(details.value);
|
|
447
|
+
}, onKeyDown: (e) => {
|
|
448
|
+
if (e.key === 'Backspace') {
|
|
449
|
+
step2Form.reset();
|
|
450
|
+
}
|
|
451
|
+
}, onValueComplete: () => {
|
|
452
|
+
form2LongForm.current?.requestSubmit();
|
|
453
|
+
}, 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.formState.errors.verificationCode?.message })] })) }), _jsx(Button, { type: "submit", className: "mt-4xl", disabled: step2Form.formState.isSubmitting, children: step2Form.formState.isSubmitting ? 'Verifying...' : '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 receive the code?" }), _jsx("button", { type: "button", className: "font-semibold text-button-secondary-fg disabled:cursor-not-allowed disabled:opacity-75", disabled: cooldown.cooling, onClick: async () => {
|
|
454
|
+
await sendVerificationCodeMutation.mutateAsync({
|
|
455
|
+
channel: 'SMS',
|
|
456
|
+
recipient: mobileNumberParser.format(step1Form.getValues('mobileNumber')),
|
|
457
|
+
});
|
|
458
|
+
cooldown.start();
|
|
459
|
+
}, children: cooldown.cooling
|
|
460
|
+
? `Resend in ${cooldown.countdown}s`
|
|
461
|
+
: 'Resend' })] }), _jsxs("button", { type: "button", className: "mx-auto mt-3xl flex w-fit items-center gap-1 font-semibold text-sm text-text-tertiary-600", onClick: () => {
|
|
462
|
+
setStep(1);
|
|
463
|
+
step2Form.reset();
|
|
464
|
+
cooldown.stop();
|
|
465
|
+
}, children: [_jsx(ArrowLeftIcon, { className: "size-5" }), "Back"] })] })] })) }));
|
|
466
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ImageProps } from 'next/image';
|
|
2
|
+
import { type ReactNode } from 'react';
|
|
3
|
+
import type { Branch } from '../../../types';
|
|
4
|
+
export interface SignUpLongFormProps {
|
|
5
|
+
layout: 'long-form';
|
|
6
|
+
logo: ImageProps['src'];
|
|
7
|
+
branches?: Branch[];
|
|
8
|
+
termsAndConditionsUrl?: string;
|
|
9
|
+
responsibleGamingUrl?: string;
|
|
10
|
+
className?: string;
|
|
11
|
+
children?: ReactNode;
|
|
12
|
+
showPublicPlayProhibition?: boolean;
|
|
13
|
+
pagcorLogo?: ImageProps['src'];
|
|
14
|
+
responsibleGamingLogo?: ImageProps['src'];
|
|
15
|
+
isRegulated?: boolean;
|
|
16
|
+
}
|
|
17
|
+
declare function SignUpLongForm__lazy(props: SignUpLongFormProps): import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
export default SignUpLongForm__lazy;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import Image from 'next/image';
|
|
3
|
+
import { Suspense } from 'react';
|
|
4
|
+
import { twMerge } from 'tailwind-merge';
|
|
5
|
+
import { useShallow } from 'zustand/shallow';
|
|
6
|
+
import { useGlobalStore } from '../../../client/hooks/useGlobalStore.js';
|
|
7
|
+
import { XIcon } from '../../../icons/XIcon.js';
|
|
8
|
+
import { Dialog } from '../../../ui/Dialog/index.js';
|
|
9
|
+
import { Portal } from '../../../ui/Portal/index.js';
|
|
10
|
+
import SignUpLongForm from './SignUpLongForm.js';
|
|
11
|
+
import { SignUpLongFormPropsProvider } from './SignUpLongFormContext.js';
|
|
12
|
+
function SignUpLongForm__lazy(props) {
|
|
13
|
+
const signUpStore = useGlobalStore(useShallow((ctx) => ctx.signUp));
|
|
14
|
+
const globalStore = useGlobalStore(useShallow((ctx) => ({
|
|
15
|
+
termsAndConditions: ctx.termsAndConditions,
|
|
16
|
+
responsibleGaming: ctx.responsibleGaming,
|
|
17
|
+
})));
|
|
18
|
+
return (_jsx(SignUpLongFormPropsProvider, { value: props, children: _jsx(Dialog.Root, { open: signUpStore.open, onOpenChange: (details) => {
|
|
19
|
+
signUpStore.setOpen(details.open);
|
|
20
|
+
}, lazyMount: true, unmountOnExit: true, closeOnEscape: false, closeOnInteractOutside: false, onExitComplete: () => {
|
|
21
|
+
// Reset checkbox state when dialog closes
|
|
22
|
+
globalStore.termsAndConditions.setAccepted(false);
|
|
23
|
+
globalStore.responsibleGaming.setAccepted(false);
|
|
24
|
+
}, children: _jsxs(Portal, { children: [_jsx(Dialog.Backdrop, {}), _jsx(Dialog.Positioner, { children: _jsx(Dialog.Content, { className: twMerge('z-banner mx-auto flex size-full h-full flex-col items-start overflow-y-auto bg-bg-primary-alt p-3xl pb-4xl lg:max-h-[80vh] lg:w-fit lg:rounded-xl', 'scrollbar:h-2 scrollbar:w-2 scrollbar-thumb:rounded-full scrollbar-thumb:bg-bg-quaternary scrollbar-track:bg-transparent'), children: _jsxs("div", { className: "size-full lg:w-[400px]", children: [_jsx(Dialog.CloseTrigger, { children: _jsx(XIcon, {}) }), _jsx(Image, { src: props.logo, alt: "", width: 200, height: 100, className: "mx-auto h-auto w-[7.5rem]", draggable: false }), _jsx(Suspense, { children: _jsx(SignUpLongForm, {}) })] }) }) })] }) }) }));
|
|
25
|
+
}
|
|
26
|
+
export default SignUpLongForm__lazy;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { SignUpLongFormProps } from './SignUpLongForm.lazy';
|
|
2
|
+
export declare const SignUpLongFormPropsProvider: (props: {
|
|
3
|
+
value: SignUpLongFormProps;
|
|
4
|
+
} & {
|
|
5
|
+
children?: import("react").ReactNode | undefined;
|
|
6
|
+
}) => React.ReactNode, useSignUpLongFormPropsContext: () => SignUpLongFormProps;
|