@opexa/portal-components 0.0.242 → 0.0.243
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 +6 -1
- package/dist/client/hooks/useCamera.js +57 -12
- package/dist/client/hooks/useFeatureFlag.d.ts +21 -5
- package/dist/client/hooks/useFeatureFlag.js +49 -9
- package/dist/components/PortalProvider/FeatureFlag.d.ts +1 -0
- package/dist/components/PortalProvider/FeatureFlag.js +26 -0
- package/dist/components/PortalProvider/PortalProvider.js +2 -1
- package/dist/components/SignIn/MobileNumberSignIn.js +4 -2
- package/dist/components/SignIn/NameAndPasswordSignIn.js +3 -2
- package/dist/components/shared/IdDocumentField.js +18 -27
- package/dist/components/shared/SelfieField.d.ts +1 -3
- package/dist/components/shared/SelfieField.js +120 -123
- package/dist/constants/StorageKey.d.ts +1 -0
- package/dist/constants/StorageKey.js +1 -0
- package/dist/services/httpRequest.js +25 -4
- package/dist/ui/AlertDialog/AlertDialog.d.ts +88 -88
- package/dist/ui/AlertDialog/alertDialog.recipe.d.ts +8 -8
- 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 +88 -88
- package/dist/ui/Dialog/dialog.recipe.d.ts +8 -8
- package/dist/ui/Drawer/Drawer.d.ts +33 -33
- package/dist/ui/Drawer/drawer.recipe.d.ts +3 -3
- package/dist/ui/Field/Field.d.ts +21 -21
- package/dist/ui/Field/field.recipe.d.ts +3 -3
- package/dist/ui/Popover/Popover.d.ts +55 -55
- package/dist/ui/Popover/popover.recipe.d.ts +5 -5
- package/dist/ui/Select/Select.d.ts +45 -45
- package/dist/ui/Select/select.recipe.d.ts +3 -3
- package/package.json +2 -6
|
@@ -7,6 +7,7 @@ export interface CameraError<T extends string = never> {
|
|
|
7
7
|
export interface CameraData {
|
|
8
8
|
url: string;
|
|
9
9
|
file: File;
|
|
10
|
+
image: HTMLImageElement;
|
|
10
11
|
}
|
|
11
12
|
export interface CameraCropPoints {
|
|
12
13
|
x: number;
|
|
@@ -29,11 +30,15 @@ export interface UseCameraReturn<T extends string = never> {
|
|
|
29
30
|
data: CameraData | null;
|
|
30
31
|
error: CameraError<T> | null;
|
|
31
32
|
loading: boolean;
|
|
33
|
+
snapping: boolean;
|
|
32
34
|
videoRef: React.RefObject<HTMLVideoElement | null>;
|
|
33
35
|
videoProps: ComponentPropsWithRef<'video'>;
|
|
34
36
|
}
|
|
37
|
+
export interface CameraTransformData extends CameraData {
|
|
38
|
+
canvas: HTMLCanvasElement;
|
|
39
|
+
}
|
|
35
40
|
export interface UseCameraOptions<T extends string = never> extends MediaStreamConstraints {
|
|
36
|
-
transform?(data:
|
|
41
|
+
transform?(data: CameraTransformData): CameraData | Promise<CameraData>;
|
|
37
42
|
validate?(data: CameraData): CameraError<T> | void | null | undefined | Promise<CameraError<T> | void | null | undefined>;
|
|
38
43
|
}
|
|
39
44
|
export declare function useCamera<T extends string = never>(options?: UseCameraOptions<T>): UseCameraReturn<T>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isBoolean, isFunction, isObject } from 'lodash-es';
|
|
2
2
|
import { useCallback, useMemo, useRef, useState, } from 'react';
|
|
3
3
|
import invariant from 'tiny-invariant';
|
|
4
|
+
import { useMediaQuery } from 'usehooks-ts';
|
|
4
5
|
export function useCamera(options = {}) {
|
|
5
6
|
const { transform, validate, ...constraints } = options;
|
|
6
7
|
const videoRef = useRef(null);
|
|
@@ -8,6 +9,8 @@ export function useCamera(options = {}) {
|
|
|
8
9
|
const [data, setData] = useState(null);
|
|
9
10
|
const [error, setError] = useState(null);
|
|
10
11
|
const [loading, setLoading] = useState(false);
|
|
12
|
+
const [snapping, setSnapping] = useState(false);
|
|
13
|
+
const desktop = useMediaQuery('(min-width: 1024px)');
|
|
11
14
|
const [mirror, setMirror] = useState(false);
|
|
12
15
|
const open = useCallback(async () => {
|
|
13
16
|
setData(null);
|
|
@@ -31,10 +34,18 @@ export function useCamera(options = {}) {
|
|
|
31
34
|
video: {
|
|
32
35
|
facingMode: 'user',
|
|
33
36
|
noiseSuppression: true,
|
|
37
|
+
width: {
|
|
38
|
+
ideal: 9999,
|
|
39
|
+
},
|
|
40
|
+
height: {
|
|
41
|
+
ideal: 9999,
|
|
42
|
+
},
|
|
43
|
+
aspectRatio: {
|
|
44
|
+
exact: desktop ? 16 / 9 : 4 / 3,
|
|
45
|
+
},
|
|
34
46
|
frameRate: {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
ideal: 30,
|
|
47
|
+
max: 120,
|
|
48
|
+
ideal: 90,
|
|
38
49
|
},
|
|
39
50
|
...(isBoolean(constraints.video) ? {} : constraints.video),
|
|
40
51
|
},
|
|
@@ -74,10 +85,12 @@ export function useCamera(options = {}) {
|
|
|
74
85
|
finally {
|
|
75
86
|
setLoading(false);
|
|
76
87
|
}
|
|
77
|
-
}, [constraints,
|
|
88
|
+
}, [constraints, desktop]);
|
|
78
89
|
const close = useCallback(() => {
|
|
79
90
|
setData(null);
|
|
80
91
|
setError(null);
|
|
92
|
+
setLoading(false);
|
|
93
|
+
setSnapping(false);
|
|
81
94
|
return new Promise((resolve) => {
|
|
82
95
|
stream.current?.getTracks().forEach((track) => track.stop());
|
|
83
96
|
videoRef.current?.pause();
|
|
@@ -87,6 +100,7 @@ export function useCamera(options = {}) {
|
|
|
87
100
|
const snap = useCallback(async (opts = {}) => {
|
|
88
101
|
setData(null);
|
|
89
102
|
setError(null);
|
|
103
|
+
setSnapping(true);
|
|
90
104
|
const video = videoRef.current;
|
|
91
105
|
const canvas = document.createElement('canvas');
|
|
92
106
|
const context = canvas.getContext('2d');
|
|
@@ -98,12 +112,18 @@ export function useCamera(options = {}) {
|
|
|
98
112
|
const hdResolution = video.videoWidth >= 1280 && video.videoHeight >= 720;
|
|
99
113
|
const highResolution = video.videoWidth >= 1920 && video.videoHeight >= 1080;
|
|
100
114
|
const scaleFactor = lowResolution
|
|
101
|
-
? (opts.scale ?? 2)
|
|
115
|
+
? (opts.scale ?? (opts.crop ? 2.15 : 2))
|
|
102
116
|
: highResolution
|
|
103
|
-
?
|
|
117
|
+
? opts.crop
|
|
118
|
+
? 1.25
|
|
119
|
+
: 1.15
|
|
104
120
|
: hdResolution
|
|
105
|
-
?
|
|
106
|
-
|
|
121
|
+
? opts.crop
|
|
122
|
+
? 1.5
|
|
123
|
+
: 1.25
|
|
124
|
+
: opts.crop
|
|
125
|
+
? 1.5
|
|
126
|
+
: 2;
|
|
107
127
|
let x = 0;
|
|
108
128
|
let y = 0;
|
|
109
129
|
let w = video.videoWidth;
|
|
@@ -127,6 +147,7 @@ export function useCamera(options = {}) {
|
|
|
127
147
|
context.drawImage(video, x, y, w, h, 0, 0, w * scaleFactor, h * scaleFactor);
|
|
128
148
|
canvas.toBlob(async (blob) => {
|
|
129
149
|
if (!blob) {
|
|
150
|
+
setSnapping(false);
|
|
130
151
|
return setError({
|
|
131
152
|
name: 'CameraError',
|
|
132
153
|
message: "'canvas.toBlob' failed to create blob",
|
|
@@ -138,21 +159,46 @@ export function useCamera(options = {}) {
|
|
|
138
159
|
endings: 'native',
|
|
139
160
|
lastModified: Date.now(),
|
|
140
161
|
});
|
|
141
|
-
const
|
|
162
|
+
const image = new Image();
|
|
163
|
+
image.src = url;
|
|
164
|
+
image.alt = '';
|
|
165
|
+
image.width = canvas.width;
|
|
166
|
+
image.height = canvas.height;
|
|
167
|
+
if (!image.complete || image.naturalWidth === 0) {
|
|
168
|
+
await new Promise((resolve, reject) => {
|
|
169
|
+
image.onload = () => resolve();
|
|
170
|
+
image.onerror = () => reject();
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
const data = isFunction(transform)
|
|
174
|
+
? await transform({
|
|
175
|
+
url,
|
|
176
|
+
file,
|
|
177
|
+
image,
|
|
178
|
+
canvas,
|
|
179
|
+
})
|
|
180
|
+
: {
|
|
181
|
+
url,
|
|
182
|
+
file,
|
|
183
|
+
image,
|
|
184
|
+
};
|
|
142
185
|
const error = await validate?.(data);
|
|
143
186
|
if (error)
|
|
144
187
|
setError(error);
|
|
145
188
|
else
|
|
146
|
-
setData(
|
|
189
|
+
setData(data);
|
|
190
|
+
setSnapping(false);
|
|
147
191
|
}, 'image/jpeg', 1);
|
|
148
192
|
}, [mirror, transform, validate]);
|
|
149
193
|
const reset = useCallback(() => {
|
|
150
194
|
setData(null);
|
|
151
195
|
setError(null);
|
|
152
196
|
setLoading(false);
|
|
197
|
+
setSnapping(false);
|
|
153
198
|
return Promise.resolve();
|
|
154
199
|
}, [setData, setError]);
|
|
155
200
|
const reopen = useCallback(async () => {
|
|
201
|
+
setSnapping(false);
|
|
156
202
|
setLoading(true);
|
|
157
203
|
close();
|
|
158
204
|
await sleep();
|
|
@@ -171,8 +217,6 @@ export function useCamera(options = {}) {
|
|
|
171
217
|
position: 'relative',
|
|
172
218
|
boxSizing: 'border-box',
|
|
173
219
|
background: 'transparent',
|
|
174
|
-
objectFit: 'cover',
|
|
175
|
-
objectPosition: 'center',
|
|
176
220
|
transform: mirror ? 'scaleX(-1)' : 'none',
|
|
177
221
|
},
|
|
178
222
|
}), [mirror]);
|
|
@@ -185,6 +229,7 @@ export function useCamera(options = {}) {
|
|
|
185
229
|
data,
|
|
186
230
|
error,
|
|
187
231
|
loading,
|
|
232
|
+
snapping,
|
|
188
233
|
videoRef,
|
|
189
234
|
videoProps,
|
|
190
235
|
};
|
|
@@ -1,5 +1,21 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
}
|
|
1
|
+
export interface UseFeatureFlagReturn {
|
|
2
|
+
enabled: boolean;
|
|
3
|
+
enable(): Promise<void>;
|
|
4
|
+
disable(): Promise<void>;
|
|
5
|
+
}
|
|
6
|
+
export declare function useFeatureFlag(): UseFeatureFlagReturn;
|
|
7
|
+
interface FeatureFlagEventDetail {
|
|
8
|
+
enabled: boolean;
|
|
9
|
+
}
|
|
10
|
+
type FeatureFlagEvent = CustomEvent<FeatureFlagEventDetail>;
|
|
11
|
+
interface EventsMap {
|
|
12
|
+
'web-portal::feature-flag': FeatureFlagEvent;
|
|
13
|
+
}
|
|
14
|
+
type Listener<K extends keyof EventsMap> = (this: Window, event: EventsMap[K]) => void;
|
|
15
|
+
declare global {
|
|
16
|
+
interface Window {
|
|
17
|
+
addEventListener<T extends keyof EventsMap>(type: T, listener: Listener<T>): void;
|
|
18
|
+
removeEventListener<K extends keyof EventsMap>(type: K, listener: Listener<K>): void;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export {};
|
|
@@ -1,16 +1,56 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import { FEATURE_FLAG_LOCAL_STORAGE_KEY } from '../../constants/index.js';
|
|
4
|
+
const event = new CustomEvent('web-portal::feature-flag', {
|
|
5
|
+
detail: {
|
|
6
|
+
enabled: false,
|
|
7
|
+
},
|
|
8
|
+
bubbles: true,
|
|
9
|
+
cancelable: false,
|
|
10
|
+
});
|
|
11
|
+
export function useFeatureFlag() {
|
|
12
|
+
const [enabled, setEnabled] = useState(false);
|
|
13
|
+
function enable() {
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
const event = new CustomEvent('web-portal::feature-flag', {
|
|
16
|
+
detail: {
|
|
17
|
+
enabled: true,
|
|
18
|
+
},
|
|
19
|
+
bubbles: true,
|
|
20
|
+
cancelable: false,
|
|
21
|
+
});
|
|
22
|
+
window.dispatchEvent(event);
|
|
23
|
+
resolve();
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function disable() {
|
|
27
|
+
return new Promise((resolve) => {
|
|
28
|
+
window.dispatchEvent(event);
|
|
29
|
+
resolve();
|
|
30
|
+
});
|
|
31
|
+
}
|
|
6
32
|
useEffect(() => {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
setFeatureFlagEnabled(storedFlag === 'true');
|
|
33
|
+
if (localStorage.getItem(FEATURE_FLAG_LOCAL_STORAGE_KEY) != null) {
|
|
34
|
+
setEnabled(true);
|
|
10
35
|
}
|
|
11
36
|
}, []);
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
const handle = (event) => {
|
|
39
|
+
if (event.detail.enabled) {
|
|
40
|
+
setEnabled(true);
|
|
41
|
+
localStorage.setItem(FEATURE_FLAG_LOCAL_STORAGE_KEY, '');
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
setEnabled(false);
|
|
45
|
+
localStorage.removeItem(FEATURE_FLAG_LOCAL_STORAGE_KEY);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
window.addEventListener('web-portal::feature-flag', handle);
|
|
49
|
+
return () => window.removeEventListener('web-portal::feature-flag', handle);
|
|
50
|
+
}, []);
|
|
12
51
|
return {
|
|
13
|
-
|
|
14
|
-
|
|
52
|
+
enabled,
|
|
53
|
+
enable,
|
|
54
|
+
disable,
|
|
15
55
|
};
|
|
16
|
-
}
|
|
56
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function FeatureFlag(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
|
4
|
+
import { useEffect } from 'react';
|
|
5
|
+
import { useFeatureFlag } from '../../client/hooks/useFeatureFlag.js';
|
|
6
|
+
import { Portal } from '../../ui/Portal/index.js';
|
|
7
|
+
import { Presence } from '../../ui/Presence/index.js';
|
|
8
|
+
export function FeatureFlag() {
|
|
9
|
+
const router = useRouter();
|
|
10
|
+
const pathname = usePathname();
|
|
11
|
+
const searchParams = useSearchParams();
|
|
12
|
+
const featureFlag = useFeatureFlag();
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (searchParams.has('future')) {
|
|
15
|
+
featureFlag.enable();
|
|
16
|
+
}
|
|
17
|
+
}, [featureFlag, searchParams]);
|
|
18
|
+
return (_jsx(Portal, { children: _jsx(Presence, { present: featureFlag.enabled, asChild: true, children: _jsx("div", { className: "fixed bottom-3 flex w-full flex-col items-center justify-center", children: _jsxs("div", { className: "rounded-full bg-black/50 px-2 py-1 text-sm leading-none backdrop-blur-md", children: ["Experimental features are enabled.", ' ', _jsx("button", { type: "button", className: "cursor-pointer underline underline-offset-2", onClick: async () => {
|
|
19
|
+
await featureFlag.disable();
|
|
20
|
+
const newSearchParams = new URLSearchParams(searchParams);
|
|
21
|
+
newSearchParams.delete('future');
|
|
22
|
+
router.replace(`${pathname}?${newSearchParams.toString()}`, {
|
|
23
|
+
scroll: false,
|
|
24
|
+
});
|
|
25
|
+
}, children: "Disable" })] }) }) }) }));
|
|
26
|
+
}
|
|
@@ -9,6 +9,7 @@ import { prefetchSessionQuery } from '../../server/utils/prefetchSessionQuery.js
|
|
|
9
9
|
import { prefetchWalletQuery } from '../../server/utils/prefetchWalletQuery.js';
|
|
10
10
|
import { FacebookPixel, GoogleRecaptcha } from '../../third-parties/index.js';
|
|
11
11
|
import { getQueryClient } from '../../utils/getQueryClient.js';
|
|
12
|
+
import { FeatureFlag } from './FeatureFlag.js';
|
|
12
13
|
import { Providers } from './Providers.js';
|
|
13
14
|
import { SetupDomain } from './SetupDomain.js';
|
|
14
15
|
import { Snackbar } from './Snackbar.js';
|
|
@@ -21,5 +22,5 @@ export async function PortalProvider({ children, facebookPixelId, googleAnalytic
|
|
|
21
22
|
prefetchAccountQuery(),
|
|
22
23
|
prefetchWalletQuery(),
|
|
23
24
|
]);
|
|
24
|
-
return (_jsxs(_Fragment, { children: [INTERNALS__REPORT_WEB_VITALS && _jsx(WebVitals, {}), _jsx(Providers, { children: _jsxs(HydrationBoundary, { state: dehydrate(queryClient), children: [children, _jsx(Suspense, { fallback: null, children: _jsx(SetupDomain, {}) })] }) }), _jsx(Snackbar, {}), facebookPixelId && _jsx(FacebookPixel, { fbId: facebookPixelId }), googleAnalyticsId && _jsx(GoogleAnalytics, { gaId: googleAnalyticsId }), googleTagManagerId && _jsx(GoogleTagManager, { gtmId: googleTagManagerId }), googleRecaptchaSiteKey && (_jsx(GoogleRecaptcha, { siteKey: googleRecaptchaSiteKey }))] }));
|
|
25
|
+
return (_jsxs(_Fragment, { children: [INTERNALS__REPORT_WEB_VITALS && _jsx(WebVitals, {}), _jsx(Providers, { children: _jsxs(HydrationBoundary, { state: dehydrate(queryClient), children: [children, _jsx(Suspense, { fallback: null, children: _jsx(SetupDomain, {}) })] }) }), _jsx(Snackbar, {}), _jsx(Suspense, { fallback: null, children: _jsx(FeatureFlag, {}) }), facebookPixelId && _jsx(FacebookPixel, { fbId: facebookPixelId }), googleAnalyticsId && _jsx(GoogleAnalytics, { gaId: googleAnalyticsId }), googleTagManagerId && _jsx(GoogleTagManager, { gtmId: googleTagManagerId }), googleRecaptchaSiteKey && (_jsx(GoogleRecaptcha, { siteKey: googleRecaptchaSiteKey }))] }));
|
|
25
26
|
}
|
|
@@ -121,9 +121,11 @@ export function MobileNumberSignIn() {
|
|
|
121
121
|
globalStore.signIn.setOpen(false);
|
|
122
122
|
globalStore.kycReminder.setOpen(true);
|
|
123
123
|
}
|
|
124
|
-
catch {
|
|
124
|
+
catch (err) {
|
|
125
125
|
toaster.error({
|
|
126
|
-
description:
|
|
126
|
+
description: err instanceof Error
|
|
127
|
+
? err.message
|
|
128
|
+
: 'Invalid Verification Code',
|
|
127
129
|
});
|
|
128
130
|
}
|
|
129
131
|
}), 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) => {
|
|
@@ -41,9 +41,10 @@ export function NameAndPasswordSignIn() {
|
|
|
41
41
|
form.reset();
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
|
-
onError() {
|
|
44
|
+
onError(err) {
|
|
45
45
|
toaster.error({
|
|
46
|
-
description:
|
|
46
|
+
description: err.message ||
|
|
47
|
+
'An error occurred while signing in. Please try again.',
|
|
47
48
|
});
|
|
48
49
|
},
|
|
49
50
|
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useFieldContext } from '@ark-ui/react';
|
|
4
4
|
import Image from 'next/image';
|
|
5
|
-
import {
|
|
5
|
+
import { useEffect, useId, useRef, useState, } from 'react';
|
|
6
6
|
import { twMerge } from 'tailwind-merge';
|
|
7
7
|
import { useMediaQuery } from 'usehooks-ts';
|
|
8
8
|
import { useCamera } from '../../client/hooks/useCamera.js';
|
|
@@ -72,41 +72,30 @@ function Camera() {
|
|
|
72
72
|
const context = useIdDocumentFieldContext();
|
|
73
73
|
return (_jsx(Dialog.Root, { open: context.disclosure.open, onOpenChange: (details) => {
|
|
74
74
|
context.disclosure.setOpen(details.open);
|
|
75
|
-
}, onExitComplete: context.camera.close, 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 overflow-y-auto py-4", children: _jsxs(Dialog.Content, { className: "bg-bg-primary-alt mx-auto w-[calc(100dvw-1rem)] max-w-[calc(100dvw-1rem)] overflow-y-auto rounded-lg px-4 py-5 lg:w-[747px] lg:max-w-[747px] lg:px-3xl lg:py-4xl", children: [_jsx(Dialog.CloseTrigger, { children: _jsx(XIcon, {}) }), _jsx(Dialog.Title, { className: "text-center font-semibold lg:text-xl", children: "Take a Picture of Your Front ID" }), _jsxs(Dialog.Description, { className: "text-text-tertiary-600 mt-md text-center text-xs lg:text-base", children: ["Make sure your ID is clearly visible, well-lit, and not blurry.", ' ', _jsx("br", { className: "hidden lg:block" }), "Avoid glare or reflections, and ensure all corners are within\u00A0the\u00A0frame."] }), _jsxs("div", { className: "relative mt-5 lg:mt-10 lg:px-3xl", children: [_jsxs("div", { className:
|
|
76
|
-
!context.camera.error &&
|
|
77
|
-
!context.camera.loading
|
|
78
|
-
? 'block'
|
|
79
|
-
: 'hidden'), children: [_jsx("video", { ...context.camera.videoProps }), _jsx(Mask, {})] }), context.camera.error && (_jsxs("div", { className: "border-border-disabled flex aspect-[85.60/53.98] flex-col items-center justify-center rounded-md border bg-black px-4 lg:rounded-lg", children: [_jsx(CameraOffIcon, { className: "text-text-placeholder-subtle size-10 text-center lg:size-12" }), _jsx("h2", { className: "mt-3 text-sm font-semibold lg:mt-4 lg:text-base", children: context.camera.error.name }), _jsx("p", { className: "text-text-tertiary-600 mt-0.5 text-center text-xs lg:mt-1 lg:text-sm", children: context.camera.error.message }), _jsx(Button, { size: "xs", variant: "outline", colorScheme: "gray", fullWidth: false, className: "mt-4 lg:mt-5", onClick: () => {
|
|
75
|
+
}, closeOnEscape: false, closeOnInteractOutside: false, onExitComplete: context.camera.close, 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 overflow-y-auto py-4", children: _jsxs(Dialog.Content, { className: "bg-bg-primary-alt mx-auto w-[calc(100dvw-1rem)] max-w-[calc(100dvw-1rem)] overflow-y-auto rounded-lg px-4 py-5 lg:w-[747px] lg:max-w-[747px] lg:px-3xl lg:py-4xl", children: [_jsx(Dialog.CloseTrigger, { children: _jsx(XIcon, {}) }), _jsx(Dialog.Title, { className: "text-center font-semibold lg:text-xl", children: "Take a Picture of Your Front ID" }), _jsxs(Dialog.Description, { className: "text-text-tertiary-600 mt-md text-center text-xs lg:text-base", children: ["Make sure your ID is clearly visible, well-lit, and not blurry.", ' ', _jsx("br", { className: "hidden lg:block" }), "Avoid glare or reflections, and ensure all corners are within\u00A0the\u00A0frame."] }), _jsxs("div", { className: "relative mt-5 lg:mt-10 lg:px-3xl", children: [_jsx(Video, {}), context.camera.error && (_jsxs("div", { className: "border-border-disabled flex aspect-[4/3] flex-col items-center justify-center rounded-md border bg-black px-4 lg:aspect-video", children: [_jsx(CameraOffIcon, { className: "text-text-placeholder-subtle size-10 text-center lg:size-12" }), _jsx("h2", { className: "mt-3 text-sm font-semibold lg:mt-4 lg:text-base", children: context.camera.error.name }), _jsx("p", { className: "text-text-tertiary-600 mt-0.5 text-center text-xs lg:mt-1 lg:text-sm", children: context.camera.error.message }), _jsx(Button, { size: "xs", variant: "outline", colorScheme: "gray", fullWidth: false, className: "mt-4 lg:mt-5", onClick: () => {
|
|
80
76
|
context.camera.reopen();
|
|
81
|
-
}, children: "Try again" })] })), context.camera.loading && (_jsx("div", { className: "border-border-disabled flex aspect-[
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
y: rect.y,
|
|
89
|
-
x: rect.x,
|
|
90
|
-
width: rect.width,
|
|
91
|
-
height: rect.height,
|
|
92
|
-
};
|
|
93
|
-
context.camera.snap({ crop });
|
|
94
|
-
}, children: [_jsx(Camera01Icon, { className: "size-5" }), "Capture"] }) })), context.camera.data && (_jsxs("div", { className: "mt-5 flex gap-3 lg:mt-10", children: [_jsxs(Button, { onClick: () => {
|
|
77
|
+
}, children: "Try again" })] })), context.camera.loading && (_jsx("div", { className: "border-border-disabled flex aspect-[4/3] flex-col items-center justify-center rounded-md border bg-black px-4 lg:aspect-video", children: _jsx(Spinner02Icon, { className: "text-text-placeholder size-8" }) })), context.camera.data && (_jsx("div", { className: "border-border-disabled aspect-[4/3] w-full overflow-hidden rounded-md border bg-black lg:aspect-video", children: _jsx(Image, { src: context.camera.data.url, alt: "", width: 600, height: 378.08, unoptimized: true, className: "mx-auto mt-2 block h-[calc(100%-1rem)] w-auto" }) }))] }), !context.camera.data && (_jsx("div", { className: "mt-5 lg:mt-10", children: _jsxs(Button, { className: "disabled:bg-bg-disabled disabled:text-text-disabled mx-auto w-full px-xl py-lg lg:w-[10rem]", onClick: () => {
|
|
78
|
+
context.camera.snap({
|
|
79
|
+
crop: context.guideRef.current?.getBoundingClientRect(),
|
|
80
|
+
});
|
|
81
|
+
}, disabled: context.camera.snapping ||
|
|
82
|
+
context.camera.loading ||
|
|
83
|
+
!!context.camera.error, children: [context.camera.snapping ? (_jsx(Spinner02Icon, { className: "size-5" })) : (_jsx(Camera01Icon, { className: "size-5" })), "Capture"] }) })), context.camera.data && (_jsxs("div", { className: "mt-5 flex gap-3 lg:mt-10 lg:justify-center", children: [_jsxs(Button, { onClick: () => {
|
|
95
84
|
const file = context.camera.data?.file;
|
|
96
85
|
if (!file)
|
|
97
86
|
return;
|
|
98
87
|
context.mutation.mutate({ file });
|
|
99
88
|
context.disclosure.setOpen(false);
|
|
100
|
-
}, children: [_jsx(CheckCircleIcon, { className: "size-5" }), "Use Photo"] }), _jsxs(Button, { variant: "outline", colorScheme: "gray", onClick: () => {
|
|
89
|
+
}, className: "w-full lg:w-[10rem]", children: [_jsx(CheckCircleIcon, { className: "size-5" }), "Use Photo"] }), _jsxs(Button, { variant: "outline", colorScheme: "gray", onClick: () => {
|
|
101
90
|
context.camera.reset();
|
|
102
|
-
}, children: [_jsx(RefreshCcw01Icon, { className: "size-5" }), " Retake"] })] }))] }) })] }) }));
|
|
91
|
+
}, className: "w-full lg:w-[10rem]", children: [_jsx(RefreshCcw01Icon, { className: "size-5" }), " Retake"] })] }))] }) })] }) }));
|
|
103
92
|
}
|
|
104
93
|
/**
|
|
105
94
|
* -------------------------------------
|
|
106
|
-
*
|
|
95
|
+
* Video
|
|
107
96
|
* -------------------------------------
|
|
108
97
|
*/
|
|
109
|
-
function
|
|
98
|
+
function Video() {
|
|
110
99
|
const id = useId();
|
|
111
100
|
const context = useIdDocumentFieldContext();
|
|
112
101
|
const [maskBox, setMaskBox] = useState({
|
|
@@ -115,7 +104,7 @@ function Mask() {
|
|
|
115
104
|
width: 0,
|
|
116
105
|
height: 0,
|
|
117
106
|
});
|
|
118
|
-
|
|
107
|
+
useEffect(() => {
|
|
119
108
|
const update = () => {
|
|
120
109
|
const guide = context.guideRef.current;
|
|
121
110
|
const mask = context.maskRef.current;
|
|
@@ -144,7 +133,9 @@ function Mask() {
|
|
|
144
133
|
requestAnimationFrame(update);
|
|
145
134
|
return () => observer.disconnect();
|
|
146
135
|
}, [context.guideRef, context.maskRef]);
|
|
147
|
-
return (_jsxs(
|
|
136
|
+
return (_jsxs("div", { className: twMerge('relative h-auto w-full overflow-hidden', !context.camera.data && !context.camera.error && !context.camera.loading
|
|
137
|
+
? 'block'
|
|
138
|
+
: 'hidden'), children: [_jsx("video", { ...context.camera.videoProps }), _jsx("svg", { className: "pointer-events-none absolute -inset-4 size-[200%]", children: _jsx("defs", { children: _jsxs("mask", { id: id, maskUnits: "userSpaceOnUse", children: [_jsx("rect", { x: "0", y: "0", width: "200%", height: "200%", fill: "white" }), _jsx("rect", { x: maskBox.x, y: maskBox.y, width: maskBox.width, height: maskBox.height, fill: "black", rx: "8", ry: "8" })] }) }) }), _jsx("div", { ref: context.maskRef, className: "absolute -inset-4 size-[200%] bg-black/60 backdrop-blur-sm", style: {
|
|
148
139
|
mask: `url(#${id})`,
|
|
149
140
|
WebkitMask: `url(#${id})`,
|
|
150
141
|
maskRepeat: 'no-repeat',
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import '@mediapipe/face_detection';
|
|
2
|
-
import '@tensorflow/tfjs-backend-webgl';
|
|
3
|
-
import '@tensorflow/tfjs-core';
|
|
4
1
|
import { type CSSProperties, type RefObject } from 'react';
|
|
5
2
|
import { type ValidateFileError } from '../../utils/validateFile';
|
|
6
3
|
export interface SelfieFieldProps extends UseSelfieFieldProps {
|
|
@@ -20,4 +17,5 @@ interface UseSelfieFieldProps {
|
|
|
20
17
|
onChange?: (value: string) => void;
|
|
21
18
|
defaultValue?: string;
|
|
22
19
|
}
|
|
20
|
+
export declare function validateFaceFromImage(img: HTMLImageElement): Promise<boolean>;
|
|
23
21
|
export {};
|