@koraidv/react 1.5.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.
package/dist/index.mjs ADDED
@@ -0,0 +1,3223 @@
1
+ // src/context/KoraIDVProvider.tsx
2
+ import { createContext, useContext, useMemo } from "react";
3
+ import { KoraIDV } from "@koraidv/core";
4
+ import { jsx } from "react/jsx-runtime";
5
+ var KoraIDVContext = createContext(null);
6
+ function KoraIDVProvider({
7
+ apiKey,
8
+ tenantId,
9
+ config = {},
10
+ children
11
+ }) {
12
+ const sdk = useMemo(() => {
13
+ return new KoraIDV({
14
+ apiKey,
15
+ tenantId,
16
+ ...config
17
+ });
18
+ }, [apiKey, tenantId, config]);
19
+ const value = useMemo(
20
+ () => ({
21
+ sdk,
22
+ isConfigured: true
23
+ }),
24
+ [sdk]
25
+ );
26
+ return /* @__PURE__ */ jsx(KoraIDVContext.Provider, { value, children });
27
+ }
28
+ function useKoraIDVContext() {
29
+ const context = useContext(KoraIDVContext);
30
+ if (!context) {
31
+ throw new Error("useKoraIDV must be used within a KoraIDVProvider");
32
+ }
33
+ return context;
34
+ }
35
+
36
+ // src/hooks/useKoraIDV.ts
37
+ import { useState, useCallback } from "react";
38
+ import {
39
+ KoraError
40
+ } from "@koraidv/core";
41
+ function useKoraIDV() {
42
+ const { sdk } = useKoraIDVContext();
43
+ const [state, setState] = useState({
44
+ step: "consent",
45
+ verification: null,
46
+ livenessSession: null,
47
+ currentChallenge: null,
48
+ completedChallenges: 0,
49
+ isLoading: false,
50
+ error: null
51
+ });
52
+ const [selectedDocumentType, setSelectedDocumentType] = useState(null);
53
+ const [documentFrontCaptured, setDocumentFrontCaptured] = useState(false);
54
+ const startVerification = useCallback(
55
+ async (externalId, tier = "standard") => {
56
+ setState((prev) => ({ ...prev, isLoading: true, error: null }));
57
+ try {
58
+ await sdk.startVerification(
59
+ { externalId, tier },
60
+ {
61
+ onStepChange: (step) => {
62
+ setState((prev) => ({ ...prev, step }));
63
+ },
64
+ onComplete: (verification) => {
65
+ setState((prev) => ({
66
+ ...prev,
67
+ verification,
68
+ step: "complete",
69
+ isLoading: false
70
+ }));
71
+ },
72
+ onError: (error) => {
73
+ setState((prev) => ({ ...prev, error, isLoading: false }));
74
+ }
75
+ }
76
+ );
77
+ setState((prev) => ({
78
+ ...prev,
79
+ verification: sdk.getCurrentVerification(),
80
+ isLoading: false
81
+ }));
82
+ } catch (error) {
83
+ setState((prev) => ({
84
+ ...prev,
85
+ error,
86
+ isLoading: false
87
+ }));
88
+ }
89
+ },
90
+ [sdk]
91
+ );
92
+ const resumeVerification = useCallback(
93
+ async (verificationId) => {
94
+ setState((prev) => ({ ...prev, isLoading: true, error: null }));
95
+ try {
96
+ await sdk.resumeVerification(verificationId, {
97
+ onStepChange: (step) => {
98
+ setState((prev) => ({ ...prev, step }));
99
+ },
100
+ onComplete: (verification) => {
101
+ setState((prev) => ({
102
+ ...prev,
103
+ verification,
104
+ step: "complete",
105
+ isLoading: false
106
+ }));
107
+ },
108
+ onError: (error) => {
109
+ setState((prev) => ({ ...prev, error, isLoading: false }));
110
+ }
111
+ });
112
+ setState((prev) => ({
113
+ ...prev,
114
+ verification: sdk.getCurrentVerification(),
115
+ isLoading: false
116
+ }));
117
+ } catch (error) {
118
+ setState((prev) => ({
119
+ ...prev,
120
+ error,
121
+ isLoading: false
122
+ }));
123
+ }
124
+ },
125
+ [sdk]
126
+ );
127
+ const acceptConsent = useCallback(() => {
128
+ setState((prev) => ({ ...prev, step: "document_selection" }));
129
+ }, []);
130
+ const selectDocumentType = useCallback((type) => {
131
+ setSelectedDocumentType(type);
132
+ setDocumentFrontCaptured(false);
133
+ setState((prev) => ({ ...prev, step: "document_front" }));
134
+ }, []);
135
+ const checkDocumentQuality = useCallback(
136
+ async (imageData) => {
137
+ if (!selectedDocumentType) {
138
+ return { success: false, qualityScore: 0, qualityIssues: ["No document type selected"], details: { textReadability: 0, faceQuality: 0, imageClarity: 0 } };
139
+ }
140
+ return sdk.checkDocumentQuality(imageData, selectedDocumentType);
141
+ },
142
+ [sdk, selectedDocumentType]
143
+ );
144
+ const uploadDocument = useCallback(
145
+ async (imageData, side) => {
146
+ if (!selectedDocumentType) return false;
147
+ setState((prev) => ({ ...prev, isLoading: true, error: null }));
148
+ try {
149
+ const result = await sdk.uploadDocument(imageData, side, selectedDocumentType);
150
+ if (result.success) {
151
+ if (side === "front") {
152
+ setDocumentFrontCaptured(true);
153
+ const typeInfo = await import("@koraidv/core").then(
154
+ (m) => m.getDocumentTypeInfo(selectedDocumentType)
155
+ );
156
+ if (typeInfo.requiresBack) {
157
+ setState((prev) => ({ ...prev, step: "document_back", isLoading: false }));
158
+ } else {
159
+ setState((prev) => ({ ...prev, step: "selfie", isLoading: false }));
160
+ }
161
+ } else {
162
+ setState((prev) => ({ ...prev, step: "selfie", isLoading: false }));
163
+ }
164
+ return true;
165
+ } else {
166
+ setState((prev) => ({
167
+ ...prev,
168
+ error: new KoraError("QUALITY_VALIDATION_FAILED", result.qualityIssues),
169
+ isLoading: false
170
+ }));
171
+ return false;
172
+ }
173
+ } catch (error) {
174
+ setState((prev) => ({
175
+ ...prev,
176
+ error,
177
+ isLoading: false
178
+ }));
179
+ return false;
180
+ }
181
+ },
182
+ [sdk, selectedDocumentType]
183
+ );
184
+ const uploadSelfie = useCallback(
185
+ async (imageData) => {
186
+ setState((prev) => ({ ...prev, isLoading: true, error: null }));
187
+ try {
188
+ const result = await sdk.uploadSelfie(imageData);
189
+ if (result.success) {
190
+ setState((prev) => ({ ...prev, step: "liveness", isLoading: false }));
191
+ return true;
192
+ } else {
193
+ setState((prev) => ({
194
+ ...prev,
195
+ error: new KoraError("QUALITY_VALIDATION_FAILED", result.qualityIssues),
196
+ isLoading: false
197
+ }));
198
+ return false;
199
+ }
200
+ } catch (error) {
201
+ setState((prev) => ({
202
+ ...prev,
203
+ error,
204
+ isLoading: false
205
+ }));
206
+ return false;
207
+ }
208
+ },
209
+ [sdk]
210
+ );
211
+ const startLiveness = useCallback(async () => {
212
+ setState((prev) => ({ ...prev, isLoading: true, error: null }));
213
+ try {
214
+ const session = await sdk.startLivenessSession();
215
+ setState((prev) => ({
216
+ ...prev,
217
+ livenessSession: session,
218
+ currentChallenge: session.challenges[0] || null,
219
+ completedChallenges: 0,
220
+ isLoading: false
221
+ }));
222
+ } catch (error) {
223
+ setState((prev) => ({
224
+ ...prev,
225
+ error,
226
+ isLoading: false
227
+ }));
228
+ }
229
+ }, [sdk]);
230
+ const submitChallenge = useCallback(
231
+ async (imageData) => {
232
+ const { currentChallenge, livenessSession } = state;
233
+ if (!currentChallenge || !livenessSession) return false;
234
+ setState((prev) => ({ ...prev, isLoading: true }));
235
+ try {
236
+ const result = await sdk.submitLivenessChallenge(currentChallenge, imageData);
237
+ if (result.passed) {
238
+ const nextIndex = state.completedChallenges + 1;
239
+ const nextChallenge = livenessSession.challenges[nextIndex] || null;
240
+ setState((prev) => ({
241
+ ...prev,
242
+ completedChallenges: nextIndex,
243
+ currentChallenge: nextChallenge,
244
+ isLoading: false
245
+ }));
246
+ if (!nextChallenge) {
247
+ setState((prev) => ({ ...prev, step: "processing" }));
248
+ }
249
+ return true;
250
+ }
251
+ setState((prev) => ({ ...prev, isLoading: false }));
252
+ return false;
253
+ } catch (error) {
254
+ setState((prev) => ({
255
+ ...prev,
256
+ error,
257
+ isLoading: false
258
+ }));
259
+ return false;
260
+ }
261
+ },
262
+ [sdk, state]
263
+ );
264
+ const complete = useCallback(async () => {
265
+ setState((prev) => ({ ...prev, isLoading: true, error: null }));
266
+ try {
267
+ const verification = await sdk.completeVerification();
268
+ setState((prev) => ({
269
+ ...prev,
270
+ verification,
271
+ step: "complete",
272
+ isLoading: false
273
+ }));
274
+ return verification;
275
+ } catch (error) {
276
+ setState((prev) => ({
277
+ ...prev,
278
+ error,
279
+ isLoading: false
280
+ }));
281
+ return null;
282
+ }
283
+ }, [sdk]);
284
+ const cancel = useCallback(() => {
285
+ sdk.reset();
286
+ setState({
287
+ step: "consent",
288
+ verification: null,
289
+ livenessSession: null,
290
+ currentChallenge: null,
291
+ completedChallenges: 0,
292
+ isLoading: false,
293
+ error: null
294
+ });
295
+ }, [sdk]);
296
+ const retry = useCallback(() => {
297
+ setState((prev) => ({
298
+ ...prev,
299
+ error: null,
300
+ isLoading: false
301
+ }));
302
+ }, []);
303
+ return {
304
+ state,
305
+ startVerification,
306
+ resumeVerification,
307
+ acceptConsent,
308
+ selectDocumentType,
309
+ checkDocumentQuality,
310
+ uploadDocument,
311
+ uploadSelfie,
312
+ startLiveness,
313
+ submitChallenge,
314
+ complete,
315
+ cancel,
316
+ retry,
317
+ sdk
318
+ };
319
+ }
320
+
321
+ // src/components/VerificationFlow.tsx
322
+ import { useEffect as useEffect7, useState as useState6 } from "react";
323
+ import { KoraError as KoraError2, KoraErrorCode } from "@koraidv/core";
324
+
325
+ // src/components/styles.ts
326
+ var colors = {
327
+ teal: "#0D9488",
328
+ tealDark: "#0F766E",
329
+ tealLight: "#F0FDFA",
330
+ cyan: "#06B6D4",
331
+ success: "#10B981",
332
+ successBg: "#DCFCE7",
333
+ error: "#DC2626",
334
+ errorBg: "#FEF2F2",
335
+ warning: "#D97706",
336
+ warningBg: "#FFFBEB",
337
+ info: "#0284C7",
338
+ infoBg: "#EFF6FF",
339
+ purple: "#7C3AED",
340
+ white: "#FFFFFF",
341
+ black: "#000000",
342
+ darkBg: "#111111",
343
+ surface: "#F9FAFB",
344
+ border: "#E5E7EB",
345
+ borderLight: "#F3F4F6",
346
+ textPrimary: "#111111",
347
+ textSecondary: "#6B7280",
348
+ textTertiary: "#9CA3AF",
349
+ textWhite: "#FFFFFF"
350
+ };
351
+ var keyframesInjected = false;
352
+ function injectKeyframes() {
353
+ if (keyframesInjected || typeof document === "undefined") return;
354
+ keyframesInjected = true;
355
+ const style = document.createElement("style");
356
+ style.textContent = `
357
+ @keyframes kora-spin {
358
+ from { transform: rotate(0deg); }
359
+ to { transform: rotate(360deg); }
360
+ }
361
+ @keyframes kora-scan {
362
+ 0%, 100% { transform: translateY(0); }
363
+ 50% { transform: translateY(100%); }
364
+ }
365
+ @keyframes kora-rotate-ring {
366
+ from { transform: rotate(0deg); }
367
+ to { transform: rotate(360deg); }
368
+ }
369
+ @keyframes kora-pulse {
370
+ 0%, 100% { opacity: 1; }
371
+ 50% { opacity: 0.4; }
372
+ }
373
+ @keyframes kora-ring1 {
374
+ from { transform: rotate(0deg); }
375
+ to { transform: rotate(360deg); }
376
+ }
377
+ @keyframes kora-ring2 {
378
+ from { transform: rotate(0deg); }
379
+ to { transform: rotate(-360deg); }
380
+ }
381
+ @keyframes kora-ring3 {
382
+ from { transform: rotate(0deg); }
383
+ to { transform: rotate(360deg); }
384
+ }
385
+ @keyframes kora-fade-in {
386
+ from { opacity: 0; transform: translateY(8px); }
387
+ to { opacity: 1; transform: translateY(0); }
388
+ }
389
+ `;
390
+ document.head.appendChild(style);
391
+ }
392
+ var styles = {
393
+ // ─── Container ─────────────────────────────────────────────────────────
394
+ container: {
395
+ display: "flex",
396
+ flexDirection: "column",
397
+ minHeight: "100vh",
398
+ backgroundColor: colors.white
399
+ },
400
+ darkContainer: {
401
+ display: "flex",
402
+ flexDirection: "column",
403
+ minHeight: "100vh",
404
+ backgroundColor: colors.darkBg
405
+ },
406
+ // ─── Header ────────────────────────────────────────────────────────────
407
+ header: {
408
+ padding: "32px 24px",
409
+ textAlign: "center"
410
+ },
411
+ iconContainer: {
412
+ marginBottom: "20px"
413
+ },
414
+ title: {
415
+ fontSize: "24px",
416
+ fontWeight: 700,
417
+ color: colors.textPrimary,
418
+ margin: 0
419
+ },
420
+ subtitle: {
421
+ fontSize: "15px",
422
+ color: colors.textSecondary,
423
+ margin: "8px 0 0 0",
424
+ lineHeight: 1.5
425
+ },
426
+ // ─── Content ───────────────────────────────────────────────────────────
427
+ content: {
428
+ flex: 1,
429
+ padding: "0 24px",
430
+ overflowY: "auto"
431
+ },
432
+ scrollContent: {
433
+ flex: 1,
434
+ padding: "0 24px",
435
+ overflowY: "auto"
436
+ },
437
+ section: {
438
+ marginBottom: "24px"
439
+ },
440
+ sectionTitle: {
441
+ fontSize: "16px",
442
+ fontWeight: 600,
443
+ color: colors.textPrimary,
444
+ margin: "0 0 12px 0"
445
+ },
446
+ // ─── Checklist ─────────────────────────────────────────────────────────
447
+ checkList: {
448
+ display: "flex",
449
+ flexDirection: "column",
450
+ gap: "16px"
451
+ },
452
+ checklistItem: {
453
+ display: "flex",
454
+ alignItems: "flex-start",
455
+ gap: "14px"
456
+ },
457
+ checklistIconBox: {
458
+ width: "44px",
459
+ height: "44px",
460
+ borderRadius: "12px",
461
+ display: "flex",
462
+ alignItems: "center",
463
+ justifyContent: "center",
464
+ flexShrink: 0,
465
+ fontSize: "20px"
466
+ },
467
+ checklistTextWrapper: {
468
+ display: "flex",
469
+ flexDirection: "column",
470
+ paddingTop: "2px"
471
+ },
472
+ checklistTitle: {
473
+ fontSize: "15px",
474
+ fontWeight: 600,
475
+ color: colors.textPrimary
476
+ },
477
+ checklistDescription: {
478
+ fontSize: "13px",
479
+ color: colors.textSecondary,
480
+ marginTop: "2px"
481
+ },
482
+ // ─── Body text ─────────────────────────────────────────────────────────
483
+ bodyText: {
484
+ fontSize: "14px",
485
+ color: colors.textSecondary,
486
+ lineHeight: 1.6,
487
+ margin: 0
488
+ },
489
+ // ─── Footer ────────────────────────────────────────────────────────────
490
+ footer: {
491
+ padding: "16px 24px 32px",
492
+ backgroundColor: colors.white
493
+ },
494
+ darkFooter: {
495
+ padding: "16px 24px 32px",
496
+ backgroundColor: colors.darkBg
497
+ },
498
+ // ─── Buttons ───────────────────────────────────────────────────────────
499
+ primaryButton: {
500
+ width: "100%",
501
+ padding: "16px",
502
+ fontSize: "17px",
503
+ fontWeight: 600,
504
+ color: colors.white,
505
+ background: `linear-gradient(135deg, ${colors.teal}, ${colors.tealDark})`,
506
+ border: "none",
507
+ borderRadius: "16px",
508
+ cursor: "pointer",
509
+ transition: "opacity 0.2s",
510
+ display: "flex",
511
+ alignItems: "center",
512
+ justifyContent: "center",
513
+ gap: "8px"
514
+ },
515
+ secondaryButton: {
516
+ width: "100%",
517
+ padding: "16px",
518
+ fontSize: "17px",
519
+ fontWeight: 600,
520
+ color: colors.textPrimary,
521
+ backgroundColor: "transparent",
522
+ border: `2px solid ${colors.border}`,
523
+ borderRadius: "16px",
524
+ cursor: "pointer",
525
+ transition: "border-color 0.2s"
526
+ },
527
+ darkOutlineButton: {
528
+ width: "100%",
529
+ padding: "16px",
530
+ fontSize: "17px",
531
+ fontWeight: 600,
532
+ color: colors.white,
533
+ backgroundColor: "transparent",
534
+ border: "2px solid rgba(255,255,255,0.3)",
535
+ borderRadius: "16px",
536
+ cursor: "pointer"
537
+ },
538
+ textButton: {
539
+ width: "100%",
540
+ padding: "12px",
541
+ fontSize: "14px",
542
+ color: colors.textSecondary,
543
+ backgroundColor: "transparent",
544
+ border: "none",
545
+ cursor: "pointer"
546
+ },
547
+ // ─── Screen header ─────────────────────────────────────────────────────
548
+ screenHeader: {
549
+ display: "flex",
550
+ alignItems: "center",
551
+ padding: "16px 24px",
552
+ gap: "12px"
553
+ },
554
+ screenTitle: {
555
+ fontSize: "18px",
556
+ fontWeight: 600,
557
+ color: colors.textPrimary,
558
+ margin: 0,
559
+ flex: 1
560
+ },
561
+ backButton: {
562
+ width: "40px",
563
+ height: "40px",
564
+ display: "flex",
565
+ alignItems: "center",
566
+ justifyContent: "center",
567
+ fontSize: "20px",
568
+ color: colors.textPrimary,
569
+ backgroundColor: colors.borderLight,
570
+ border: "none",
571
+ borderRadius: "50%",
572
+ cursor: "pointer",
573
+ flexShrink: 0
574
+ },
575
+ closeButton: {
576
+ width: "40px",
577
+ height: "40px",
578
+ display: "flex",
579
+ alignItems: "center",
580
+ justifyContent: "center",
581
+ fontSize: "18px",
582
+ color: colors.textSecondary,
583
+ backgroundColor: colors.borderLight,
584
+ border: "none",
585
+ borderRadius: "50%",
586
+ cursor: "pointer",
587
+ flexShrink: 0
588
+ },
589
+ // ─── Dark header ──────────────────────────────────────────────────────
590
+ darkScreenHeader: {
591
+ display: "flex",
592
+ alignItems: "center",
593
+ padding: "12px 24px",
594
+ gap: "12px"
595
+ },
596
+ darkScreenTitle: {
597
+ fontSize: "18px",
598
+ fontWeight: 600,
599
+ color: colors.white,
600
+ margin: 0,
601
+ flex: 1,
602
+ textAlign: "center"
603
+ },
604
+ darkScreenSubtitle: {
605
+ fontSize: "14px",
606
+ color: "rgba(255,255,255,0.6)",
607
+ margin: "4px 0 0 0",
608
+ textAlign: "center"
609
+ },
610
+ glassCloseButton: {
611
+ width: "40px",
612
+ height: "40px",
613
+ display: "flex",
614
+ alignItems: "center",
615
+ justifyContent: "center",
616
+ fontSize: "18px",
617
+ color: colors.white,
618
+ backgroundColor: "rgba(255,255,255,0.15)",
619
+ border: "none",
620
+ borderRadius: "50%",
621
+ cursor: "pointer",
622
+ flexShrink: 0,
623
+ backdropFilter: "blur(8px)"
624
+ },
625
+ // ─── Search bar ────────────────────────────────────────────────────────
626
+ searchBar: {
627
+ display: "flex",
628
+ alignItems: "center",
629
+ gap: "10px",
630
+ padding: "12px 16px",
631
+ backgroundColor: colors.surface,
632
+ borderRadius: "14px",
633
+ border: `2px solid transparent`,
634
+ margin: "0 0 16px 0"
635
+ },
636
+ searchInput: {
637
+ flex: 1,
638
+ border: "none",
639
+ outline: "none",
640
+ backgroundColor: "transparent",
641
+ fontSize: "15px",
642
+ color: colors.textPrimary
643
+ },
644
+ // ─── Country selection ─────────────────────────────────────────────────
645
+ countryGrid: {
646
+ display: "grid",
647
+ gridTemplateColumns: "1fr 1fr",
648
+ gap: "10px"
649
+ },
650
+ countryCard: {
651
+ display: "flex",
652
+ alignItems: "center",
653
+ gap: "10px",
654
+ padding: "14px",
655
+ backgroundColor: colors.white,
656
+ border: `2px solid ${colors.border}`,
657
+ borderRadius: "14px",
658
+ cursor: "pointer",
659
+ textAlign: "left",
660
+ transition: "border-color 0.15s"
661
+ },
662
+ countryCardSelected: {
663
+ borderColor: colors.teal,
664
+ backgroundColor: colors.tealLight
665
+ },
666
+ countryFlag: {
667
+ fontSize: "24px",
668
+ flexShrink: 0
669
+ },
670
+ countryName: {
671
+ fontSize: "14px",
672
+ fontWeight: 500,
673
+ color: colors.textPrimary,
674
+ flex: 1,
675
+ overflow: "hidden",
676
+ textOverflow: "ellipsis",
677
+ whiteSpace: "nowrap"
678
+ },
679
+ countryCheck: {
680
+ width: "20px",
681
+ height: "20px",
682
+ borderRadius: "50%",
683
+ backgroundColor: colors.teal,
684
+ color: colors.white,
685
+ display: "flex",
686
+ alignItems: "center",
687
+ justifyContent: "center",
688
+ fontSize: "12px",
689
+ fontWeight: 700,
690
+ flexShrink: 0
691
+ },
692
+ // ─── Document selection ────────────────────────────────────────────────
693
+ documentCard: {
694
+ display: "flex",
695
+ alignItems: "center",
696
+ width: "100%",
697
+ padding: "16px",
698
+ marginBottom: "10px",
699
+ backgroundColor: colors.white,
700
+ border: `2px solid ${colors.border}`,
701
+ borderRadius: "16px",
702
+ cursor: "pointer",
703
+ textAlign: "left",
704
+ transition: "border-color 0.15s, background-color 0.15s"
705
+ },
706
+ documentCardSelected: {
707
+ borderColor: colors.teal,
708
+ backgroundColor: colors.tealLight
709
+ },
710
+ documentIconBox: {
711
+ width: "48px",
712
+ height: "48px",
713
+ borderRadius: "14px",
714
+ display: "flex",
715
+ alignItems: "center",
716
+ justifyContent: "center",
717
+ marginRight: "14px",
718
+ flexShrink: 0,
719
+ fontSize: "24px"
720
+ },
721
+ documentInfo: {
722
+ flex: 1,
723
+ display: "flex",
724
+ flexDirection: "column"
725
+ },
726
+ documentName: {
727
+ fontSize: "15px",
728
+ fontWeight: 600,
729
+ color: colors.textPrimary
730
+ },
731
+ documentSubtext: {
732
+ fontSize: "13px",
733
+ color: colors.textSecondary,
734
+ marginTop: "2px"
735
+ },
736
+ documentChevron: {
737
+ fontSize: "18px",
738
+ color: colors.textTertiary,
739
+ flexShrink: 0
740
+ },
741
+ // ─── Capture screens ──────────────────────────────────────────────────
742
+ captureContainer: {
743
+ display: "flex",
744
+ flexDirection: "column",
745
+ height: "100vh",
746
+ backgroundColor: colors.darkBg,
747
+ position: "relative"
748
+ },
749
+ cameraContainer: {
750
+ flex: 1,
751
+ position: "relative",
752
+ overflow: "hidden"
753
+ },
754
+ cameraVideo: {
755
+ width: "100%",
756
+ height: "100%",
757
+ objectFit: "cover"
758
+ },
759
+ // ─── Document viewfinder ──────────────────────────────────────────────
760
+ documentOverlay: {
761
+ position: "absolute",
762
+ top: 0,
763
+ left: 0,
764
+ right: 0,
765
+ bottom: 0,
766
+ display: "flex",
767
+ alignItems: "center",
768
+ justifyContent: "center"
769
+ },
770
+ documentFrame: {
771
+ width: "85%",
772
+ maxWidth: "342px",
773
+ aspectRatio: "1.586",
774
+ border: "2px solid rgba(255,255,255,0.3)",
775
+ borderRadius: "20px",
776
+ position: "relative",
777
+ backgroundColor: "transparent"
778
+ },
779
+ corner: {
780
+ position: "absolute",
781
+ width: "28px",
782
+ height: "28px",
783
+ borderColor: colors.teal,
784
+ borderStyle: "solid",
785
+ borderWidth: "3px 0 0 3px"
786
+ },
787
+ scanLine: {
788
+ position: "absolute",
789
+ left: "10px",
790
+ right: "10px",
791
+ height: "2px",
792
+ background: `linear-gradient(90deg, transparent, ${colors.teal}80, transparent)`,
793
+ animation: "kora-scan 2.5s ease-in-out infinite"
794
+ },
795
+ // ─── Selfie viewfinder ────────────────────────────────────────────────
796
+ selfieOverlay: {
797
+ position: "absolute",
798
+ top: 0,
799
+ left: 0,
800
+ right: 0,
801
+ bottom: 0,
802
+ display: "flex",
803
+ alignItems: "center",
804
+ justifyContent: "center"
805
+ },
806
+ faceGuide: {
807
+ width: "240px",
808
+ height: "300px",
809
+ border: "3px solid rgba(255,255,255,0.2)",
810
+ borderRadius: "50%",
811
+ backgroundColor: "transparent",
812
+ position: "relative"
813
+ },
814
+ rotatingRing: {
815
+ position: "absolute",
816
+ top: "-6px",
817
+ left: "-6px",
818
+ right: "-6px",
819
+ bottom: "-6px",
820
+ borderRadius: "50%",
821
+ border: "3px solid transparent",
822
+ borderTopColor: colors.teal,
823
+ borderRightColor: colors.cyan,
824
+ animation: "kora-rotate-ring 3s linear infinite"
825
+ },
826
+ // ─── Step pills ────────────────────────────────────────────────────────
827
+ stepPillsRow: {
828
+ display: "flex",
829
+ justifyContent: "center",
830
+ gap: "8px",
831
+ padding: "8px 0"
832
+ },
833
+ stepPill: {
834
+ padding: "6px 16px",
835
+ borderRadius: "20px",
836
+ fontSize: "13px",
837
+ fontWeight: 600,
838
+ border: "none",
839
+ cursor: "default"
840
+ },
841
+ // ─── Guidance pill ─────────────────────────────────────────────────────
842
+ guidancePill: {
843
+ display: "inline-flex",
844
+ alignItems: "center",
845
+ gap: "8px",
846
+ padding: "10px 20px",
847
+ borderRadius: "24px",
848
+ fontSize: "14px",
849
+ fontWeight: 500
850
+ },
851
+ pulsingDot: {
852
+ width: "8px",
853
+ height: "8px",
854
+ borderRadius: "50%",
855
+ animation: "kora-pulse 1.5s ease-in-out infinite"
856
+ },
857
+ // ─── Review screen ────────────────────────────────────────────────────
858
+ reviewCard: {
859
+ backgroundColor: "rgba(255,255,255,0.05)",
860
+ borderRadius: "20px",
861
+ padding: "20px",
862
+ margin: "0 24px"
863
+ },
864
+ reviewBadge: {
865
+ display: "inline-flex",
866
+ alignItems: "center",
867
+ gap: "6px",
868
+ padding: "6px 14px",
869
+ borderRadius: "20px",
870
+ backgroundColor: "rgba(16,185,129,0.15)",
871
+ color: colors.success,
872
+ fontSize: "13px",
873
+ fontWeight: 600
874
+ },
875
+ qualityChecks: {
876
+ display: "flex",
877
+ justifyContent: "center",
878
+ gap: "20px",
879
+ marginTop: "16px"
880
+ },
881
+ qualityCheck: {
882
+ display: "flex",
883
+ flexDirection: "column",
884
+ alignItems: "center",
885
+ gap: "4px"
886
+ },
887
+ qualityCheckIcon: {
888
+ width: "28px",
889
+ height: "28px",
890
+ borderRadius: "50%",
891
+ backgroundColor: "rgba(16,185,129,0.15)",
892
+ color: colors.success,
893
+ display: "flex",
894
+ alignItems: "center",
895
+ justifyContent: "center",
896
+ fontSize: "14px"
897
+ },
898
+ qualityCheckLabel: {
899
+ fontSize: "12px",
900
+ color: "rgba(255,255,255,0.6)"
901
+ },
902
+ // ─── Review buttons ───────────────────────────────────────────────────
903
+ reviewButtonsRow: {
904
+ display: "flex",
905
+ gap: "12px",
906
+ padding: "24px"
907
+ },
908
+ // ─── Liveness ──────────────────────────────────────────────────────────
909
+ challengeTitle: {
910
+ fontSize: "24px",
911
+ fontWeight: 700,
912
+ color: colors.white,
913
+ textAlign: "center",
914
+ margin: "0 32px"
915
+ },
916
+ challengeSubtitle: {
917
+ fontSize: "15px",
918
+ color: "rgba(255,255,255,0.6)",
919
+ textAlign: "center",
920
+ margin: "8px 32px 0"
921
+ },
922
+ countdownBadge: {
923
+ width: "40px",
924
+ height: "40px",
925
+ borderRadius: "50%",
926
+ backgroundColor: colors.error,
927
+ color: colors.white,
928
+ display: "flex",
929
+ alignItems: "center",
930
+ justifyContent: "center",
931
+ fontSize: "18px",
932
+ fontWeight: 700,
933
+ position: "absolute",
934
+ top: "-20px",
935
+ left: "50%",
936
+ transform: "translateX(-50%)"
937
+ },
938
+ progressDots: {
939
+ display: "flex",
940
+ justifyContent: "center",
941
+ gap: "8px"
942
+ },
943
+ progressDot: {
944
+ width: "10px",
945
+ height: "10px",
946
+ borderRadius: "50%",
947
+ transition: "background-color 0.3s"
948
+ },
949
+ progressText: {
950
+ fontSize: "14px",
951
+ color: "rgba(255,255,255,0.6)",
952
+ textAlign: "center",
953
+ margin: "8px 0 0 0"
954
+ },
955
+ // ─── Progress bar ──────────────────────────────────────────────────────
956
+ progressBar: {
957
+ display: "flex",
958
+ gap: "4px",
959
+ padding: "12px 24px"
960
+ },
961
+ progressSegment: {
962
+ flex: 1,
963
+ height: "4px",
964
+ borderRadius: "2px",
965
+ transition: "background-color 0.3s"
966
+ },
967
+ // ─── Processing screen ─────────────────────────────────────────────────
968
+ processingContainer: {
969
+ display: "flex",
970
+ flexDirection: "column",
971
+ alignItems: "center",
972
+ justifyContent: "center",
973
+ minHeight: "100vh",
974
+ backgroundColor: colors.darkBg,
975
+ padding: "24px"
976
+ },
977
+ spinnerContainer: {
978
+ position: "relative",
979
+ width: "120px",
980
+ height: "120px",
981
+ marginBottom: "48px"
982
+ },
983
+ spinnerRing: {
984
+ position: "absolute",
985
+ borderRadius: "50%",
986
+ border: "2px solid transparent"
987
+ },
988
+ processingSteps: {
989
+ display: "flex",
990
+ flexDirection: "column",
991
+ gap: "16px",
992
+ width: "100%",
993
+ maxWidth: "280px"
994
+ },
995
+ processingStep: {
996
+ display: "flex",
997
+ alignItems: "center",
998
+ gap: "12px",
999
+ fontSize: "15px"
1000
+ },
1001
+ processingStepIcon: {
1002
+ width: "24px",
1003
+ height: "24px",
1004
+ borderRadius: "50%",
1005
+ display: "flex",
1006
+ alignItems: "center",
1007
+ justifyContent: "center",
1008
+ fontSize: "12px",
1009
+ flexShrink: 0
1010
+ },
1011
+ // ─── Result screens ────────────────────────────────────────────────────
1012
+ resultContainer: {
1013
+ display: "flex",
1014
+ flexDirection: "column",
1015
+ minHeight: "100vh",
1016
+ backgroundColor: colors.white
1017
+ },
1018
+ resultContent: {
1019
+ flex: 1,
1020
+ padding: "32px 24px",
1021
+ textAlign: "center"
1022
+ },
1023
+ resultIconCircle: {
1024
+ width: "72px",
1025
+ height: "72px",
1026
+ borderRadius: "50%",
1027
+ display: "flex",
1028
+ alignItems: "center",
1029
+ justifyContent: "center",
1030
+ margin: "0 auto 20px",
1031
+ fontSize: "32px"
1032
+ },
1033
+ resultIconOuterRing: {
1034
+ width: "88px",
1035
+ height: "88px",
1036
+ borderRadius: "50%",
1037
+ display: "flex",
1038
+ alignItems: "center",
1039
+ justifyContent: "center",
1040
+ margin: "0 auto 20px"
1041
+ },
1042
+ resultTitle: {
1043
+ fontSize: "24px",
1044
+ fontWeight: 700,
1045
+ color: colors.textPrimary,
1046
+ margin: "0 0 8px 0"
1047
+ },
1048
+ resultSubtitle: {
1049
+ fontSize: "15px",
1050
+ color: colors.textSecondary,
1051
+ margin: "0 0 24px 0",
1052
+ lineHeight: 1.5
1053
+ },
1054
+ // ─── Score card ────────────────────────────────────────────────────────
1055
+ scoreCard: {
1056
+ borderRadius: "20px",
1057
+ padding: "24px",
1058
+ margin: "0 0 24px 0",
1059
+ textAlign: "center",
1060
+ color: colors.white
1061
+ },
1062
+ scoreValue: {
1063
+ fontSize: "48px",
1064
+ fontWeight: 700,
1065
+ lineHeight: 1
1066
+ },
1067
+ scoreBadge: {
1068
+ display: "inline-block",
1069
+ padding: "4px 12px",
1070
+ borderRadius: "12px",
1071
+ fontSize: "12px",
1072
+ fontWeight: 700,
1073
+ letterSpacing: "0.05em",
1074
+ margin: "8px 0 16px",
1075
+ backgroundColor: "rgba(255,255,255,0.2)"
1076
+ },
1077
+ scoreProgressBg: {
1078
+ height: "6px",
1079
+ borderRadius: "3px",
1080
+ backgroundColor: "rgba(255,255,255,0.2)",
1081
+ overflow: "hidden"
1082
+ },
1083
+ scoreProgressFill: {
1084
+ height: "100%",
1085
+ borderRadius: "3px",
1086
+ backgroundColor: colors.white,
1087
+ transition: "width 0.8s ease-out"
1088
+ },
1089
+ // ─── Metric rows ──────────────────────────────────────────────────────
1090
+ metricRow: {
1091
+ display: "flex",
1092
+ alignItems: "center",
1093
+ padding: "14px 16px",
1094
+ borderRadius: "14px",
1095
+ marginBottom: "8px",
1096
+ gap: "12px"
1097
+ },
1098
+ metricIcon: {
1099
+ width: "36px",
1100
+ height: "36px",
1101
+ borderRadius: "10px",
1102
+ display: "flex",
1103
+ alignItems: "center",
1104
+ justifyContent: "center",
1105
+ fontSize: "18px",
1106
+ flexShrink: 0
1107
+ },
1108
+ metricInfo: {
1109
+ flex: 1
1110
+ },
1111
+ metricLabel: {
1112
+ fontSize: "14px",
1113
+ fontWeight: 500,
1114
+ color: colors.textPrimary
1115
+ },
1116
+ metricMessage: {
1117
+ fontSize: "12px",
1118
+ marginTop: "2px"
1119
+ },
1120
+ metricScore: {
1121
+ fontSize: "15px",
1122
+ fontWeight: 700
1123
+ },
1124
+ metricBadge: {
1125
+ fontSize: "11px",
1126
+ fontWeight: 600,
1127
+ padding: "2px 8px",
1128
+ borderRadius: "8px",
1129
+ marginLeft: "8px"
1130
+ },
1131
+ // ─── Expired document ──────────────────────────────────────────────────
1132
+ expiryCard: {
1133
+ backgroundColor: colors.surface,
1134
+ borderRadius: "16px",
1135
+ padding: "20px",
1136
+ marginBottom: "24px",
1137
+ textAlign: "left"
1138
+ },
1139
+ expiryRow: {
1140
+ display: "flex",
1141
+ justifyContent: "space-between",
1142
+ alignItems: "center",
1143
+ marginBottom: "12px"
1144
+ },
1145
+ expiryLabel: {
1146
+ fontSize: "13px",
1147
+ color: colors.textSecondary
1148
+ },
1149
+ expiryValue: {
1150
+ fontSize: "14px",
1151
+ fontWeight: 600,
1152
+ color: colors.textPrimary
1153
+ },
1154
+ expiryBadge: {
1155
+ display: "inline-block",
1156
+ padding: "4px 10px",
1157
+ borderRadius: "10px",
1158
+ fontSize: "12px",
1159
+ fontWeight: 600,
1160
+ backgroundColor: colors.errorBg,
1161
+ color: colors.error
1162
+ },
1163
+ guidanceTip: {
1164
+ display: "flex",
1165
+ alignItems: "flex-start",
1166
+ gap: "12px",
1167
+ marginBottom: "16px"
1168
+ },
1169
+ guidanceTipNumber: {
1170
+ width: "28px",
1171
+ height: "28px",
1172
+ borderRadius: "50%",
1173
+ backgroundColor: colors.tealLight,
1174
+ color: colors.teal,
1175
+ display: "flex",
1176
+ alignItems: "center",
1177
+ justifyContent: "center",
1178
+ fontSize: "13px",
1179
+ fontWeight: 700,
1180
+ flexShrink: 0
1181
+ },
1182
+ guidanceTipText: {
1183
+ fontSize: "14px",
1184
+ color: colors.textSecondary,
1185
+ lineHeight: 1.5,
1186
+ paddingTop: "4px"
1187
+ },
1188
+ // ─── Info card ─────────────────────────────────────────────────────────
1189
+ infoCard: {
1190
+ width: "100%",
1191
+ backgroundColor: colors.surface,
1192
+ borderRadius: "16px",
1193
+ overflow: "hidden",
1194
+ marginBottom: "24px"
1195
+ },
1196
+ infoCardHeader: {
1197
+ display: "flex",
1198
+ alignItems: "center",
1199
+ gap: "10px",
1200
+ padding: "16px",
1201
+ borderBottom: `1px solid ${colors.border}`
1202
+ },
1203
+ infoCardIcon: {
1204
+ fontSize: "20px"
1205
+ },
1206
+ infoCardTitle: {
1207
+ fontSize: "15px",
1208
+ fontWeight: 600,
1209
+ color: colors.textPrimary
1210
+ },
1211
+ infoCardBody: {
1212
+ padding: "16px"
1213
+ },
1214
+ infoRow: {
1215
+ display: "flex",
1216
+ justifyContent: "space-between",
1217
+ marginBottom: "10px"
1218
+ },
1219
+ infoLabel: {
1220
+ fontSize: "13px",
1221
+ color: colors.textSecondary
1222
+ },
1223
+ infoValue: {
1224
+ fontSize: "14px",
1225
+ fontWeight: 500,
1226
+ color: colors.textPrimary
1227
+ },
1228
+ // ─── Loading ───────────────────────────────────────────────────────────
1229
+ loadingContainer: {
1230
+ flex: 1,
1231
+ display: "flex",
1232
+ flexDirection: "column",
1233
+ alignItems: "center",
1234
+ justifyContent: "center",
1235
+ gap: "16px",
1236
+ padding: "24px"
1237
+ },
1238
+ spinner: {
1239
+ width: "40px",
1240
+ height: "40px",
1241
+ border: "3px solid rgba(13,148,136,0.15)",
1242
+ borderTopColor: colors.teal,
1243
+ borderRadius: "50%",
1244
+ animation: "kora-spin 1s linear infinite"
1245
+ },
1246
+ loadingText: {
1247
+ fontSize: "15px",
1248
+ color: colors.textSecondary
1249
+ },
1250
+ // ─── Error ─────────────────────────────────────────────────────────────
1251
+ errorContainer: {
1252
+ flex: 1,
1253
+ display: "flex",
1254
+ flexDirection: "column",
1255
+ alignItems: "center",
1256
+ justifyContent: "center",
1257
+ padding: "24px",
1258
+ textAlign: "center"
1259
+ },
1260
+ errorText: {
1261
+ fontSize: "16px",
1262
+ color: colors.error,
1263
+ marginBottom: "24px"
1264
+ },
1265
+ // ─── Capture footer ───────────────────────────────────────────────────
1266
+ captureFooter: {
1267
+ padding: "24px",
1268
+ display: "flex",
1269
+ justifyContent: "center"
1270
+ },
1271
+ captureButton: {
1272
+ width: "72px",
1273
+ height: "72px",
1274
+ borderRadius: "50%",
1275
+ border: "4px solid #FFFFFF",
1276
+ backgroundColor: "transparent",
1277
+ cursor: "pointer",
1278
+ display: "flex",
1279
+ alignItems: "center",
1280
+ justifyContent: "center",
1281
+ padding: "4px"
1282
+ },
1283
+ captureButtonInner: {
1284
+ width: "100%",
1285
+ height: "100%",
1286
+ borderRadius: "50%",
1287
+ backgroundColor: "#FFFFFF"
1288
+ },
1289
+ // ─── Capture instructions ─────────────────────────────────────────────
1290
+ captureInstructions: {
1291
+ textAlign: "center",
1292
+ padding: "16px 24px"
1293
+ },
1294
+ instructionText: {
1295
+ fontSize: "15px",
1296
+ color: "rgba(255,255,255,0.8)",
1297
+ margin: 0
1298
+ }
1299
+ };
1300
+
1301
+ // src/components/DesignSystem.tsx
1302
+ import { useEffect } from "react";
1303
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
1304
+ function StepProgressBar({ total, current, isDark = false }) {
1305
+ return /* @__PURE__ */ jsx2("div", { style: styles.progressBar, children: Array.from({ length: total }).map((_, i) => /* @__PURE__ */ jsx2(
1306
+ "div",
1307
+ {
1308
+ style: {
1309
+ ...styles.progressSegment,
1310
+ backgroundColor: i < current ? colors.teal : isDark ? "rgba(255,255,255,0.15)" : colors.border
1311
+ }
1312
+ },
1313
+ i
1314
+ )) });
1315
+ }
1316
+ function ScoreCard({ score, badge, gradient }) {
1317
+ return /* @__PURE__ */ jsxs("div", { style: { ...styles.scoreCard, background: gradient }, children: [
1318
+ /* @__PURE__ */ jsxs("div", { style: styles.scoreValue, children: [
1319
+ score,
1320
+ "%"
1321
+ ] }),
1322
+ /* @__PURE__ */ jsx2("div", { style: styles.scoreBadge, children: badge }),
1323
+ /* @__PURE__ */ jsx2("div", { style: styles.scoreProgressBg, children: /* @__PURE__ */ jsx2("div", { style: { ...styles.scoreProgressFill, width: `${score}%` } }) })
1324
+ ] });
1325
+ }
1326
+ function ScoreMetricRow({ label, score, icon, status, message }) {
1327
+ const bgColor = status === "pass" ? colors.successBg : status === "fail" ? colors.errorBg : colors.warningBg;
1328
+ const borderColor = status === "pass" ? colors.success : status === "fail" ? colors.error : colors.warning;
1329
+ const textColor = borderColor;
1330
+ const badgeText = status === "pass" ? "PASS" : status === "fail" ? "FAIL" : "REVIEW";
1331
+ return /* @__PURE__ */ jsxs(
1332
+ "div",
1333
+ {
1334
+ style: {
1335
+ ...styles.metricRow,
1336
+ backgroundColor: bgColor,
1337
+ borderLeft: `3px solid ${borderColor}`
1338
+ },
1339
+ children: [
1340
+ /* @__PURE__ */ jsx2(
1341
+ "div",
1342
+ {
1343
+ style: {
1344
+ ...styles.metricIcon,
1345
+ backgroundColor: `${borderColor}15`
1346
+ },
1347
+ children: icon
1348
+ }
1349
+ ),
1350
+ /* @__PURE__ */ jsxs("div", { style: styles.metricInfo, children: [
1351
+ /* @__PURE__ */ jsx2("div", { style: styles.metricLabel, children: label }),
1352
+ message && /* @__PURE__ */ jsx2("div", { style: { ...styles.metricMessage, color: textColor }, children: message })
1353
+ ] }),
1354
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center" }, children: [
1355
+ /* @__PURE__ */ jsxs("span", { style: { ...styles.metricScore, color: textColor }, children: [
1356
+ score,
1357
+ "%"
1358
+ ] }),
1359
+ /* @__PURE__ */ jsx2(
1360
+ "span",
1361
+ {
1362
+ style: {
1363
+ ...styles.metricBadge,
1364
+ backgroundColor: `${borderColor}15`,
1365
+ color: textColor
1366
+ },
1367
+ children: badgeText
1368
+ }
1369
+ )
1370
+ ] })
1371
+ ]
1372
+ }
1373
+ );
1374
+ }
1375
+ function ProcessingScreen({ steps }) {
1376
+ useEffect(() => {
1377
+ injectKeyframes();
1378
+ }, []);
1379
+ return /* @__PURE__ */ jsxs("div", { style: styles.processingContainer, children: [
1380
+ /* @__PURE__ */ jsxs("div", { style: styles.spinnerContainer, children: [
1381
+ /* @__PURE__ */ jsx2(
1382
+ "div",
1383
+ {
1384
+ style: {
1385
+ ...styles.spinnerRing,
1386
+ inset: "0",
1387
+ borderTopColor: `${colors.teal}40`,
1388
+ animation: "kora-ring1 3s linear infinite"
1389
+ }
1390
+ }
1391
+ ),
1392
+ /* @__PURE__ */ jsx2(
1393
+ "div",
1394
+ {
1395
+ style: {
1396
+ ...styles.spinnerRing,
1397
+ inset: "15px",
1398
+ borderRightColor: `${colors.cyan}40`,
1399
+ animation: "kora-ring2 2s linear infinite"
1400
+ }
1401
+ }
1402
+ ),
1403
+ /* @__PURE__ */ jsx2(
1404
+ "div",
1405
+ {
1406
+ style: {
1407
+ ...styles.spinnerRing,
1408
+ inset: "30px",
1409
+ borderBottomColor: `${colors.teal}40`,
1410
+ animation: "kora-ring3 1.5s linear infinite"
1411
+ }
1412
+ }
1413
+ ),
1414
+ /* @__PURE__ */ jsx2(
1415
+ "div",
1416
+ {
1417
+ style: {
1418
+ position: "absolute",
1419
+ inset: "0",
1420
+ display: "flex",
1421
+ alignItems: "center",
1422
+ justifyContent: "center",
1423
+ fontSize: "28px"
1424
+ },
1425
+ children: "\u{1F6E1}\uFE0F"
1426
+ }
1427
+ )
1428
+ ] }),
1429
+ /* @__PURE__ */ jsx2("div", { style: styles.processingSteps, children: steps.map((step, i) => /* @__PURE__ */ jsxs("div", { style: styles.processingStep, children: [
1430
+ /* @__PURE__ */ jsx2(
1431
+ "div",
1432
+ {
1433
+ style: {
1434
+ ...styles.processingStepIcon,
1435
+ backgroundColor: step.status === "done" ? colors.success : step.status === "active" ? colors.teal : "rgba(255,255,255,0.1)",
1436
+ color: step.status === "pending" ? "rgba(255,255,255,0.3)" : colors.white
1437
+ },
1438
+ children: step.status === "done" ? "\u2713" : step.status === "active" ? "\u2026" : "\xB7"
1439
+ }
1440
+ ),
1441
+ /* @__PURE__ */ jsx2(
1442
+ "span",
1443
+ {
1444
+ style: {
1445
+ color: step.status === "pending" ? "rgba(255,255,255,0.3)" : colors.white
1446
+ },
1447
+ children: step.label
1448
+ }
1449
+ )
1450
+ ] }, i)) })
1451
+ ] });
1452
+ }
1453
+ function computeScoreBreakdown(verification) {
1454
+ const liveness = verification.livenessVerification?.livenessScore ?? 0;
1455
+ const livenessPercent = Math.round(liveness * 100);
1456
+ const docQuality = verification.documentVerification?.authenticityScore ?? 0;
1457
+ const docPercent = Math.round(docQuality * 100);
1458
+ const nameMatch = verification.documentVerification?.firstName && verification.documentVerification?.lastName ? 100 : 0;
1459
+ const selfieMatch = verification.faceVerification?.matchScore ?? 0;
1460
+ const selfiePercent = Math.round(selfieMatch * 100);
1461
+ function getStatus(score) {
1462
+ if (score >= 75) return "pass";
1463
+ if (score >= 50) return "borderline";
1464
+ return "fail";
1465
+ }
1466
+ function getMessage(status) {
1467
+ if (status === "fail") return "Below threshold";
1468
+ if (status === "borderline") return "Requires review";
1469
+ return void 0;
1470
+ }
1471
+ const metrics = [
1472
+ {
1473
+ label: "Liveness",
1474
+ score: livenessPercent,
1475
+ icon: "\u{1F441}\uFE0F",
1476
+ status: getStatus(livenessPercent),
1477
+ message: getMessage(getStatus(livenessPercent))
1478
+ },
1479
+ {
1480
+ label: "Name Match",
1481
+ score: nameMatch,
1482
+ icon: "\u{1F4DD}",
1483
+ status: getStatus(nameMatch),
1484
+ message: getMessage(getStatus(nameMatch))
1485
+ },
1486
+ {
1487
+ label: "Document Quality",
1488
+ score: docPercent,
1489
+ icon: "\u{1F4C4}",
1490
+ status: getStatus(docPercent),
1491
+ message: getMessage(getStatus(docPercent))
1492
+ },
1493
+ {
1494
+ label: "Selfie Match",
1495
+ score: selfiePercent,
1496
+ icon: "\u{1F933}",
1497
+ status: getStatus(selfiePercent),
1498
+ message: getMessage(getStatus(selfiePercent))
1499
+ }
1500
+ ];
1501
+ return metrics;
1502
+ }
1503
+
1504
+ // src/components/ConsentScreen.tsx
1505
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1506
+ function ConsentScreen({ onAccept, onDecline }) {
1507
+ return /* @__PURE__ */ jsxs2("div", { style: styles.container, children: [
1508
+ /* @__PURE__ */ jsx3(StepProgressBar, { total: 5, current: 1 }),
1509
+ /* @__PURE__ */ jsxs2("div", { style: styles.header, children: [
1510
+ /* @__PURE__ */ jsx3("div", { style: styles.iconContainer, children: /* @__PURE__ */ jsx3(
1511
+ "div",
1512
+ {
1513
+ style: {
1514
+ width: "72px",
1515
+ height: "72px",
1516
+ borderRadius: "20px",
1517
+ background: `linear-gradient(135deg, ${colors.teal}, ${colors.cyan})`,
1518
+ display: "flex",
1519
+ alignItems: "center",
1520
+ justifyContent: "center",
1521
+ margin: "0 auto",
1522
+ fontSize: "32px"
1523
+ },
1524
+ children: "\u{1F6E1}\uFE0F"
1525
+ }
1526
+ ) }),
1527
+ /* @__PURE__ */ jsx3("h1", { style: styles.title, children: "Verify your identity" }),
1528
+ /* @__PURE__ */ jsx3("p", { style: styles.subtitle, children: "We need to confirm your identity to continue" })
1529
+ ] }),
1530
+ /* @__PURE__ */ jsxs2("div", { style: styles.content, children: [
1531
+ /* @__PURE__ */ jsxs2("div", { style: styles.checkList, children: [
1532
+ /* @__PURE__ */ jsx3(
1533
+ ConsentItem,
1534
+ {
1535
+ icon: "\u{1FAAA}",
1536
+ bgColor: colors.infoBg,
1537
+ title: "Government-issued ID",
1538
+ description: "Photo of your passport or front & back of your ID"
1539
+ }
1540
+ ),
1541
+ /* @__PURE__ */ jsx3(
1542
+ ConsentItem,
1543
+ {
1544
+ icon: "\u{1F4F8}",
1545
+ bgColor: colors.successBg,
1546
+ title: "Selfie photo",
1547
+ description: "A quick photo to match your face to your document"
1548
+ }
1549
+ ),
1550
+ /* @__PURE__ */ jsx3(
1551
+ ConsentItem,
1552
+ {
1553
+ icon: "\u2728",
1554
+ bgColor: "#F3E8FF",
1555
+ title: "Liveness check",
1556
+ description: "Follow simple prompts to confirm you're a real person"
1557
+ }
1558
+ )
1559
+ ] }),
1560
+ /* @__PURE__ */ jsx3("div", { style: { marginTop: "24px" }, children: /* @__PURE__ */ jsxs2("p", { style: styles.bodyText, children: [
1561
+ "Your data is encrypted and stored securely. We only use your information for identity verification purposes and in accordance with our",
1562
+ " ",
1563
+ /* @__PURE__ */ jsx3("span", { style: { color: colors.teal, cursor: "pointer" }, children: "privacy policy" }),
1564
+ "."
1565
+ ] }) })
1566
+ ] }),
1567
+ /* @__PURE__ */ jsxs2("div", { style: styles.footer, children: [
1568
+ /* @__PURE__ */ jsxs2("button", { style: styles.primaryButton, onClick: onAccept, children: [
1569
+ "Get started ",
1570
+ /* @__PURE__ */ jsx3("span", { style: { fontSize: "18px" }, children: "\u2192" })
1571
+ ] }),
1572
+ /* @__PURE__ */ jsx3("button", { style: styles.textButton, onClick: onDecline, children: "Decline" })
1573
+ ] })
1574
+ ] });
1575
+ }
1576
+ function ConsentItem({
1577
+ icon,
1578
+ bgColor,
1579
+ title,
1580
+ description
1581
+ }) {
1582
+ return /* @__PURE__ */ jsxs2("div", { style: styles.checklistItem, children: [
1583
+ /* @__PURE__ */ jsx3("div", { style: { ...styles.checklistIconBox, backgroundColor: bgColor }, children: icon }),
1584
+ /* @__PURE__ */ jsxs2("div", { style: styles.checklistTextWrapper, children: [
1585
+ /* @__PURE__ */ jsx3("span", { style: styles.checklistTitle, children: title }),
1586
+ /* @__PURE__ */ jsx3("span", { style: styles.checklistDescription, children: description })
1587
+ ] })
1588
+ ] });
1589
+ }
1590
+
1591
+ // src/components/CountrySelectionScreen.tsx
1592
+ import { useState as useState2, useMemo as useMemo2 } from "react";
1593
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1594
+ function CountrySelectionScreen({ countries, onSelect, onCancel }) {
1595
+ const [selected, setSelected] = useState2(null);
1596
+ const [searchQuery, setSearchQuery] = useState2("");
1597
+ const filteredCountries = useMemo2(() => {
1598
+ const countryList = countries || [];
1599
+ if (!searchQuery.trim()) return countryList;
1600
+ const q = searchQuery.toLowerCase();
1601
+ return countryList.filter((c) => c.name.toLowerCase().includes(q));
1602
+ }, [searchQuery, countries]);
1603
+ return /* @__PURE__ */ jsxs3("div", { style: styles.container, children: [
1604
+ /* @__PURE__ */ jsx4(StepProgressBar, { total: 5, current: 2 }),
1605
+ /* @__PURE__ */ jsxs3("div", { style: styles.screenHeader, children: [
1606
+ /* @__PURE__ */ jsx4("button", { style: styles.backButton, onClick: onCancel, children: "\u2190" }),
1607
+ /* @__PURE__ */ jsx4("h1", { style: styles.screenTitle, children: "Select your country" })
1608
+ ] }),
1609
+ selected && /* @__PURE__ */ jsx4("div", { style: { padding: "0 24px 12px" }, children: /* @__PURE__ */ jsxs3(
1610
+ "div",
1611
+ {
1612
+ style: {
1613
+ ...styles.countryCard,
1614
+ ...styles.countryCardSelected,
1615
+ gridColumn: "1 / -1"
1616
+ },
1617
+ children: [
1618
+ /* @__PURE__ */ jsx4("span", { style: styles.countryFlag, children: selected.flagEmoji }),
1619
+ /* @__PURE__ */ jsx4("span", { style: styles.countryName, children: selected.name }),
1620
+ /* @__PURE__ */ jsx4("span", { style: styles.countryCheck, children: "\u2713" })
1621
+ ]
1622
+ }
1623
+ ) }),
1624
+ /* @__PURE__ */ jsx4("div", { style: { padding: "0 24px" }, children: /* @__PURE__ */ jsxs3("div", { style: styles.searchBar, children: [
1625
+ /* @__PURE__ */ jsx4("span", { style: { color: colors.textTertiary, fontSize: "16px" }, children: "\u{1F50D}" }),
1626
+ /* @__PURE__ */ jsx4(
1627
+ "input",
1628
+ {
1629
+ style: styles.searchInput,
1630
+ placeholder: "Search countries...",
1631
+ value: searchQuery,
1632
+ onChange: (e) => setSearchQuery(e.target.value)
1633
+ }
1634
+ )
1635
+ ] }) }),
1636
+ /* @__PURE__ */ jsx4("div", { style: { ...styles.scrollContent, flex: 1 }, children: /* @__PURE__ */ jsx4("div", { style: styles.countryGrid, children: filteredCountries.filter((c) => c.id !== selected?.id).map((country) => /* @__PURE__ */ jsxs3(
1637
+ "button",
1638
+ {
1639
+ style: styles.countryCard,
1640
+ onClick: () => setSelected(country),
1641
+ children: [
1642
+ /* @__PURE__ */ jsx4("span", { style: styles.countryFlag, children: country.flagEmoji }),
1643
+ /* @__PURE__ */ jsx4("span", { style: styles.countryName, children: country.name })
1644
+ ]
1645
+ },
1646
+ country.id
1647
+ )) }) }),
1648
+ /* @__PURE__ */ jsx4("div", { style: styles.footer, children: /* @__PURE__ */ jsx4(
1649
+ "button",
1650
+ {
1651
+ style: {
1652
+ ...styles.primaryButton,
1653
+ opacity: selected ? 1 : 0.5,
1654
+ cursor: selected ? "pointer" : "not-allowed"
1655
+ },
1656
+ onClick: () => selected && onSelect(selected),
1657
+ disabled: !selected,
1658
+ children: "Continue"
1659
+ }
1660
+ ) })
1661
+ ] });
1662
+ }
1663
+
1664
+ // src/components/DocumentSelectionScreen.tsx
1665
+ import { DocumentType as DocumentType2, getDocumentTypeInfo } from "@koraidv/core";
1666
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1667
+ var defaultDocumentTypes = [
1668
+ DocumentType2.INTERNATIONAL_PASSPORT,
1669
+ DocumentType2.US_DRIVERS_LICENSE
1670
+ ];
1671
+ function DocumentSelectionScreen({
1672
+ documentTypes = defaultDocumentTypes,
1673
+ selectedCountry,
1674
+ onSelect,
1675
+ onCancel
1676
+ }) {
1677
+ const countryDocTypes = selectedCountry?.documentTypes ? selectedCountry.documentTypes : null;
1678
+ const availableTypes = countryDocTypes || documentTypes;
1679
+ const typesToShow = availableTypes.length > 0 ? availableTypes : documentTypes;
1680
+ return /* @__PURE__ */ jsxs4("div", { style: styles.container, children: [
1681
+ /* @__PURE__ */ jsx5(StepProgressBar, { total: 5, current: 2 }),
1682
+ /* @__PURE__ */ jsxs4("div", { style: styles.screenHeader, children: [
1683
+ /* @__PURE__ */ jsx5("button", { style: styles.backButton, onClick: onCancel, children: "\u2190" }),
1684
+ /* @__PURE__ */ jsx5("h1", { style: styles.screenTitle, children: "Choose your document" })
1685
+ ] }),
1686
+ selectedCountry && /* @__PURE__ */ jsx5("div", { style: { padding: "0 24px 16px" }, children: /* @__PURE__ */ jsxs4(
1687
+ "div",
1688
+ {
1689
+ style: {
1690
+ display: "inline-flex",
1691
+ alignItems: "center",
1692
+ gap: "6px",
1693
+ padding: "6px 12px",
1694
+ borderRadius: "20px",
1695
+ backgroundColor: colors.surface,
1696
+ fontSize: "13px",
1697
+ color: colors.textSecondary
1698
+ },
1699
+ children: [
1700
+ /* @__PURE__ */ jsx5("span", { children: selectedCountry.flagEmoji }),
1701
+ /* @__PURE__ */ jsx5("span", { children: selectedCountry.name })
1702
+ ]
1703
+ }
1704
+ ) }),
1705
+ /* @__PURE__ */ jsx5("div", { style: styles.scrollContent, children: typesToShow.map((type) => {
1706
+ const info = getDocumentTypeInfo(type);
1707
+ return /* @__PURE__ */ jsxs4(
1708
+ "button",
1709
+ {
1710
+ style: styles.documentCard,
1711
+ onClick: () => onSelect(type),
1712
+ children: [
1713
+ /* @__PURE__ */ jsx5(
1714
+ "div",
1715
+ {
1716
+ style: {
1717
+ ...styles.documentIconBox,
1718
+ backgroundColor: colors.surface
1719
+ },
1720
+ children: getIcon(type)
1721
+ }
1722
+ ),
1723
+ /* @__PURE__ */ jsxs4("div", { style: styles.documentInfo, children: [
1724
+ /* @__PURE__ */ jsx5("span", { style: styles.documentName, children: info.displayName }),
1725
+ info.requiresBack && /* @__PURE__ */ jsx5("span", { style: styles.documentSubtext, children: "Front and back required" })
1726
+ ] }),
1727
+ /* @__PURE__ */ jsx5("span", { style: styles.documentChevron, children: "\u203A" })
1728
+ ]
1729
+ },
1730
+ type
1731
+ );
1732
+ }) })
1733
+ ] });
1734
+ }
1735
+ function getIcon(type) {
1736
+ if (type === DocumentType2.INTERNATIONAL_PASSPORT) {
1737
+ return "\u{1F4D5}";
1738
+ }
1739
+ if (type === DocumentType2.US_DRIVERS_LICENSE) {
1740
+ return "\u{1F697}";
1741
+ }
1742
+ return "\u{1FAAA}";
1743
+ }
1744
+
1745
+ // src/components/DocumentCaptureScreen.tsx
1746
+ import { useRef, useEffect as useEffect2, useState as useState3, useCallback as useCallback2 } from "react";
1747
+ import { Fragment, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1748
+ var qualityIssueMessages = {
1749
+ face_blurred: "Photo on document is blurry. Retake in better lighting.",
1750
+ low_resolution: "Image quality too low. Move closer to document.",
1751
+ multiple_faces: "Multiple faces detected. Only document should be in frame.",
1752
+ no_face_detected: "No photo detected on document. Ensure front is visible.",
1753
+ low_image_clarity: "Image not clear enough. Hold steady with good lighting.",
1754
+ insufficient_text: "Document not fully in frame. Ensure it's well-lit.",
1755
+ low_ocr_confidence: "Text hard to read. Try better lighting.",
1756
+ face_not_frontal: "Document appears tilted. Place on flat surface."
1757
+ };
1758
+ function DocumentCaptureScreen({
1759
+ side,
1760
+ documentType,
1761
+ requiresBack = true,
1762
+ onQualityCheck,
1763
+ onCapture,
1764
+ onCancel
1765
+ }) {
1766
+ const videoRef = useRef(null);
1767
+ const canvasRef = useRef(null);
1768
+ const [stream, setStream] = useState3(null);
1769
+ const [isCapturing, setIsCapturing] = useState3(false);
1770
+ const [error, setError] = useState3(null);
1771
+ const [capturedImage, setCapturedImage] = useState3(null);
1772
+ const [capturedBlob, setCapturedBlob] = useState3(null);
1773
+ const [qualityResult, setQualityResult] = useState3(null);
1774
+ const [isCheckingQuality, setIsCheckingQuality] = useState3(false);
1775
+ const [retakeCount, setRetakeCount] = useState3(0);
1776
+ useEffect2(() => {
1777
+ injectKeyframes();
1778
+ }, []);
1779
+ useEffect2(() => {
1780
+ let mounted = true;
1781
+ async function startCamera() {
1782
+ try {
1783
+ const mediaStream = await navigator.mediaDevices.getUserMedia({
1784
+ video: { facingMode: "environment", width: { ideal: 1920 }, height: { ideal: 1080 } }
1785
+ });
1786
+ if (mounted) {
1787
+ setStream(mediaStream);
1788
+ if (videoRef.current) {
1789
+ videoRef.current.srcObject = mediaStream;
1790
+ }
1791
+ }
1792
+ } catch {
1793
+ if (mounted) setError("Camera access denied. Please enable camera permissions.");
1794
+ }
1795
+ }
1796
+ if (!capturedImage) startCamera();
1797
+ return () => {
1798
+ mounted = false;
1799
+ };
1800
+ }, [capturedImage]);
1801
+ useEffect2(() => {
1802
+ return () => {
1803
+ stream?.getTracks().forEach((t) => t.stop());
1804
+ };
1805
+ }, [stream]);
1806
+ const handleCapture = useCallback2(() => {
1807
+ if (!videoRef.current || !canvasRef.current || isCapturing) return;
1808
+ setIsCapturing(true);
1809
+ const video = videoRef.current;
1810
+ const canvas = canvasRef.current;
1811
+ const ctx = canvas.getContext("2d");
1812
+ if (!ctx) {
1813
+ setIsCapturing(false);
1814
+ return;
1815
+ }
1816
+ canvas.width = video.videoWidth;
1817
+ canvas.height = video.videoHeight;
1818
+ ctx.drawImage(video, 0, 0);
1819
+ const dataUrl = canvas.toDataURL("image/jpeg", 0.85);
1820
+ canvas.toBlob(
1821
+ (blob) => {
1822
+ if (blob) {
1823
+ setCapturedImage(dataUrl);
1824
+ setCapturedBlob(blob);
1825
+ stream?.getTracks().forEach((t) => t.stop());
1826
+ }
1827
+ setIsCapturing(false);
1828
+ },
1829
+ "image/jpeg",
1830
+ 0.85
1831
+ );
1832
+ }, [isCapturing, stream]);
1833
+ const handleRetake = () => {
1834
+ setCapturedImage(null);
1835
+ setCapturedBlob(null);
1836
+ setQualityResult(null);
1837
+ setRetakeCount((c) => c + 1);
1838
+ };
1839
+ const handleAccept = async () => {
1840
+ if (!capturedBlob) return;
1841
+ if (onQualityCheck && !qualityResult) {
1842
+ setIsCheckingQuality(true);
1843
+ try {
1844
+ const result = await onQualityCheck(capturedBlob);
1845
+ setQualityResult(result);
1846
+ setIsCheckingQuality(false);
1847
+ if (result.qualityScore >= 60) {
1848
+ await onCapture(capturedBlob);
1849
+ }
1850
+ } catch {
1851
+ setIsCheckingQuality(false);
1852
+ await onCapture(capturedBlob);
1853
+ }
1854
+ return;
1855
+ }
1856
+ await onCapture(capturedBlob);
1857
+ };
1858
+ const handleContinueAnyway = async () => {
1859
+ if (capturedBlob) {
1860
+ await onCapture(capturedBlob);
1861
+ }
1862
+ };
1863
+ if (error) {
1864
+ return /* @__PURE__ */ jsx6("div", { style: styles.container, children: /* @__PURE__ */ jsxs5("div", { style: styles.errorContainer, children: [
1865
+ /* @__PURE__ */ jsx6("p", { style: styles.errorText, children: error }),
1866
+ /* @__PURE__ */ jsx6("button", { style: styles.primaryButton, onClick: onCancel, children: "Go Back" })
1867
+ ] }) });
1868
+ }
1869
+ if (capturedImage) {
1870
+ const qualityPassed = qualityResult && qualityResult.qualityScore >= 60;
1871
+ const qualityFailed = qualityResult && qualityResult.qualityScore < 60;
1872
+ const canContinueAnyway = qualityFailed && retakeCount >= 2;
1873
+ return /* @__PURE__ */ jsxs5("div", { style: styles.darkContainer, children: [
1874
+ /* @__PURE__ */ jsx6(StepProgressBar, { total: 5, current: 3, isDark: true }),
1875
+ /* @__PURE__ */ jsxs5("div", { style: styles.darkScreenHeader, children: [
1876
+ /* @__PURE__ */ jsx6("div", { style: { width: 40 } }),
1877
+ /* @__PURE__ */ jsx6("h1", { style: styles.darkScreenTitle, children: "Review your photo" }),
1878
+ /* @__PURE__ */ jsx6("button", { style: styles.glassCloseButton, onClick: onCancel, children: "\u2715" })
1879
+ ] }),
1880
+ /* @__PURE__ */ jsx6("div", { style: { flex: 1, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", padding: "24px" }, children: /* @__PURE__ */ jsxs5("div", { style: styles.reviewCard, children: [
1881
+ /* @__PURE__ */ jsx6(
1882
+ "img",
1883
+ {
1884
+ src: capturedImage,
1885
+ alt: "Captured document",
1886
+ style: { width: "100%", maxWidth: "300px", borderRadius: "16px", display: "block", margin: "0 auto" }
1887
+ }
1888
+ ),
1889
+ isCheckingQuality && /* @__PURE__ */ jsx6("div", { style: { textAlign: "center", marginTop: "16px" }, children: /* @__PURE__ */ jsx6("span", { style: { ...styles.reviewBadge, backgroundColor: "rgba(255,255,255,0.1)" }, children: "Checking quality..." }) }),
1890
+ qualityPassed && /* @__PURE__ */ jsxs5(Fragment, { children: [
1891
+ /* @__PURE__ */ jsx6("div", { style: { textAlign: "center", marginTop: "16px" }, children: /* @__PURE__ */ jsxs5("span", { style: styles.reviewBadge, children: [
1892
+ "\u2713 Quality score: ",
1893
+ Math.round(qualityResult.qualityScore),
1894
+ "%"
1895
+ ] }) }),
1896
+ /* @__PURE__ */ jsxs5("div", { style: styles.qualityChecks, children: [
1897
+ /* @__PURE__ */ jsx6(QualityCheck, { label: "Sharp" }),
1898
+ /* @__PURE__ */ jsx6(QualityCheck, { label: "Well-lit" }),
1899
+ /* @__PURE__ */ jsx6(QualityCheck, { label: "Readable" })
1900
+ ] })
1901
+ ] }),
1902
+ qualityFailed && /* @__PURE__ */ jsxs5(Fragment, { children: [
1903
+ /* @__PURE__ */ jsx6("div", { style: { textAlign: "center", marginTop: "16px" }, children: /* @__PURE__ */ jsxs5("span", { style: { ...styles.reviewBadge, backgroundColor: "rgba(239,68,68,0.15)", color: "#ef4444" }, children: [
1904
+ "\u26A0 Quality score: ",
1905
+ Math.round(qualityResult.qualityScore),
1906
+ "%"
1907
+ ] }) }),
1908
+ /* @__PURE__ */ jsx6("div", { style: { padding: "12px 0" }, children: qualityResult.qualityIssues.map((issue, i) => /* @__PURE__ */ jsx6("p", { style: { color: "rgba(255,255,255,0.7)", fontSize: "13px", margin: "4px 0", textAlign: "center" }, children: qualityIssueMessages[issue] || issue }, i)) })
1909
+ ] }),
1910
+ !qualityResult && !isCheckingQuality && /* @__PURE__ */ jsxs5(Fragment, { children: [
1911
+ /* @__PURE__ */ jsx6("div", { style: { textAlign: "center", marginTop: "16px" }, children: /* @__PURE__ */ jsx6("span", { style: styles.reviewBadge, children: "\u2713 Good quality" }) }),
1912
+ /* @__PURE__ */ jsxs5("div", { style: styles.qualityChecks, children: [
1913
+ /* @__PURE__ */ jsx6(QualityCheck, { label: "Sharp" }),
1914
+ /* @__PURE__ */ jsx6(QualityCheck, { label: "Well-lit" }),
1915
+ /* @__PURE__ */ jsx6(QualityCheck, { label: "No glare" })
1916
+ ] })
1917
+ ] })
1918
+ ] }) }),
1919
+ /* @__PURE__ */ jsx6("div", { style: styles.reviewButtonsRow, children: qualityFailed ? /* @__PURE__ */ jsxs5(Fragment, { children: [
1920
+ /* @__PURE__ */ jsx6("button", { style: { ...styles.darkOutlineButton, flex: 1 }, onClick: handleRetake, children: "Retake" }),
1921
+ canContinueAnyway && /* @__PURE__ */ jsx6("button", { style: { ...styles.primaryButton, flex: 1 }, onClick: handleContinueAnyway, children: "Continue anyway" })
1922
+ ] }) : /* @__PURE__ */ jsxs5(Fragment, { children: [
1923
+ /* @__PURE__ */ jsx6("button", { style: { ...styles.darkOutlineButton, flex: 1 }, onClick: handleRetake, children: "Retake" }),
1924
+ /* @__PURE__ */ jsx6(
1925
+ "button",
1926
+ {
1927
+ style: { ...styles.primaryButton, flex: 1, opacity: isCheckingQuality ? 0.5 : 1 },
1928
+ onClick: handleAccept,
1929
+ disabled: isCheckingQuality,
1930
+ children: "Looks good"
1931
+ }
1932
+ )
1933
+ ] }) })
1934
+ ] });
1935
+ }
1936
+ return /* @__PURE__ */ jsxs5("div", { style: styles.captureContainer, children: [
1937
+ /* @__PURE__ */ jsx6(StepProgressBar, { total: 5, current: 3, isDark: true }),
1938
+ /* @__PURE__ */ jsxs5("div", { style: styles.darkScreenHeader, children: [
1939
+ /* @__PURE__ */ jsx6("div", { style: { width: 40 } }),
1940
+ /* @__PURE__ */ jsxs5("div", { style: { flex: 1, textAlign: "center" }, children: [
1941
+ /* @__PURE__ */ jsx6("h1", { style: { ...styles.darkScreenTitle, margin: 0 }, children: side === "front" ? "Front of ID" : "Back of ID" }),
1942
+ documentType && /* @__PURE__ */ jsx6("p", { style: styles.darkScreenSubtitle, children: documentType })
1943
+ ] }),
1944
+ /* @__PURE__ */ jsx6("button", { style: styles.glassCloseButton, onClick: onCancel, children: "\u2715" })
1945
+ ] }),
1946
+ /* @__PURE__ */ jsxs5("div", { style: styles.cameraContainer, children: [
1947
+ /* @__PURE__ */ jsx6("video", { ref: videoRef, autoPlay: true, playsInline: true, muted: true, style: styles.cameraVideo }),
1948
+ /* @__PURE__ */ jsx6("div", { style: styles.documentOverlay, children: /* @__PURE__ */ jsxs5("div", { style: styles.documentFrame, children: [
1949
+ /* @__PURE__ */ jsx6("div", { style: { ...styles.corner, top: 0, left: 0 } }),
1950
+ /* @__PURE__ */ jsx6("div", { style: { ...styles.corner, top: 0, right: 0, transform: "rotate(90deg)" } }),
1951
+ /* @__PURE__ */ jsx6("div", { style: { ...styles.corner, bottom: 0, right: 0, transform: "rotate(180deg)" } }),
1952
+ /* @__PURE__ */ jsx6("div", { style: { ...styles.corner, bottom: 0, left: 0, transform: "rotate(270deg)" } }),
1953
+ /* @__PURE__ */ jsx6("div", { style: styles.scanLine })
1954
+ ] }) }),
1955
+ /* @__PURE__ */ jsx6("canvas", { ref: canvasRef, style: { display: "none" } })
1956
+ ] }),
1957
+ /* @__PURE__ */ jsxs5("div", { style: styles.stepPillsRow, children: [
1958
+ /* @__PURE__ */ jsx6(
1959
+ "span",
1960
+ {
1961
+ style: {
1962
+ ...styles.stepPill,
1963
+ backgroundColor: side === "front" ? colors.teal : "rgba(255,255,255,0.15)",
1964
+ color: side === "front" ? colors.white : "rgba(255,255,255,0.5)"
1965
+ },
1966
+ children: "Front"
1967
+ }
1968
+ ),
1969
+ requiresBack && /* @__PURE__ */ jsx6(
1970
+ "span",
1971
+ {
1972
+ style: {
1973
+ ...styles.stepPill,
1974
+ backgroundColor: side === "back" ? colors.teal : "rgba(255,255,255,0.15)",
1975
+ color: side === "back" ? colors.white : "rgba(255,255,255,0.5)"
1976
+ },
1977
+ children: "Back"
1978
+ }
1979
+ )
1980
+ ] }),
1981
+ /* @__PURE__ */ jsx6("div", { style: { textAlign: "center", padding: "8px 0" }, children: /* @__PURE__ */ jsxs5(
1982
+ "span",
1983
+ {
1984
+ style: {
1985
+ ...styles.guidancePill,
1986
+ backgroundColor: "rgba(13,148,136,0.15)",
1987
+ color: colors.teal
1988
+ },
1989
+ children: [
1990
+ /* @__PURE__ */ jsx6("span", { style: { ...styles.pulsingDot, backgroundColor: colors.teal } }),
1991
+ "Scanning document..."
1992
+ ]
1993
+ }
1994
+ ) }),
1995
+ /* @__PURE__ */ jsx6("div", { style: styles.captureFooter, children: /* @__PURE__ */ jsx6(
1996
+ "button",
1997
+ {
1998
+ style: { ...styles.captureButton, opacity: isCapturing ? 0.5 : 1 },
1999
+ onClick: handleCapture,
2000
+ disabled: isCapturing,
2001
+ children: /* @__PURE__ */ jsx6("div", { style: styles.captureButtonInner })
2002
+ }
2003
+ ) })
2004
+ ] });
2005
+ }
2006
+ function QualityCheck({ label }) {
2007
+ return /* @__PURE__ */ jsxs5("div", { style: styles.qualityCheck, children: [
2008
+ /* @__PURE__ */ jsx6("div", { style: styles.qualityCheckIcon, children: "\u2713" }),
2009
+ /* @__PURE__ */ jsx6("span", { style: styles.qualityCheckLabel, children: label })
2010
+ ] });
2011
+ }
2012
+
2013
+ // src/components/FlipDocumentScreen.tsx
2014
+ import { useEffect as useEffect3 } from "react";
2015
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
2016
+ function FlipDocumentScreen({ onContinue, onCancel }) {
2017
+ useEffect3(() => {
2018
+ injectKeyframes();
2019
+ }, []);
2020
+ return /* @__PURE__ */ jsxs6("div", { style: styles.darkContainer, children: [
2021
+ /* @__PURE__ */ jsx7(StepProgressBar, { total: 5, current: 3, isDark: true }),
2022
+ /* @__PURE__ */ jsxs6("div", { style: styles.darkScreenHeader, children: [
2023
+ /* @__PURE__ */ jsx7("div", { style: { width: 40 } }),
2024
+ /* @__PURE__ */ jsx7("h1", { style: styles.darkScreenTitle, children: "Flip your document" }),
2025
+ /* @__PURE__ */ jsx7("button", { style: styles.glassCloseButton, onClick: onCancel, children: "\u2715" })
2026
+ ] }),
2027
+ /* @__PURE__ */ jsxs6("div", { style: {
2028
+ flex: 1,
2029
+ display: "flex",
2030
+ flexDirection: "column",
2031
+ alignItems: "center",
2032
+ justifyContent: "center",
2033
+ padding: "24px",
2034
+ gap: "32px"
2035
+ }, children: [
2036
+ /* @__PURE__ */ jsx7("div", { style: {
2037
+ width: "120px",
2038
+ height: "120px",
2039
+ borderRadius: "50%",
2040
+ backgroundColor: "rgba(13,148,136,0.15)",
2041
+ display: "flex",
2042
+ alignItems: "center",
2043
+ justifyContent: "center"
2044
+ }, children: /* @__PURE__ */ jsxs6("svg", { width: "56", height: "56", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
2045
+ /* @__PURE__ */ jsx7("path", { d: "M9 3L5 6.99H8V14H10V6.99H13L9 3Z", fill: colors.teal }),
2046
+ /* @__PURE__ */ jsx7("path", { d: "M16 17.01V10H14V17.01H11L15 21L19 17.01H16Z", fill: colors.teal })
2047
+ ] }) }),
2048
+ /* @__PURE__ */ jsxs6("div", { style: { textAlign: "center" }, children: [
2049
+ /* @__PURE__ */ jsx7("h2", { style: {
2050
+ fontSize: "22px",
2051
+ fontWeight: 700,
2052
+ color: colors.white,
2053
+ margin: "0 0 12px 0"
2054
+ }, children: "Now capture the back" }),
2055
+ /* @__PURE__ */ jsx7("p", { style: {
2056
+ fontSize: "15px",
2057
+ color: "rgba(255,255,255,0.6)",
2058
+ margin: 0,
2059
+ lineHeight: 1.6,
2060
+ maxWidth: "280px"
2061
+ }, children: "Turn your document over to the back side, then tap continue to take a photo." })
2062
+ ] }),
2063
+ /* @__PURE__ */ jsxs6("div", { style: styles.stepPillsRow, children: [
2064
+ /* @__PURE__ */ jsx7("span", { style: {
2065
+ ...styles.stepPill,
2066
+ backgroundColor: "rgba(16,185,129,0.15)",
2067
+ color: colors.success
2068
+ }, children: "\u2713 Front" }),
2069
+ /* @__PURE__ */ jsx7("span", { style: {
2070
+ ...styles.stepPill,
2071
+ backgroundColor: colors.teal,
2072
+ color: colors.white
2073
+ }, children: "Back" })
2074
+ ] })
2075
+ ] }),
2076
+ /* @__PURE__ */ jsx7("div", { style: { padding: "24px" }, children: /* @__PURE__ */ jsx7("button", { style: styles.primaryButton, onClick: onContinue, children: "Continue" }) })
2077
+ ] });
2078
+ }
2079
+
2080
+ // src/components/SelfieCaptureScreen.tsx
2081
+ import { useRef as useRef2, useEffect as useEffect4, useState as useState4, useCallback as useCallback3 } from "react";
2082
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2083
+ function SelfieCaptureScreen({ onCapture, onCancel }) {
2084
+ const videoRef = useRef2(null);
2085
+ const canvasRef = useRef2(null);
2086
+ const [stream, setStream] = useState4(null);
2087
+ const [isCapturing, setIsCapturing] = useState4(false);
2088
+ const [error, setError] = useState4(null);
2089
+ const [capturedImage, setCapturedImage] = useState4(null);
2090
+ const [capturedBlob, setCapturedBlob] = useState4(null);
2091
+ useEffect4(() => {
2092
+ injectKeyframes();
2093
+ }, []);
2094
+ useEffect4(() => {
2095
+ let mounted = true;
2096
+ async function startCamera() {
2097
+ try {
2098
+ const mediaStream = await navigator.mediaDevices.getUserMedia({
2099
+ video: { facingMode: "user", width: { ideal: 1280 }, height: { ideal: 720 } }
2100
+ });
2101
+ if (mounted) {
2102
+ setStream(mediaStream);
2103
+ if (videoRef.current) {
2104
+ videoRef.current.srcObject = mediaStream;
2105
+ }
2106
+ }
2107
+ } catch {
2108
+ if (mounted) setError("Camera access denied. Please enable camera permissions.");
2109
+ }
2110
+ }
2111
+ if (!capturedImage) startCamera();
2112
+ return () => {
2113
+ mounted = false;
2114
+ };
2115
+ }, [capturedImage]);
2116
+ useEffect4(() => {
2117
+ return () => {
2118
+ stream?.getTracks().forEach((t) => t.stop());
2119
+ };
2120
+ }, [stream]);
2121
+ const handleCapture = useCallback3(() => {
2122
+ if (!videoRef.current || !canvasRef.current || isCapturing) return;
2123
+ setIsCapturing(true);
2124
+ const video = videoRef.current;
2125
+ const canvas = canvasRef.current;
2126
+ const ctx = canvas.getContext("2d");
2127
+ if (!ctx) {
2128
+ setIsCapturing(false);
2129
+ return;
2130
+ }
2131
+ canvas.width = video.videoWidth;
2132
+ canvas.height = video.videoHeight;
2133
+ ctx.translate(canvas.width, 0);
2134
+ ctx.scale(-1, 1);
2135
+ ctx.drawImage(video, 0, 0);
2136
+ const dataUrl = canvas.toDataURL("image/jpeg", 0.85);
2137
+ canvas.toBlob(
2138
+ (blob) => {
2139
+ if (blob) {
2140
+ setCapturedImage(dataUrl);
2141
+ setCapturedBlob(blob);
2142
+ stream?.getTracks().forEach((t) => t.stop());
2143
+ }
2144
+ setIsCapturing(false);
2145
+ },
2146
+ "image/jpeg",
2147
+ 0.85
2148
+ );
2149
+ }, [isCapturing, stream]);
2150
+ const handleRetake = () => {
2151
+ setCapturedImage(null);
2152
+ setCapturedBlob(null);
2153
+ };
2154
+ const handleAccept = async () => {
2155
+ if (capturedBlob) {
2156
+ await onCapture(capturedBlob);
2157
+ }
2158
+ };
2159
+ if (error) {
2160
+ return /* @__PURE__ */ jsx8("div", { style: styles.container, children: /* @__PURE__ */ jsxs7("div", { style: styles.errorContainer, children: [
2161
+ /* @__PURE__ */ jsx8("p", { style: styles.errorText, children: error }),
2162
+ /* @__PURE__ */ jsx8("button", { style: styles.primaryButton, onClick: onCancel, children: "Go Back" })
2163
+ ] }) });
2164
+ }
2165
+ if (capturedImage) {
2166
+ return /* @__PURE__ */ jsxs7("div", { style: styles.darkContainer, children: [
2167
+ /* @__PURE__ */ jsx8(StepProgressBar, { total: 5, current: 4, isDark: true }),
2168
+ /* @__PURE__ */ jsxs7("div", { style: styles.darkScreenHeader, children: [
2169
+ /* @__PURE__ */ jsx8("div", { style: { width: 40 } }),
2170
+ /* @__PURE__ */ jsx8("h1", { style: styles.darkScreenTitle, children: "Does this look like you?" }),
2171
+ /* @__PURE__ */ jsx8("button", { style: styles.glassCloseButton, onClick: onCancel, children: "\u2715" })
2172
+ ] }),
2173
+ /* @__PURE__ */ jsx8("p", { style: styles.darkScreenSubtitle, children: "Check clarity and lighting" }),
2174
+ /* @__PURE__ */ jsx8("div", { style: { flex: 1, display: "flex", alignItems: "center", justifyContent: "center", padding: "24px" }, children: /* @__PURE__ */ jsxs7("div", { style: { position: "relative" }, children: [
2175
+ /* @__PURE__ */ jsx8("div", { style: { width: "240px", height: "300px", borderRadius: "50%", overflow: "hidden", border: `3px solid ${colors.teal}` }, children: /* @__PURE__ */ jsx8(
2176
+ "img",
2177
+ {
2178
+ src: capturedImage,
2179
+ alt: "Selfie",
2180
+ style: { width: "100%", height: "100%", objectFit: "cover" }
2181
+ }
2182
+ ) }),
2183
+ /* @__PURE__ */ jsx8("div", { style: { textAlign: "center", marginTop: "16px" }, children: /* @__PURE__ */ jsx8("span", { style: styles.reviewBadge, children: "\u2713 Face detected" }) }),
2184
+ /* @__PURE__ */ jsxs7("div", { style: styles.qualityChecks, children: [
2185
+ /* @__PURE__ */ jsx8(QualityCheck2, { label: "Clear" }),
2186
+ /* @__PURE__ */ jsx8(QualityCheck2, { label: "Centered" }),
2187
+ /* @__PURE__ */ jsx8(QualityCheck2, { label: "Well-lit" })
2188
+ ] })
2189
+ ] }) }),
2190
+ /* @__PURE__ */ jsxs7("div", { style: styles.reviewButtonsRow, children: [
2191
+ /* @__PURE__ */ jsx8("button", { style: { ...styles.darkOutlineButton, flex: 1 }, onClick: handleRetake, children: "Retake" }),
2192
+ /* @__PURE__ */ jsx8("button", { style: { ...styles.primaryButton, flex: 1 }, onClick: handleAccept, children: "Use this" })
2193
+ ] })
2194
+ ] });
2195
+ }
2196
+ return /* @__PURE__ */ jsxs7("div", { style: styles.captureContainer, children: [
2197
+ /* @__PURE__ */ jsx8(StepProgressBar, { total: 5, current: 4, isDark: true }),
2198
+ /* @__PURE__ */ jsxs7("div", { style: styles.darkScreenHeader, children: [
2199
+ /* @__PURE__ */ jsx8("div", { style: { width: 40 } }),
2200
+ /* @__PURE__ */ jsxs7("div", { style: { flex: 1, textAlign: "center" }, children: [
2201
+ /* @__PURE__ */ jsx8("h1", { style: { ...styles.darkScreenTitle, margin: 0, fontSize: "24px", fontWeight: 700 }, children: "Face the camera" }),
2202
+ /* @__PURE__ */ jsx8("p", { style: styles.darkScreenSubtitle, children: "Keep a neutral expression" })
2203
+ ] }),
2204
+ /* @__PURE__ */ jsx8("button", { style: styles.glassCloseButton, onClick: onCancel, children: "\u2715" })
2205
+ ] }),
2206
+ /* @__PURE__ */ jsxs7("div", { style: styles.cameraContainer, children: [
2207
+ /* @__PURE__ */ jsx8(
2208
+ "video",
2209
+ {
2210
+ ref: videoRef,
2211
+ autoPlay: true,
2212
+ playsInline: true,
2213
+ muted: true,
2214
+ style: { ...styles.cameraVideo, transform: "scaleX(-1)" }
2215
+ }
2216
+ ),
2217
+ /* @__PURE__ */ jsx8("div", { style: styles.selfieOverlay, children: /* @__PURE__ */ jsx8("div", { style: styles.faceGuide, children: /* @__PURE__ */ jsx8("div", { style: styles.rotatingRing }) }) }),
2218
+ /* @__PURE__ */ jsx8("canvas", { ref: canvasRef, style: { display: "none" } })
2219
+ ] }),
2220
+ /* @__PURE__ */ jsx8("div", { style: { textAlign: "center", padding: "8px 0" }, children: /* @__PURE__ */ jsxs7(
2221
+ "span",
2222
+ {
2223
+ style: {
2224
+ ...styles.guidancePill,
2225
+ backgroundColor: "rgba(13,148,136,0.15)",
2226
+ color: colors.teal
2227
+ },
2228
+ children: [
2229
+ /* @__PURE__ */ jsx8("span", { style: { ...styles.pulsingDot, backgroundColor: colors.teal } }),
2230
+ "Position your face in the oval"
2231
+ ]
2232
+ }
2233
+ ) }),
2234
+ /* @__PURE__ */ jsx8("div", { style: styles.captureFooter, children: /* @__PURE__ */ jsx8(
2235
+ "button",
2236
+ {
2237
+ style: { ...styles.captureButton, opacity: isCapturing ? 0.5 : 1 },
2238
+ onClick: handleCapture,
2239
+ disabled: isCapturing,
2240
+ children: /* @__PURE__ */ jsx8("div", { style: styles.captureButtonInner })
2241
+ }
2242
+ ) })
2243
+ ] });
2244
+ }
2245
+ function QualityCheck2({ label }) {
2246
+ return /* @__PURE__ */ jsxs7("div", { style: styles.qualityCheck, children: [
2247
+ /* @__PURE__ */ jsx8("div", { style: styles.qualityCheckIcon, children: "\u2713" }),
2248
+ /* @__PURE__ */ jsx8("span", { style: styles.qualityCheckLabel, children: label })
2249
+ ] });
2250
+ }
2251
+
2252
+ // src/components/LivenessScreen.tsx
2253
+ import { useEffect as useEffect5, useState as useState5 } from "react";
2254
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
2255
+ function LivenessScreen({
2256
+ session,
2257
+ currentChallenge,
2258
+ completedChallenges,
2259
+ onChallengeComplete,
2260
+ onStart,
2261
+ onComplete,
2262
+ onCancel
2263
+ }) {
2264
+ const [countdown, setCountdown] = useState5(3);
2265
+ useEffect5(() => {
2266
+ injectKeyframes();
2267
+ }, []);
2268
+ useEffect5(() => {
2269
+ if (!session) onStart();
2270
+ }, [session, onStart]);
2271
+ useEffect5(() => {
2272
+ if (session && !currentChallenge && completedChallenges > 0) {
2273
+ onComplete();
2274
+ }
2275
+ }, [session, currentChallenge, completedChallenges, onComplete]);
2276
+ useEffect5(() => {
2277
+ if (!currentChallenge) return;
2278
+ setCountdown(3);
2279
+ const interval = setInterval(() => {
2280
+ setCountdown((c) => {
2281
+ if (c <= 1) {
2282
+ clearInterval(interval);
2283
+ return 0;
2284
+ }
2285
+ return c - 1;
2286
+ });
2287
+ }, 1e3);
2288
+ return () => clearInterval(interval);
2289
+ }, [currentChallenge?.id]);
2290
+ if (!session) {
2291
+ return /* @__PURE__ */ jsx9("div", { style: styles.darkContainer, children: /* @__PURE__ */ jsxs8("div", { style: styles.loadingContainer, children: [
2292
+ /* @__PURE__ */ jsx9("div", { style: styles.spinner }),
2293
+ /* @__PURE__ */ jsx9("p", { style: { ...styles.loadingText, color: "rgba(255,255,255,0.6)" }, children: "Starting liveness check..." })
2294
+ ] }) });
2295
+ }
2296
+ const totalChallenges = session.challenges.length;
2297
+ return /* @__PURE__ */ jsxs8("div", { style: styles.captureContainer, children: [
2298
+ /* @__PURE__ */ jsx9(StepProgressBar, { total: 5, current: 5, isDark: true }),
2299
+ /* @__PURE__ */ jsxs8("div", { style: styles.darkScreenHeader, children: [
2300
+ /* @__PURE__ */ jsx9("div", { style: { width: 40 } }),
2301
+ /* @__PURE__ */ jsx9("h1", { style: styles.darkScreenTitle, children: "Liveness Check" }),
2302
+ /* @__PURE__ */ jsx9("button", { style: styles.glassCloseButton, onClick: onCancel, children: "\u2715" })
2303
+ ] }),
2304
+ currentChallenge && /* @__PURE__ */ jsx9("div", { style: { padding: "16px 0" }, children: /* @__PURE__ */ jsx9("h2", { style: styles.challengeTitle, children: currentChallenge.instruction }) }),
2305
+ /* @__PURE__ */ jsx9("div", { style: { flex: 1, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsxs8("div", { style: { position: "relative" }, children: [
2306
+ /* @__PURE__ */ jsx9("div", { style: styles.faceGuide, children: /* @__PURE__ */ jsx9(
2307
+ "svg",
2308
+ {
2309
+ style: { position: "absolute", top: "-8px", left: "-8px" },
2310
+ width: "256",
2311
+ height: "316",
2312
+ viewBox: "0 0 256 316",
2313
+ children: /* @__PURE__ */ jsx9(
2314
+ "ellipse",
2315
+ {
2316
+ cx: "128",
2317
+ cy: "158",
2318
+ rx: "124",
2319
+ ry: "154",
2320
+ fill: "none",
2321
+ stroke: colors.teal,
2322
+ strokeWidth: "5",
2323
+ strokeDasharray: `${completedChallenges / totalChallenges * 880} 880`,
2324
+ transform: "rotate(-90 128 158)",
2325
+ strokeLinecap: "round"
2326
+ }
2327
+ )
2328
+ }
2329
+ ) }),
2330
+ countdown > 0 && /* @__PURE__ */ jsx9("div", { style: styles.countdownBadge, children: countdown })
2331
+ ] }) }),
2332
+ /* @__PURE__ */ jsxs8("div", { style: { padding: "16px 0" }, children: [
2333
+ /* @__PURE__ */ jsx9("div", { style: styles.progressDots, children: session.challenges.map((_, index) => /* @__PURE__ */ jsx9(
2334
+ "div",
2335
+ {
2336
+ style: {
2337
+ ...styles.progressDot,
2338
+ backgroundColor: index < completedChallenges ? colors.success : index === completedChallenges ? colors.teal : "rgba(255,255,255,0.15)"
2339
+ }
2340
+ },
2341
+ index
2342
+ )) }),
2343
+ /* @__PURE__ */ jsxs8("p", { style: styles.progressText, children: [
2344
+ "Challenge ",
2345
+ completedChallenges + 1,
2346
+ " of ",
2347
+ totalChallenges
2348
+ ] })
2349
+ ] }),
2350
+ /* @__PURE__ */ jsx9("div", { style: { padding: "16px 24px 32px" }, children: /* @__PURE__ */ jsx9(
2351
+ "button",
2352
+ {
2353
+ style: styles.primaryButton,
2354
+ onClick: async () => {
2355
+ const canvas = document.createElement("canvas");
2356
+ canvas.width = 100;
2357
+ canvas.height = 100;
2358
+ canvas.toBlob(async (blob) => {
2359
+ if (blob) await onChallengeComplete(blob);
2360
+ });
2361
+ },
2362
+ children: "Complete Challenge"
2363
+ }
2364
+ ) })
2365
+ ] });
2366
+ }
2367
+
2368
+ // src/components/ResultScreen.tsx
2369
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
2370
+ function ResultScreen({ verification, onDone, onRetry, resultPageMode, simplified, customMessages }) {
2371
+ const { status } = verification;
2372
+ const effectiveMode = resultPageMode ?? (simplified ? "simplified" : "detailed");
2373
+ if (effectiveMode === "simplified") {
2374
+ switch (status) {
2375
+ case "approved":
2376
+ return /* @__PURE__ */ jsx10(SimplifiedSuccess, { onDone, customMessages });
2377
+ case "rejected":
2378
+ return /* @__PURE__ */ jsx10(SimplifiedFailed, { onRetry: onRetry || onDone, customMessages });
2379
+ case "review_required":
2380
+ return /* @__PURE__ */ jsx10(SimplifiedReview, { verification, onDone, customMessages });
2381
+ case "expired":
2382
+ return /* @__PURE__ */ jsx10(SimplifiedFailed, { onRetry: onRetry || onDone, customMessages: { failedTitle: "Document Expired", failedMessage: "The document you submitted has expired. Please use a valid document." } });
2383
+ default:
2384
+ return /* @__PURE__ */ jsx10(SimplifiedSuccess, { onDone, customMessages });
2385
+ }
2386
+ }
2387
+ switch (status) {
2388
+ case "approved":
2389
+ return /* @__PURE__ */ jsx10(SuccessResult, { verification, onDone });
2390
+ case "rejected":
2391
+ return /* @__PURE__ */ jsx10(RejectedResult, { verification, onRetry: onRetry || onDone });
2392
+ case "expired":
2393
+ return /* @__PURE__ */ jsx10(ExpiredResult, { verification, onRetry: onRetry || onDone });
2394
+ case "review_required":
2395
+ return /* @__PURE__ */ jsx10(ManualReviewResult, { verification, onDone });
2396
+ default:
2397
+ return /* @__PURE__ */ jsx10(SuccessResult, { verification, onDone });
2398
+ }
2399
+ }
2400
+ function SuccessResult({ verification, onDone }) {
2401
+ const score = verification.riskScore ?? 84;
2402
+ const metrics = computeScoreBreakdown(verification);
2403
+ return /* @__PURE__ */ jsxs9("div", { style: styles.resultContainer, children: [
2404
+ /* @__PURE__ */ jsxs9("div", { style: styles.resultContent, children: [
2405
+ /* @__PURE__ */ jsx10(
2406
+ "div",
2407
+ {
2408
+ style: {
2409
+ ...styles.resultIconOuterRing,
2410
+ backgroundColor: `${colors.success}15`
2411
+ },
2412
+ children: /* @__PURE__ */ jsx10(
2413
+ "div",
2414
+ {
2415
+ style: {
2416
+ ...styles.resultIconCircle,
2417
+ background: `linear-gradient(135deg, ${colors.success}, #059669)`,
2418
+ color: colors.white,
2419
+ margin: 0
2420
+ },
2421
+ children: "\u2713"
2422
+ }
2423
+ )
2424
+ }
2425
+ ),
2426
+ /* @__PURE__ */ jsx10("h1", { style: styles.resultTitle, children: "Verification approved" }),
2427
+ /* @__PURE__ */ jsx10("p", { style: styles.resultSubtitle, children: "Your identity has been successfully verified." }),
2428
+ /* @__PURE__ */ jsx10(
2429
+ ScoreCard,
2430
+ {
2431
+ score,
2432
+ badge: "PASSED",
2433
+ gradient: `linear-gradient(135deg, ${colors.teal}, ${colors.tealDark})`
2434
+ }
2435
+ ),
2436
+ metrics.map((m, i) => /* @__PURE__ */ jsx10(ScoreMetricRow, { ...m }, i))
2437
+ ] }),
2438
+ /* @__PURE__ */ jsx10("div", { style: styles.footer, children: /* @__PURE__ */ jsx10("button", { style: styles.primaryButton, onClick: onDone, children: "Done" }) })
2439
+ ] });
2440
+ }
2441
+ function RejectedResult({ verification, onRetry }) {
2442
+ const score = verification.riskScore ?? 42;
2443
+ const metrics = computeScoreBreakdown(verification);
2444
+ return /* @__PURE__ */ jsxs9("div", { style: styles.resultContainer, children: [
2445
+ /* @__PURE__ */ jsxs9("div", { style: styles.resultContent, children: [
2446
+ /* @__PURE__ */ jsx10(
2447
+ "div",
2448
+ {
2449
+ style: {
2450
+ ...styles.resultIconOuterRing,
2451
+ backgroundColor: `${colors.error}15`
2452
+ },
2453
+ children: /* @__PURE__ */ jsx10(
2454
+ "div",
2455
+ {
2456
+ style: {
2457
+ ...styles.resultIconCircle,
2458
+ background: `linear-gradient(135deg, ${colors.error}, #B91C1C)`,
2459
+ color: colors.white,
2460
+ margin: 0
2461
+ },
2462
+ children: "\u2715"
2463
+ }
2464
+ )
2465
+ }
2466
+ ),
2467
+ /* @__PURE__ */ jsx10("h1", { style: styles.resultTitle, children: "Verification rejected" }),
2468
+ /* @__PURE__ */ jsx10("p", { style: styles.resultSubtitle, children: "We could not verify your identity. Please try again with a valid document." }),
2469
+ /* @__PURE__ */ jsx10(
2470
+ ScoreCard,
2471
+ {
2472
+ score,
2473
+ badge: "REJECTED",
2474
+ gradient: `linear-gradient(135deg, ${colors.error}, #B91C1C)`
2475
+ }
2476
+ ),
2477
+ metrics.map((m, i) => /* @__PURE__ */ jsx10(ScoreMetricRow, { ...m }, i))
2478
+ ] }),
2479
+ /* @__PURE__ */ jsx10("div", { style: styles.footer, children: /* @__PURE__ */ jsx10("button", { style: styles.primaryButton, onClick: onRetry, children: "Try again" }) })
2480
+ ] });
2481
+ }
2482
+ function ExpiredResult({ verification, onRetry }) {
2483
+ return /* @__PURE__ */ jsxs9("div", { style: styles.resultContainer, children: [
2484
+ /* @__PURE__ */ jsxs9("div", { style: styles.resultContent, children: [
2485
+ /* @__PURE__ */ jsx10(
2486
+ "div",
2487
+ {
2488
+ style: {
2489
+ ...styles.resultIconOuterRing,
2490
+ backgroundColor: `${colors.warning}15`,
2491
+ border: `2px solid ${colors.warning}30`
2492
+ },
2493
+ children: /* @__PURE__ */ jsx10(
2494
+ "div",
2495
+ {
2496
+ style: {
2497
+ ...styles.resultIconCircle,
2498
+ background: `linear-gradient(135deg, ${colors.warning}, #B45309)`,
2499
+ color: colors.white,
2500
+ margin: 0
2501
+ },
2502
+ children: "\u26A0"
2503
+ }
2504
+ )
2505
+ }
2506
+ ),
2507
+ /* @__PURE__ */ jsx10("h1", { style: styles.resultTitle, children: "Document expired" }),
2508
+ /* @__PURE__ */ jsx10("p", { style: styles.resultSubtitle, children: "The document you submitted has expired. Please use a valid, non-expired document." }),
2509
+ verification.documentVerification && /* @__PURE__ */ jsxs9("div", { style: styles.expiryCard, children: [
2510
+ /* @__PURE__ */ jsxs9("div", { style: styles.expiryRow, children: [
2511
+ /* @__PURE__ */ jsx10("span", { style: styles.expiryLabel, children: "Document type" }),
2512
+ /* @__PURE__ */ jsx10("span", { style: styles.expiryValue, children: verification.documentVerification.documentType || "ID Card" })
2513
+ ] }),
2514
+ verification.documentVerification.issuingCountry && /* @__PURE__ */ jsxs9("div", { style: styles.expiryRow, children: [
2515
+ /* @__PURE__ */ jsx10("span", { style: styles.expiryLabel, children: "Country" }),
2516
+ /* @__PURE__ */ jsx10("span", { style: styles.expiryValue, children: verification.documentVerification.issuingCountry })
2517
+ ] }),
2518
+ verification.documentVerification.expirationDate && /* @__PURE__ */ jsxs9("div", { style: styles.expiryRow, children: [
2519
+ /* @__PURE__ */ jsx10("span", { style: styles.expiryLabel, children: "Expired on" }),
2520
+ /* @__PURE__ */ jsx10("span", { style: styles.expiryBadge, children: verification.documentVerification.expirationDate })
2521
+ ] })
2522
+ ] }),
2523
+ /* @__PURE__ */ jsxs9("div", { style: { textAlign: "left" }, children: [
2524
+ /* @__PURE__ */ jsx10(GuidanceTip, { number: 1, text: "Check the expiration date on your document" }),
2525
+ /* @__PURE__ */ jsx10(GuidanceTip, { number: 2, text: "Use a different document that is currently valid" }),
2526
+ /* @__PURE__ */ jsx10(GuidanceTip, { number: 3, text: "Ensure the document details are clearly visible" })
2527
+ ] })
2528
+ ] }),
2529
+ /* @__PURE__ */ jsx10("div", { style: styles.footer, children: /* @__PURE__ */ jsx10("button", { style: styles.primaryButton, onClick: onRetry, children: "Try with a valid document" }) })
2530
+ ] });
2531
+ }
2532
+ function ManualReviewResult({ verification, onDone }) {
2533
+ const score = verification.riskScore ?? 68;
2534
+ const metrics = computeScoreBreakdown(verification);
2535
+ return /* @__PURE__ */ jsxs9("div", { style: styles.resultContainer, children: [
2536
+ /* @__PURE__ */ jsxs9("div", { style: styles.resultContent, children: [
2537
+ /* @__PURE__ */ jsx10(
2538
+ "div",
2539
+ {
2540
+ style: {
2541
+ ...styles.resultIconOuterRing,
2542
+ backgroundColor: `${colors.info}15`,
2543
+ border: `2px solid ${colors.info}30`
2544
+ },
2545
+ children: /* @__PURE__ */ jsx10(
2546
+ "div",
2547
+ {
2548
+ style: {
2549
+ ...styles.resultIconCircle,
2550
+ background: `linear-gradient(135deg, ${colors.info}, #0369A1)`,
2551
+ color: colors.white,
2552
+ margin: 0
2553
+ },
2554
+ children: "\u{1F550}"
2555
+ }
2556
+ )
2557
+ }
2558
+ ),
2559
+ /* @__PURE__ */ jsx10("h1", { style: styles.resultTitle, children: "Under review" }),
2560
+ /* @__PURE__ */ jsx10("p", { style: styles.resultSubtitle, children: "Your verification requires manual review. We'll notify you of the result." }),
2561
+ /* @__PURE__ */ jsx10(
2562
+ ScoreCard,
2563
+ {
2564
+ score,
2565
+ badge: "REVIEW",
2566
+ gradient: `linear-gradient(135deg, ${colors.info}, #0369A1)`
2567
+ }
2568
+ ),
2569
+ metrics.map((m, i) => /* @__PURE__ */ jsx10(ScoreMetricRow, { ...m }, i))
2570
+ ] }),
2571
+ /* @__PURE__ */ jsx10("div", { style: styles.footer, children: /* @__PURE__ */ jsx10("button", { style: styles.primaryButton, onClick: onDone, children: "Got it" }) })
2572
+ ] });
2573
+ }
2574
+ function GuidanceTip({ number, text }) {
2575
+ return /* @__PURE__ */ jsxs9("div", { style: styles.guidanceTip, children: [
2576
+ /* @__PURE__ */ jsx10("div", { style: styles.guidanceTipNumber, children: number }),
2577
+ /* @__PURE__ */ jsx10("span", { style: styles.guidanceTipText, children: text })
2578
+ ] });
2579
+ }
2580
+ function SimplifiedSuccess({ onDone, customMessages }) {
2581
+ return /* @__PURE__ */ jsxs9("div", { style: styles.resultContainer, children: [
2582
+ /* @__PURE__ */ jsxs9("div", { style: { ...styles.resultContent, textAlign: "center" }, children: [
2583
+ /* @__PURE__ */ jsx10(
2584
+ "div",
2585
+ {
2586
+ style: {
2587
+ ...styles.resultIconOuterRing,
2588
+ backgroundColor: `${colors.success}15`,
2589
+ width: 96,
2590
+ height: 96
2591
+ },
2592
+ children: /* @__PURE__ */ jsx10(
2593
+ "div",
2594
+ {
2595
+ style: {
2596
+ ...styles.resultIconCircle,
2597
+ background: `linear-gradient(135deg, ${colors.success}, #059669)`,
2598
+ color: colors.white,
2599
+ margin: 0,
2600
+ width: 64,
2601
+ height: 64,
2602
+ fontSize: 28
2603
+ },
2604
+ children: "\u2713"
2605
+ }
2606
+ )
2607
+ }
2608
+ ),
2609
+ /* @__PURE__ */ jsx10("h1", { style: { ...styles.resultTitle, fontSize: 24, marginTop: 16 }, children: customMessages?.successTitle || "Verification Successful" }),
2610
+ /* @__PURE__ */ jsx10("p", { style: { ...styles.resultSubtitle, fontSize: 16, maxWidth: 320, margin: "8px auto 0" }, children: customMessages?.successMessage || "Your identity has been successfully verified. You can now proceed." })
2611
+ ] }),
2612
+ /* @__PURE__ */ jsx10("div", { style: styles.footer, children: /* @__PURE__ */ jsx10("button", { style: styles.primaryButton, onClick: onDone, children: "Continue" }) })
2613
+ ] });
2614
+ }
2615
+ function SimplifiedFailed({ onRetry, customMessages }) {
2616
+ return /* @__PURE__ */ jsxs9("div", { style: styles.resultContainer, children: [
2617
+ /* @__PURE__ */ jsxs9("div", { style: { ...styles.resultContent, textAlign: "center" }, children: [
2618
+ /* @__PURE__ */ jsx10(
2619
+ "div",
2620
+ {
2621
+ style: {
2622
+ ...styles.resultIconOuterRing,
2623
+ backgroundColor: `${colors.error}15`,
2624
+ width: 96,
2625
+ height: 96
2626
+ },
2627
+ children: /* @__PURE__ */ jsx10(
2628
+ "div",
2629
+ {
2630
+ style: {
2631
+ ...styles.resultIconCircle,
2632
+ background: `linear-gradient(135deg, ${colors.error}, #B91C1C)`,
2633
+ color: colors.white,
2634
+ margin: 0,
2635
+ width: 64,
2636
+ height: 64,
2637
+ fontSize: 28
2638
+ },
2639
+ children: "\u2715"
2640
+ }
2641
+ )
2642
+ }
2643
+ ),
2644
+ /* @__PURE__ */ jsx10("h1", { style: { ...styles.resultTitle, fontSize: 24, marginTop: 16 }, children: customMessages?.failedTitle || "Verification Failed" }),
2645
+ /* @__PURE__ */ jsx10("p", { style: { ...styles.resultSubtitle, fontSize: 16, maxWidth: 320, margin: "8px auto 0" }, children: customMessages?.failedMessage || "We could not verify your identity. Please try again with a valid document." })
2646
+ ] }),
2647
+ /* @__PURE__ */ jsx10("div", { style: styles.footer, children: /* @__PURE__ */ jsx10("button", { style: styles.primaryButton, onClick: onRetry, children: "Try Again" }) })
2648
+ ] });
2649
+ }
2650
+ function SimplifiedReview({ verification, onDone, customMessages }) {
2651
+ return /* @__PURE__ */ jsxs9("div", { style: styles.resultContainer, children: [
2652
+ /* @__PURE__ */ jsxs9("div", { style: { ...styles.resultContent, textAlign: "center" }, children: [
2653
+ /* @__PURE__ */ jsx10(
2654
+ "div",
2655
+ {
2656
+ style: {
2657
+ ...styles.resultIconOuterRing,
2658
+ backgroundColor: `${colors.warning}15`,
2659
+ width: 96,
2660
+ height: 96
2661
+ },
2662
+ children: /* @__PURE__ */ jsx10(
2663
+ "div",
2664
+ {
2665
+ style: {
2666
+ ...styles.resultIconCircle,
2667
+ background: `linear-gradient(135deg, ${colors.warning}, #B45309)`,
2668
+ color: colors.white,
2669
+ margin: 0,
2670
+ width: 64,
2671
+ height: 64,
2672
+ fontSize: 28
2673
+ },
2674
+ children: "\u{1F550}"
2675
+ }
2676
+ )
2677
+ }
2678
+ ),
2679
+ /* @__PURE__ */ jsx10("h1", { style: { ...styles.resultTitle, fontSize: 24, marginTop: 16 }, children: customMessages?.reviewTitle || "Verification Under Review" }),
2680
+ /* @__PURE__ */ jsx10("p", { style: { ...styles.resultSubtitle, fontSize: 16, maxWidth: 320, margin: "8px auto 0" }, children: customMessages?.reviewMessage || "Your verification requires additional review. We will notify you of the result." }),
2681
+ /* @__PURE__ */ jsxs9("div", { style: {
2682
+ marginTop: 24,
2683
+ padding: "12px 24px",
2684
+ backgroundColor: `${colors.info}10`,
2685
+ borderRadius: 8,
2686
+ border: `1px solid ${colors.info}30`,
2687
+ display: "inline-block"
2688
+ }, children: [
2689
+ /* @__PURE__ */ jsx10("span", { style: { fontSize: 12, color: colors.textSecondary }, children: "Reference: " }),
2690
+ /* @__PURE__ */ jsx10("span", { style: { fontSize: 14, fontWeight: 600, fontFamily: "monospace" }, children: verification.id.slice(0, 8) })
2691
+ ] })
2692
+ ] }),
2693
+ /* @__PURE__ */ jsx10("div", { style: styles.footer, children: /* @__PURE__ */ jsx10("button", { style: styles.primaryButton, onClick: onDone, children: "Got It" }) })
2694
+ ] });
2695
+ }
2696
+
2697
+ // src/components/ErrorScreen.tsx
2698
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
2699
+ function ErrorScreen({ error, onRetry, onCancel }) {
2700
+ return /* @__PURE__ */ jsxs10("div", { style: styles.resultContainer, children: [
2701
+ /* @__PURE__ */ jsxs10("div", { style: styles.resultContent, children: [
2702
+ /* @__PURE__ */ jsx11(
2703
+ "div",
2704
+ {
2705
+ style: {
2706
+ ...styles.resultIconOuterRing,
2707
+ backgroundColor: colors.errorBg
2708
+ },
2709
+ children: /* @__PURE__ */ jsx11(
2710
+ "div",
2711
+ {
2712
+ style: {
2713
+ ...styles.resultIconCircle,
2714
+ background: `linear-gradient(135deg, ${colors.error}, #B91C1C)`,
2715
+ color: colors.white,
2716
+ margin: 0
2717
+ },
2718
+ children: "!"
2719
+ }
2720
+ )
2721
+ }
2722
+ ),
2723
+ /* @__PURE__ */ jsx11("h1", { style: styles.resultTitle, children: "Something went wrong" }),
2724
+ /* @__PURE__ */ jsx11("p", { style: styles.resultSubtitle, children: error.message }),
2725
+ error.recoverySuggestion && /* @__PURE__ */ jsx11("p", { style: { ...styles.bodyText, marginTop: "12px" }, children: error.recoverySuggestion })
2726
+ ] }),
2727
+ /* @__PURE__ */ jsxs10("div", { style: styles.footer, children: [
2728
+ error.isRetryable && /* @__PURE__ */ jsx11("button", { style: styles.primaryButton, onClick: onRetry, children: "Try Again" }),
2729
+ /* @__PURE__ */ jsx11("button", { style: styles.textButton, onClick: onCancel, children: "Cancel" })
2730
+ ] })
2731
+ ] });
2732
+ }
2733
+
2734
+ // src/components/LoadingScreen.tsx
2735
+ import { useEffect as useEffect6 } from "react";
2736
+ import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
2737
+ function LoadingScreen({ message = "Loading..." }) {
2738
+ useEffect6(() => {
2739
+ injectKeyframes();
2740
+ }, []);
2741
+ return /* @__PURE__ */ jsx12("div", { style: styles.container, children: /* @__PURE__ */ jsxs11("div", { style: styles.loadingContainer, children: [
2742
+ /* @__PURE__ */ jsx12(
2743
+ "div",
2744
+ {
2745
+ style: {
2746
+ width: "56px",
2747
+ height: "56px",
2748
+ borderRadius: "50%",
2749
+ background: `linear-gradient(135deg, ${colors.teal}, ${colors.tealDark})`,
2750
+ display: "flex",
2751
+ alignItems: "center",
2752
+ justifyContent: "center",
2753
+ fontSize: "24px",
2754
+ animation: "kora-pulse 2s ease-in-out infinite"
2755
+ },
2756
+ children: "\u{1F6E1}\uFE0F"
2757
+ }
2758
+ ),
2759
+ /* @__PURE__ */ jsx12("p", { style: styles.loadingText, children: message })
2760
+ ] }) });
2761
+ }
2762
+
2763
+ // src/components/VerificationFlow.tsx
2764
+ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
2765
+ function VerificationFlow({
2766
+ externalId,
2767
+ tier = "standard",
2768
+ documentTypes,
2769
+ onComplete,
2770
+ onError,
2771
+ onCancel,
2772
+ className,
2773
+ style
2774
+ }) {
2775
+ const {
2776
+ state,
2777
+ startVerification,
2778
+ acceptConsent,
2779
+ selectDocumentType,
2780
+ checkDocumentQuality,
2781
+ uploadDocument,
2782
+ uploadSelfie,
2783
+ startLiveness,
2784
+ submitChallenge,
2785
+ complete,
2786
+ cancel,
2787
+ retry,
2788
+ sdk
2789
+ } = useKoraIDV();
2790
+ const [selectedCountry, setSelectedCountry] = useState6(null);
2791
+ const [flowStep, setFlowStep] = useState6("consent");
2792
+ const [showFlipInstruction, setShowFlipInstruction] = useState6(true);
2793
+ const [supportedCountries, setSupportedCountries] = useState6([]);
2794
+ const [countriesLoading, setCountriesLoading] = useState6(false);
2795
+ useEffect7(() => {
2796
+ if (state.step === "document_front") {
2797
+ setShowFlipInstruction(true);
2798
+ }
2799
+ }, [state.step]);
2800
+ useEffect7(() => {
2801
+ startVerification(externalId, tier);
2802
+ }, [externalId, tier, startVerification]);
2803
+ useEffect7(() => {
2804
+ if (state.step === "complete" && state.verification && onComplete) {
2805
+ onComplete(state.verification);
2806
+ }
2807
+ }, [state.step, state.verification, onComplete]);
2808
+ useEffect7(() => {
2809
+ if (state.error && onError) {
2810
+ onError(state.error);
2811
+ }
2812
+ }, [state.error, onError]);
2813
+ const fetchCountries = async () => {
2814
+ setCountriesLoading(true);
2815
+ try {
2816
+ const countries = await sdk.getSupportedCountries();
2817
+ setSupportedCountries(
2818
+ countries.map((c) => ({
2819
+ id: c.id,
2820
+ name: c.name,
2821
+ flagEmoji: c.flagEmoji,
2822
+ documentTypes: c.documentTypes
2823
+ }))
2824
+ );
2825
+ } catch (error) {
2826
+ onError?.(
2827
+ error instanceof KoraError2 ? error : new KoraError2(KoraErrorCode.NETWORK_ERROR, "Failed to load supported countries")
2828
+ );
2829
+ } finally {
2830
+ setCountriesLoading(false);
2831
+ }
2832
+ };
2833
+ const handleCancel = () => {
2834
+ cancel();
2835
+ onCancel?.();
2836
+ };
2837
+ const handleAcceptConsent = () => {
2838
+ fetchCountries();
2839
+ setFlowStep("country_selection");
2840
+ };
2841
+ const handleCountrySelect = (country) => {
2842
+ setSelectedCountry(country);
2843
+ setFlowStep("flow");
2844
+ acceptConsent();
2845
+ };
2846
+ const containerStyle = {
2847
+ width: "100%",
2848
+ maxWidth: "480px",
2849
+ margin: "0 auto",
2850
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
2851
+ ...style
2852
+ };
2853
+ if (state.error) {
2854
+ return /* @__PURE__ */ jsx13("div", { className, style: containerStyle, children: /* @__PURE__ */ jsx13(ErrorScreen, { error: state.error, onRetry: retry, onCancel: handleCancel }) });
2855
+ }
2856
+ if (state.isLoading && state.step !== "processing") {
2857
+ return /* @__PURE__ */ jsx13("div", { className, style: containerStyle, children: /* @__PURE__ */ jsx13(LoadingScreen, {}) });
2858
+ }
2859
+ if (flowStep === "consent" && state.step === "consent") {
2860
+ return /* @__PURE__ */ jsx13("div", { className, style: containerStyle, children: /* @__PURE__ */ jsx13(ConsentScreen, { onAccept: handleAcceptConsent, onDecline: handleCancel }) });
2861
+ }
2862
+ if (flowStep === "country_selection") {
2863
+ if (countriesLoading) {
2864
+ return /* @__PURE__ */ jsx13("div", { className, style: containerStyle, children: /* @__PURE__ */ jsx13(LoadingScreen, {}) });
2865
+ }
2866
+ return /* @__PURE__ */ jsx13("div", { className, style: containerStyle, children: /* @__PURE__ */ jsx13(
2867
+ CountrySelectionScreen,
2868
+ {
2869
+ countries: supportedCountries,
2870
+ onSelect: handleCountrySelect,
2871
+ onCancel: handleCancel
2872
+ }
2873
+ ) });
2874
+ }
2875
+ return /* @__PURE__ */ jsxs12("div", { className, style: containerStyle, children: [
2876
+ state.step === "document_selection" && /* @__PURE__ */ jsx13(
2877
+ DocumentSelectionScreen,
2878
+ {
2879
+ documentTypes,
2880
+ selectedCountry,
2881
+ onSelect: selectDocumentType,
2882
+ onCancel: handleCancel
2883
+ }
2884
+ ),
2885
+ state.step === "document_front" && /* @__PURE__ */ jsx13(
2886
+ DocumentCaptureScreen,
2887
+ {
2888
+ side: "front",
2889
+ onQualityCheck: (blob) => checkDocumentQuality(blob),
2890
+ onCapture: (imageData) => uploadDocument(imageData, "front"),
2891
+ onCancel: handleCancel
2892
+ }
2893
+ ),
2894
+ state.step === "document_back" && showFlipInstruction && /* @__PURE__ */ jsx13(
2895
+ FlipDocumentScreen,
2896
+ {
2897
+ onContinue: () => setShowFlipInstruction(false),
2898
+ onCancel: handleCancel
2899
+ }
2900
+ ),
2901
+ state.step === "document_back" && !showFlipInstruction && /* @__PURE__ */ jsx13(
2902
+ DocumentCaptureScreen,
2903
+ {
2904
+ side: "back",
2905
+ onCapture: (imageData) => uploadDocument(imageData, "back"),
2906
+ onCancel: handleCancel
2907
+ }
2908
+ ),
2909
+ state.step === "selfie" && /* @__PURE__ */ jsx13(SelfieCaptureScreen, { onCapture: uploadSelfie, onCancel: handleCancel }),
2910
+ state.step === "liveness" && /* @__PURE__ */ jsx13(
2911
+ LivenessScreen,
2912
+ {
2913
+ session: state.livenessSession,
2914
+ currentChallenge: state.currentChallenge,
2915
+ completedChallenges: state.completedChallenges,
2916
+ onChallengeComplete: submitChallenge,
2917
+ onStart: startLiveness,
2918
+ onComplete: complete,
2919
+ onCancel: handleCancel
2920
+ }
2921
+ ),
2922
+ state.step === "processing" && /* @__PURE__ */ jsx13(
2923
+ ProcessingScreen,
2924
+ {
2925
+ steps: [
2926
+ { label: "Document analyzed", status: "done" },
2927
+ { label: "Checking face match", status: "active" },
2928
+ { label: "Finalizing results", status: "pending" }
2929
+ ]
2930
+ }
2931
+ ),
2932
+ state.step === "complete" && state.verification && /* @__PURE__ */ jsx13(
2933
+ ResultScreen,
2934
+ {
2935
+ verification: state.verification,
2936
+ onDone: () => onComplete?.(state.verification),
2937
+ onRetry: retry
2938
+ }
2939
+ )
2940
+ ] });
2941
+ }
2942
+
2943
+ // src/components/QrHandoffScreen.tsx
2944
+ import { useEffect as useEffect8, useState as useState7, useRef as useRef3 } from "react";
2945
+ import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
2946
+ function QrHandoffScreen({
2947
+ session,
2948
+ onMobileCaptureComplete,
2949
+ onContinueOnDevice,
2950
+ onExpired,
2951
+ onRefresh,
2952
+ eventSource
2953
+ }) {
2954
+ const [timeLeft, setTimeLeft] = useState7(session.expiresIn);
2955
+ const [scanned, setScanned] = useState7(false);
2956
+ const [expired, setExpired] = useState7(false);
2957
+ const timerRef = useRef3();
2958
+ useEffect8(() => {
2959
+ setTimeLeft(session.expiresIn);
2960
+ setExpired(false);
2961
+ setScanned(false);
2962
+ timerRef.current = setInterval(() => {
2963
+ setTimeLeft((prev) => {
2964
+ if (prev <= 1) {
2965
+ clearInterval(timerRef.current);
2966
+ setExpired(true);
2967
+ onExpired();
2968
+ return 0;
2969
+ }
2970
+ return prev - 1;
2971
+ });
2972
+ }, 1e3);
2973
+ return () => clearInterval(timerRef.current);
2974
+ }, [session.token]);
2975
+ useEffect8(() => {
2976
+ if (!eventSource) return;
2977
+ const handleStatus = (event) => {
2978
+ try {
2979
+ const data = JSON.parse(event.data);
2980
+ if (data.status === "document_required" || data.status === "selfie_required") {
2981
+ setScanned(true);
2982
+ }
2983
+ } catch {
2984
+ }
2985
+ };
2986
+ const handleComplete = () => {
2987
+ clearInterval(timerRef.current);
2988
+ onMobileCaptureComplete();
2989
+ };
2990
+ eventSource.addEventListener("status", handleStatus);
2991
+ eventSource.addEventListener("complete", handleComplete);
2992
+ return () => {
2993
+ eventSource.removeEventListener("status", handleStatus);
2994
+ eventSource.removeEventListener("complete", handleComplete);
2995
+ };
2996
+ }, [eventSource, onMobileCaptureComplete]);
2997
+ const minutes = Math.floor(timeLeft / 60);
2998
+ const seconds = timeLeft % 60;
2999
+ const qrSize = 200;
3000
+ if (expired) {
3001
+ return /* @__PURE__ */ jsx14("div", { style: qrStyles.container, children: /* @__PURE__ */ jsxs13("div", { style: qrStyles.content, children: [
3002
+ /* @__PURE__ */ jsx14("div", { style: qrStyles.expiredIcon, children: "\u23F1" }),
3003
+ /* @__PURE__ */ jsx14("h2", { style: qrStyles.title, children: "QR Code Expired" }),
3004
+ /* @__PURE__ */ jsx14("p", { style: qrStyles.subtitle, children: "The QR code has expired. Generate a new one to continue." }),
3005
+ /* @__PURE__ */ jsx14("button", { style: qrStyles.primaryButton, onClick: onRefresh, children: "Generate New QR Code" }),
3006
+ /* @__PURE__ */ jsx14("button", { style: qrStyles.secondaryButton, onClick: onContinueOnDevice, children: "Continue on this device instead" })
3007
+ ] }) });
3008
+ }
3009
+ if (scanned) {
3010
+ return /* @__PURE__ */ jsx14("div", { style: qrStyles.container, children: /* @__PURE__ */ jsxs13("div", { style: qrStyles.content, children: [
3011
+ /* @__PURE__ */ jsx14("div", { style: qrStyles.spinnerContainer, children: /* @__PURE__ */ jsx14("div", { style: qrStyles.spinner }) }),
3012
+ /* @__PURE__ */ jsx14("h2", { style: qrStyles.title, children: "Capturing on your phone..." }),
3013
+ /* @__PURE__ */ jsx14("p", { style: qrStyles.subtitle, children: "Complete the document scan and selfie on your mobile device. This page will update automatically when done." }),
3014
+ /* @__PURE__ */ jsxs13("div", { style: qrStyles.statusBadge, children: [
3015
+ /* @__PURE__ */ jsx14("span", { style: qrStyles.statusDot }),
3016
+ "Connected \u2014 waiting for capture"
3017
+ ] })
3018
+ ] }) });
3019
+ }
3020
+ return /* @__PURE__ */ jsx14("div", { style: qrStyles.container, children: /* @__PURE__ */ jsxs13("div", { style: qrStyles.content, children: [
3021
+ /* @__PURE__ */ jsx14("h2", { style: qrStyles.title, children: "Scan with your phone" }),
3022
+ /* @__PURE__ */ jsx14("p", { style: qrStyles.subtitle, children: "Use your phone's camera for a better capture experience. Scan the QR code below to continue on your mobile device." }),
3023
+ /* @__PURE__ */ jsxs13("div", { style: qrStyles.qrContainer, children: [
3024
+ /* @__PURE__ */ jsx14("div", { style: {
3025
+ ...qrStyles.qrBox,
3026
+ width: qrSize,
3027
+ height: qrSize
3028
+ }, children: /* @__PURE__ */ jsx14(
3029
+ "img",
3030
+ {
3031
+ src: `https://api.qrserver.com/v1/create-qr-code/?size=${qrSize}x${qrSize}&data=${encodeURIComponent(session.captureUrl)}&margin=8`,
3032
+ alt: "QR Code",
3033
+ width: qrSize,
3034
+ height: qrSize,
3035
+ style: { borderRadius: 12 }
3036
+ }
3037
+ ) }),
3038
+ /* @__PURE__ */ jsxs13("div", { style: qrStyles.timer, children: [
3039
+ minutes,
3040
+ ":",
3041
+ seconds.toString().padStart(2, "0"),
3042
+ " remaining"
3043
+ ] })
3044
+ ] }),
3045
+ /* @__PURE__ */ jsxs13("div", { style: qrStyles.steps, children: [
3046
+ /* @__PURE__ */ jsx14(Step, { number: 1, text: "Open your phone's camera" }),
3047
+ /* @__PURE__ */ jsx14(Step, { number: 2, text: "Point at the QR code" }),
3048
+ /* @__PURE__ */ jsx14(Step, { number: 3, text: "Complete the capture on your phone" })
3049
+ ] }),
3050
+ /* @__PURE__ */ jsx14("div", { style: qrStyles.divider, children: /* @__PURE__ */ jsx14("span", { style: qrStyles.dividerText, children: "or" }) }),
3051
+ /* @__PURE__ */ jsx14("button", { style: qrStyles.secondaryButton, onClick: onContinueOnDevice, children: "Continue on this device" })
3052
+ ] }) });
3053
+ }
3054
+ function Step({ number, text }) {
3055
+ return /* @__PURE__ */ jsxs13("div", { style: qrStyles.step, children: [
3056
+ /* @__PURE__ */ jsx14("div", { style: qrStyles.stepNumber, children: number }),
3057
+ /* @__PURE__ */ jsx14("span", { style: qrStyles.stepText, children: text })
3058
+ ] });
3059
+ }
3060
+ var qrStyles = {
3061
+ container: {
3062
+ display: "flex",
3063
+ flexDirection: "column",
3064
+ alignItems: "center",
3065
+ justifyContent: "center",
3066
+ minHeight: "100%",
3067
+ padding: 24,
3068
+ fontFamily: "Inter, system-ui, sans-serif"
3069
+ },
3070
+ content: {
3071
+ textAlign: "center",
3072
+ maxWidth: 400,
3073
+ width: "100%"
3074
+ },
3075
+ title: {
3076
+ fontSize: 22,
3077
+ fontWeight: 700,
3078
+ color: "#1a1a2e",
3079
+ marginBottom: 8
3080
+ },
3081
+ subtitle: {
3082
+ fontSize: 14,
3083
+ color: "#6b7280",
3084
+ lineHeight: "1.5",
3085
+ marginBottom: 24
3086
+ },
3087
+ qrContainer: {
3088
+ display: "flex",
3089
+ flexDirection: "column",
3090
+ alignItems: "center",
3091
+ gap: 12,
3092
+ marginBottom: 24
3093
+ },
3094
+ qrBox: {
3095
+ padding: 16,
3096
+ background: "#ffffff",
3097
+ borderRadius: 16,
3098
+ boxShadow: "0 4px 24px rgba(0,0,0,0.08)",
3099
+ border: "1px solid #e5e7eb"
3100
+ },
3101
+ timer: {
3102
+ fontSize: 13,
3103
+ color: "#9ca3af",
3104
+ fontVariantNumeric: "tabular-nums"
3105
+ },
3106
+ steps: {
3107
+ textAlign: "left",
3108
+ marginBottom: 24
3109
+ },
3110
+ step: {
3111
+ display: "flex",
3112
+ alignItems: "center",
3113
+ gap: 12,
3114
+ marginBottom: 12
3115
+ },
3116
+ stepNumber: {
3117
+ width: 28,
3118
+ height: 28,
3119
+ borderRadius: 14,
3120
+ background: `${colors.teal}15`,
3121
+ color: colors.teal,
3122
+ display: "flex",
3123
+ alignItems: "center",
3124
+ justifyContent: "center",
3125
+ fontSize: 13,
3126
+ fontWeight: 700,
3127
+ flexShrink: 0
3128
+ },
3129
+ stepText: {
3130
+ fontSize: 14,
3131
+ color: "#374151"
3132
+ },
3133
+ divider: {
3134
+ position: "relative",
3135
+ textAlign: "center",
3136
+ margin: "16px 0",
3137
+ borderTop: "1px solid #e5e7eb"
3138
+ },
3139
+ dividerText: {
3140
+ position: "relative",
3141
+ top: -10,
3142
+ background: "#f9fafb",
3143
+ padding: "0 12px",
3144
+ fontSize: 13,
3145
+ color: "#9ca3af"
3146
+ },
3147
+ primaryButton: {
3148
+ width: "100%",
3149
+ padding: "12px 24px",
3150
+ fontSize: 15,
3151
+ fontWeight: 600,
3152
+ color: "#ffffff",
3153
+ background: colors.teal,
3154
+ border: "none",
3155
+ borderRadius: 10,
3156
+ cursor: "pointer",
3157
+ marginBottom: 12
3158
+ },
3159
+ secondaryButton: {
3160
+ width: "100%",
3161
+ padding: "12px 24px",
3162
+ fontSize: 14,
3163
+ fontWeight: 500,
3164
+ color: "#6b7280",
3165
+ background: "transparent",
3166
+ border: "1px solid #d1d5db",
3167
+ borderRadius: 10,
3168
+ cursor: "pointer"
3169
+ },
3170
+ expiredIcon: {
3171
+ fontSize: 48,
3172
+ marginBottom: 16
3173
+ },
3174
+ spinnerContainer: {
3175
+ display: "flex",
3176
+ justifyContent: "center",
3177
+ marginBottom: 16
3178
+ },
3179
+ spinner: {
3180
+ width: 48,
3181
+ height: 48,
3182
+ borderRadius: "50%",
3183
+ border: `3px solid ${colors.teal}30`,
3184
+ borderTopColor: colors.teal,
3185
+ animation: "spin 1s linear infinite"
3186
+ },
3187
+ statusBadge: {
3188
+ display: "inline-flex",
3189
+ alignItems: "center",
3190
+ gap: 8,
3191
+ padding: "8px 16px",
3192
+ background: `${colors.teal}10`,
3193
+ borderRadius: 20,
3194
+ fontSize: 13,
3195
+ color: colors.teal,
3196
+ fontWeight: 500,
3197
+ marginTop: 16
3198
+ },
3199
+ statusDot: {
3200
+ width: 8,
3201
+ height: 8,
3202
+ borderRadius: 4,
3203
+ background: colors.teal
3204
+ }
3205
+ };
3206
+ export {
3207
+ ConsentScreen,
3208
+ CountrySelectionScreen,
3209
+ DocumentCaptureScreen,
3210
+ DocumentSelectionScreen,
3211
+ ErrorScreen,
3212
+ KoraIDVProvider,
3213
+ LivenessScreen,
3214
+ ProcessingScreen,
3215
+ QrHandoffScreen,
3216
+ ResultScreen,
3217
+ ScoreCard,
3218
+ ScoreMetricRow,
3219
+ SelfieCaptureScreen,
3220
+ StepProgressBar,
3221
+ VerificationFlow,
3222
+ useKoraIDV
3223
+ };