astra-sdk-web 1.1.5 → 1.1.7
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/astra-sdk.cjs.js +131 -53
- package/dist/astra-sdk.cjs.js.map +1 -1
- package/dist/astra-sdk.css +64 -0
- package/dist/astra-sdk.css.map +1 -1
- package/dist/astra-sdk.es.js +132 -54
- package/dist/astra-sdk.es.js.map +1 -1
- package/dist/components.cjs.js +131 -53
- package/dist/components.cjs.js.map +1 -1
- package/dist/components.css +64 -0
- package/dist/components.css.map +1 -1
- package/dist/components.es.js +132 -54
- package/dist/components.es.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Toast.tsx +82 -0
- package/src/pages/FaceScanModal.tsx +37 -3
- package/src/pages/QRCodePage.tsx +3 -3
- package/src/services/kycApiService.ts +5 -1
package/package.json
CHANGED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
interface ToastProps {
|
|
4
|
+
message: string;
|
|
5
|
+
type?: 'success' | 'error' | 'info' | 'warning';
|
|
6
|
+
duration?: number;
|
|
7
|
+
onClose?: () => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function Toast({ message, type = 'info', duration = 5000, onClose }: ToastProps) {
|
|
11
|
+
const [isVisible, setIsVisible] = useState(true);
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const timer = setTimeout(() => {
|
|
15
|
+
setIsVisible(false);
|
|
16
|
+
setTimeout(() => {
|
|
17
|
+
if (onClose) onClose();
|
|
18
|
+
}, 300); // Wait for fade out animation
|
|
19
|
+
}, duration);
|
|
20
|
+
|
|
21
|
+
return () => clearTimeout(timer);
|
|
22
|
+
}, [duration, onClose]);
|
|
23
|
+
|
|
24
|
+
const bgColor = {
|
|
25
|
+
success: 'bg-green-600',
|
|
26
|
+
error: 'bg-red-600',
|
|
27
|
+
info: 'bg-blue-600',
|
|
28
|
+
warning: 'bg-yellow-600',
|
|
29
|
+
}[type];
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div
|
|
33
|
+
className={`fixed top-4 right-4 z-[10000] transition-all duration-300 ${
|
|
34
|
+
isVisible ? 'opacity-100 translate-y-0' : 'opacity-0 -translate-y-2'
|
|
35
|
+
}`}
|
|
36
|
+
>
|
|
37
|
+
<div
|
|
38
|
+
className={`${bgColor} text-white px-6 py-4 rounded-lg shadow-lg flex items-center gap-3 min-w-[300px] max-w-[500px]`}
|
|
39
|
+
>
|
|
40
|
+
<div className="flex-1">
|
|
41
|
+
<p className="m-0 text-sm font-medium">{message}</p>
|
|
42
|
+
</div>
|
|
43
|
+
<button
|
|
44
|
+
onClick={() => {
|
|
45
|
+
setIsVisible(false);
|
|
46
|
+
setTimeout(() => {
|
|
47
|
+
if (onClose) onClose();
|
|
48
|
+
}, 300);
|
|
49
|
+
}}
|
|
50
|
+
className="text-white hover:text-gray-200 transition-colors"
|
|
51
|
+
aria-label="Close"
|
|
52
|
+
>
|
|
53
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
54
|
+
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
55
|
+
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
56
|
+
</svg>
|
|
57
|
+
</button>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface ToastContainerProps {
|
|
64
|
+
toasts: Array<{ id: string; message: string; type?: 'success' | 'error' | 'info' | 'warning' }>;
|
|
65
|
+
onRemove: (id: string) => void;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function ToastContainer({ toasts, onRemove }: ToastContainerProps) {
|
|
69
|
+
return (
|
|
70
|
+
<div className="fixed top-4 right-4 z-[10000] flex flex-col gap-2">
|
|
71
|
+
{toasts.map((toast) => (
|
|
72
|
+
<Toast
|
|
73
|
+
key={toast.id}
|
|
74
|
+
message={toast.message}
|
|
75
|
+
type={toast.type}
|
|
76
|
+
onClose={() => onRemove(toast.id)}
|
|
77
|
+
/>
|
|
78
|
+
))}
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
@@ -4,6 +4,7 @@ import DocumentUploadModal from './DocumentUploadModal';
|
|
|
4
4
|
import { useCamera } from '../features/faceScan/hooks/useCamera';
|
|
5
5
|
import { useFaceScan } from '../features/faceScan/hooks/useFaceScan';
|
|
6
6
|
import { useKycContext } from '../contexts/KycContext';
|
|
7
|
+
import { Toast } from '../components/Toast';
|
|
7
8
|
import '../index.css';
|
|
8
9
|
|
|
9
10
|
interface FaceScanModalProps {
|
|
@@ -16,6 +17,7 @@ function FaceScanModal({ onComplete }: FaceScanModalProps) {
|
|
|
16
17
|
const navigate = useNavigate();
|
|
17
18
|
const { apiService } = useKycContext();
|
|
18
19
|
const [sessionError, setSessionError] = useState<string | null>(null);
|
|
20
|
+
const [toast, setToast] = useState<{ message: string; type: 'success' | 'error' | 'info' | 'warning' } | null>(null);
|
|
19
21
|
|
|
20
22
|
const { videoRef, cameraReady, stopCamera } = useCamera();
|
|
21
23
|
const { state, setState, refs, handleFaceCapture } = useFaceScan(videoRef, faceCanvasRef, {
|
|
@@ -23,7 +25,29 @@ function FaceScanModal({ onComplete }: FaceScanModalProps) {
|
|
|
23
25
|
if (!apiService) {
|
|
24
26
|
throw new Error('API service not initialized');
|
|
25
27
|
}
|
|
26
|
-
|
|
28
|
+
try {
|
|
29
|
+
await apiService.uploadFaceScan(blob);
|
|
30
|
+
} catch (error: any) {
|
|
31
|
+
// Check if it's a "Face already registered" error
|
|
32
|
+
const errorMessage = error?.message || '';
|
|
33
|
+
const errorData = (error as any)?.errorData || {};
|
|
34
|
+
|
|
35
|
+
if (
|
|
36
|
+
errorMessage.includes('Face already registered') ||
|
|
37
|
+
errorMessage.includes('already registered') ||
|
|
38
|
+
errorData?.message?.includes('Face already registered') ||
|
|
39
|
+
(error as any)?.statusCode === 500 && errorMessage.includes('Face')
|
|
40
|
+
) {
|
|
41
|
+
setToast({
|
|
42
|
+
message: 'Face has already been registered for this session. Proceeding to document upload.',
|
|
43
|
+
type: 'warning',
|
|
44
|
+
});
|
|
45
|
+
// Don't throw error - allow flow to continue to document upload
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
// Re-throw other errors
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
27
51
|
},
|
|
28
52
|
onFaceCaptureComplete: (imageData: string) => {
|
|
29
53
|
if (onComplete) {
|
|
@@ -147,8 +171,17 @@ function FaceScanModal({ onComplete }: FaceScanModalProps) {
|
|
|
147
171
|
}
|
|
148
172
|
|
|
149
173
|
return (
|
|
150
|
-
|
|
151
|
-
|
|
174
|
+
<>
|
|
175
|
+
{toast && (
|
|
176
|
+
<Toast
|
|
177
|
+
message={toast.message}
|
|
178
|
+
type={toast.type}
|
|
179
|
+
onClose={() => setToast(null)}
|
|
180
|
+
duration={6000}
|
|
181
|
+
/>
|
|
182
|
+
)}
|
|
183
|
+
<div className="fixed inset-0 bg-black p-5 z-[1000] flex items-center justify-center font-sans overflow-y-auto custom__scrollbar">
|
|
184
|
+
<div className="max-w-[400px] w-full mx-auto bg-[#0b0f17] rounded-2xl p-6 shadow-xl mt-48">
|
|
152
185
|
<div className="relative mb-4">
|
|
153
186
|
<h2 className="m-0 mb-4 text-[26px] font-bold text-white text-center">
|
|
154
187
|
Capture Face
|
|
@@ -231,6 +264,7 @@ function FaceScanModal({ onComplete }: FaceScanModalProps) {
|
|
|
231
264
|
</div>
|
|
232
265
|
</div>
|
|
233
266
|
</div>
|
|
267
|
+
</>
|
|
234
268
|
);
|
|
235
269
|
}
|
|
236
270
|
|
package/src/pages/QRCodePage.tsx
CHANGED
|
@@ -86,14 +86,14 @@ function QRCodePage({ onClose, onNavigate, mobileBaseUrl = 'https://astra-sdk-re
|
|
|
86
86
|
</p>
|
|
87
87
|
|
|
88
88
|
{qrUrl && (
|
|
89
|
-
<div className="flex justify-center items-center p-3 sm:p-4 bg-
|
|
89
|
+
<div className="flex justify-center items-center p-3 sm:p-4 bg-black rounded-xl border-2 border-white shadow-lg">
|
|
90
90
|
<QRCodeSVG
|
|
91
91
|
value={qrUrl}
|
|
92
92
|
size={180}
|
|
93
93
|
level="H"
|
|
94
94
|
includeMargin={true}
|
|
95
|
-
bgColor="
|
|
96
|
-
fgColor="#
|
|
95
|
+
bgColor="#000000"
|
|
96
|
+
fgColor="#FFFFFF"
|
|
97
97
|
/>
|
|
98
98
|
</div>
|
|
99
99
|
)}
|
|
@@ -114,7 +114,11 @@ export class KycApiService {
|
|
|
114
114
|
if (!response.ok) {
|
|
115
115
|
const errorData = await response.json().catch(() => ({}));
|
|
116
116
|
const message = errorData?.message || `Face upload failed with status ${response.status}`;
|
|
117
|
-
|
|
117
|
+
// Preserve the original error message for better error handling
|
|
118
|
+
const error = new Error(message);
|
|
119
|
+
(error as any).statusCode = response.status;
|
|
120
|
+
(error as any).errorData = errorData;
|
|
121
|
+
throw error;
|
|
118
122
|
}
|
|
119
123
|
|
|
120
124
|
const data = await response.json();
|