astra-sdk-web 1.1.6 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astra-sdk-web",
3
- "version": "1.1.6",
3
+ "version": "1.1.7",
4
4
  "description": "Official Astra SDK for JavaScript/TypeScript",
5
5
  "type": "module",
6
6
  "main": "./dist/astra-sdk.cjs.js",
@@ -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
- await apiService.uploadFaceScan(blob);
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
- <div className="fixed inset-0 bg-black p-5 z-[1000] flex items-center justify-center font-sans overflow-y-auto custom__scrollbar">
151
- <div className="max-w-[400px] w-full mx-auto bg-[#0b0f17] rounded-2xl p-6 shadow-xl mt-48">
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
 
@@ -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
- throw new Error(message);
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();