@incodetech/web 2.0.0-alpha.1 → 2.0.0-alpha.10
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/base.css +1071 -0
- package/dist/browser-ponyfill-B6W6hHVY.js +344 -0
- package/dist/email/email.es.js +145 -0
- package/dist/email/styles.css +123 -0
- package/dist/flow/flow.es.js +555 -0
- package/dist/flow/styles.css +1119 -0
- package/dist/incodeModule-Dv8Qllrv.js +254 -0
- package/dist/index.es.js +6 -0
- package/dist/instance-B-q0ZREN.js +2140 -0
- package/dist/otpInput-BtoZe0Wz.js +151 -0
- package/dist/page-Dh_Zw2ik.js +234 -0
- package/dist/phone/phone.es.js +3441 -0
- package/dist/phone/styles.css +305 -0
- package/dist/selfie/selfie.es.js +893 -0
- package/dist/selfie/styles.css +590 -0
- package/dist/selfieTutorial-C-u5GufD.js +29 -0
- package/dist/setup-wNL83jmW.js +20 -0
- package/dist/themes/dark.css +652 -0
- package/dist/themes/light.css +543 -0
- package/dist/title-BfO5Dlzk.js +25 -0
- package/dist/types/base.d.ts +1 -0
- package/dist/types/dark.d.ts +1 -0
- package/dist/types/email.d.ts +57 -0
- package/dist/types/flow.d.ts +69 -0
- package/dist/types/index.d.ts +38 -0
- package/dist/types/light.d.ts +1 -0
- package/dist/types/phone.d.ts +58 -0
- package/dist/types/selfie.d.ts +31 -0
- package/dist/types/styles.d.ts +1 -0
- package/dist/types/themes/dark.d.ts +1 -0
- package/dist/types/themes/light.d.ts +1 -0
- package/dist/uiConfig-CQ1W9cUD.js +23 -0
- package/dist/vendor-preact-CK0WeTOR.js +584 -0
- package/package.json +32 -26
- package/dev/README.md +0 -163
- package/dev/getToken.ts +0 -36
- package/dev/headless.html +0 -875
- package/dev/index.html +0 -366
- package/dev/main-headless.tsx +0 -1332
- package/dev/main-orchestrated-flow.tsx +0 -1158
- package/dev/main-preact.tsx +0 -323
- package/dev/main-simplified.tsx +0 -123
- package/dev/main-web-component.tsx +0 -256
- package/dev/main.tsx +0 -332
- package/dev/manual.html +0 -27
- package/dev/orchestrated-flow.html +0 -64
- package/dev/simplified.html +0 -64
- package/dev/tiktok-logo.svg +0 -7
- package/src/defineCustomElement.tsx +0 -30
- package/src/email/email.test.tsx +0 -368
- package/src/email/email.tsx +0 -255
- package/src/email/emailInput.test.tsx +0 -264
- package/src/email/emailInput.tsx +0 -85
- package/src/email/styles.css +0 -59
- package/src/flow/flow.test.tsx +0 -796
- package/src/flow/flow.tsx +0 -292
- package/src/flow/flowCompleted.css +0 -30
- package/src/flow/flowCompleted.test.tsx +0 -331
- package/src/flow/flowCompleted.tsx +0 -121
- package/src/flow/flowInit.test.ts +0 -264
- package/src/flow/flowInit.ts +0 -94
- package/src/flow/flowStart.css +0 -58
- package/src/flow/flowStart.test.tsx +0 -49
- package/src/flow/flowStart.tsx +0 -41
- package/src/flow/incode-logo.svg +0 -8
- package/src/flow/index.ts +0 -7
- package/src/flow/preloadFlow.test.ts +0 -421
- package/src/flow/preloadFlow.ts +0 -171
- package/src/flow/styles.css +0 -9
- package/src/flow/unsupportedModule.css +0 -21
- package/src/flow/unsupportedModule.tsx +0 -39
- package/src/flow/useFlowInitialization.test.tsx +0 -292
- package/src/flow/useFlowInitialization.ts +0 -128
- package/src/flow/useModuleLoader.test.tsx +0 -212
- package/src/flow/useModuleLoader.ts +0 -92
- package/src/hooks/index.ts +0 -1
- package/src/hooks/useManager.test.ts +0 -91
- package/src/hooks/useManager.ts +0 -40
- package/src/i18n/index.ts +0 -3
- package/src/i18n/instance.ts +0 -16
- package/src/i18n/setup.ts +0 -184
- package/src/i18n/useTranslation.ts +0 -42
- package/src/index.ts +0 -27
- package/src/permissions/assets/android-dots-icon.svg +0 -7
- package/src/permissions/assets/android-settings-icon.svg +0 -16
- package/src/permissions/assets/android-toggle-icon.svg +0 -20
- package/src/permissions/assets/bank-card-icon.svg +0 -14
- package/src/permissions/assets/camera-icon.svg +0 -12
- package/src/permissions/assets/camera-ios.svg +0 -53
- package/src/permissions/assets/check-icon.svg +0 -8
- package/src/permissions/assets/chrome-icon.svg +0 -43
- package/src/permissions/assets/password-icon.svg +0 -11
- package/src/permissions/assets/permissions-img.svg +0 -51
- package/src/permissions/assets/safari-icon.svg +0 -37
- package/src/permissions/assets/settings-icon.svg +0 -33
- package/src/permissions/assets/toggle-icon.svg +0 -19
- package/src/permissions/assets/warning-icon.svg +0 -6
- package/src/permissions/boldWithArrow.css +0 -9
- package/src/permissions/boldWithArrow.tsx +0 -41
- package/src/permissions/denied.css +0 -37
- package/src/permissions/denied.tsx +0 -29
- package/src/permissions/deniedAndroid.tsx +0 -56
- package/src/permissions/deniedDesktop.css +0 -9
- package/src/permissions/deniedDesktop.tsx +0 -64
- package/src/permissions/deniedIOS.tsx +0 -73
- package/src/permissions/deniedInstructions.tsx +0 -19
- package/src/permissions/iconWrapper.css +0 -9
- package/src/permissions/iconWrapper.tsx +0 -15
- package/src/permissions/learnMore.css +0 -37
- package/src/permissions/learnMore.tsx +0 -85
- package/src/permissions/numberedStep.css +0 -13
- package/src/permissions/numberedStep.tsx +0 -14
- package/src/permissions/permissions.css +0 -13
- package/src/permissions/permissions.tsx +0 -68
- package/src/phone/phone.tsx +0 -246
- package/src/phone/phoneInput.test.tsx +0 -275
- package/src/phone/phoneInput.tsx +0 -249
- package/src/phone/styles.css +0 -158
- package/src/selfie/cameraButton.css +0 -13
- package/src/selfie/cameraButton.tsx +0 -35
- package/src/selfie/capture.css +0 -57
- package/src/selfie/capture.tsx +0 -232
- package/src/selfie/errorModal.tsx +0 -218
- package/src/selfie/errorModalContent.css +0 -33
- package/src/selfie/errorModalContent.tsx +0 -44
- package/src/selfie/faceOutline.css +0 -5
- package/src/selfie/faceOutline.tsx +0 -22
- package/src/selfie/loadingBorder.css +0 -12
- package/src/selfie/loadingBorder.tsx +0 -77
- package/src/selfie/manualCaptureButton.css +0 -13
- package/src/selfie/manualCaptureButton.tsx +0 -35
- package/src/selfie/noMoreAttemptsModal.tsx +0 -44
- package/src/selfie/notification.css +0 -9
- package/src/selfie/notification.tsx +0 -36
- package/src/selfie/retryErrorModal.tsx +0 -56
- package/src/selfie/selfie.test.tsx +0 -458
- package/src/selfie/selfie.tsx +0 -83
- package/src/selfie/selfieTutorial.json +0 -2626
- package/src/selfie/styles.css +0 -1
- package/src/selfie/tutorial.test.tsx +0 -200
- package/src/selfie/tutorial.tsx +0 -43
- package/src/setup.ts +0 -33
- package/src/shared/baseTutorial/baseTutorial.css +0 -21
- package/src/shared/baseTutorial/baseTutorial.test.tsx +0 -184
- package/src/shared/baseTutorial/baseTutorial.tsx +0 -55
- package/src/shared/baseTutorial/replaceBaseTutorial.test.ts +0 -267
- package/src/shared/baseTutorial/replaceBaseTutorial.ts +0 -68
- package/src/shared/button/button.css +0 -55
- package/src/shared/button/button.test.tsx +0 -101
- package/src/shared/button/button.tsx +0 -47
- package/src/shared/componentRoot/incodeComponent.tsx +0 -12
- package/src/shared/countries/countries.test.ts +0 -75
- package/src/shared/countries/countries.ts +0 -139
- package/src/shared/countries/index.ts +0 -6
- package/src/shared/icons/chevronDown.tsx +0 -22
- package/src/shared/icons/index.ts +0 -2
- package/src/shared/icons/successIcon.css +0 -5
- package/src/shared/icons/successIcon.test.tsx +0 -40
- package/src/shared/icons/successIcon.tsx +0 -26
- package/src/shared/loader/loadingIcon.css +0 -28
- package/src/shared/loader/loadingIcon.tsx +0 -67
- package/src/shared/lottie/lottie.tsx +0 -108
- package/src/shared/otpInput/otpInput.css +0 -85
- package/src/shared/otpInput/otpInput.test.tsx +0 -356
- package/src/shared/otpInput/otpInput.tsx +0 -241
- package/src/shared/page/incode-logo.svg +0 -3
- package/src/shared/page/page.css +0 -47
- package/src/shared/page/page.test.tsx +0 -97
- package/src/shared/page/page.tsx +0 -91
- package/src/shared/page/pageUiConfig.test.ts +0 -112
- package/src/shared/page/pageUiConfig.ts +0 -64
- package/src/shared/page/verifiedByIncode.css +0 -5
- package/src/shared/page/verifiedByIncode.tsx +0 -75
- package/src/shared/spacer/spacer.css +0 -149
- package/src/shared/spacer/spacer.test.tsx +0 -143
- package/src/shared/spacer/spacer.tsx +0 -88
- package/src/shared/spinner/index.ts +0 -2
- package/src/shared/spinner/spinner.css +0 -28
- package/src/shared/spinner/spinner.test.tsx +0 -82
- package/src/shared/spinner/spinner.tsx +0 -65
- package/src/shared/title/title.css +0 -7
- package/src/shared/title/title.tsx +0 -12
- package/src/shared/uiConfig/uiConfig.ts +0 -36
- package/src/shared/webComponent/incodeModule.ts +0 -29
- package/src/shared/webComponent/registerIncodeElement.ts +0 -15
- package/src/styles/__mocks__/fetchTheme.ts +0 -19
- package/src/styles/applyTheme.ts +0 -37
- package/src/styles/cn.test.tsx +0 -57
- package/src/styles/cn.tsx +0 -21
- package/src/styles/core.css +0 -12
- package/src/styles/fetchTheme.test.ts +0 -390
- package/src/styles/fetchTheme.ts +0 -88
- package/src/styles/generatePalette.ts +0 -111
- package/src/styles/reset.css +0 -65
- package/src/styles/resolveCssVariableToHex.ts +0 -28
- package/src/styles/tailwind.css +0 -291
- package/src/styles/themeTypes.ts +0 -18
- package/src/styles/tokens/colors.css +0 -190
- package/src/styles/tokens/components.css +0 -174
- package/src/styles/tokens/index.css +0 -4
- package/src/styles/tokens/primitives.css +0 -129
- package/src/styles/tokens/semantic.css +0 -51
- package/src/svg.d.ts +0 -4
- package/src/types/assets.d.ts +0 -1
- package/src/types/custom-elements.d.ts +0 -104
- package/tsconfig.json +0 -22
- package/vite.config.ts +0 -260
- package/vitest.config.ts +0 -40
- package/vitest.setup.ts +0 -16
|
@@ -1,1158 +0,0 @@
|
|
|
1
|
-
import type { WasmConfig } from '@incodetech/core';
|
|
2
|
-
import {
|
|
3
|
-
createSession,
|
|
4
|
-
initializeWasmUtil,
|
|
5
|
-
setWasmConfig,
|
|
6
|
-
warmupWasm,
|
|
7
|
-
} from '@incodetech/core';
|
|
8
|
-
import { emailMachine } from '@incodetech/core/email';
|
|
9
|
-
import type { FlowModuleConfig } from '@incodetech/core/flow';
|
|
10
|
-
import {
|
|
11
|
-
createOrchestratedFlowManager,
|
|
12
|
-
getRequiredWasmPipelines,
|
|
13
|
-
type OrchestratedFlowReadyState,
|
|
14
|
-
} from '@incodetech/core/flow';
|
|
15
|
-
import { phoneMachine } from '@incodetech/core/phone';
|
|
16
|
-
import { selfieMachine } from '@incodetech/core/selfie';
|
|
17
|
-
import { render } from 'preact';
|
|
18
|
-
import type { ComponentType, FC } from 'preact/compat';
|
|
19
|
-
import { useEffect, useRef, useState } from 'preact/hooks';
|
|
20
|
-
import { useManager } from '../src/hooks';
|
|
21
|
-
import '../src/styles/tokens/index.css';
|
|
22
|
-
import '../src/styles/reset.css';
|
|
23
|
-
import '../src/phone/styles.css';
|
|
24
|
-
import '../src/selfie/styles.css';
|
|
25
|
-
import '../src/email/styles.css';
|
|
26
|
-
import '../src/shared/loader/loadingIcon.css';
|
|
27
|
-
import '../src/shared/spinner/spinner.css';
|
|
28
|
-
import '../src/shared/spacer/spacer.css';
|
|
29
|
-
import '../src/shared/title/title.css';
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* SDK's reusable Spinner component - LoadingIcon + optional text
|
|
33
|
-
*
|
|
34
|
-
* Can be styled via CSS variables (see orchestrated-flow.html):
|
|
35
|
-
* --spinner-surface-primary, --spinner-surface-secondary
|
|
36
|
-
* --spinner-text-title, --spinner-text-subtitle
|
|
37
|
-
* --spinner-surface-overlay, --spinner-surface-overlay-opacity
|
|
38
|
-
*/
|
|
39
|
-
import { Spinner } from '../src/shared/spinner';
|
|
40
|
-
|
|
41
|
-
// Lazy-load UI modules for code splitting
|
|
42
|
-
const LAZY_UI_MODULES = {
|
|
43
|
-
PHONE: () => import('../src/phone/phone').then((m) => m.Phone),
|
|
44
|
-
EMAIL: () => import('../src/email/email').then((m) => m.Email),
|
|
45
|
-
SELFIE: () => import('../src/selfie/selfie').then((m) => m.Selfie),
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
import tiktokLogo from './tiktok-logo.svg';
|
|
49
|
-
|
|
50
|
-
// ============================================================================
|
|
51
|
-
// CUSTOM BRAND THEME - Override SDK tokens for your brand colors
|
|
52
|
-
// ============================================================================
|
|
53
|
-
const CustomBrandTheme: FC = () => (
|
|
54
|
-
<style>
|
|
55
|
-
{`
|
|
56
|
-
.IncodeTikTokTheme {
|
|
57
|
-
--tiktok-logo-height: 24px;
|
|
58
|
-
|
|
59
|
-
--surface-neutral-0: #000000;
|
|
60
|
-
--surface-neutral-50: #0b0b0f;
|
|
61
|
-
--surface-neutral-100: #14141a;
|
|
62
|
-
--text-body-primary: #ffffff;
|
|
63
|
-
--text-body-secondary: rgba(255, 255, 255, 0.72);
|
|
64
|
-
--text-body-tertiary: rgba(255, 255, 255, 0.45);
|
|
65
|
-
|
|
66
|
-
--text-link-default: #25f4ee;
|
|
67
|
-
--text-accent-brand-static: #25f4ee;
|
|
68
|
-
|
|
69
|
-
--button-primary-surface-default: #fe2c55;
|
|
70
|
-
--button-primary-surface-hover: #e7244a;
|
|
71
|
-
--button-primary-surface-pressed: #c61c3f;
|
|
72
|
-
--button-primary-text-default: #ffffff;
|
|
73
|
-
--button-primary-border-default: #fe2c55;
|
|
74
|
-
--button-primary-border-hover: #e7244a;
|
|
75
|
-
--button-primary-border-pressed: #c61c3f;
|
|
76
|
-
|
|
77
|
-
--button-secondary-surface-default: rgba(255, 255, 255, 0.04);
|
|
78
|
-
--button-secondary-surface-hover: rgba(255, 255, 255, 0.08);
|
|
79
|
-
--button-secondary-text-default: #25f4ee;
|
|
80
|
-
--button-secondary-border-default: rgba(37, 244, 238, 0.55);
|
|
81
|
-
--button-secondary-border-hover: rgba(37, 244, 238, 0.8);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
.IncodeTikTokTheme .IncodePageHeader {
|
|
85
|
-
background: linear-gradient(135deg, #0b0b0f 0%, #000000 100%);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
.IncodeTikTokTheme .IncodePageHeader .IncodePageLogo {
|
|
89
|
-
filter: brightness(0) invert(1);
|
|
90
|
-
height: var(--tiktok-logo-height);
|
|
91
|
-
width: auto;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
.IncodeTikTokTheme .IncodeButton:hover {
|
|
95
|
-
box-shadow: 0 4px 14px rgba(254, 44, 85, 0.25);
|
|
96
|
-
}
|
|
97
|
-
`}
|
|
98
|
-
</style>
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
import { setup } from '../src/setup';
|
|
102
|
-
import { getConfigIdFromUrl } from './getToken';
|
|
103
|
-
|
|
104
|
-
// Configuration
|
|
105
|
-
const API_URL = 'https://user-service-k8s.stage.incodetest.com/0';
|
|
106
|
-
const API_KEY = '5fbc0ab5a652b7808fbec42fffd8b4c9ec248413';
|
|
107
|
-
|
|
108
|
-
const WASM_CONFIG: WasmConfig = {
|
|
109
|
-
wasmPath:
|
|
110
|
-
'https://d3vv997wtl2myz.cloudfront.net/webcamera/onnx-backend-wasm/v2.12.47/webLib.wasm',
|
|
111
|
-
wasmSimdPath:
|
|
112
|
-
'https://d3vv997wtl2myz.cloudfront.net/webcamera/onnx-backend-wasm/v2.12.47/webLibSimd.wasm',
|
|
113
|
-
glueCodePath:
|
|
114
|
-
'https://d3vv997wtl2myz.cloudfront.net/webcamera/onnx-backend-wasm/v2.12.47/webLib.js',
|
|
115
|
-
modelsBasePath:
|
|
116
|
-
'https://d3vv997wtl2myz.cloudfront.net/webcamera/onnx-backend-wasm/v2.12.47/models',
|
|
117
|
-
// pipelines will be determined by flow analysis
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
// ============================================================================
|
|
121
|
-
// CUSTOM COMPONENTS - These demonstrate full UI customization
|
|
122
|
-
// ============================================================================
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Custom animated loader with pulse effect
|
|
126
|
-
*/
|
|
127
|
-
const CustomLoader: FC<{ message?: string }> = ({ message = 'Loading...' }) => (
|
|
128
|
-
<div
|
|
129
|
-
style={{
|
|
130
|
-
minHeight: '100vh',
|
|
131
|
-
background:
|
|
132
|
-
'linear-gradient(135deg, #0f172a 0%, #1e3a5f 50%, #2563eb 100%)',
|
|
133
|
-
display: 'flex',
|
|
134
|
-
flexDirection: 'column',
|
|
135
|
-
alignItems: 'center',
|
|
136
|
-
justifyContent: 'center',
|
|
137
|
-
gap: '2rem',
|
|
138
|
-
}}
|
|
139
|
-
>
|
|
140
|
-
{/* Animated rings - Blue & Gold */}
|
|
141
|
-
<div style={{ position: 'relative', width: '120px', height: '120px' }}>
|
|
142
|
-
<div
|
|
143
|
-
style={{
|
|
144
|
-
position: 'absolute',
|
|
145
|
-
inset: 0,
|
|
146
|
-
border: '3px solid transparent',
|
|
147
|
-
borderTopColor: '#3b82f6',
|
|
148
|
-
borderRadius: '50%',
|
|
149
|
-
animation: 'spin 1s linear infinite',
|
|
150
|
-
}}
|
|
151
|
-
/>
|
|
152
|
-
<div
|
|
153
|
-
style={{
|
|
154
|
-
position: 'absolute',
|
|
155
|
-
inset: '10px',
|
|
156
|
-
border: '3px solid transparent',
|
|
157
|
-
borderTopColor: '#f59e0b',
|
|
158
|
-
borderRadius: '50%',
|
|
159
|
-
animation: 'spin 1.5s linear infinite reverse',
|
|
160
|
-
}}
|
|
161
|
-
/>
|
|
162
|
-
<div
|
|
163
|
-
style={{
|
|
164
|
-
position: 'absolute',
|
|
165
|
-
inset: '20px',
|
|
166
|
-
border: '3px solid transparent',
|
|
167
|
-
borderTopColor: '#60a5fa',
|
|
168
|
-
borderRadius: '50%',
|
|
169
|
-
animation: 'spin 2s linear infinite',
|
|
170
|
-
}}
|
|
171
|
-
/>
|
|
172
|
-
{/* Center dot - Gold */}
|
|
173
|
-
<div
|
|
174
|
-
style={{
|
|
175
|
-
position: 'absolute',
|
|
176
|
-
top: '50%',
|
|
177
|
-
left: '50%',
|
|
178
|
-
transform: 'translate(-50%, -50%)',
|
|
179
|
-
width: '16px',
|
|
180
|
-
height: '16px',
|
|
181
|
-
background: '#f59e0b',
|
|
182
|
-
borderRadius: '50%',
|
|
183
|
-
animation: 'pulse 1s ease-in-out infinite',
|
|
184
|
-
}}
|
|
185
|
-
/>
|
|
186
|
-
</div>
|
|
187
|
-
|
|
188
|
-
{/* Message */}
|
|
189
|
-
<div style={{ textAlign: 'center' }}>
|
|
190
|
-
<p
|
|
191
|
-
style={{
|
|
192
|
-
color: '#fff',
|
|
193
|
-
fontSize: '1.25rem',
|
|
194
|
-
fontWeight: 500,
|
|
195
|
-
marginBottom: '0.5rem',
|
|
196
|
-
}}
|
|
197
|
-
>
|
|
198
|
-
{message}
|
|
199
|
-
</p>
|
|
200
|
-
<p style={{ color: '#94a3b8', fontSize: '0.875rem' }}>
|
|
201
|
-
Custom loader • Your brand, your style
|
|
202
|
-
</p>
|
|
203
|
-
</div>
|
|
204
|
-
|
|
205
|
-
{/* CSS Animations */}
|
|
206
|
-
<style>
|
|
207
|
-
{`
|
|
208
|
-
@keyframes spin {
|
|
209
|
-
to { transform: rotate(360deg); }
|
|
210
|
-
}
|
|
211
|
-
@keyframes pulse {
|
|
212
|
-
0%, 100% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
|
|
213
|
-
50% { opacity: 0.5; transform: translate(-50%, -50%) scale(0.8); }
|
|
214
|
-
}
|
|
215
|
-
`}
|
|
216
|
-
</style>
|
|
217
|
-
</div>
|
|
218
|
-
);
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Custom landing page with gradient design
|
|
222
|
-
*/
|
|
223
|
-
const CustomLandingPage: FC<{
|
|
224
|
-
onStart: () => void;
|
|
225
|
-
}> = ({ onStart }) => (
|
|
226
|
-
<div
|
|
227
|
-
style={{
|
|
228
|
-
minHeight: '100vh',
|
|
229
|
-
background: 'linear-gradient(135deg, #1e3a5f 0%, #2563eb 100%)',
|
|
230
|
-
display: 'flex',
|
|
231
|
-
flexDirection: 'column',
|
|
232
|
-
alignItems: 'center',
|
|
233
|
-
justifyContent: 'center',
|
|
234
|
-
padding: '2rem',
|
|
235
|
-
position: 'relative',
|
|
236
|
-
overflow: 'hidden',
|
|
237
|
-
}}
|
|
238
|
-
>
|
|
239
|
-
{/* Background decoration */}
|
|
240
|
-
<div
|
|
241
|
-
style={{
|
|
242
|
-
position: 'absolute',
|
|
243
|
-
top: '-50%',
|
|
244
|
-
right: '-20%',
|
|
245
|
-
width: '600px',
|
|
246
|
-
height: '600px',
|
|
247
|
-
background: 'rgba(255,255,255,0.1)',
|
|
248
|
-
borderRadius: '50%',
|
|
249
|
-
filter: 'blur(60px)',
|
|
250
|
-
}}
|
|
251
|
-
/>
|
|
252
|
-
<div
|
|
253
|
-
style={{
|
|
254
|
-
position: 'absolute',
|
|
255
|
-
bottom: '-30%',
|
|
256
|
-
left: '-10%',
|
|
257
|
-
width: '400px',
|
|
258
|
-
height: '400px',
|
|
259
|
-
background: 'rgba(255,255,255,0.08)',
|
|
260
|
-
borderRadius: '50%',
|
|
261
|
-
filter: 'blur(40px)',
|
|
262
|
-
}}
|
|
263
|
-
/>
|
|
264
|
-
|
|
265
|
-
{/* Content */}
|
|
266
|
-
<div
|
|
267
|
-
style={{
|
|
268
|
-
position: 'relative',
|
|
269
|
-
zIndex: 1,
|
|
270
|
-
textAlign: 'center',
|
|
271
|
-
maxWidth: '500px',
|
|
272
|
-
}}
|
|
273
|
-
>
|
|
274
|
-
{/* Badge */}
|
|
275
|
-
<div
|
|
276
|
-
style={{
|
|
277
|
-
display: 'inline-flex',
|
|
278
|
-
alignItems: 'center',
|
|
279
|
-
gap: '0.5rem',
|
|
280
|
-
background: 'rgba(255,255,255,0.2)',
|
|
281
|
-
backdropFilter: 'blur(10px)',
|
|
282
|
-
borderRadius: '9999px',
|
|
283
|
-
padding: '0.5rem 1rem',
|
|
284
|
-
marginBottom: '2rem',
|
|
285
|
-
}}
|
|
286
|
-
>
|
|
287
|
-
<span style={{ fontSize: '0.875rem' }}>🎨</span>
|
|
288
|
-
<span
|
|
289
|
-
style={{
|
|
290
|
-
color: '#fff',
|
|
291
|
-
fontSize: '0.75rem',
|
|
292
|
-
fontWeight: 600,
|
|
293
|
-
textTransform: 'uppercase',
|
|
294
|
-
letterSpacing: '0.1em',
|
|
295
|
-
}}
|
|
296
|
-
>
|
|
297
|
-
Custom Landing Page
|
|
298
|
-
</span>
|
|
299
|
-
</div>
|
|
300
|
-
|
|
301
|
-
{/* Title */}
|
|
302
|
-
<h1
|
|
303
|
-
style={{
|
|
304
|
-
color: '#fff',
|
|
305
|
-
fontSize: '2.5rem',
|
|
306
|
-
fontWeight: 700,
|
|
307
|
-
marginBottom: '1rem',
|
|
308
|
-
lineHeight: 1.2,
|
|
309
|
-
}}
|
|
310
|
-
>
|
|
311
|
-
Welcome to Your
|
|
312
|
-
<br />
|
|
313
|
-
<span style={{ color: '#ffd700' }}>Branded Experience</span>
|
|
314
|
-
</h1>
|
|
315
|
-
|
|
316
|
-
{/* Subtitle */}
|
|
317
|
-
<p
|
|
318
|
-
style={{
|
|
319
|
-
color: 'rgba(255,255,255,0.85)',
|
|
320
|
-
fontSize: '1.1rem',
|
|
321
|
-
lineHeight: 1.6,
|
|
322
|
-
marginBottom: '1rem',
|
|
323
|
-
}}
|
|
324
|
-
>
|
|
325
|
-
This is a fully customized start screen. With the orchestrated flow
|
|
326
|
-
pattern, you control every pixel of the user experience.
|
|
327
|
-
</p>
|
|
328
|
-
|
|
329
|
-
{/* Feature badges */}
|
|
330
|
-
<div
|
|
331
|
-
style={{
|
|
332
|
-
display: 'flex',
|
|
333
|
-
flexWrap: 'wrap',
|
|
334
|
-
justifyContent: 'center',
|
|
335
|
-
gap: '0.5rem',
|
|
336
|
-
marginBottom: '2rem',
|
|
337
|
-
}}
|
|
338
|
-
>
|
|
339
|
-
{[
|
|
340
|
-
'🎨 Custom Colors',
|
|
341
|
-
'✨ Custom Loader',
|
|
342
|
-
'📝 Custom Screens',
|
|
343
|
-
'🎯 Brand Fonts',
|
|
344
|
-
].map((feature) => (
|
|
345
|
-
<span
|
|
346
|
-
key={feature}
|
|
347
|
-
style={{
|
|
348
|
-
background: 'rgba(255,255,255,0.1)',
|
|
349
|
-
borderRadius: '9999px',
|
|
350
|
-
padding: '0.375rem 0.75rem',
|
|
351
|
-
fontSize: '0.75rem',
|
|
352
|
-
color: 'rgba(255,255,255,0.9)',
|
|
353
|
-
}}
|
|
354
|
-
>
|
|
355
|
-
{feature}
|
|
356
|
-
</span>
|
|
357
|
-
))}
|
|
358
|
-
</div>
|
|
359
|
-
|
|
360
|
-
{/* Modules available */}
|
|
361
|
-
<div
|
|
362
|
-
style={{
|
|
363
|
-
display: 'flex',
|
|
364
|
-
justifyContent: 'center',
|
|
365
|
-
gap: '0.75rem',
|
|
366
|
-
marginBottom: '2.5rem',
|
|
367
|
-
flexWrap: 'wrap',
|
|
368
|
-
}}
|
|
369
|
-
>
|
|
370
|
-
{['📸 Selfie', '📱 Phone', '✉️ Email'].map((module) => (
|
|
371
|
-
<div
|
|
372
|
-
key={module}
|
|
373
|
-
style={{
|
|
374
|
-
background: 'rgba(255,255,255,0.15)',
|
|
375
|
-
borderRadius: '8px',
|
|
376
|
-
padding: '0.5rem 1rem',
|
|
377
|
-
}}
|
|
378
|
-
>
|
|
379
|
-
<span style={{ color: '#fff', fontSize: '0.875rem' }}>
|
|
380
|
-
{module}
|
|
381
|
-
</span>
|
|
382
|
-
</div>
|
|
383
|
-
))}
|
|
384
|
-
</div>
|
|
385
|
-
<p
|
|
386
|
-
style={{
|
|
387
|
-
color: 'rgba(255,255,255,0.6)',
|
|
388
|
-
fontSize: '0.75rem',
|
|
389
|
-
marginBottom: '1.5rem',
|
|
390
|
-
}}
|
|
391
|
-
>
|
|
392
|
-
Step order determined by your flow configuration
|
|
393
|
-
</p>
|
|
394
|
-
|
|
395
|
-
{/* CTA Button - Golden yellow */}
|
|
396
|
-
<button
|
|
397
|
-
type="button"
|
|
398
|
-
onClick={onStart}
|
|
399
|
-
style={{
|
|
400
|
-
background: 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)',
|
|
401
|
-
color: '#1e3a5f',
|
|
402
|
-
border: 'none',
|
|
403
|
-
borderRadius: '12px',
|
|
404
|
-
padding: '1rem 3rem',
|
|
405
|
-
fontSize: '1.1rem',
|
|
406
|
-
fontWeight: 700,
|
|
407
|
-
cursor: 'pointer',
|
|
408
|
-
boxShadow: '0 10px 30px rgba(245, 158, 11, 0.4)',
|
|
409
|
-
transition: 'transform 0.2s, box-shadow 0.2s',
|
|
410
|
-
}}
|
|
411
|
-
>
|
|
412
|
-
Begin Verification →
|
|
413
|
-
</button>
|
|
414
|
-
|
|
415
|
-
{/* Footer */}
|
|
416
|
-
<p
|
|
417
|
-
style={{
|
|
418
|
-
color: 'rgba(255,255,255,0.5)',
|
|
419
|
-
fontSize: '0.75rem',
|
|
420
|
-
marginTop: '3rem',
|
|
421
|
-
}}
|
|
422
|
-
>
|
|
423
|
-
Powered by Incode SDK • Orchestrated Flow Pattern
|
|
424
|
-
</p>
|
|
425
|
-
</div>
|
|
426
|
-
</div>
|
|
427
|
-
);
|
|
428
|
-
|
|
429
|
-
/**
|
|
430
|
-
* Custom error screen
|
|
431
|
-
*/
|
|
432
|
-
const CustomErrorScreen: FC<{
|
|
433
|
-
error: string;
|
|
434
|
-
onRetry: () => void;
|
|
435
|
-
}> = ({ error, onRetry }) => (
|
|
436
|
-
<div
|
|
437
|
-
style={{
|
|
438
|
-
minHeight: '100vh',
|
|
439
|
-
background: 'linear-gradient(135deg, #0f172a 0%, #1e3a5f 100%)',
|
|
440
|
-
display: 'flex',
|
|
441
|
-
flexDirection: 'column',
|
|
442
|
-
alignItems: 'center',
|
|
443
|
-
justifyContent: 'center',
|
|
444
|
-
padding: '2rem',
|
|
445
|
-
}}
|
|
446
|
-
>
|
|
447
|
-
{/* Error icon */}
|
|
448
|
-
<div
|
|
449
|
-
style={{
|
|
450
|
-
width: '80px',
|
|
451
|
-
height: '80px',
|
|
452
|
-
borderRadius: '50%',
|
|
453
|
-
background: 'rgba(255, 91, 91, 0.1)',
|
|
454
|
-
border: '2px solid rgba(255, 91, 91, 0.3)',
|
|
455
|
-
display: 'flex',
|
|
456
|
-
alignItems: 'center',
|
|
457
|
-
justifyContent: 'center',
|
|
458
|
-
marginBottom: '1.5rem',
|
|
459
|
-
}}
|
|
460
|
-
>
|
|
461
|
-
<span style={{ fontSize: '2rem' }}>⚠️</span>
|
|
462
|
-
</div>
|
|
463
|
-
|
|
464
|
-
<h2 style={{ color: '#fff', fontSize: '1.5rem', marginBottom: '0.5rem' }}>
|
|
465
|
-
Something went wrong
|
|
466
|
-
</h2>
|
|
467
|
-
|
|
468
|
-
<p
|
|
469
|
-
style={{
|
|
470
|
-
color: '#ff6b6b',
|
|
471
|
-
fontSize: '0.875rem',
|
|
472
|
-
marginBottom: '2rem',
|
|
473
|
-
maxWidth: '400px',
|
|
474
|
-
textAlign: 'center',
|
|
475
|
-
}}
|
|
476
|
-
>
|
|
477
|
-
{error}
|
|
478
|
-
</p>
|
|
479
|
-
|
|
480
|
-
<button
|
|
481
|
-
type="button"
|
|
482
|
-
onClick={onRetry}
|
|
483
|
-
style={{
|
|
484
|
-
background: 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)',
|
|
485
|
-
color: '#1e3a5f',
|
|
486
|
-
border: 'none',
|
|
487
|
-
borderRadius: '8px',
|
|
488
|
-
padding: '0.75rem 2rem',
|
|
489
|
-
fontSize: '1rem',
|
|
490
|
-
fontWeight: 600,
|
|
491
|
-
cursor: 'pointer',
|
|
492
|
-
}}
|
|
493
|
-
>
|
|
494
|
-
Try Again
|
|
495
|
-
</button>
|
|
496
|
-
|
|
497
|
-
<p style={{ color: '#64748b', fontSize: '0.75rem', marginTop: '2rem' }}>
|
|
498
|
-
Custom error screen • Full control over error states
|
|
499
|
-
</p>
|
|
500
|
-
</div>
|
|
501
|
-
);
|
|
502
|
-
|
|
503
|
-
/**
|
|
504
|
-
* Custom completion screen with JSON viewer
|
|
505
|
-
*/
|
|
506
|
-
const CustomCompletionScreen: FC<{
|
|
507
|
-
finishStatus: {
|
|
508
|
-
redirectionUrl?: string;
|
|
509
|
-
action?: string;
|
|
510
|
-
scoreStatus?: string;
|
|
511
|
-
} | null;
|
|
512
|
-
onReset: () => void;
|
|
513
|
-
}> = ({ finishStatus, onReset }) => {
|
|
514
|
-
const finishData = {
|
|
515
|
-
status: 'completed',
|
|
516
|
-
timestamp: new Date().toISOString(),
|
|
517
|
-
result: {
|
|
518
|
-
redirectionUrl: finishStatus?.redirectionUrl ?? null,
|
|
519
|
-
action: finishStatus?.action ?? null,
|
|
520
|
-
scoreStatus: finishStatus?.scoreStatus ?? null,
|
|
521
|
-
},
|
|
522
|
-
};
|
|
523
|
-
|
|
524
|
-
return (
|
|
525
|
-
<div
|
|
526
|
-
style={{
|
|
527
|
-
minHeight: '100vh',
|
|
528
|
-
background: 'linear-gradient(135deg, #0a0a0f 0%, #1a1a2e 100%)',
|
|
529
|
-
display: 'flex',
|
|
530
|
-
flexDirection: 'column',
|
|
531
|
-
alignItems: 'center',
|
|
532
|
-
justifyContent: 'center',
|
|
533
|
-
padding: '2rem',
|
|
534
|
-
fontFamily:
|
|
535
|
-
'"SF Mono", Monaco, "Cascadia Code", "Roboto Mono", monospace',
|
|
536
|
-
}}
|
|
537
|
-
>
|
|
538
|
-
{/* Success badge */}
|
|
539
|
-
<div
|
|
540
|
-
style={{
|
|
541
|
-
background: 'rgba(16, 185, 129, 0.1)',
|
|
542
|
-
border: '1px solid rgba(16, 185, 129, 0.3)',
|
|
543
|
-
borderRadius: '9999px',
|
|
544
|
-
padding: '0.5rem 1rem',
|
|
545
|
-
marginBottom: '1.5rem',
|
|
546
|
-
display: 'flex',
|
|
547
|
-
alignItems: 'center',
|
|
548
|
-
gap: '0.5rem',
|
|
549
|
-
}}
|
|
550
|
-
>
|
|
551
|
-
<span style={{ color: '#10b981', fontSize: '1.25rem' }}>✓</span>
|
|
552
|
-
<span
|
|
553
|
-
style={{
|
|
554
|
-
color: '#10b981',
|
|
555
|
-
fontSize: '0.875rem',
|
|
556
|
-
fontWeight: 600,
|
|
557
|
-
textTransform: 'uppercase',
|
|
558
|
-
letterSpacing: '0.05em',
|
|
559
|
-
}}
|
|
560
|
-
>
|
|
561
|
-
Flow Completed
|
|
562
|
-
</span>
|
|
563
|
-
</div>
|
|
564
|
-
|
|
565
|
-
{/* Title */}
|
|
566
|
-
<h1
|
|
567
|
-
style={{
|
|
568
|
-
color: '#fff',
|
|
569
|
-
fontSize: '1.5rem',
|
|
570
|
-
fontWeight: 600,
|
|
571
|
-
marginBottom: '0.5rem',
|
|
572
|
-
fontFamily: 'system-ui, sans-serif',
|
|
573
|
-
}}
|
|
574
|
-
>
|
|
575
|
-
Orchestrated Flow Result
|
|
576
|
-
</h1>
|
|
577
|
-
<p
|
|
578
|
-
style={{
|
|
579
|
-
color: '#6b7280',
|
|
580
|
-
fontSize: '0.875rem',
|
|
581
|
-
marginBottom: '2rem',
|
|
582
|
-
}}
|
|
583
|
-
>
|
|
584
|
-
Custom completion screen • Your UI, your data
|
|
585
|
-
</p>
|
|
586
|
-
|
|
587
|
-
{/* JSON Viewer */}
|
|
588
|
-
<div
|
|
589
|
-
style={{
|
|
590
|
-
background: '#0d1117',
|
|
591
|
-
border: '1px solid #30363d',
|
|
592
|
-
borderRadius: '12px',
|
|
593
|
-
width: '100%',
|
|
594
|
-
maxWidth: '500px',
|
|
595
|
-
overflow: 'hidden',
|
|
596
|
-
boxShadow: '0 4px 24px rgba(0, 0, 0, 0.4)',
|
|
597
|
-
}}
|
|
598
|
-
>
|
|
599
|
-
{/* Header bar */}
|
|
600
|
-
<div
|
|
601
|
-
style={{
|
|
602
|
-
background: '#161b22',
|
|
603
|
-
padding: '0.75rem 1rem',
|
|
604
|
-
borderBottom: '1px solid #30363d',
|
|
605
|
-
display: 'flex',
|
|
606
|
-
alignItems: 'center',
|
|
607
|
-
gap: '0.5rem',
|
|
608
|
-
}}
|
|
609
|
-
>
|
|
610
|
-
<div style={{ display: 'flex', gap: '6px' }}>
|
|
611
|
-
<span
|
|
612
|
-
style={{
|
|
613
|
-
width: '12px',
|
|
614
|
-
height: '12px',
|
|
615
|
-
borderRadius: '50%',
|
|
616
|
-
background: '#ff5f56',
|
|
617
|
-
}}
|
|
618
|
-
/>
|
|
619
|
-
<span
|
|
620
|
-
style={{
|
|
621
|
-
width: '12px',
|
|
622
|
-
height: '12px',
|
|
623
|
-
borderRadius: '50%',
|
|
624
|
-
background: '#ffbd2e',
|
|
625
|
-
}}
|
|
626
|
-
/>
|
|
627
|
-
<span
|
|
628
|
-
style={{
|
|
629
|
-
width: '12px',
|
|
630
|
-
height: '12px',
|
|
631
|
-
borderRadius: '50%',
|
|
632
|
-
background: '#27ca40',
|
|
633
|
-
}}
|
|
634
|
-
/>
|
|
635
|
-
</div>
|
|
636
|
-
<span
|
|
637
|
-
style={{
|
|
638
|
-
color: '#8b949e',
|
|
639
|
-
fontSize: '0.75rem',
|
|
640
|
-
marginLeft: 'auto',
|
|
641
|
-
}}
|
|
642
|
-
>
|
|
643
|
-
response.json
|
|
644
|
-
</span>
|
|
645
|
-
</div>
|
|
646
|
-
|
|
647
|
-
{/* JSON content */}
|
|
648
|
-
<pre
|
|
649
|
-
style={{
|
|
650
|
-
margin: 0,
|
|
651
|
-
padding: '1.25rem',
|
|
652
|
-
color: '#e6edf3',
|
|
653
|
-
fontSize: '0.8rem',
|
|
654
|
-
lineHeight: 1.6,
|
|
655
|
-
overflow: 'auto',
|
|
656
|
-
}}
|
|
657
|
-
>
|
|
658
|
-
<code>
|
|
659
|
-
<span style={{ color: '#8b949e' }}>{'{\n'}</span>
|
|
660
|
-
<span style={{ color: '#7ee787' }}> "status"</span>
|
|
661
|
-
<span style={{ color: '#8b949e' }}>: </span>
|
|
662
|
-
<span style={{ color: '#a5d6ff' }}>"{finishData.status}"</span>
|
|
663
|
-
<span style={{ color: '#8b949e' }}>,{'\n'}</span>
|
|
664
|
-
<span style={{ color: '#7ee787' }}> "timestamp"</span>
|
|
665
|
-
<span style={{ color: '#8b949e' }}>: </span>
|
|
666
|
-
<span style={{ color: '#a5d6ff' }}>"{finishData.timestamp}"</span>
|
|
667
|
-
<span style={{ color: '#8b949e' }}>,{'\n'}</span>
|
|
668
|
-
<span style={{ color: '#7ee787' }}> "result"</span>
|
|
669
|
-
<span style={{ color: '#8b949e' }}>: {'{\n'}</span>
|
|
670
|
-
<span style={{ color: '#7ee787' }}> "redirectionUrl"</span>
|
|
671
|
-
<span style={{ color: '#8b949e' }}>: </span>
|
|
672
|
-
<span style={{ color: '#a5d6ff' }}>
|
|
673
|
-
{finishData.result.redirectionUrl
|
|
674
|
-
? `"${finishData.result.redirectionUrl}"`
|
|
675
|
-
: 'null'}
|
|
676
|
-
</span>
|
|
677
|
-
<span style={{ color: '#8b949e' }}>,{'\n'}</span>
|
|
678
|
-
<span style={{ color: '#7ee787' }}> "action"</span>
|
|
679
|
-
<span style={{ color: '#8b949e' }}>: </span>
|
|
680
|
-
<span
|
|
681
|
-
style={{
|
|
682
|
-
color:
|
|
683
|
-
finishData.result.action === 'approved'
|
|
684
|
-
? '#7ee787'
|
|
685
|
-
: finishData.result.action === 'rejected'
|
|
686
|
-
? '#ff7b72'
|
|
687
|
-
: '#a5d6ff',
|
|
688
|
-
}}
|
|
689
|
-
>
|
|
690
|
-
{finishData.result.action
|
|
691
|
-
? `"${finishData.result.action}"`
|
|
692
|
-
: 'null'}
|
|
693
|
-
</span>
|
|
694
|
-
<span style={{ color: '#8b949e' }}>,{'\n'}</span>
|
|
695
|
-
<span style={{ color: '#7ee787' }}> "scoreStatus"</span>
|
|
696
|
-
<span style={{ color: '#8b949e' }}>: </span>
|
|
697
|
-
<span
|
|
698
|
-
style={{
|
|
699
|
-
color:
|
|
700
|
-
finishData.result.scoreStatus === 'OK'
|
|
701
|
-
? '#7ee787'
|
|
702
|
-
: finishData.result.scoreStatus === 'FAIL'
|
|
703
|
-
? '#ff7b72'
|
|
704
|
-
: '#ffa657',
|
|
705
|
-
}}
|
|
706
|
-
>
|
|
707
|
-
{finishData.result.scoreStatus
|
|
708
|
-
? `"${finishData.result.scoreStatus}"`
|
|
709
|
-
: 'null'}
|
|
710
|
-
</span>
|
|
711
|
-
<span style={{ color: '#8b949e' }}>{'\n }\n}'}</span>
|
|
712
|
-
</code>
|
|
713
|
-
</pre>
|
|
714
|
-
</div>
|
|
715
|
-
|
|
716
|
-
{/* Actions */}
|
|
717
|
-
<div style={{ display: 'flex', gap: '1rem', marginTop: '2rem' }}>
|
|
718
|
-
<button
|
|
719
|
-
type="button"
|
|
720
|
-
onClick={onReset}
|
|
721
|
-
style={{
|
|
722
|
-
background: 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)',
|
|
723
|
-
color: '#1e3a5f',
|
|
724
|
-
border: 'none',
|
|
725
|
-
borderRadius: '8px',
|
|
726
|
-
padding: '0.75rem 1.5rem',
|
|
727
|
-
fontSize: '0.875rem',
|
|
728
|
-
fontWeight: 600,
|
|
729
|
-
cursor: 'pointer',
|
|
730
|
-
}}
|
|
731
|
-
>
|
|
732
|
-
Start Over
|
|
733
|
-
</button>
|
|
734
|
-
{finishData.result.redirectionUrl && (
|
|
735
|
-
<button
|
|
736
|
-
type="button"
|
|
737
|
-
onClick={() => {
|
|
738
|
-
const url = finishData.result.redirectionUrl;
|
|
739
|
-
if (url) {
|
|
740
|
-
window.location.assign(url);
|
|
741
|
-
}
|
|
742
|
-
}}
|
|
743
|
-
style={{
|
|
744
|
-
background: 'transparent',
|
|
745
|
-
color: '#60a5fa',
|
|
746
|
-
border: '1px solid #30363d',
|
|
747
|
-
borderRadius: '8px',
|
|
748
|
-
padding: '0.75rem 1.5rem',
|
|
749
|
-
fontSize: '0.875rem',
|
|
750
|
-
fontWeight: 600,
|
|
751
|
-
cursor: 'pointer',
|
|
752
|
-
}}
|
|
753
|
-
>
|
|
754
|
-
Go to Redirect URL →
|
|
755
|
-
</button>
|
|
756
|
-
)}
|
|
757
|
-
</div>
|
|
758
|
-
|
|
759
|
-
<p style={{ color: '#484f58', fontSize: '0.75rem', marginTop: '3rem' }}>
|
|
760
|
-
Custom completion screen • Orchestrated Flow Pattern
|
|
761
|
-
</p>
|
|
762
|
-
</div>
|
|
763
|
-
);
|
|
764
|
-
};
|
|
765
|
-
|
|
766
|
-
// ============================================================================
|
|
767
|
-
// MAIN APP
|
|
768
|
-
// ============================================================================
|
|
769
|
-
|
|
770
|
-
const App: FC = () => {
|
|
771
|
-
const [isInitializing, setIsInitializing] = useState(true);
|
|
772
|
-
const [initError, setInitError] = useState<string | null>(null);
|
|
773
|
-
const [hasStarted, setHasStarted] = useState(false);
|
|
774
|
-
const [loadedComponent, setLoadedComponent] = useState<ComponentType<{
|
|
775
|
-
config: FlowModuleConfig[keyof FlowModuleConfig];
|
|
776
|
-
onFinish: () => void;
|
|
777
|
-
onError: (error: string | undefined) => void;
|
|
778
|
-
}> | null>(null);
|
|
779
|
-
const [loadingModule, setLoadingModule] = useState<string | null>(null);
|
|
780
|
-
const [moduleError, setModuleError] = useState<string | null>(null);
|
|
781
|
-
const prefetchedModulesRef = useRef<Set<string>>(new Set());
|
|
782
|
-
|
|
783
|
-
const [state, flowManager] = useManager(
|
|
784
|
-
() =>
|
|
785
|
-
createOrchestratedFlowManager({
|
|
786
|
-
modules: {
|
|
787
|
-
SELFIE: selfieMachine,
|
|
788
|
-
PHONE: phoneMachine,
|
|
789
|
-
EMAIL: emailMachine,
|
|
790
|
-
},
|
|
791
|
-
}),
|
|
792
|
-
{ autoLoad: false },
|
|
793
|
-
);
|
|
794
|
-
|
|
795
|
-
// Initialize SDK and create session
|
|
796
|
-
useEffect(() => {
|
|
797
|
-
const init = async () => {
|
|
798
|
-
try {
|
|
799
|
-
const configurationId = getConfigIdFromUrl();
|
|
800
|
-
|
|
801
|
-
// Setup SDK without token first (no WASM - will warmup conditionally)
|
|
802
|
-
await setup({
|
|
803
|
-
apiURL: API_URL,
|
|
804
|
-
token: '',
|
|
805
|
-
i18n: { lang: 'en' },
|
|
806
|
-
uiConfig: {
|
|
807
|
-
logoSrc: tiktokLogo,
|
|
808
|
-
logoHeight: '24px',
|
|
809
|
-
},
|
|
810
|
-
});
|
|
811
|
-
|
|
812
|
-
// Create session
|
|
813
|
-
const session = await createSession(API_KEY, {
|
|
814
|
-
configurationId,
|
|
815
|
-
language: 'en-US',
|
|
816
|
-
});
|
|
817
|
-
|
|
818
|
-
// Re-setup with the session token (no WASM - will warmup conditionally)
|
|
819
|
-
await setup({
|
|
820
|
-
apiURL: API_URL,
|
|
821
|
-
token: session.token,
|
|
822
|
-
i18n: { lang: 'en' },
|
|
823
|
-
uiConfig: {
|
|
824
|
-
logoSrc: tiktokLogo,
|
|
825
|
-
logoHeight: '24px',
|
|
826
|
-
},
|
|
827
|
-
});
|
|
828
|
-
|
|
829
|
-
setIsInitializing(false);
|
|
830
|
-
} catch (error) {
|
|
831
|
-
console.error('Initialization error:', error);
|
|
832
|
-
setInitError(
|
|
833
|
-
error instanceof Error ? error.message : 'Failed to initialize',
|
|
834
|
-
);
|
|
835
|
-
setIsInitializing(false);
|
|
836
|
-
}
|
|
837
|
-
};
|
|
838
|
-
init();
|
|
839
|
-
}, []);
|
|
840
|
-
|
|
841
|
-
// Handle starting the flow
|
|
842
|
-
const handleStart = () => {
|
|
843
|
-
setHasStarted(true);
|
|
844
|
-
flowManager.load();
|
|
845
|
-
};
|
|
846
|
-
|
|
847
|
-
// Conditional WASM warmup based on flow modules
|
|
848
|
-
useEffect(() => {
|
|
849
|
-
if (state.status === 'ready' && WASM_CONFIG) {
|
|
850
|
-
const readyState = state as OrchestratedFlowReadyState;
|
|
851
|
-
const requiredPipelines = getRequiredWasmPipelines(readyState.flow);
|
|
852
|
-
if (requiredPipelines.length > 0) {
|
|
853
|
-
const warmupConfig = {
|
|
854
|
-
...WASM_CONFIG,
|
|
855
|
-
pipelines: requiredPipelines,
|
|
856
|
-
};
|
|
857
|
-
setWasmConfig(warmupConfig);
|
|
858
|
-
void (async () => {
|
|
859
|
-
try {
|
|
860
|
-
await warmupWasm(warmupConfig);
|
|
861
|
-
await initializeWasmUtil(warmupConfig);
|
|
862
|
-
} catch (error) {
|
|
863
|
-
console.error('WASM warmup failed:', error);
|
|
864
|
-
}
|
|
865
|
-
})();
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
}, [state.status, state]);
|
|
869
|
-
|
|
870
|
-
// Prefetch next module while user interacts with current module
|
|
871
|
-
useEffect(() => {
|
|
872
|
-
if (state.status === 'ready') {
|
|
873
|
-
const readyState = state;
|
|
874
|
-
if (readyState.currentStepIndex < readyState.steps.length - 1) {
|
|
875
|
-
const nextStep = readyState.steps[readyState.currentStepIndex + 1];
|
|
876
|
-
if (nextStep && !prefetchedModulesRef.current.has(nextStep)) {
|
|
877
|
-
prefetchedModulesRef.current.add(nextStep);
|
|
878
|
-
const loader =
|
|
879
|
-
LAZY_UI_MODULES[nextStep as keyof typeof LAZY_UI_MODULES];
|
|
880
|
-
if (loader) {
|
|
881
|
-
loader().catch(() => {
|
|
882
|
-
prefetchedModulesRef.current.delete(nextStep);
|
|
883
|
-
});
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
}, [state]);
|
|
889
|
-
|
|
890
|
-
// Load current module component
|
|
891
|
-
useEffect(() => {
|
|
892
|
-
if (state.status === 'ready') {
|
|
893
|
-
const readyState = state;
|
|
894
|
-
const moduleKey = readyState.currentStep;
|
|
895
|
-
if (!moduleKey) {
|
|
896
|
-
setLoadedComponent(null);
|
|
897
|
-
setLoadingModule(null);
|
|
898
|
-
setModuleError(null);
|
|
899
|
-
return;
|
|
900
|
-
}
|
|
901
|
-
const loader = LAZY_UI_MODULES[moduleKey as keyof typeof LAZY_UI_MODULES];
|
|
902
|
-
|
|
903
|
-
if (!loader) {
|
|
904
|
-
setModuleError(`Unsupported module: ${moduleKey}`);
|
|
905
|
-
setLoadingModule(null);
|
|
906
|
-
setLoadedComponent(null);
|
|
907
|
-
return;
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
setLoadingModule(moduleKey);
|
|
911
|
-
setModuleError(null);
|
|
912
|
-
setLoadedComponent(null);
|
|
913
|
-
|
|
914
|
-
let cancelled = false;
|
|
915
|
-
|
|
916
|
-
loader()
|
|
917
|
-
.then((Component) => {
|
|
918
|
-
if (!cancelled) {
|
|
919
|
-
setLoadedComponent(() => Component);
|
|
920
|
-
setLoadingModule(null);
|
|
921
|
-
}
|
|
922
|
-
})
|
|
923
|
-
.catch((error) => {
|
|
924
|
-
if (!cancelled) {
|
|
925
|
-
const errorMessage =
|
|
926
|
-
error instanceof Error ? error.message : 'Failed to load module';
|
|
927
|
-
setModuleError(errorMessage);
|
|
928
|
-
setLoadingModule(null);
|
|
929
|
-
console.error(`Failed to load ${moduleKey}:`, errorMessage);
|
|
930
|
-
}
|
|
931
|
-
});
|
|
932
|
-
|
|
933
|
-
return () => {
|
|
934
|
-
cancelled = true;
|
|
935
|
-
};
|
|
936
|
-
} else {
|
|
937
|
-
setLoadedComponent(null);
|
|
938
|
-
setLoadingModule(null);
|
|
939
|
-
setModuleError(null);
|
|
940
|
-
}
|
|
941
|
-
}, [state]);
|
|
942
|
-
|
|
943
|
-
// ============================================================================
|
|
944
|
-
// RENDER STATES
|
|
945
|
-
// ============================================================================
|
|
946
|
-
|
|
947
|
-
// 1. Custom Loader while initializing
|
|
948
|
-
if (isInitializing) {
|
|
949
|
-
return <CustomLoader message="Preparing your experience..." />;
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
// 2. Custom Error Screen
|
|
953
|
-
if (initError) {
|
|
954
|
-
return (
|
|
955
|
-
<CustomErrorScreen
|
|
956
|
-
error={initError}
|
|
957
|
-
onRetry={() => window.location.reload()}
|
|
958
|
-
/>
|
|
959
|
-
);
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
// 3. Custom Landing Page (before user clicks start)
|
|
963
|
-
if (!hasStarted) {
|
|
964
|
-
return <CustomLandingPage onStart={handleStart} />;
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
// 4. Loading flow configuration
|
|
968
|
-
if (state.status === 'loading') {
|
|
969
|
-
return <CustomLoader message="Loading verification steps..." />;
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
// 5. Flow error
|
|
973
|
-
if (state.status === 'error') {
|
|
974
|
-
return (
|
|
975
|
-
<CustomErrorScreen
|
|
976
|
-
error={state.error ?? 'An unknown error occurred'}
|
|
977
|
-
onRetry={() => flowManager.reset()}
|
|
978
|
-
/>
|
|
979
|
-
);
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
// 6. Flow completed - Custom completion screen
|
|
983
|
-
if (state.status === 'finished') {
|
|
984
|
-
return (
|
|
985
|
-
<CustomCompletionScreen
|
|
986
|
-
finishStatus={state.finishStatus}
|
|
987
|
-
onReset={() => {
|
|
988
|
-
setHasStarted(false);
|
|
989
|
-
flowManager.reset();
|
|
990
|
-
}}
|
|
991
|
-
/>
|
|
992
|
-
);
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
// 7. Ready - Render modules
|
|
996
|
-
if (state.status === 'ready') {
|
|
997
|
-
const { currentStep, config, moduleState } = state;
|
|
998
|
-
|
|
999
|
-
if (!currentStep) {
|
|
1000
|
-
return <CustomLoader message="Preparing next step..." />;
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
// Show loading state while module is being loaded
|
|
1004
|
-
// This demonstrates using the SDK's reusable Spinner component
|
|
1005
|
-
// with CSS variable overrides (see orchestrated-flow.html for the CSS)
|
|
1006
|
-
if (loadingModule || !loadedComponent) {
|
|
1007
|
-
return (
|
|
1008
|
-
<div class="IncodeComponent">
|
|
1009
|
-
<Spinner title="Loading..." size="large" fullScreen />
|
|
1010
|
-
</div>
|
|
1011
|
-
);
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
// Show error state if module failed to load
|
|
1015
|
-
if (moduleError) {
|
|
1016
|
-
return (
|
|
1017
|
-
<CustomErrorScreen
|
|
1018
|
-
error={`Failed to load ${currentStep} module: ${moduleError}`}
|
|
1019
|
-
onRetry={() => {
|
|
1020
|
-
setModuleError(null);
|
|
1021
|
-
setLoadingModule(currentStep);
|
|
1022
|
-
const loader =
|
|
1023
|
-
LAZY_UI_MODULES[currentStep as keyof typeof LAZY_UI_MODULES];
|
|
1024
|
-
if (loader) {
|
|
1025
|
-
loader()
|
|
1026
|
-
.then((Component) => {
|
|
1027
|
-
setLoadedComponent(() => Component);
|
|
1028
|
-
setLoadingModule(null);
|
|
1029
|
-
})
|
|
1030
|
-
.catch((error) => {
|
|
1031
|
-
setModuleError(
|
|
1032
|
-
error instanceof Error
|
|
1033
|
-
? error.message
|
|
1034
|
-
: 'Failed to load module',
|
|
1035
|
-
);
|
|
1036
|
-
setLoadingModule(null);
|
|
1037
|
-
});
|
|
1038
|
-
}
|
|
1039
|
-
}}
|
|
1040
|
-
/>
|
|
1041
|
-
);
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
// Render the appropriate module based on current step
|
|
1045
|
-
// Note: CustomBrandTheme overrides SDK colors for your brand
|
|
1046
|
-
const Component = loadedComponent;
|
|
1047
|
-
|
|
1048
|
-
if (currentStep === 'SELFIE') {
|
|
1049
|
-
const selfieConfig = config as FlowModuleConfig['SELFIE'];
|
|
1050
|
-
return (
|
|
1051
|
-
<div class="IncodeComponent IncodeTikTokTheme">
|
|
1052
|
-
<CustomBrandTheme />
|
|
1053
|
-
<Component
|
|
1054
|
-
key="selfie"
|
|
1055
|
-
config={selfieConfig}
|
|
1056
|
-
onFinish={() => flowManager.completeModule()}
|
|
1057
|
-
onError={(error: string | undefined) => {
|
|
1058
|
-
console.error('Selfie error:', error);
|
|
1059
|
-
}}
|
|
1060
|
-
/>
|
|
1061
|
-
</div>
|
|
1062
|
-
);
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
if (currentStep === 'PHONE') {
|
|
1066
|
-
const phoneConfig = config as FlowModuleConfig['PHONE'];
|
|
1067
|
-
return (
|
|
1068
|
-
<div class="IncodeComponent IncodeTikTokTheme">
|
|
1069
|
-
<CustomBrandTheme />
|
|
1070
|
-
<Component
|
|
1071
|
-
key="phone"
|
|
1072
|
-
config={phoneConfig}
|
|
1073
|
-
onFinish={() => flowManager.completeModule()}
|
|
1074
|
-
onError={(error: string | undefined) => {
|
|
1075
|
-
console.error('Phone error:', error);
|
|
1076
|
-
alert(`Phone error: ${error ?? 'Unknown error'}`);
|
|
1077
|
-
}}
|
|
1078
|
-
/>
|
|
1079
|
-
</div>
|
|
1080
|
-
);
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
if (currentStep === 'EMAIL') {
|
|
1084
|
-
const flowEmailConfig = config as FlowModuleConfig['EMAIL'];
|
|
1085
|
-
const emailConfig = {
|
|
1086
|
-
...flowEmailConfig,
|
|
1087
|
-
prefill: flowEmailConfig.prefill ?? false,
|
|
1088
|
-
};
|
|
1089
|
-
return (
|
|
1090
|
-
<div class="IncodeComponent IncodeTikTokTheme">
|
|
1091
|
-
<CustomBrandTheme />
|
|
1092
|
-
<Component
|
|
1093
|
-
key="email"
|
|
1094
|
-
config={emailConfig}
|
|
1095
|
-
onFinish={() => flowManager.completeModule()}
|
|
1096
|
-
onError={(error: string | undefined) => {
|
|
1097
|
-
console.error('Email error:', error);
|
|
1098
|
-
alert(`Email error: ${error ?? 'Unknown error'}`);
|
|
1099
|
-
}}
|
|
1100
|
-
/>
|
|
1101
|
-
</div>
|
|
1102
|
-
);
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
// Unsupported module - show debug info
|
|
1106
|
-
return (
|
|
1107
|
-
<div
|
|
1108
|
-
style={{
|
|
1109
|
-
minHeight: '100vh',
|
|
1110
|
-
background: '#1a1a2e',
|
|
1111
|
-
display: 'flex',
|
|
1112
|
-
flexDirection: 'column',
|
|
1113
|
-
alignItems: 'center',
|
|
1114
|
-
justifyContent: 'center',
|
|
1115
|
-
padding: '2rem',
|
|
1116
|
-
}}
|
|
1117
|
-
>
|
|
1118
|
-
<h2 style={{ color: '#ffa500', marginBottom: '1rem' }}>
|
|
1119
|
-
Unsupported Module: {currentStep}
|
|
1120
|
-
</h2>
|
|
1121
|
-
<pre
|
|
1122
|
-
style={{
|
|
1123
|
-
background: '#0d1117',
|
|
1124
|
-
padding: '1rem',
|
|
1125
|
-
borderRadius: '8px',
|
|
1126
|
-
color: '#e6edf3',
|
|
1127
|
-
maxWidth: '500px',
|
|
1128
|
-
overflow: 'auto',
|
|
1129
|
-
}}
|
|
1130
|
-
>
|
|
1131
|
-
{JSON.stringify(moduleState, null, 2)}
|
|
1132
|
-
</pre>
|
|
1133
|
-
<button
|
|
1134
|
-
type="button"
|
|
1135
|
-
onClick={() => flowManager.completeModule()}
|
|
1136
|
-
style={{
|
|
1137
|
-
marginTop: '1rem',
|
|
1138
|
-
background: '#ffa500',
|
|
1139
|
-
color: '#000',
|
|
1140
|
-
border: 'none',
|
|
1141
|
-
borderRadius: '8px',
|
|
1142
|
-
padding: '0.75rem 1.5rem',
|
|
1143
|
-
cursor: 'pointer',
|
|
1144
|
-
}}
|
|
1145
|
-
>
|
|
1146
|
-
Skip Module
|
|
1147
|
-
</button>
|
|
1148
|
-
</div>
|
|
1149
|
-
);
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
return <CustomLoader message="Initializing..." />;
|
|
1153
|
-
};
|
|
1154
|
-
|
|
1155
|
-
const root = document.getElementById('root');
|
|
1156
|
-
if (root) {
|
|
1157
|
-
render(<App />, root);
|
|
1158
|
-
}
|