@opexa/portal-components 0.0.680 → 0.0.682
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/useCamera.d.ts +2 -1
- package/dist/client/hooks/useCamera.js +128 -21
- package/dist/client/hooks/useSignOutMutation.js +15 -0
- package/dist/client/utils/biometric.js +1 -1
- package/dist/components/AccountInfo/GoogleDisconnect.d.ts +7 -0
- package/dist/components/AccountInfo/GoogleDisconnect.js +11 -0
- package/dist/components/DepositWithdrawal/AiOPaymentMethods.js +4 -4
- package/dist/components/DepositWithdrawal/PaymentMethods.js +4 -3
- package/dist/components/GameLaunch/GameLaunchTrigger.js +64 -11
- package/dist/components/KYC/KYCDefault/PersonalInformation.js +11 -1
- package/dist/components/KYC/KYCReminder.lazy.js +8 -10
- package/dist/components/KYC/KYCVerificationStatus.lazy.js +4 -7
- package/dist/components/KYC/KycOpenOnHomeMount.js +1 -0
- package/dist/components/PortalProvider/CXDTokenObserver.js +11 -11
- package/dist/components/RegisterBiometrics/RegisterBiometrics.js +3 -1
- package/dist/components/SignIn/MobileNumberSignIn.js +13 -1
- package/dist/components/SignIn/NameAndPasswordSignIn.js +23 -3
- package/dist/components/SignIn/SignInTrigger.d.ts +1 -1
- package/dist/components/SignIn/SignInTrigger.js +62 -7
- package/dist/components/TopWins/TopWins.client.js +1 -1
- package/dist/components/shared/IdFrontImageField/IdFrontImageField.client.js +12 -2
- package/dist/components/shared/SelfieImageField/SelfieImageField.client.js +11 -2
- package/dist/icons/LinkBrokenIcon.d.ts +2 -0
- package/dist/icons/LinkBrokenIcon.js +4 -0
- package/dist/ui/AlertDialog/AlertDialog.d.ts +88 -88
- package/dist/ui/AlertDialog/alertDialog.recipe.d.ts +8 -8
- package/dist/ui/Checkbox/Checkbox.d.ts +23 -23
- package/dist/ui/Checkbox/checkbox.recipe.d.ts +3 -3
- package/dist/ui/Combobox/Combobox.d.ts +42 -42
- package/dist/ui/Combobox/combobox.recipe.d.ts +3 -3
- package/dist/ui/DatePicker/DatePicker.d.ts +72 -72
- package/dist/ui/DatePicker/datePicker.recipe.d.ts +3 -3
- package/dist/ui/Dialog/Dialog.d.ts +33 -33
- package/dist/ui/Dialog/dialog.recipe.d.ts +3 -3
- package/dist/ui/Drawer/Drawer.d.ts +33 -33
- package/dist/ui/Drawer/drawer.recipe.d.ts +3 -3
- package/dist/ui/Menu/Menu.d.ts +252 -252
- package/dist/ui/Menu/menu.recipe.d.ts +14 -14
- package/dist/ui/Popover/Popover.d.ts +88 -88
- package/dist/ui/Popover/popover.recipe.d.ts +8 -8
- package/dist/ui/Select/Select.d.ts +45 -45
- package/dist/ui/Select/select.recipe.d.ts +3 -3
- package/dist/ui/Tooltip/Tooltip.d.ts +30 -30
- package/dist/ui/Tooltip/tooltip.recipe.d.ts +5 -5
- package/package.json +2 -1
- package/dist/components/Banner/Banner.client.d.ts +0 -12
- package/dist/components/Banner/Banner.client.js +0 -49
- package/dist/components/PortalProvider/AndroidOnlyComponents.d.ts +0 -1
- package/dist/components/PortalProvider/AndroidOnlyComponents.js +0 -12
- package/dist/components/SignIn/utils.d.ts +0 -8
- package/dist/components/SignIn/utils.js +0 -26
- package/dist/constants/Branches.d.ts +0 -2
- package/dist/constants/Branches.js +0 -42
- package/dist/third-parties/FacebookPixel/FacebookPixel.d.ts +0 -4
- package/dist/third-parties/FacebookPixel/FacebookPixel.js +0 -4
- package/dist/third-parties/FacebookPixel/api.d.ts +0 -0
- package/dist/third-parties/FacebookPixel/api.js +0 -1
- package/dist/third-parties/FacebookPixel/index.d.ts +0 -1
- package/dist/third-parties/FacebookPixel/index.js +0 -1
- package/dist/third-parties/GoogleRecaptcha/GoogleRecaptcha.d.ts +0 -4
- package/dist/third-parties/GoogleRecaptcha/GoogleRecaptcha.js +0 -4
- package/dist/third-parties/GoogleRecaptcha/api.d.ts +0 -0
- package/dist/third-parties/GoogleRecaptcha/api.js +0 -1
- package/dist/third-parties/GoogleRecaptcha/index.d.ts +0 -1
- package/dist/third-parties/GoogleRecaptcha/index.js +0 -1
- package/dist/third-parties/index.d.ts +0 -2
- package/dist/third-parties/index.js +0 -2
|
@@ -11,8 +11,9 @@ export interface CameraData {
|
|
|
11
11
|
}
|
|
12
12
|
export interface UseCameraReturn<T extends string = never> {
|
|
13
13
|
open(): Promise<void>;
|
|
14
|
+
openNativeCamera(): Promise<CameraData | null>;
|
|
14
15
|
close(): Promise<void>;
|
|
15
|
-
snap(): CameraData | null
|
|
16
|
+
snap(): Promise<CameraData | null>;
|
|
16
17
|
reopen(): Promise<void>;
|
|
17
18
|
reset(): Promise<void>;
|
|
18
19
|
data: CameraData | null;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isBoolean } from 'lodash-es';
|
|
2
2
|
import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
|
|
3
|
+
import invariant from 'tiny-invariant';
|
|
3
4
|
import { useMediaQuery } from 'usehooks-ts';
|
|
4
5
|
export function useCamera(options = {}) {
|
|
5
6
|
const videoRef = useRef(null);
|
|
@@ -78,6 +79,68 @@ export function useCamera(options = {}) {
|
|
|
78
79
|
setLoading(false);
|
|
79
80
|
}
|
|
80
81
|
}, [options, desktop]);
|
|
82
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: Intentional empty deps: we need a stable openNativeCamera reference.
|
|
83
|
+
const openNativeCamera = useCallback(async () => {
|
|
84
|
+
setData(null);
|
|
85
|
+
setError(null);
|
|
86
|
+
setSnapping(true);
|
|
87
|
+
try {
|
|
88
|
+
// Dynamically import only when running native
|
|
89
|
+
const { Camera, CameraResultType } = await import('@capacitor/camera');
|
|
90
|
+
const photo = await Camera.getPhoto({
|
|
91
|
+
quality: 90,
|
|
92
|
+
resultType: CameraResultType.Uri,
|
|
93
|
+
saveToGallery: false,
|
|
94
|
+
allowEditing: false,
|
|
95
|
+
});
|
|
96
|
+
if (!photo.webPath) {
|
|
97
|
+
throw new Error('No photo returned from native camera');
|
|
98
|
+
}
|
|
99
|
+
console.log(photo, 'photo');
|
|
100
|
+
const response = await fetch(photo.webPath);
|
|
101
|
+
const blob = await response.blob();
|
|
102
|
+
const file = new File([blob], `${crypto.randomUUID()}.jpeg`, {
|
|
103
|
+
type: blob.type,
|
|
104
|
+
lastModified: Date.now(),
|
|
105
|
+
});
|
|
106
|
+
// Convert blob → base64 data URL
|
|
107
|
+
const url = await new Promise((resolve, reject) => {
|
|
108
|
+
const reader = new FileReader();
|
|
109
|
+
reader.onloadend = () => {
|
|
110
|
+
if (typeof reader.result === 'string')
|
|
111
|
+
resolve(reader.result);
|
|
112
|
+
else
|
|
113
|
+
reject(new Error('Failed to read image as data URL'));
|
|
114
|
+
};
|
|
115
|
+
reader.onerror = reject;
|
|
116
|
+
reader.readAsDataURL(blob);
|
|
117
|
+
});
|
|
118
|
+
const image = new Image();
|
|
119
|
+
image.src = url;
|
|
120
|
+
if (!image.complete || image.naturalWidth === 0) {
|
|
121
|
+
await new Promise((resolve, reject) => {
|
|
122
|
+
image.onload = () => resolve();
|
|
123
|
+
image.onerror = () => reject(new Error('Failed to load preview image'));
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
const data = {
|
|
127
|
+
url,
|
|
128
|
+
file,
|
|
129
|
+
image,
|
|
130
|
+
};
|
|
131
|
+
setData(data);
|
|
132
|
+
setSnapping(false);
|
|
133
|
+
return data;
|
|
134
|
+
}
|
|
135
|
+
catch (e) {
|
|
136
|
+
setError({
|
|
137
|
+
name: 'CameraError',
|
|
138
|
+
message: e instanceof Error ? e.message : 'Failed to open native camera',
|
|
139
|
+
});
|
|
140
|
+
setSnapping(false);
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
}, [setData, setError, setSnapping]);
|
|
81
144
|
const close = useCallback(() => {
|
|
82
145
|
setData(null);
|
|
83
146
|
setError(null);
|
|
@@ -89,33 +152,76 @@ export function useCamera(options = {}) {
|
|
|
89
152
|
resolve();
|
|
90
153
|
});
|
|
91
154
|
}, []);
|
|
92
|
-
const snap = useCallback(() => {
|
|
155
|
+
const snap = useCallback(async () => {
|
|
156
|
+
setData(null);
|
|
157
|
+
setError(null);
|
|
158
|
+
setSnapping(true);
|
|
93
159
|
const video = videoRef.current;
|
|
94
|
-
if (!video)
|
|
95
|
-
return null;
|
|
96
160
|
const canvas = document.createElement('canvas');
|
|
97
161
|
const context = canvas.getContext('2d');
|
|
98
|
-
|
|
99
|
-
|
|
162
|
+
invariant(video, 'Could not find video element');
|
|
163
|
+
invariant(context, 'Could not get canvas context');
|
|
164
|
+
video.currentTime = 1;
|
|
100
165
|
canvas.width = video.videoWidth;
|
|
101
166
|
canvas.height = video.videoHeight;
|
|
102
|
-
context.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
167
|
+
context.imageSmoothingEnabled = true;
|
|
168
|
+
context.imageSmoothingQuality = 'high';
|
|
169
|
+
context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
|
|
170
|
+
return new Promise((resolve) => {
|
|
171
|
+
canvas.toBlob(async (blob) => {
|
|
172
|
+
if (!blob) {
|
|
173
|
+
setSnapping(false);
|
|
174
|
+
resolve(null);
|
|
175
|
+
return setError({
|
|
176
|
+
name: 'CameraError',
|
|
177
|
+
message: "'canvas.toBlob' failed to create blob",
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
const url = canvas.toDataURL('image/jpeg', 1);
|
|
181
|
+
const file = new File([blob], `${crypto.randomUUID()}.jpeg`, {
|
|
182
|
+
type: 'image/jpeg',
|
|
183
|
+
endings: 'native',
|
|
184
|
+
lastModified: Date.now(),
|
|
185
|
+
});
|
|
186
|
+
const image = new Image();
|
|
187
|
+
image.src = url;
|
|
188
|
+
image.alt = '';
|
|
189
|
+
image.width = canvas.width;
|
|
190
|
+
image.height = canvas.height;
|
|
191
|
+
if (!image.complete || image.naturalWidth === 0) {
|
|
192
|
+
await new Promise((resolve, reject) => {
|
|
193
|
+
image.onload = () => resolve();
|
|
194
|
+
image.onerror = () => reject();
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
const data = {
|
|
198
|
+
url,
|
|
199
|
+
file,
|
|
200
|
+
image,
|
|
201
|
+
};
|
|
202
|
+
if (!options.transform) {
|
|
203
|
+
setData(data);
|
|
204
|
+
setSnapping(false);
|
|
205
|
+
resolve(data);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const transformResult = await options.transform({
|
|
209
|
+
...data,
|
|
210
|
+
video,
|
|
211
|
+
canvas,
|
|
212
|
+
});
|
|
213
|
+
if (transformResult.ok) {
|
|
214
|
+
setData(transformResult.data);
|
|
215
|
+
resolve(transformResult.data);
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
setError(transformResult.error);
|
|
219
|
+
resolve(null);
|
|
220
|
+
}
|
|
221
|
+
setSnapping(false);
|
|
222
|
+
}, 'image/jpeg', 1);
|
|
110
223
|
});
|
|
111
|
-
|
|
112
|
-
image.src = url;
|
|
113
|
-
image.width = canvas.width;
|
|
114
|
-
image.height = canvas.height;
|
|
115
|
-
const data = { url, file, image };
|
|
116
|
-
setData(data);
|
|
117
|
-
return data;
|
|
118
|
-
}, []);
|
|
224
|
+
}, [options]);
|
|
119
225
|
const reset = useCallback(() => {
|
|
120
226
|
setData(null);
|
|
121
227
|
setError(null);
|
|
@@ -157,6 +263,7 @@ export function useCamera(options = {}) {
|
|
|
157
263
|
return {
|
|
158
264
|
snap,
|
|
159
265
|
open,
|
|
266
|
+
openNativeCamera,
|
|
160
267
|
close,
|
|
161
268
|
reopen,
|
|
162
269
|
reset,
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { useMutation } from '@tanstack/react-query';
|
|
2
|
+
import invariant from 'tiny-invariant';
|
|
3
|
+
import { unregisterFCMDevice } from '../../services/trigger.js';
|
|
2
4
|
import { getQueryClient } from '../../utils/getQueryClient.js';
|
|
3
5
|
import { getSignOutMutationKey } from '../../utils/mutationKeys.js';
|
|
4
6
|
import { getSessionQueryKey } from '../../utils/queryKeys.js';
|
|
7
|
+
import { getSession } from '../services/getSession.js';
|
|
5
8
|
import { signOut } from '../services/signOut.js';
|
|
6
9
|
const IDLE_TIMESTAMP_KEY = 'idle-logout-timestamp';
|
|
7
10
|
export const useSignOutMutation = (config) => {
|
|
@@ -10,6 +13,18 @@ export const useSignOutMutation = (config) => {
|
|
|
10
13
|
...config,
|
|
11
14
|
mutationKey: getSignOutMutationKey(),
|
|
12
15
|
mutationFn: async () => {
|
|
16
|
+
const session = await getQueryClient().fetchQuery({
|
|
17
|
+
queryKey: getSessionQueryKey(),
|
|
18
|
+
queryFn: async () => getSession(),
|
|
19
|
+
});
|
|
20
|
+
invariant(session.status === 'authenticated');
|
|
21
|
+
await unregisterFCMDevice({
|
|
22
|
+
type: ['IOS', 'ANDROID'],
|
|
23
|
+
}, {
|
|
24
|
+
headers: {
|
|
25
|
+
Authorization: `Bearer ${session.token}`,
|
|
26
|
+
},
|
|
27
|
+
});
|
|
13
28
|
await signOut();
|
|
14
29
|
await queryClient.invalidateQueries({ queryKey: getSessionQueryKey() });
|
|
15
30
|
queryClient.removeQueries();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { addDays, isAfter } from 'date-fns';
|
|
2
|
-
const SERVER =
|
|
2
|
+
const SERVER = `${process.env.NEXT_PUBLIC_PLATFORM_CODE?.toLocaleLowerCase()}.app`;
|
|
3
3
|
export const BIOMETRIC_STORAGE_KEY = `${process.env.NEXT_PUBLIC_PLATFORM_CODE}__BiometricEnabled`;
|
|
4
4
|
export var BiometryType;
|
|
5
5
|
(function (BiometryType) {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type UseDisclosureReturn } from '../../client/hooks/useDisclosure';
|
|
2
|
+
interface GoogleDisconnectProps {
|
|
3
|
+
onConfirmAction?: (ctx: UseDisclosureReturn) => React.ReactNode;
|
|
4
|
+
children?: (ctx: UseDisclosureReturn) => React.ReactNode;
|
|
5
|
+
}
|
|
6
|
+
export declare function GoogleDisconnect(props: GoogleDisconnectProps): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useDisclosure, } from '../../client/hooks/useDisclosure.js';
|
|
4
|
+
import { LinkBrokenIcon } from '../../icons/LinkBrokenIcon.js';
|
|
5
|
+
import { XIcon } from '../../icons/XIcon.js';
|
|
6
|
+
import { Dialog } from '../../ui/Dialog/index.js';
|
|
7
|
+
import { Portal } from '../../ui/Portal/index.js';
|
|
8
|
+
export function GoogleDisconnect(props) {
|
|
9
|
+
const disclosure = useDisclosure();
|
|
10
|
+
return (_jsxs(_Fragment, { children: [props.children?.(disclosure), _jsx(Dialog.Root, { lazyMount: true, unmountOnExit: true, open: disclosure.open, onOpenChange: (details) => disclosure.setOpen(details.open), closeOnEscape: false, closeOnInteractOutside: false, children: _jsxs(Portal, { children: [_jsx(Dialog.Backdrop, { className: "!z-[calc(var(--z-dialog)+1)]" }), _jsx(Dialog.Positioner, { className: "!z-[calc(var(--z-dialog)+2)] flex items-center justify-center", children: _jsxs(Dialog.Content, { className: "mx-auto min-h-auto max-w-[25rem] overflow-y-auto rounded-xl p-6", children: [_jsx(Dialog.CloseTrigger, { children: _jsx(XIcon, {}) }), _jsxs("div", { className: "flex flex-col ", children: [_jsx("div", { className: "mx-auto flex size-12 items-center justify-center rounded-full bg-bg-brand-secondary text-text-brand", children: _jsx(LinkBrokenIcon, {}) }), _jsx("h2", { className: "mb-1 text-center font-semibold text-lg xl:mt-xl", children: "Disconnect Google Account" }), _jsx("p", { className: "text-center text-sm text-text-tertiary-600 leading-2xl", children: "Are you sure you want to disconnect your Google account? This may affect your ability to log in or sync data." })] }), _jsx("div", { className: "pt-6", children: props.onConfirmAction?.(disclosure) })] }) })] }) })] }));
|
|
11
|
+
}
|
|
@@ -4,12 +4,12 @@ import { twMerge } from 'tailwind-merge';
|
|
|
4
4
|
import { useControllableState } from '../../client/hooks/useControllableState.js';
|
|
5
5
|
import { CheckIcon } from '../../icons/CheckIcon.js';
|
|
6
6
|
import gcash from '../../images/gcash.png';
|
|
7
|
-
import maya from '../../images/maya.png';
|
|
8
7
|
import grabPay from '../../images/grabpay.png';
|
|
8
|
+
import maya from '../../images/maya.png';
|
|
9
9
|
import palawanPay from '../../images/palawanpay.png';
|
|
10
10
|
import { Checkbox } from '../../ui/Checkbox/index.js';
|
|
11
11
|
import { Field } from '../../ui/Field/index.js';
|
|
12
|
-
import { AiOeWalletPaymentMethodDefinition } from './utils.js';
|
|
12
|
+
import { AiOeWalletPaymentMethodDefinition, } from './utils.js';
|
|
13
13
|
const AIO_EWALLET_OPTIONS = [
|
|
14
14
|
{
|
|
15
15
|
value: 'AIO_GCASH',
|
|
@@ -50,6 +50,6 @@ export function AiOPaymentMethods(props) {
|
|
|
50
50
|
return;
|
|
51
51
|
setValue(parseValue(lastValue));
|
|
52
52
|
}, className: "grid grid-cols-2 gap-x-4 gap-y-3", children: options.map((option) => (_jsxs(Checkbox.Root, { value: option.value, className: "flex cursor-pointer items-center justify-between rounded-xl border border-border-secondary ui-checked:border-border-brand-solid p-lg", children: [_jsx("div", { className: twMerge('rounded-xs', option.value === 'AIO_GRAB_PAY'
|
|
53
|
-
? 'bg-transparent
|
|
54
|
-
: 'bg-white py-[0.688rem]
|
|
53
|
+
? 'bg-transparent px-0 py-0'
|
|
54
|
+
: 'bg-white px-sm py-[0.688rem]', option.value === 'AIO_GCASH' && 'bg-[#017EFF]', option.value === 'AIO_PALAWAN_PAY' && 'bg-[#026308]', option.value === 'AIO_PAY_MAYA' && 'bg-black'), children: _jsx(Image, { src: option.image, alt: "", width: 200, height: 40, className: twMerge('h-[1.063rem] w-auto', option.value === 'AIO_GRAB_PAY' && 'h-[3rem] rounded-[4px]', option.value === 'AIO_PALAWAN_PAY' && 'h-[2rem]'), draggable: false }) }), _jsx(Checkbox.Control, { className: "shrink-0", children: _jsx(Checkbox.Indicator, { asChild: true, children: _jsx(CheckIcon, {}) }) }), _jsx(Checkbox.HiddenInput, {})] }, option.value))) })] }));
|
|
55
55
|
}
|
|
@@ -3,13 +3,13 @@ import Image from 'next/image';
|
|
|
3
3
|
import { twMerge } from 'tailwind-merge';
|
|
4
4
|
import { useControllableState } from '../../client/hooks/useControllableState.js';
|
|
5
5
|
import { CheckIcon } from '../../icons/CheckIcon.js';
|
|
6
|
-
import qrph from '../../images/QRPH.png';
|
|
7
6
|
import gcash from '../../images/gcash.png';
|
|
8
7
|
import instapay from '../../images/instapay.png';
|
|
9
8
|
import libangan from '../../images/libangan.png';
|
|
10
9
|
import maya from '../../images/maya.png';
|
|
11
10
|
import onlineBank from '../../images/online-bank.png';
|
|
12
11
|
import pisoPay from '../../images/piso-pay.png';
|
|
12
|
+
import qrph from '../../images/QRPH.png';
|
|
13
13
|
import wallet from '../../images/wallet.png';
|
|
14
14
|
import { Checkbox } from '../../ui/Checkbox/index.js';
|
|
15
15
|
import { Field } from '../../ui/Field/index.js';
|
|
@@ -80,8 +80,9 @@ export function PaymentMethods(props) {
|
|
|
80
80
|
if (!lastValue)
|
|
81
81
|
return;
|
|
82
82
|
setValue(PaymentMethodDefinition.parse(lastValue));
|
|
83
|
-
}, className: "grid grid-cols-2 gap-x-4 gap-y-3", children: options.map((option) => (_jsxs(Checkbox.Root, { value: option.value, className: "flex cursor-pointer items-center justify-between rounded-xl border border-border-secondary ui-checked:border-border-brand-solid p-lg", children: [_jsxs("div", { className: twMerge('rounded-xs bg-white px-sm py-[0.688rem]', option.value === 'GCASH' && 'bg-[#017EFF]', option.value === 'AIO_EWALLET' &&
|
|
83
|
+
}, className: "grid grid-cols-2 gap-x-4 gap-y-3", children: options.map((option) => (_jsxs(Checkbox.Root, { value: option.value, className: "flex cursor-pointer items-center justify-between rounded-xl border border-border-secondary ui-checked:border-border-brand-solid p-lg", children: [_jsxs("div", { className: twMerge('rounded-xs bg-white px-sm py-[0.688rem]', option.value === 'GCASH' && 'bg-[#017EFF]', option.value === 'AIO_EWALLET' &&
|
|
84
|
+
'flex items-center space-x-sm bg-bg-secondary'), children: [_jsx(Image, { src: option.image, alt: "", width: 200, height: 40, className: twMerge('w-auto', option.value === 'LIBANGAN_PAY_IN' ||
|
|
84
85
|
option.value === 'VENTAJA_DISBURSEMENT'
|
|
85
86
|
? 'h-[2.5rem]'
|
|
86
|
-
: 'h-[1.063rem]'), draggable: false }), option.value === 'AIO_EWALLET' && _jsx("p", { className:
|
|
87
|
+
: 'h-[1.063rem]'), draggable: false }), option.value === 'AIO_EWALLET' && (_jsx("p", { className: "text-text-secondary-700 text-xs leading-tight", children: "AIO eWallet" }))] }), _jsx(Checkbox.Control, { className: "shrink-0", children: _jsx(Checkbox.Indicator, { asChild: true, children: _jsx(CheckIcon, {}) }) }), _jsx(Checkbox.HiddenInput, {})] }, option.value))) })] }));
|
|
87
88
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { ark } from '@ark-ui/react/factory';
|
|
4
4
|
import { BiometricAuthError } from 'capacitor-native-biometric';
|
|
5
|
+
import { useState } from 'react';
|
|
5
6
|
import { useShallow } from 'zustand/shallow';
|
|
6
7
|
import { useCreateGameSessionMutation } from '../../client/hooks/useCreateGameSessionMutation.js';
|
|
7
8
|
import { useGlobalStore } from '../../client/hooks/useGlobalStore.js';
|
|
@@ -9,7 +10,7 @@ import { useMemberVerificationQuery } from '../../client/hooks/useMemberVerifica
|
|
|
9
10
|
import { useSessionQuery } from '../../client/hooks/useSessionQuery.js';
|
|
10
11
|
import { getSession } from '../../client/services/getSession.js';
|
|
11
12
|
import { signIn } from '../../client/services/signIn.js';
|
|
12
|
-
import { getBiometricCredentials, getBiometricInfo, hasSavedBiometry, performBiometricVerification, saveBiometricCredentials, } from '../../client/utils/biometric.js';
|
|
13
|
+
import { deleteBiometricCredentials, getBiometricCredentials, getBiometricInfo, hasSavedBiometry, performBiometricVerification, saveBiometricCredentials, } from '../../client/utils/biometric.js';
|
|
13
14
|
import { toaster } from '../../client/utils/toaster.js';
|
|
14
15
|
import { createSingleUseToken } from '../../services/auth.js';
|
|
15
16
|
import { getQueryClient } from '../../utils/getQueryClient.js';
|
|
@@ -23,6 +24,7 @@ export function GameLaunchTrigger(props) {
|
|
|
23
24
|
gameLaunch: ctx.gameLaunch,
|
|
24
25
|
kycVerificationStatus: ctx.kycVerificationStatus,
|
|
25
26
|
})));
|
|
27
|
+
const [hasCancelledBiometric, setHasCancelledBiometric] = useState(false);
|
|
26
28
|
const verificationStatus = verificationQuery.data?.status ?? 'UNVERIFIED';
|
|
27
29
|
const currentHour = new Date().getHours();
|
|
28
30
|
const between3amAnd3pm = currentHour >= 15 || currentHour < 3;
|
|
@@ -44,7 +46,7 @@ export function GameLaunchTrigger(props) {
|
|
|
44
46
|
: 'open', ...props, disabled: disabled, onClick: async (e) => {
|
|
45
47
|
props.onClick?.(e);
|
|
46
48
|
if (sessionQuery.data?.status === 'unauthenticated') {
|
|
47
|
-
if (hasSavedBiometry()) {
|
|
49
|
+
if (hasSavedBiometry() && !hasCancelledBiometric) {
|
|
48
50
|
const ok = await performBiometricVerification({
|
|
49
51
|
reason: 'Login to your account',
|
|
50
52
|
title: 'Login',
|
|
@@ -56,7 +58,10 @@ export function GameLaunchTrigger(props) {
|
|
|
56
58
|
const info = await getBiometricInfo();
|
|
57
59
|
if (info.errorCode === BiometricAuthError.APP_CANCEL ||
|
|
58
60
|
info.errorCode === BiometricAuthError.USER_CANCEL ||
|
|
59
|
-
info.errorCode === BiometricAuthError.SYSTEM_CANCEL
|
|
61
|
+
info.errorCode === BiometricAuthError.SYSTEM_CANCEL ||
|
|
62
|
+
info.errorCode === undefined ||
|
|
63
|
+
info.errorCode === null) {
|
|
64
|
+
setHasCancelledBiometric(true);
|
|
60
65
|
console.log('Biometric verification cancelled');
|
|
61
66
|
}
|
|
62
67
|
else {
|
|
@@ -71,10 +76,21 @@ export function GameLaunchTrigger(props) {
|
|
|
71
76
|
globalStore.signIn.setOpen(!globalStore.signIn.open);
|
|
72
77
|
return;
|
|
73
78
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
79
|
+
try {
|
|
80
|
+
console.log('Signing in using biometric credentials');
|
|
81
|
+
await signIn({
|
|
82
|
+
type: 'SINGLE_USE_TOKEN',
|
|
83
|
+
token: credentials.password,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
toaster.error({
|
|
88
|
+
title: 'Biometric sign-in token has expired.',
|
|
89
|
+
description: 'Please sign in with your mobile number or username to re-enable biometric login.',
|
|
90
|
+
});
|
|
91
|
+
deleteBiometricCredentials();
|
|
92
|
+
globalStore.signIn.setOpen(!globalStore.signIn.open);
|
|
93
|
+
}
|
|
78
94
|
getQueryClient().invalidateQueries({
|
|
79
95
|
queryKey: getSessionQueryKey(),
|
|
80
96
|
});
|
|
@@ -95,19 +111,56 @@ export function GameLaunchTrigger(props) {
|
|
|
95
111
|
else {
|
|
96
112
|
console.warn('Failed to updated biometric credentials');
|
|
97
113
|
globalStore.signIn.setOpen(!globalStore.signIn.open);
|
|
98
|
-
return;
|
|
99
114
|
}
|
|
100
115
|
}
|
|
101
116
|
else {
|
|
102
117
|
console.error('Failed to create token');
|
|
103
118
|
globalStore.signIn.setOpen(!globalStore.signIn.open);
|
|
104
|
-
return;
|
|
105
119
|
}
|
|
106
120
|
}
|
|
107
121
|
else {
|
|
108
|
-
|
|
109
|
-
|
|
122
|
+
// still update biometric credentials if user has cancelled biometric once
|
|
123
|
+
if (hasCancelledBiometric) {
|
|
124
|
+
const credentials = await getBiometricCredentials();
|
|
125
|
+
if (!credentials) {
|
|
126
|
+
toaster.error({ description: 'Biometric verification failed' });
|
|
127
|
+
globalStore.signIn.setOpen(!globalStore.signIn.open);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
await signIn({
|
|
131
|
+
type: 'SINGLE_USE_TOKEN',
|
|
132
|
+
token: credentials.password,
|
|
133
|
+
});
|
|
134
|
+
getQueryClient().invalidateQueries({
|
|
135
|
+
queryKey: getSessionQueryKey(),
|
|
136
|
+
});
|
|
137
|
+
const session = await getSession();
|
|
138
|
+
const r = await createSingleUseToken({
|
|
139
|
+
headers: {
|
|
140
|
+
Authorization: `Bearer ${session.token}`,
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
if (r.token) {
|
|
144
|
+
const saved = await saveBiometricCredentials({
|
|
145
|
+
username: credentials.username,
|
|
146
|
+
password: r.token,
|
|
147
|
+
});
|
|
148
|
+
if (saved) {
|
|
149
|
+
console.info('Biometric credentials has been updated');
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
console.warn('Failed to updated biometric credentials');
|
|
153
|
+
globalStore.signIn.setOpen(!globalStore.signIn.open);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
console.error('Failed to create token');
|
|
158
|
+
globalStore.signIn.setOpen(!globalStore.signIn.open);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
globalStore.signIn.setOpen(!globalStore.signIn.open);
|
|
110
162
|
}
|
|
163
|
+
return props.onClick?.(e);
|
|
111
164
|
}
|
|
112
165
|
//handle new kyc process to play only on verified members only
|
|
113
166
|
if (verificationStatus === 'PENDING' ||
|
|
@@ -10,6 +10,7 @@ import { useGlobalStore } from '../../../client/hooks/useGlobalStore.js';
|
|
|
10
10
|
import { useMemberVerificationQuery } from '../../../client/hooks/useMemberVerificationQuery.js';
|
|
11
11
|
import { useSignOutMutation } from '../../../client/hooks/useSignOutMutation.js';
|
|
12
12
|
import { useUpdateMemberVerificationMutation } from '../../../client/hooks/useUpdateMemberVerificationMutation.js';
|
|
13
|
+
import { BIOMETRIC_STORAGE_KEY } from '../../../client/utils/biometric.js';
|
|
13
14
|
import { toaster } from '../../../client/utils/toaster.js';
|
|
14
15
|
import { CheckIcon } from '../../../icons/CheckIcon.js';
|
|
15
16
|
import { Button } from '../../../ui/Button/index.js';
|
|
@@ -38,7 +39,16 @@ export function PersonalInformation() {
|
|
|
38
39
|
const router = useRouter();
|
|
39
40
|
const signOutMutation = useSignOutMutation({
|
|
40
41
|
onSuccess() {
|
|
41
|
-
|
|
42
|
+
const keep = new Set([BIOMETRIC_STORAGE_KEY]);
|
|
43
|
+
for (let i = 0; i < localStorage.length;) {
|
|
44
|
+
const key = localStorage.key(i);
|
|
45
|
+
if (key && !keep.has(key)) {
|
|
46
|
+
localStorage.removeItem(key);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
i++;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
42
52
|
sessionStorage.clear();
|
|
43
53
|
router.replace('/');
|
|
44
54
|
},
|
|
@@ -28,16 +28,14 @@ export function KYCReminder(props) {
|
|
|
28
28
|
const signOutMutation = useSignOutMutation({
|
|
29
29
|
onSuccess() {
|
|
30
30
|
// Clear everything except the 'biometric' entry
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
i++;
|
|
40
|
-
}
|
|
31
|
+
const keep = new Set([BIOMETRIC_STORAGE_KEY]);
|
|
32
|
+
for (let i = 0; i < localStorage.length;) {
|
|
33
|
+
const key = localStorage.key(i);
|
|
34
|
+
if (key && !keep.has(key)) {
|
|
35
|
+
localStorage.removeItem(key);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
i++;
|
|
41
39
|
}
|
|
42
40
|
}
|
|
43
41
|
sessionStorage.clear();
|
|
@@ -19,18 +19,15 @@ export function KYCVerificationStatus() {
|
|
|
19
19
|
const icons = status === 'PENDING' ? bellIcon : alertIcon;
|
|
20
20
|
return (_jsx(Dialog.Root, { open: globalStore.kycVerificationStatus.open, onOpenChange: (details) => {
|
|
21
21
|
globalStore.kycVerificationStatus.setOpen(details.open);
|
|
22
|
-
}, closeOnEscape: false, closeOnInteractOutside: false, lazyMount: true, unmountOnExit: true, children: _jsxs(Portal, { children: [_jsx(Dialog.Backdrop, { className: "!z-[calc(var(--z-dialog)+3)]" }), _jsx(Dialog.Positioner, { className: "!z-[calc(var(--z-dialog)+4)] flex items-center justify-center", children: _jsx(Dialog.Content, { className: "mx-auto h-fit w-[450px] overflow-y-auto rounded-lg bg-bg-primary", children: _jsxs("div", { className: "p-3xl text-center", children: [_jsx("div", { className: "mb-3xl grid h-[200px] w-full place-items-center rounded-xl bg-radial from-40% from-button-primary-bg to-bg-brand-solid", children: _jsx(Image, { src: icons, alt: "icon", className: "w-60 object-contain", draggable: false, width: 120, height: 120 }) }), _jsxs("h1", { className: "font-semibold text-lg text-white", children: [status === 'PENDING' && 'Verification in Progress', status === 'REJECTED' && 'Verification Rejected', status === 'UNVERIFIED'
|
|
23
|
-
(status === 'CREATED' && 'Verification Required')] }), _jsxs("p", { className: "mb-4xl text-[#94969C] text-base", children: [status === 'PENDING' &&
|
|
22
|
+
}, closeOnEscape: false, closeOnInteractOutside: false, lazyMount: true, unmountOnExit: true, children: _jsxs(Portal, { children: [_jsx(Dialog.Backdrop, { className: "!z-[calc(var(--z-dialog)+3)]" }), _jsx(Dialog.Positioner, { className: "!z-[calc(var(--z-dialog)+4)] flex items-center justify-center", children: _jsx(Dialog.Content, { className: "mx-auto h-fit w-[450px] overflow-y-auto rounded-lg bg-bg-primary", children: _jsxs("div", { className: "p-3xl text-center", children: [_jsx("div", { className: "mb-3xl grid h-[200px] w-full place-items-center rounded-xl bg-radial from-40% from-button-primary-bg to-bg-brand-solid", children: _jsx(Image, { src: icons, alt: "icon", className: "w-60 object-contain", draggable: false, width: 120, height: 120 }) }), _jsxs("h1", { className: "font-semibold text-lg text-white", children: [status === 'PENDING' && 'Verification in Progress', status === 'REJECTED' && 'Verification Rejected', status === 'UNVERIFIED' && 'Verification Required'] }), _jsxs("p", { className: "mb-4xl text-[#94969C] text-base", children: [status === 'PENDING' &&
|
|
24
23
|
`Your account verification is still under review. Please wait
|
|
25
24
|
until it's approved before you can continue playing or
|
|
26
25
|
depositing.`, status === 'REJECTED' &&
|
|
27
|
-
'Your account verification was not approved. Please resubmit your verification to regain full access.', status === 'UNVERIFIED'
|
|
28
|
-
(status === '
|
|
29
|
-
'Your account is not yet verified. Please complete the verification process to continue playing or depositing.')] }), _jsxs(Button, { variant: "solid", className: twMerge('mb-2 w-full', status === 'PENDING' && 'hidden'), onClick: () => {
|
|
26
|
+
'Your account verification was not approved. Please resubmit your verification to regain full access.', status === 'UNVERIFIED' &&
|
|
27
|
+
'Your account is not yet verified. Please complete the verification process to continue playing or depositing.'] }), _jsxs(Button, { variant: "solid", className: twMerge('mb-2 w-full', status === 'PENDING' && 'hidden'), onClick: () => {
|
|
30
28
|
globalStore.kycVerificationStatus.setOpen(false);
|
|
31
29
|
globalStore.kyc.setOpen(true);
|
|
32
|
-
}, children: [status === 'REJECTED' && 'Resubmit Verification', status === 'UNVERIFIED'
|
|
33
|
-
(status === 'CREATED' && 'Verify Now')] }), _jsx(Button, { type: "button", variant: "outline", onClick: () => {
|
|
30
|
+
}, children: [status === 'REJECTED' && 'Resubmit Verification', status === 'UNVERIFIED' && 'Verify Now'] }), _jsx(Button, { type: "button", variant: "outline", onClick: () => {
|
|
34
31
|
globalStore.kycVerificationStatus.setOpen(false);
|
|
35
32
|
}, children: "Close" })] }) }) })] }) }));
|
|
36
33
|
}
|
|
@@ -33,6 +33,7 @@ export function KycOpenOnHomeMount(props) {
|
|
|
33
33
|
!verification?.placeOfBirth ||
|
|
34
34
|
!verification?.address;
|
|
35
35
|
useEffect(() => {
|
|
36
|
+
console.log(hasntSubmittedCompliantDocs, hasntCompletedKYC);
|
|
36
37
|
if (!verificationLoading && !accountLoading) {
|
|
37
38
|
// Handle pending case with feature flag
|
|
38
39
|
if (isPending) {
|
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { addHours } from 'date-fns';
|
|
3
3
|
import { clamp } from 'lodash-es';
|
|
4
|
+
import { useSearchParams } from 'next/navigation';
|
|
4
5
|
import { useLocalStorage, useTimeout } from 'usehooks-ts';
|
|
5
6
|
import { useAccountQuery } from '../../client/hooks/useAccountQuery.js';
|
|
6
7
|
export function CXDTokenObserver() {
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const [cxd, setCxd, removeCxd] = useLocalStorage('
|
|
8
|
+
const searchParams = useSearchParams();
|
|
9
|
+
const cxdToken = searchParams.get('cxd');
|
|
10
|
+
const accountQuery = useAccountQuery();
|
|
11
|
+
const account = accountQuery.data;
|
|
12
|
+
const [cxd, setCxd, removeCxd] = useLocalStorage('WebPortalCellxpertCxd', null);
|
|
12
13
|
const now = new Date();
|
|
14
|
+
const shouldTimeoutRun = cxdToken && account;
|
|
13
15
|
const removeCxdUntilInMs = cxd?.timestamp
|
|
14
16
|
? clamp(cxd.timestamp - now.getTime(), 0, Infinity)
|
|
15
17
|
: 0;
|
|
16
18
|
useTimeout(() => {
|
|
17
|
-
const isSame = cxd?.cxd ===
|
|
19
|
+
const isSame = cxd?.cxd === cxdToken;
|
|
18
20
|
if (!isSame) {
|
|
19
21
|
const extendedTimestamp = addHours(new Date(), 6).getTime();
|
|
20
22
|
setCxd({
|
|
21
|
-
cxd:
|
|
23
|
+
cxd: cxdToken,
|
|
22
24
|
timestamp: extendedTimestamp,
|
|
23
25
|
});
|
|
24
26
|
}
|
|
25
|
-
},
|
|
26
|
-
useTimeout(() =>
|
|
27
|
-
removeCxd();
|
|
28
|
-
}, account ? removeCxdUntilInMs : null);
|
|
27
|
+
}, shouldTimeoutRun ? 100 : null);
|
|
28
|
+
useTimeout(() => removeCxd(), shouldTimeoutRun ? removeCxdUntilInMs : null);
|
|
29
29
|
return null;
|
|
30
30
|
}
|
|
@@ -45,7 +45,9 @@ export function RegisterBiometrics() {
|
|
|
45
45
|
const info = await getBiometricInfo();
|
|
46
46
|
if (info.errorCode === BiometricAuthError.APP_CANCEL ||
|
|
47
47
|
info.errorCode === BiometricAuthError.USER_CANCEL ||
|
|
48
|
-
info.errorCode === BiometricAuthError.SYSTEM_CANCEL
|
|
48
|
+
info.errorCode === BiometricAuthError.SYSTEM_CANCEL ||
|
|
49
|
+
info.errorCode === undefined ||
|
|
50
|
+
info.errorCode === null) {
|
|
49
51
|
return;
|
|
50
52
|
}
|
|
51
53
|
else {
|
|
@@ -6,6 +6,7 @@ import { useRouter } from 'next/navigation';
|
|
|
6
6
|
import { useRef } from 'react';
|
|
7
7
|
import { Controller, useForm } from 'react-hook-form';
|
|
8
8
|
import { twMerge } from 'tailwind-merge';
|
|
9
|
+
import invariant from 'tiny-invariant';
|
|
9
10
|
import { z } from 'zod';
|
|
10
11
|
import { useShallow } from 'zustand/shallow';
|
|
11
12
|
import { useCooldown } from '../../client/hooks/useCooldown.js';
|
|
@@ -14,12 +15,14 @@ import { useLocaleInfo } from '../../client/hooks/useLocaleInfo.js';
|
|
|
14
15
|
import { useMobileNumberParser } from '../../client/hooks/useMobileNumberParser.js';
|
|
15
16
|
import { useSendVerificationCodeMutation } from '../../client/hooks/useSendVerificationCodeMutation.js';
|
|
16
17
|
import { useSignInMutation } from '../../client/hooks/useSignInMutation.js';
|
|
18
|
+
import { getSession } from '../../client/services/getSession.js';
|
|
17
19
|
import { hasSavedBiometry } from '../../client/utils/biometric.js';
|
|
18
20
|
import { toaster } from '../../client/utils/toaster.js';
|
|
19
21
|
import { ArrowLeftIcon } from '../../icons/ArrowLeftIcon.js';
|
|
20
22
|
import { CheckIcon } from '../../icons/CheckIcon.js';
|
|
21
23
|
import pagcorLogo from '../../images/pagcor-round-icon.png';
|
|
22
24
|
import responsibleGamingLogo from '../../images/responsible-gaming-gold.png';
|
|
25
|
+
import { unregisterFCMDevice } from '../../services/trigger.js';
|
|
23
26
|
import { Button } from '../../ui/Button/index.js';
|
|
24
27
|
import { Checkbox } from '../../ui/Checkbox/index.js';
|
|
25
28
|
import { Field } from '../../ui/Field/index.js';
|
|
@@ -45,11 +48,20 @@ export function MobileNumberSignIn() {
|
|
|
45
48
|
registerBiometrics: ctx.registerBiometrics,
|
|
46
49
|
})));
|
|
47
50
|
const signInMutation = useSignInMutation({
|
|
48
|
-
onSuccess: () => {
|
|
51
|
+
onSuccess: async () => {
|
|
49
52
|
step1Form.reset();
|
|
50
53
|
step2Form.reset();
|
|
51
54
|
context.setStep(1);
|
|
52
55
|
globalStore.signIn.setOpen(false);
|
|
56
|
+
const session = await getSession();
|
|
57
|
+
invariant(session.status === 'authenticated');
|
|
58
|
+
await unregisterFCMDevice({
|
|
59
|
+
type: ['IOS', 'ANDROID'],
|
|
60
|
+
}, {
|
|
61
|
+
headers: {
|
|
62
|
+
Authorization: `Bearer ${session.token}`,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
53
65
|
if (signInProps.shouldShowResponsibleGamingReminder) {
|
|
54
66
|
globalStore.responsibleGamingReminder.setOpen(true);
|
|
55
67
|
}
|