@casperid/react 1.0.0

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.
@@ -0,0 +1,83 @@
1
+ import React from 'react';
2
+ import { motion } from 'framer-motion';
3
+ import { Verified, RefreshCw } from 'lucide-react';
4
+ import { t } from '../utils/i18n';
5
+
6
+ interface SecurityUpgradeProps {
7
+ onNext: () => void;
8
+ onSkip?: () => void;
9
+ language?: string;
10
+ }
11
+
12
+ const SecurityUpgrade: React.FC<SecurityUpgradeProps> = ({ onNext, onSkip, language = 'EN' }) => (
13
+ <motion.div
14
+ initial={{ opacity: 0 }}
15
+ animate={{ opacity: 1 }}
16
+ exit={{ opacity: 0 }}
17
+ className="flex-1 flex flex-col"
18
+ >
19
+ <div className="p-6 pb-2">
20
+ <div className="flex justify-between items-center mb-4">
21
+ <span className="text-xs font-semibold uppercase tracking-widest text-brand/80">
22
+ {t('verification_step', language)}
23
+ </span>
24
+ <span className="text-xs font-bold text-muted">{t('kyc_required', language)}</span>
25
+ </div>
26
+ <div className="w-full bg-brand/10 h-1.5 rounded-full overflow-hidden">
27
+ <div
28
+ className="bg-brand h-full rounded-full"
29
+ style={{ width: '40%' }}
30
+ />
31
+ </div>
32
+ </div>
33
+
34
+ <div className="flex-1 flex flex-col items-center justify-center px-8 text-center gap-8">
35
+ <div className="relative group">
36
+ <div className="absolute inset-0 bg-cyan-400/20 blur-3xl rounded-full scale-75 group-hover:scale-100 transition-transform duration-500" />
37
+ <div className="relative w-32 h-32 rounded-full bg-gradient-to-tr from-brand-20 to-brand-5 flex items-center justify-center border border-brand-30 shadow-inner">
38
+ <Verified className="w-16 h-16 text-brand" />
39
+ </div>
40
+ <div className="absolute bottom-1 right-1 w-8 h-8 rounded-full dark:bg-[#171220] bg-white border-2 border-brand flex items-center justify-center">
41
+ <RefreshCw className="w-4 h-4 text-brand" />
42
+ </div>
43
+ </div>
44
+
45
+ <div className="space-y-4">
46
+ <h1 className="text-2xl font-bold tracking-tight text-main leading-tight">
47
+ {t('security_upgrade_required', language)}
48
+ </h1>
49
+ <p className="text-muted text-base leading-relaxed font-medium">
50
+ {t('security_upgrade_desc', language)}
51
+ </p>
52
+ </div>
53
+
54
+ <div className="flex flex-wrap justify-center gap-2">
55
+ <span className="px-3 py-1 bg-brand/10 rounded-xl text-[10px] font-bold text-brand uppercase tracking-tighter border border-brand-10">
56
+ {t('biometric_secure', language)}
57
+ </span>
58
+ <span className="px-3 py-1 bg-cyan-400/10 rounded-xl text-[10px] font-bold text-cyan-500 uppercase tracking-tighter border border-cyan-400/10">
59
+ {t('zero_knowledge', language)}
60
+ </span>
61
+ </div>
62
+ </div>
63
+
64
+ <div className="p-8 space-y-4">
65
+ <button
66
+ onClick={onNext}
67
+ className="w-full bg-brand hover:bg-brand/90 text-white rounded-2xl py-4 font-bold text-lg shadow-xl shadow-brand transition-all active:scale-[0.98]"
68
+ >
69
+ {t('start_upgrade', language)}
70
+ </button>
71
+ {onSkip && (
72
+ <button
73
+ onClick={onSkip}
74
+ className="w-full dark:text-slate-400 text-slate-500 text-sm font-bold dark:hover:text-slate-200 hover:text-slate-800 transition-colors"
75
+ >
76
+ {t('do_this_later', language)}
77
+ </button>
78
+ )}
79
+ </div>
80
+ </motion.div>
81
+ );
82
+
83
+ export default SecurityUpgrade;
@@ -0,0 +1,59 @@
1
+ import React from 'react';
2
+ import { motion } from 'framer-motion';
3
+ import { Globe, CreditCard, Smartphone, ShieldCheck, ChevronRight } from 'lucide-react';
4
+ import type { DocumentType } from '../types';
5
+ import { t } from '../utils/i18n';
6
+
7
+ interface VerifyIdentityChoiceProps {
8
+ onNext: (documentType: DocumentType) => void;
9
+ language?: string;
10
+ }
11
+
12
+ const docs: { id: DocumentType; title: string; desc: string; Icon: React.FC<any> }[] = [
13
+ { id: 'passport', title: 'Passport', desc: 'All countries supported', Icon: Globe },
14
+ { id: 'drivers_license', title: 'Driver License', desc: 'Government issued card', Icon: CreditCard },
15
+ { id: 'national_id', title: 'ID Card', desc: 'National identity document', Icon: Smartphone },
16
+ ];
17
+
18
+ const VerifyIdentityChoice: React.FC<VerifyIdentityChoiceProps> = ({ onNext, language = 'EN' }) => (
19
+ <motion.div
20
+ initial={{ opacity: 0, x: 20 }}
21
+ animate={{ opacity: 1, x: 0 }}
22
+ exit={{ opacity: 0, x: -20 }}
23
+ className="flex-1 px-6 pt-4 flex flex-col"
24
+ >
25
+ <h3 className="text-2xl font-bold mb-2 text-main">{t('verify_identity', language)}</h3>
26
+ <p className="text-muted text-sm mb-8 leading-relaxed">
27
+ {t('choose_document', language)}
28
+ </p>
29
+
30
+ <div className="space-y-4 overflow-y-auto pr-1 pb-6">
31
+ {docs.map(({ id, title, desc, Icon }) => (
32
+ <button
33
+ key={id}
34
+ onClick={() => onNext(id)}
35
+ className="w-full group cursor-pointer dark:bg-white/5 bg-slate-100/50 border dark:border-white/10 border-slate-200 rounded-2xl p-5 flex items-center gap-4 hover:dark:bg-brand/15 hover:bg-brand/5 hover:dark:border-brand-40 hover:border-brand-30 transition-all transform hover:-translate-y-1"
36
+ >
37
+ <div className="w-14 h-14 rounded-xl dark:bg-slate-800/50 bg-white flex items-center justify-center text-brand group-hover:bg-brand/20 transition-colors border dark:border-transparent border-slate-100">
38
+ <Icon className="w-8 h-8" />
39
+ </div>
40
+ <div className="flex-1 text-left">
41
+ <h4 className="font-bold text-base text-main">{t(id, language)}</h4>
42
+ <p className="text-xs text-muted font-medium">{t(`${id}_desc`, language)}</p>
43
+ </div>
44
+ <ChevronRight className="text-brand w-5 h-5" />
45
+ </button>
46
+ ))}
47
+
48
+ <div className="mt-8 relative h-32 w-full rounded-2xl overflow-hidden dark:bg-white/5 bg-slate-100/30 flex items-center justify-center border border-dashed dark:border-brand-30 border-brand-40">
49
+ <div className="absolute inset-0 opacity-10 bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-brand via-transparent to-transparent" />
50
+ <div className="text-center z-10">
51
+ <ShieldCheck className="text-brand/40 w-10 h-10 mx-auto mb-1" />
52
+ <p className="text-[10px] text-muted uppercase tracking-tighter font-bold">Encrypted E2E</p>
53
+ </div>
54
+ </div>
55
+ </div>
56
+ </motion.div>
57
+ );
58
+
59
+ export default VerifyIdentityChoice;
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+
3
+ interface FooterProps {
4
+ children: React.ReactNode;
5
+ }
6
+
7
+ const Footer: React.FC<FooterProps> = ({ children }) => (
8
+ <footer className="p-8 text-center relative z-20">
9
+ {children}
10
+ </footer>
11
+ );
12
+
13
+ export default Footer;
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+
3
+ interface GlassContainerProps {
4
+ children: React.ReactNode;
5
+ className?: string;
6
+ theme?: string; // Added theme prop
7
+ }
8
+
9
+ const GlassContainer: React.FC<GlassContainerProps> = ({ children, className = '', theme = '' }) => ( // Destructure theme and provide default
10
+ <div
11
+ className={`glass-container relative w-full h-full rounded-[var(--casperid-radius,1.5rem)] overflow-hidden flex flex-col shadow-2xl border dark:border-white/10 border-white/40 ${className} ${theme}`} // Added theme to className
12
+ >
13
+ {children}
14
+ </div>
15
+ );
16
+
17
+ export default GlassContainer;
@@ -0,0 +1,62 @@
1
+ import React from 'react';
2
+ import { Shield, X, Sun, Moon } from 'lucide-react';
3
+
4
+ interface HeaderProps {
5
+ onClose?: () => void;
6
+ title?: string;
7
+ subtitle?: string;
8
+ showClose?: boolean;
9
+ theme: 'light' | 'dark';
10
+ toggleTheme: () => void;
11
+ logoUrl?: string;
12
+ }
13
+
14
+ const Header: React.FC<HeaderProps> = ({
15
+ onClose,
16
+ title = 'CasperID',
17
+ subtitle = 'SECURE SDK',
18
+ showClose = true,
19
+ theme,
20
+ toggleTheme,
21
+ logoUrl,
22
+ }) => (
23
+ <header className="flex items-center justify-between px-6 pt-8 pb-4 relative z-20">
24
+ <div className="flex items-center gap-3">
25
+ <div className="p-2 bg-brand rounded-xl flex items-center justify-center shadow-brand">
26
+ {logoUrl ? (
27
+ <img src={logoUrl} alt={title} className="w-5 h-5 object-contain" />
28
+ ) : (
29
+ <Shield className="text-white w-5 h-5" />
30
+ )}
31
+ </div>
32
+ <div>
33
+ <h2 className="text-lg font-bold tracking-tight text-main">{title}</h2>
34
+ {subtitle && (
35
+ <p className="text-[10px] uppercase tracking-widest text-brand font-bold">
36
+ {subtitle}
37
+ </p>
38
+ )}
39
+ </div>
40
+ </div>
41
+ <div className="flex items-center gap-2">
42
+ <button
43
+ onClick={toggleTheme}
44
+ className="p-2 dark:hover:bg-white/10 hover:bg-slate-200/50 rounded-xl transition-colors text-muted"
45
+ aria-label="Toggle theme"
46
+ >
47
+ {theme === 'dark' ? <Sun className="w-5 h-5" /> : <Moon className="w-5 h-5" />}
48
+ </button>
49
+ {showClose && (
50
+ <button
51
+ onClick={onClose}
52
+ className="p-2 dark:hover:bg-white/10 hover:bg-slate-200/50 rounded-full transition-colors text-muted"
53
+ aria-label="Close"
54
+ >
55
+ <X className="w-5 h-5" />
56
+ </button>
57
+ )}
58
+ </div>
59
+ </header>
60
+ );
61
+
62
+ export default Header;
package/src/types.ts ADDED
@@ -0,0 +1,342 @@
1
+ export type Screen =
2
+ | 'AuthSelection'
3
+ | 'Login'
4
+ | 'PasskeyAuth' // Returning user biometric authentication
5
+ | 'PasskeyRegister' // New user passkey creation
6
+ | 'PinVerification'
7
+ | 'SecurityUpgrade'
8
+ | 'FaceScan'
9
+ | 'VerifyIdentityChoice'
10
+ | 'DocumentScan'
11
+ | 'ReviewData'
12
+ | 'MintingIdentity' // Unified screen for L1/L2/L3 minting
13
+ | 'IdentityVerified'
14
+ | 'PermissionsRequest';
15
+
16
+ export type SDKMode = 'light' | 'dark' | 'system';
17
+ export type VerificationTier = 'L1' | 'L2' | 'L3';
18
+ export type LayoutMode = 'auto' | 'modal' | 'fullscreen';
19
+
20
+ export interface CasperIDTheme {
21
+ primaryColor?: string;
22
+ borderRadius?: string;
23
+ fontFamily?: string;
24
+ mode?: SDKMode;
25
+ layout?: LayoutMode;
26
+ }
27
+
28
+ export interface CasperIDModalProps {
29
+ /** Whether the modal is visible */
30
+ isOpen: boolean;
31
+ /** Your CasperID API key (from the Business Dashboard) */
32
+ apiKey: string;
33
+ /** Theme overrides — merged with cloud-synced config */
34
+ theme?: CasperIDTheme;
35
+ /** Minimum verification tier required. SDK will auto step-up if needed. */
36
+ requiredTier?: VerificationTier;
37
+ /** Called when authentication/verification succeeds */
38
+ onSuccess?: (sessionToken: string, data?: { businessToken?: string }) => void;
39
+ /** Called when the user dismisses the modal */
40
+ onClose?: () => void;
41
+ /** Called when an unrecoverable error occurs */
42
+ onError?: (error: Error) => void;
43
+ /** Use 'login' for full auth, 'verify' for KYC-only mode */
44
+ mode?: 'login' | 'verify';
45
+ /**
46
+ * When true, renders the modal card inline (no fixed overlay, no backdrop).
47
+ * Used by the Design Studio preview pane.
48
+ */
49
+ previewMode?: boolean;
50
+ /** Force a specific screen (used by Design Studio step navigation) */
51
+ initialScreen?: Screen;
52
+ /** Base URL for API requests. Defaults to https://apis.casperid.com */
53
+ apiBaseUrl?: string;
54
+ /** Current layout mode — 'auto' (default), 'modal', or 'fullscreen' */
55
+ layout?: LayoutMode;
56
+ /** Terms of Service URL */
57
+ termsUrl?: string;
58
+ /** Privacy Policy URL */
59
+ privacyUrl?: string;
60
+ /** Logo URL for branding */
61
+ logoUrl?: string;
62
+ /** Default language for the SDK (e.g., 'EN', 'ES', 'FR') */
63
+ language?: string;
64
+ }
65
+
66
+ export interface ScreenProps {
67
+ onNext: (screen?: Screen) => void;
68
+ theme: 'light' | 'dark';
69
+ apiBaseUrl?: string;
70
+ language?: string;
71
+ }
72
+
73
+ // OTP Authentication State
74
+ export interface OTPState {
75
+ email: string;
76
+ verificationId: string;
77
+ }
78
+
79
+ // API Response types
80
+ export interface SendOTPResponse {
81
+ success: boolean;
82
+ verificationId?: string;
83
+ userExists?: boolean;
84
+ hasPasskey?: boolean;
85
+ error?: string;
86
+ message?: string;
87
+ }
88
+
89
+ export interface VerifyOTPResponse {
90
+ success: boolean;
91
+ sessionToken?: string;
92
+ business_token?: string;
93
+ expiresAt?: string;
94
+ error?: string;
95
+ attemptsLeft?: number;
96
+ userExists?: boolean;
97
+ hasPasskey?: boolean;
98
+ userAccount?: {
99
+ user_id: string;
100
+ wallet?: string;
101
+ tier?: string;
102
+ did_address?: string;
103
+ } | null;
104
+ }
105
+
106
+ // SDK Configuration
107
+ export interface CasperIDConfig {
108
+ apiBaseUrl?: string;
109
+ }
110
+
111
+ // KYC Types
112
+ export type DocumentType = 'passport' | 'drivers_license' | 'national_id';
113
+
114
+ export interface KYCState {
115
+ requestId: string;
116
+ verificationTier: 'layer_2' | 'layer_3';
117
+ documentType?: DocumentType;
118
+ extractedData?: ExtractedDocumentData;
119
+ livenessVerified?: boolean;
120
+ }
121
+
122
+ export interface ExtractedDocumentData {
123
+ document_type?: string;
124
+ document_number?: string;
125
+ first_name?: string;
126
+ last_name?: string;
127
+ date_of_birth?: string;
128
+ gender?: string;
129
+ nationality?: string;
130
+ confidence?: number;
131
+ }
132
+
133
+ export interface StartVerificationResponse {
134
+ success: boolean;
135
+ verification?: {
136
+ requestId: string;
137
+ verificationType: string;
138
+ status: string;
139
+ requirements: {
140
+ livenessCheck?: boolean;
141
+ walletLink?: boolean;
142
+ identityDocument?: boolean;
143
+ nimcVerification?: boolean;
144
+ };
145
+ verificationMethod?: string;
146
+ expiresAt: string;
147
+ nextStep: string;
148
+ };
149
+ error?: string;
150
+ }
151
+
152
+ export interface LivenessResponse {
153
+ success: boolean;
154
+ liveness?: {
155
+ passed: boolean;
156
+ confidence: number;
157
+ antiSpoofingScore?: number;
158
+ };
159
+ error?: string;
160
+ }
161
+
162
+ export interface DocumentUploadResponse {
163
+ success: boolean;
164
+ upload_id?: string;
165
+ extracted_data?: ExtractedDocumentData;
166
+ confidence?: number;
167
+ needs_review?: boolean;
168
+ error?: string;
169
+ }
170
+
171
+ export interface VerificationStatusResponse {
172
+ success: boolean;
173
+ verification?: {
174
+ requestId: string;
175
+ status: 'pending' | 'in_progress' | 'completed' | 'failed' | 'cancelled';
176
+ verificationType?: string;
177
+ createdAt?: string;
178
+ completedAt?: string;
179
+ expiresAt?: string;
180
+ progress?: {
181
+ livenessCompleted?: boolean;
182
+ documentCompleted?: boolean;
183
+ livenessScore?: number;
184
+ };
185
+ results?: {
186
+ verified: boolean;
187
+ tier: string;
188
+ proofHash: string;
189
+ signature: string;
190
+ };
191
+ error?: string;
192
+ };
193
+ error?: string;
194
+ }
195
+
196
+ export interface Layer3SubmitResponse {
197
+ success: boolean;
198
+ verification_request_id?: string;
199
+ status?: string;
200
+ message?: string;
201
+ error?: string;
202
+ }
203
+
204
+ // Passkey / WebAuthn Types
205
+ export interface PasskeyRegisterInitResponse {
206
+ success: boolean;
207
+ userId?: string;
208
+ options?: PublicKeyCredentialCreationOptionsJSON;
209
+ expiresIn?: number;
210
+ error?: string;
211
+ }
212
+
213
+ export interface PasskeyRegisterCompleteResponse {
214
+ success: boolean;
215
+ credential_id?: string;
216
+ session_token?: string;
217
+ business_token?: string;
218
+ expires_at?: string;
219
+ user?: {
220
+ user_id: string;
221
+ email: string;
222
+ tier: string;
223
+ };
224
+ error?: string;
225
+ }
226
+
227
+ export interface PasskeyAuthInitResponse {
228
+ success: boolean;
229
+ options?: PublicKeyCredentialRequestOptionsJSON;
230
+ error?: string;
231
+ }
232
+
233
+ export interface PasskeyAuthCompleteResponse {
234
+ success: boolean;
235
+ session_token?: string;
236
+ business_token?: string;
237
+ expires_at?: string;
238
+ user?: {
239
+ user_id: string;
240
+ email: string;
241
+ wallet?: string;
242
+ tier: string;
243
+ did_address?: string;
244
+ };
245
+ error?: string;
246
+ }
247
+
248
+ // WebAuthn JSON types (for transport)
249
+ export interface PublicKeyCredentialCreationOptionsJSON {
250
+ challenge: string;
251
+ rp: {
252
+ name: string;
253
+ id?: string;
254
+ };
255
+ user: {
256
+ id: string;
257
+ name: string;
258
+ displayName: string;
259
+ };
260
+ pubKeyCredParams: Array<{
261
+ type: 'public-key';
262
+ alg: number;
263
+ }>;
264
+ timeout?: number;
265
+ attestation?: AttestationConveyancePreference;
266
+ authenticatorSelection?: {
267
+ authenticatorAttachment?: AuthenticatorAttachment;
268
+ residentKey?: ResidentKeyRequirement;
269
+ requireResidentKey?: boolean;
270
+ userVerification?: UserVerificationRequirement;
271
+ };
272
+ excludeCredentials?: Array<{
273
+ type: 'public-key';
274
+ id: string;
275
+ transports?: AuthenticatorTransport[];
276
+ }>;
277
+ }
278
+
279
+ export interface PublicKeyCredentialRequestOptionsJSON {
280
+ challenge: string;
281
+ timeout?: number;
282
+ rpId?: string;
283
+ allowCredentials?: Array<{
284
+ type: 'public-key';
285
+ id: string;
286
+ transports?: AuthenticatorTransport[];
287
+ }>;
288
+ userVerification?: UserVerificationRequirement;
289
+ }
290
+
291
+ // L1 Setup Types
292
+ export interface WalletGenerateResponse {
293
+ success: boolean;
294
+ walletInfo?: {
295
+ walletAddress: string;
296
+ humanId: string;
297
+ publicKey: string;
298
+ };
299
+ alreadyExists?: boolean;
300
+ error?: string;
301
+ }
302
+
303
+ export interface DIDMintResponse {
304
+ success: boolean;
305
+ did_address?: string;
306
+ transaction_hash?: string;
307
+ error?: string;
308
+ }
309
+
310
+ // App Metadata for Branding
311
+ export interface AppMetadata {
312
+ app_id: string;
313
+ name: string;
314
+ logo_url?: string;
315
+ theme?: {
316
+ primaryColor?: string;
317
+ borderRadius?: string;
318
+ fontFamily?: string;
319
+ layout?: LayoutMode;
320
+ };
321
+ }
322
+
323
+ export interface AppMetadataResponse {
324
+ success: boolean;
325
+ app?: AppMetadata;
326
+ error?: string;
327
+ }
328
+
329
+ // Auth State for SDK internal use
330
+ export interface AuthState {
331
+ email: string;
332
+ sessionToken?: string;
333
+ businessToken?: string;
334
+ userExists: boolean;
335
+ hasPasskey: boolean;
336
+ userAccount?: {
337
+ user_id: string;
338
+ wallet?: string;
339
+ tier?: string;
340
+ did_address?: string;
341
+ } | null;
342
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Fetch wrapper with timeout support
3
+ * @param url - The URL to fetch
4
+ * @param options - Fetch options
5
+ * @param timeout - Timeout in milliseconds (default: 30000)
6
+ * @returns Promise<Response>
7
+ */
8
+ export async function fetchWithTimeout(
9
+ url: string,
10
+ options: RequestInit = {},
11
+ timeout: number = 30000
12
+ ): Promise<Response> {
13
+ const controller = new AbortController();
14
+ const id = setTimeout(() => controller.abort(), timeout);
15
+
16
+ try {
17
+ const response = await fetch(url, {
18
+ ...options,
19
+ signal: controller.signal
20
+ });
21
+ return response;
22
+ } finally {
23
+ clearTimeout(id);
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Error class for API errors with status code
29
+ */
30
+ export class ApiError extends Error {
31
+ status: number;
32
+
33
+ constructor(message: string, status: number) {
34
+ super(message);
35
+ this.name = 'ApiError';
36
+ this.status = status;
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Helper to handle API response and throw on error
42
+ */
43
+ export async function handleApiResponse<T>(response: Response): Promise<T> {
44
+ if (!response.ok) {
45
+ const errorData = await response.json().catch(() => ({}));
46
+ throw new ApiError(
47
+ errorData.error || errorData.message || `Request failed with status ${response.status}`,
48
+ response.status
49
+ );
50
+ }
51
+ return response.json();
52
+ }