@opexa/portal-components 0.0.176 → 0.0.178
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/useUpdateSessionMutation.d.ts +3 -0
- package/dist/client/hooks/useUpdateSessionMutation.js +26 -0
- package/dist/client/services/updateSession.d.ts +5 -0
- package/dist/client/services/updateSession.js +15 -0
- package/dist/components/KYC/CaptureIdDocument.js +18 -8
- package/dist/components/KYC/CaptureSelfie.js +55 -28
- package/dist/components/KYC/NoCameraError.d.ts +7 -0
- package/dist/components/KYC/NoCameraError.js +6 -0
- package/dist/components/KYC/utils.d.ts +1 -0
- package/dist/components/KYC/utils.js +22 -0
- package/dist/components/PortalProvider/PortalProvider.d.ts +1 -1
- package/dist/components/PortalProvider/PortalProvider.js +4 -2
- package/dist/components/PortalProvider/SetupDomain.d.ts +1 -0
- package/dist/components/PortalProvider/SetupDomain.js +20 -0
- package/dist/handlers.d.ts +5 -0
- package/dist/handlers.js +36 -1
- package/dist/middleware.js +2 -16
- package/dist/server/services/getSession.js +4 -3
- package/dist/utils/mutationKeys.d.ts +1 -0
- package/dist/utils/mutationKeys.js +4 -0
- package/package.json +1 -1
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useMutation } from '@tanstack/react-query';
|
|
2
|
+
import { getQueryClient } from '../../utils/getQueryClient.js';
|
|
3
|
+
import { getUpdateSessionMutationKey } from '../../utils/mutationKeys.js';
|
|
4
|
+
import { getSessionQueryKey } from '../../utils/queryKeys.js';
|
|
5
|
+
import { updateSession, } from '../services/updateSession.js';
|
|
6
|
+
export const useUpdateSessionMutation = (config) => {
|
|
7
|
+
const queryClient = getQueryClient();
|
|
8
|
+
return useMutation({
|
|
9
|
+
...config,
|
|
10
|
+
mutationKey: getUpdateSessionMutationKey(),
|
|
11
|
+
mutationFn: async (input) => {
|
|
12
|
+
const ok = await updateSession(input);
|
|
13
|
+
queryClient.setQueryData(getSessionQueryKey(), (prev) => {
|
|
14
|
+
if (!prev)
|
|
15
|
+
return prev;
|
|
16
|
+
if (!input.domain)
|
|
17
|
+
return prev;
|
|
18
|
+
return {
|
|
19
|
+
...prev,
|
|
20
|
+
...input,
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
return ok;
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { cache } from 'react';
|
|
2
|
+
import { httpRequest, } from '../../services/httpRequest.js';
|
|
3
|
+
export const updateSession = cache(async (input, options) => {
|
|
4
|
+
try {
|
|
5
|
+
const { ok } = await httpRequest.json('/api/sessions', {
|
|
6
|
+
...options,
|
|
7
|
+
method: 'PATCH',
|
|
8
|
+
body: JSON.stringify(input),
|
|
9
|
+
});
|
|
10
|
+
return ok;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
});
|
|
@@ -10,7 +10,8 @@ import { XIcon } from '../../icons/XIcon.js';
|
|
|
10
10
|
import { Button } from '../../ui/Button/index.js';
|
|
11
11
|
import { Dialog } from '../../ui/Dialog/index.js';
|
|
12
12
|
import { useKYCContext } from './KYCContext.js';
|
|
13
|
-
import
|
|
13
|
+
import NoCameraError from './NoCameraError.js';
|
|
14
|
+
import { dataURLtoBlob, resizeFile, showPermissionInstructions } from './utils.js';
|
|
14
15
|
export function CaptureIdDocument() {
|
|
15
16
|
const kyc = useKYCContext();
|
|
16
17
|
const { mutate, isPending } = useUploadImageFileMutation();
|
|
@@ -89,7 +90,7 @@ export function CaptureIdDocument() {
|
|
|
89
90
|
setIsLoading(false);
|
|
90
91
|
if (error instanceof DOMException) {
|
|
91
92
|
console.error('Error accessing camera:', error.message);
|
|
92
|
-
setCameraError(error.
|
|
93
|
+
setCameraError(error.name);
|
|
93
94
|
}
|
|
94
95
|
else {
|
|
95
96
|
console.error('Unknown error accessing camera:', error);
|
|
@@ -149,12 +150,12 @@ export function CaptureIdDocument() {
|
|
|
149
150
|
};
|
|
150
151
|
newImg.onerror = () => {
|
|
151
152
|
console.error('Error loading image');
|
|
152
|
-
setCameraError('
|
|
153
|
+
setCameraError('NoIdDetectedError');
|
|
153
154
|
};
|
|
154
155
|
}
|
|
155
156
|
catch (error) {
|
|
156
157
|
console.error('Error capturing image:', error);
|
|
157
|
-
setCameraError('
|
|
158
|
+
setCameraError('NoIdDetectedError');
|
|
158
159
|
setCaptureInProgress(false);
|
|
159
160
|
}
|
|
160
161
|
}
|
|
@@ -194,11 +195,20 @@ export function CaptureIdDocument() {
|
|
|
194
195
|
}
|
|
195
196
|
startCamera();
|
|
196
197
|
}
|
|
198
|
+
function checkCameraPermission() {
|
|
199
|
+
const constraints = { video: true };
|
|
200
|
+
navigator.mediaDevices
|
|
201
|
+
.getUserMedia(constraints)
|
|
202
|
+
.then(() => {
|
|
203
|
+
setCameraError(null);
|
|
204
|
+
startCamera();
|
|
205
|
+
})
|
|
206
|
+
.catch(() => {
|
|
207
|
+
showPermissionInstructions();
|
|
208
|
+
});
|
|
209
|
+
}
|
|
197
210
|
return (_jsx(_Fragment, { children: _jsxs("div", { className: "w-full p-4 lg:w-[640px] lg:px-12 lg:py-4xl", children: [_jsxs("button", { type: "button", className: "flex items-center gap-lg lg:absolute lg:right-5 lg:top-5 lg:p-md", onClick: () => {
|
|
198
211
|
stopCamera();
|
|
199
212
|
kyc.setCapturing(null);
|
|
200
|
-
}, children: [_jsx(XIcon, { className: "text-text-senary-300 hidden size-5 lg:block" }), _jsx(ArrowLeftIcon, { className: "text-text-senary-300 size-5 lg:hidden" }), _jsx("span", { className: "text-lg font-semibold lg:hidden", children: "Back" })] }), _jsx(Dialog.Title, { className: "mt-6 text-center text-lg font-semibold lg:mt-0", children: "Take a Picture of Your Front ID" }), _jsxs(Dialog.Description, { className: "lg:max-w-auto text-text-tertiary-600 mx-auto mt-sm max-w-[19rem] text-center text-sm lg:max-w-full", 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 the frame."] }), _jsx("div", { id: "parent", className: "border-border-disabled mt-6 aspect-video rounded-xl border bg-black lg:mt-8", children: cameraError ? (_jsx(
|
|
201
|
-
setCameraError(null);
|
|
202
|
-
startCamera();
|
|
203
|
-
}, children: "Try Again" }) })) : captureInProgress ? (_jsxs(Button, { className: "disabled:bg-bg-disabled disabled:text-text-disabled mx-auto mt-6 px-xl disabled:opacity-100 lg:mt-8 lg:w-fit", onClick: captureAndProcessImage, children: ["Capture Image", _jsx(Camera01Icon, { className: "size-5" })] })) : selectedImage && selectedImage.src ? (_jsxs("div", { className: "mt-4 flex flex-col items-center justify-center gap-2 md:flex-row", children: [_jsxs(Button, { className: "lg:w-fit", type: "button", onClick: retakePhoto, children: ["Retake Photo ", _jsx(Camera01Icon, { className: "ml-1" })] }), _jsxs(Button, { className: "lg:w-fit", type: "button", onClick: uploadPhoto, disabled: isPending, children: ["Use Photo ", _jsx(CheckCircleIcon, { className: "ml-1" })] })] })) : (_jsxs(Button, { className: "disabled:bg-bg-disabled disabled:text-text-disabled mx-auto mt-6 px-xl disabled:opacity-100 lg:mt-8 lg:w-fit", onClick: startCamera, disabled: isLoading || !!videoStream, children: [isLoading ? 'Starting Camera...' : 'Start Camera', _jsx(Camera01Icon, { className: "size-5" })] }))] }) }));
|
|
213
|
+
}, children: [_jsx(XIcon, { className: "text-text-senary-300 hidden size-5 lg:block" }), _jsx(ArrowLeftIcon, { className: "text-text-senary-300 size-5 lg:hidden" }), _jsx("span", { className: "text-lg font-semibold lg:hidden", children: "Back" })] }), _jsx(Dialog.Title, { className: "mt-6 text-center text-lg font-semibold lg:mt-0", children: "Take a Picture of Your Front ID" }), _jsxs(Dialog.Description, { className: "lg:max-w-auto text-text-tertiary-600 mx-auto mt-sm max-w-[19rem] text-center text-sm lg:max-w-full", 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 the frame."] }), _jsx("div", { id: "parent", className: "border-border-disabled mt-6 aspect-video rounded-xl border bg-black lg:mt-8", children: ['NotAllowedError', 'NotReadableError', 'NotFoundError'].includes(cameraError) ? (_jsx(NoCameraError, { onRetry: checkCameraPermission, title: "Camera Access Required", description: "We were unable to detect a camera on your device. Please ensure it is properly connected and\r\n accessible to this application." })) : captureInProgress ? (_jsxs("div", { className: "relative flex h-fit w-full flex-col items-center justify-center", children: [_jsx("video", { ref: videoElement, autoPlay: true, muted: true, playsInline: true, className: "relative h-[60vh] w-full rounded-lg object-cover md:aspect-video md:max-h-[333px]", width: "100%", height: "100%" }), _jsxs("div", { className: "pointer-events-none absolute inset-0 z-50", children: [_jsx("div", { className: twMerge('absolute inset-0 w-full bg-black/60 backdrop-blur-sm [clip-path:polygon(0%_0%,0%_100%,calc(50%-158px)_100%,calc(50%-158px)_calc(50%-100px),calc(50%+158px)_calc(50%-100px),calc(50%+158px)_calc(50%+100px),calc(50%-158px)_calc(50%+100px),calc(50%-158px)_100%,100%_100%,100%_0%)] md:[clip-path:polygon(0%_0%,0%_100%,calc(50%-225px)_100%,calc(50%-225px)_calc(50%-142px),calc(50%+225px)_calc(50%-142px),calc(50%+225px)_calc(50%+142px),calc(50%-225px)_calc(50%+142px),calc(50%-225px)_100%,100%_100%,100%_0%)]') }), _jsx("div", { className: "absolute inset-0 z-[60] mx-auto flex items-center justify-center", children: _jsx("div", { className: "relative aspect-[1011/638] h-[200px] md:h-[295px] md:w-[450px]", children: _jsx("div", { className: "h-full w-full rounded-lg border-4 border-dashed border-[#B54708]" }) }) })] })] })) : (_jsx("div", { ref: containerRef, className: "relative grid h-full w-full place-items-center rounded-lg object-contain md:max-h-[333px]" })) }), captureInProgress ? (_jsxs(Button, { className: "disabled:bg-bg-disabled disabled:text-text-disabled mx-auto mt-6 px-xl disabled:opacity-100 lg:mt-8 lg:w-fit", onClick: captureAndProcessImage, children: ["Capture Image", _jsx(Camera01Icon, { className: "size-5" })] })) : selectedImage && selectedImage.src ? (_jsxs("div", { className: "mt-4 flex flex-col items-center justify-center gap-2 md:flex-row", children: [_jsxs(Button, { className: "lg:w-fit", type: "button", onClick: retakePhoto, children: ["Retake Photo ", _jsx(Camera01Icon, { className: "ml-1" })] }), _jsxs(Button, { className: "lg:w-fit", type: "button", onClick: uploadPhoto, disabled: isPending, children: ["Use Photo ", _jsx(CheckCircleIcon, { className: "ml-1" })] })] })) : (_jsxs(Button, { className: twMerge('disabled:bg-bg-disabled disabled:text-text-disabled mx-auto mt-6 px-xl disabled:opacity-100 lg:mt-8 lg:w-fit', cameraError && 'hidden'), onClick: startCamera, disabled: isLoading || !!videoStream, children: [isLoading ? 'Starting Camera...' : 'Start Camera', _jsx(Camera01Icon, { className: "size-5" })] }))] }) }));
|
|
204
214
|
}
|
|
@@ -11,8 +11,9 @@ import { Button } from '../../ui/Button/index.js';
|
|
|
11
11
|
import { Dialog } from '../../ui/Dialog/index.js';
|
|
12
12
|
import { onMobileDevice } from '../../utils/onMobileDevice.js';
|
|
13
13
|
import { useKYCContext } from './KYCContext.js';
|
|
14
|
+
import NoCameraError from './NoCameraError.js';
|
|
14
15
|
import PersonOverlayDesktop from './PersonOverlayDesktop.js';
|
|
15
|
-
import { dataURLtoBlob, loadFaceApiModels, resizeFile } from './utils.js';
|
|
16
|
+
import { dataURLtoBlob, loadFaceApiModels, resizeFile, showPermissionInstructions, } from './utils.js';
|
|
16
17
|
export function CaptureSelfie() {
|
|
17
18
|
const kyc = useKYCContext();
|
|
18
19
|
const { mutate, isPending } = useUploadImageFileMutation();
|
|
@@ -40,32 +41,46 @@ export function CaptureSelfie() {
|
|
|
40
41
|
}
|
|
41
42
|
}, [captureInProgress]);
|
|
42
43
|
async function startCamera() {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
videoRef.current
|
|
56
|
-
|
|
57
|
-
videoRef.current
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
44
|
+
try {
|
|
45
|
+
setCaptureInProgress(true);
|
|
46
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
47
|
+
video: {
|
|
48
|
+
facingMode: mobileDevice ? 'user' : 'environment',
|
|
49
|
+
aspectRatio: mobileDevice ? 9 / 16 : 16 / 9,
|
|
50
|
+
width: { ideal: 1920 },
|
|
51
|
+
height: { ideal: 1080 },
|
|
52
|
+
},
|
|
53
|
+
audio: false,
|
|
54
|
+
});
|
|
55
|
+
setVideoStream(stream);
|
|
56
|
+
if (videoRef.current) {
|
|
57
|
+
videoRef.current.srcObject = stream;
|
|
58
|
+
videoRef.current.onloadedmetadata = () => {
|
|
59
|
+
videoRef.current
|
|
60
|
+
?.play()
|
|
61
|
+
.then(() => {
|
|
62
|
+
if (modelsLoaded) {
|
|
63
|
+
startFaceDetection();
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
.catch((error) => {
|
|
67
|
+
console.error('Error playing video:', error);
|
|
68
|
+
setCameraError('Failed to play video stream. Please check your camera settings.');
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (error instanceof DOMException) {
|
|
75
|
+
console.error('Error accessing camera:', error.message);
|
|
76
|
+
setCameraError(error.name);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
console.error('Unknown error accessing camera:', error);
|
|
80
|
+
setCameraError('An unknown error occurred. Please check permissions and try again.');
|
|
81
|
+
}
|
|
82
|
+
setCaptureInProgress(false);
|
|
83
|
+
stopCamera();
|
|
69
84
|
}
|
|
70
85
|
}
|
|
71
86
|
function startFaceDetection() {
|
|
@@ -241,6 +256,18 @@ export function CaptureSelfie() {
|
|
|
241
256
|
});
|
|
242
257
|
}
|
|
243
258
|
}
|
|
259
|
+
function checkCameraPermission() {
|
|
260
|
+
const constraints = { video: true };
|
|
261
|
+
navigator.mediaDevices
|
|
262
|
+
.getUserMedia(constraints)
|
|
263
|
+
.then(() => {
|
|
264
|
+
setCameraError(null);
|
|
265
|
+
startCamera();
|
|
266
|
+
})
|
|
267
|
+
.catch(() => {
|
|
268
|
+
showPermissionInstructions();
|
|
269
|
+
});
|
|
270
|
+
}
|
|
244
271
|
useEffect(() => {
|
|
245
272
|
return () => {
|
|
246
273
|
if (detectionIntervalRef.current) {
|
|
@@ -254,5 +281,5 @@ export function CaptureSelfie() {
|
|
|
254
281
|
return (_jsxs("div", { className: "w-full p-4 lg:w-[640px] lg:px-12 lg:py-4xl", children: [_jsxs("button", { type: "button", className: "flex items-center gap-lg lg:absolute lg:right-5 lg:top-5 lg:p-md", onClick: () => {
|
|
255
282
|
stopCamera();
|
|
256
283
|
kyc.setCapturing(null);
|
|
257
|
-
}, children: [_jsx(XIcon, { className: "text-text-senary-300 hidden size-5 lg:block" }), _jsx(ArrowLeftIcon, { className: "text-text-senary-300 size-5 lg:hidden" }), _jsx("span", { className: "text-lg font-semibold lg:hidden", children: "Back" })] }), _jsx(Dialog.Title, { className: "mt-6 text-center text-lg font-semibold lg:mt-0", children: "Take a Selfie Holding Your ID" }), _jsx(Dialog.Description, { className: "text-text-tertiary-600 mx-auto mt-sm max-w-[19rem] text-center text-sm lg:max-w-full", children: "Make sure your face and ID are clearly visible, with no glare or blur." }), _jsx("div", { id: "parent", className: "border-border-disabled mt-6 aspect-video rounded-xl border bg-black lg:mt-8", children: captureInProgress ? (_jsxs("div", { className: "relative flex h-fit w-full flex-col items-center justify-center", children: [_jsx("video", { ref: videoRef, autoPlay: true, muted: true, className: "h-[60vh] w-full rounded-lg object-cover md:aspect-video md:h-[333px]", width: "100%", height: "100%" }), _jsx("canvas", { ref: faceCanvasRef, className: "absolute inset-0 h-[60vh] w-full object-cover md:aspect-video md:h-[333px] md:object-contain", width: "100%", height: "100%" }), isCapturing && (_jsx("span", { className: "absolute inset-x-0 bottom-8 flex items-center justify-center text-5xl font-bold text-[#713B12CC] md:bottom-4", children: countdown })), !isFaceCentered && (_jsx("span", { className: "absolute inset-x-0 bottom-12 mx-auto w-1/2 text-center text-xs text-white md:bottom-3", children: "Please center your face in the camera view. Make sure your ID is clearly visible next to your face." })), _jsx(PersonOverlayDesktop, { isFaceCentered: isFaceCentered, width: videoRef.current?.clientWidth || 543, height: videoRef.current?.clientHeight || 333 })] })) : (selectedImage && (_jsx("img", { src: selectedImage, alt: "Captured Photo", className: "h-full w-full rounded-lg object-cover md:aspect-video md:h-[333px]" }))) }), selectedImage && (_jsxs("div", { className: "flex flex-col items-center justify-center gap-2 md:flex-row", children: [_jsxs(Button, { className: "disabled:bg-bg-disabled disabled:text-text-disabled mx-auto mt-4 px-xl disabled:opacity-100", size: "sm", onClick: retakePhoto, type: "button", children: ["Rekate Photo", _jsx(Camera01Icon, { className: "size-5" })] }), _jsxs(Button, { className: "disabled:bg-bg-disabled disabled:text-text-disabled mx-auto mt-4 px-xl disabled:opacity-100", size: "sm", onClick: usePhoto, type: "button", disabled: isPending, children: ["Use Photo", _jsx(Camera01Icon, { className: "size-5" })] })] })), !captureInProgress && (_jsxs(Button, { className: twMerge('mx-auto mt-6 px-xl lg:mt-8', selectedImage && 'hidden'), onClick: startCamera, disabled: !modelsLoaded, type: "button", children: ["Start Camera", _jsx(Camera01Icon, { className: "size-5" })] }))] }));
|
|
284
|
+
}, children: [_jsx(XIcon, { className: "text-text-senary-300 hidden size-5 lg:block" }), _jsx(ArrowLeftIcon, { className: "text-text-senary-300 size-5 lg:hidden" }), _jsx("span", { className: "text-lg font-semibold lg:hidden", children: "Back" })] }), _jsx(Dialog.Title, { className: "mt-6 text-center text-lg font-semibold lg:mt-0", children: "Take a Selfie Holding Your ID" }), _jsx(Dialog.Description, { className: "text-text-tertiary-600 mx-auto mt-sm max-w-[19rem] text-center text-sm lg:max-w-full", children: "Make sure your face and ID are clearly visible, with no glare or blur." }), _jsx("div", { id: "parent", className: "border-border-disabled mt-6 aspect-video rounded-xl border bg-black lg:mt-8", children: ['NotAllowedError', 'NotReadableError', 'NotFoundError'].includes(cameraError) ? (_jsx(NoCameraError, { onRetry: checkCameraPermission, title: "Camera Access Required", description: "We were unable to detect a camera on your device. Please ensure it is properly connected and accessible to this application." })) : captureInProgress && !cameraError ? (_jsxs("div", { className: "relative flex h-fit w-full flex-col items-center justify-center", children: [_jsx("video", { ref: videoRef, autoPlay: true, muted: true, className: "h-[60vh] w-full rounded-lg object-cover md:aspect-video md:h-[333px]", width: "100%", height: "100%" }), _jsx("canvas", { ref: faceCanvasRef, className: "absolute inset-0 h-[60vh] w-full object-cover md:aspect-video md:h-[333px] md:object-contain", width: "100%", height: "100%" }), isCapturing && (_jsx("span", { className: "absolute inset-x-0 bottom-8 flex items-center justify-center text-5xl font-bold text-[#713B12CC] md:bottom-4", children: countdown })), !isFaceCentered && (_jsx("span", { className: "absolute inset-x-0 bottom-12 mx-auto w-1/2 text-center text-xs text-white md:bottom-3", children: "Please center your face in the camera view. Make sure your ID is clearly visible next to your face." })), _jsx(PersonOverlayDesktop, { isFaceCentered: isFaceCentered, width: videoRef.current?.clientWidth || 543, height: videoRef.current?.clientHeight || 333 })] })) : (selectedImage && (_jsx("img", { src: selectedImage, alt: "Captured Photo", className: "h-full w-full rounded-lg object-cover md:aspect-video md:h-[333px]" }))) }), selectedImage && (_jsxs("div", { className: "flex flex-col items-center justify-center gap-2 md:flex-row", children: [_jsxs(Button, { className: "disabled:bg-bg-disabled disabled:text-text-disabled mx-auto mt-4 px-xl disabled:opacity-100", size: "sm", onClick: retakePhoto, type: "button", children: ["Rekate Photo", _jsx(Camera01Icon, { className: "size-5" })] }), _jsxs(Button, { className: "disabled:bg-bg-disabled disabled:text-text-disabled mx-auto mt-4 px-xl disabled:opacity-100", size: "sm", onClick: usePhoto, type: "button", disabled: isPending, children: ["Use Photo", _jsx(Camera01Icon, { className: "size-5" })] })] })), !captureInProgress && !cameraError && (_jsxs(Button, { className: twMerge('mx-auto mt-6 px-xl lg:mt-8', selectedImage && 'hidden'), onClick: startCamera, disabled: !modelsLoaded, type: "button", children: ["Start Camera", _jsx(Camera01Icon, { className: "size-5" })] }))] }));
|
|
258
285
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button } from '../../ui/Button/index.js';
|
|
3
|
+
function NoCameraError({ onRetry, title, description }) {
|
|
4
|
+
return (_jsxs("div", { className: "flex h-full flex-col items-center justify-center gap-2 p-4", children: [_jsxs("div", { className: "text-center", children: [_jsx("p", { className: "text-red-500 mb-2", children: title }), _jsx("p", { className: "mb-2 text-sm", children: description })] }), _jsx(Button, { className: "mx-auto px-xl lg:w-fit", onClick: onRetry, size: "sm", children: "Try Again" })] }));
|
|
5
|
+
}
|
|
6
|
+
export default NoCameraError;
|
|
@@ -5,3 +5,4 @@ export declare const MAX_FILE_SIZE = 10;
|
|
|
5
5
|
export declare const MAX_NAME_LENGTH = 52;
|
|
6
6
|
export declare const SUPPORTED_FILES: string[];
|
|
7
7
|
export declare function validateFile(file: File): "File too large" | "File name too long" | "Unsupported file" | undefined;
|
|
8
|
+
export declare function showPermissionInstructions(): void;
|
|
@@ -48,3 +48,25 @@ export function validateFile(file) {
|
|
|
48
48
|
if (!SUPPORTED_FILES.includes(fileType))
|
|
49
49
|
return 'Unsupported file';
|
|
50
50
|
}
|
|
51
|
+
export function showPermissionInstructions() {
|
|
52
|
+
const instructions = `
|
|
53
|
+
Camera access is blocked. To enable camera access:
|
|
54
|
+
|
|
55
|
+
Chrome/Edge:
|
|
56
|
+
1. Click the camera icon in the address bar
|
|
57
|
+
2. Select "Always allow" and click "Done"
|
|
58
|
+
3. Or go to Settings > Privacy and security > Site settings > Camera
|
|
59
|
+
|
|
60
|
+
Firefox:
|
|
61
|
+
1. Click the shield icon in the address bar
|
|
62
|
+
2. Click on "Camera blocked"
|
|
63
|
+
3. Select "Allow" and refresh the page
|
|
64
|
+
|
|
65
|
+
Safari:
|
|
66
|
+
1. Go to Safari > Settings > Websites > Camera
|
|
67
|
+
2. Find this website and change to "Allow"
|
|
68
|
+
|
|
69
|
+
After changing permissions, please refresh the page and try again.
|
|
70
|
+
`;
|
|
71
|
+
alert(instructions);
|
|
72
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { GoogleAnalytics, GoogleTagManager } from '@next/third-parties/google';
|
|
3
3
|
import { dehydrate, HydrationBoundary } from '@tanstack/react-query';
|
|
4
|
+
import { Suspense } from 'react';
|
|
4
5
|
import { prefetchAccountQuery } from '../../server/utils/prefetchAccountQuery.js';
|
|
5
6
|
import { prefetchPlatformQuery } from '../../server/utils/prefetchPlatformQuery.js';
|
|
6
7
|
import { prefetchSessionQuery } from '../../server/utils/prefetchSessionQuery.js';
|
|
@@ -8,6 +9,7 @@ import { prefetchWalletQuery } from '../../server/utils/prefetchWalletQuery.js';
|
|
|
8
9
|
import { FacebookPixel, GoogleRecaptcha } from '../../third-parties/index.js';
|
|
9
10
|
import { getQueryClient } from '../../utils/getQueryClient.js';
|
|
10
11
|
import { Providers } from './Providers.js';
|
|
12
|
+
import { SetupDomain } from './SetupDomain.js';
|
|
11
13
|
import { Snackbar } from './Snackbar.js';
|
|
12
14
|
import { WebVitals } from './WebVitals.js';
|
|
13
15
|
export async function PortalProvider({ children, facebookPixelId, googleAnalyticsId, googleTagManagerId, googleRecaptchaSiteKey, }) {
|
|
@@ -18,5 +20,5 @@ export async function PortalProvider({ children, facebookPixelId, googleAnalytic
|
|
|
18
20
|
prefetchAccountQuery(),
|
|
19
21
|
prefetchWalletQuery(),
|
|
20
22
|
]);
|
|
21
|
-
return (_jsxs(_Fragment, { children: [_jsx(WebVitals, {}), _jsx(Providers, { children:
|
|
23
|
+
return (_jsxs(_Fragment, { children: [_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 }))] }));
|
|
22
24
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function SetupDomain(): null;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useSearchParams } from 'next/navigation';
|
|
3
|
+
import { useTimeout } from 'usehooks-ts';
|
|
4
|
+
import { useSessionQuery } from '../../client/hooks/useSessionQuery.js';
|
|
5
|
+
import { useUpdateSessionMutation } from '../../client/hooks/useUpdateSessionMutation.js';
|
|
6
|
+
export function SetupDomain() {
|
|
7
|
+
const searchParams = useSearchParams();
|
|
8
|
+
const sessionQuery = useSessionQuery();
|
|
9
|
+
const updateSessionMutation = useUpdateSessionMutation();
|
|
10
|
+
useTimeout(() => {
|
|
11
|
+
const market = searchParams.get('m');
|
|
12
|
+
const domain = market
|
|
13
|
+
? `${window.location.host}/${market}`
|
|
14
|
+
: `${window.location.host}`;
|
|
15
|
+
if (domain === sessionQuery.data?.domain)
|
|
16
|
+
return;
|
|
17
|
+
updateSessionMutation.mutate({ domain });
|
|
18
|
+
}, sessionQuery.isPending ? null : 1000);
|
|
19
|
+
return null;
|
|
20
|
+
}
|
package/dist/handlers.d.ts
CHANGED
|
@@ -36,3 +36,8 @@ export declare const POST: (request: NextRequest, { params }: {
|
|
|
36
36
|
auth: string[];
|
|
37
37
|
}>;
|
|
38
38
|
}) => Promise<NextResponse>;
|
|
39
|
+
export declare const PATCH: (request: NextRequest, { params }: {
|
|
40
|
+
params: Promise<{
|
|
41
|
+
auth: string[];
|
|
42
|
+
}>;
|
|
43
|
+
}) => Promise<NextResponse>;
|
package/dist/handlers.js
CHANGED
|
@@ -62,7 +62,7 @@ export const GET = async (request, { params }) => {
|
|
|
62
62
|
if (path === 'sessions') {
|
|
63
63
|
const accessToken = request.cookies.get(ACCESS_TOKEN_COOKIE_NAME)?.value;
|
|
64
64
|
const refreshToken = request.cookies.get(REFRESH_TOKEN_COOKIE_NAME)?.value;
|
|
65
|
-
const domain = request.cookies.get(DOMAIN_COOKIE_NAME)?.value ?? request.nextUrl.
|
|
65
|
+
const domain = request.cookies.get(DOMAIN_COOKIE_NAME)?.value ?? request.nextUrl.host;
|
|
66
66
|
if (!refreshToken) {
|
|
67
67
|
const response = NextResponse.json({
|
|
68
68
|
ok: true,
|
|
@@ -260,3 +260,38 @@ export const POST = async (request, { params }) => {
|
|
|
260
260
|
message: 'Not found',
|
|
261
261
|
}, { status: 404 });
|
|
262
262
|
};
|
|
263
|
+
const UpdateSessionDefinition = z.object({
|
|
264
|
+
domain: z.string().optional().nullable(),
|
|
265
|
+
});
|
|
266
|
+
export const PATCH = async (request, { params }) => {
|
|
267
|
+
const { auth } = await params;
|
|
268
|
+
const path = auth[0];
|
|
269
|
+
/*
|
|
270
|
+
*
|
|
271
|
+
* PATCH -> /api/sessions
|
|
272
|
+
*
|
|
273
|
+
*/
|
|
274
|
+
if (path === 'sessions') {
|
|
275
|
+
const body = await request.json();
|
|
276
|
+
const parsed = UpdateSessionDefinition.safeParse(body);
|
|
277
|
+
if (!parsed.success) {
|
|
278
|
+
return NextResponse.json({
|
|
279
|
+
ok: false,
|
|
280
|
+
message: parsed.error.issues[0].message,
|
|
281
|
+
}, { status: 400 });
|
|
282
|
+
}
|
|
283
|
+
const response = NextResponse.json({ ok: true });
|
|
284
|
+
if (parsed.data.domain) {
|
|
285
|
+
response.cookies.set(DOMAIN_COOKIE_NAME, parsed.data.domain, {
|
|
286
|
+
expires: addDays(new Date(), 30),
|
|
287
|
+
httpOnly: true,
|
|
288
|
+
sameSite: 'strict',
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
return response;
|
|
292
|
+
}
|
|
293
|
+
return NextResponse.json({
|
|
294
|
+
ok: false,
|
|
295
|
+
message: 'Not found',
|
|
296
|
+
}, { status: 404 });
|
|
297
|
+
};
|
package/dist/middleware.js
CHANGED
|
@@ -1,25 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { addMinutes } from 'date-fns';
|
|
2
2
|
import { NextFetchEvent, NextResponse, } from 'next/server';
|
|
3
|
-
import { ACCESS_TOKEN_COOKIE_NAME,
|
|
3
|
+
import { ACCESS_TOKEN_COOKIE_NAME, REFRESH_TOKEN_COOKIE_NAME, } from './constants/index.js';
|
|
4
4
|
import { refreshSession } from './services/auth.js';
|
|
5
5
|
export const middleware = async (req) => {
|
|
6
6
|
const accessToken = req.cookies.get(ACCESS_TOKEN_COOKIE_NAME)?.value;
|
|
7
7
|
const refreshToken = req.cookies.get(REFRESH_TOKEN_COOKIE_NAME)?.value;
|
|
8
8
|
const res = NextResponse.next();
|
|
9
|
-
if (req.nextUrl.searchParams.has('m')) {
|
|
10
|
-
res.cookies.set(DOMAIN_COOKIE_NAME, `${req.nextUrl.hostname}/${req.nextUrl.searchParams.get('m')}`, {
|
|
11
|
-
expires: addDays(new Date(), 30),
|
|
12
|
-
httpOnly: true,
|
|
13
|
-
sameSite: 'strict',
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
if (!res.cookies.has(DOMAIN_COOKIE_NAME)) {
|
|
17
|
-
res.cookies.set(DOMAIN_COOKIE_NAME, req.nextUrl.origin, {
|
|
18
|
-
expires: addDays(new Date(), 30),
|
|
19
|
-
httpOnly: true,
|
|
20
|
-
sameSite: 'strict',
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
9
|
if (!accessToken && refreshToken) {
|
|
24
10
|
try {
|
|
25
11
|
const data = await refreshSession({
|
|
@@ -5,9 +5,10 @@ export const getSession = cache(async () => {
|
|
|
5
5
|
const Cookies = await cookies();
|
|
6
6
|
const Headers = await headers();
|
|
7
7
|
const token = Cookies.get(ACCESS_TOKEN_COOKIE_NAME)?.value;
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
const domain = Cookies.get(DOMAIN_COOKIE_NAME)?.value ??
|
|
9
|
+
Headers.get('host') ??
|
|
10
|
+
Headers.get('x-forwarded-host') ??
|
|
11
|
+
'localhost';
|
|
11
12
|
return token
|
|
12
13
|
? {
|
|
13
14
|
status: 'authenticated',
|
|
@@ -30,3 +30,4 @@ export declare const getCompleteOnboardingMutationKey: () => MutationKey;
|
|
|
30
30
|
export declare const getSkipOnboardingMutationKey: () => MutationKey;
|
|
31
31
|
export declare const getUploadImageFileMutationKey: () => MutationKey;
|
|
32
32
|
export declare const getResetPasswordMutationKey: () => MutationKey;
|
|
33
|
+
export declare const getUpdateSessionMutationKey: () => MutationKey;
|