@incodetech/web 2.0.0-alpha.1
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/dev/README.md +163 -0
- package/dev/getToken.ts +36 -0
- package/dev/headless.html +875 -0
- package/dev/index.html +366 -0
- package/dev/main-headless.tsx +1332 -0
- package/dev/main-orchestrated-flow.tsx +1158 -0
- package/dev/main-preact.tsx +323 -0
- package/dev/main-simplified.tsx +123 -0
- package/dev/main-web-component.tsx +256 -0
- package/dev/main.tsx +332 -0
- package/dev/manual.html +27 -0
- package/dev/orchestrated-flow.html +64 -0
- package/dev/simplified.html +64 -0
- package/dev/tiktok-logo.svg +7 -0
- package/package.json +85 -0
- package/src/defineCustomElement.tsx +30 -0
- package/src/email/email.test.tsx +368 -0
- package/src/email/email.tsx +255 -0
- package/src/email/emailInput.test.tsx +264 -0
- package/src/email/emailInput.tsx +85 -0
- package/src/email/styles.css +59 -0
- package/src/flow/flow.test.tsx +796 -0
- package/src/flow/flow.tsx +292 -0
- package/src/flow/flowCompleted.css +30 -0
- package/src/flow/flowCompleted.test.tsx +331 -0
- package/src/flow/flowCompleted.tsx +121 -0
- package/src/flow/flowInit.test.ts +264 -0
- package/src/flow/flowInit.ts +94 -0
- package/src/flow/flowStart.css +58 -0
- package/src/flow/flowStart.test.tsx +49 -0
- package/src/flow/flowStart.tsx +41 -0
- package/src/flow/incode-logo.svg +8 -0
- package/src/flow/index.ts +7 -0
- package/src/flow/preloadFlow.test.ts +421 -0
- package/src/flow/preloadFlow.ts +171 -0
- package/src/flow/styles.css +9 -0
- package/src/flow/unsupportedModule.css +21 -0
- package/src/flow/unsupportedModule.tsx +39 -0
- package/src/flow/useFlowInitialization.test.tsx +292 -0
- package/src/flow/useFlowInitialization.ts +128 -0
- package/src/flow/useModuleLoader.test.tsx +212 -0
- package/src/flow/useModuleLoader.ts +92 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useManager.test.ts +91 -0
- package/src/hooks/useManager.ts +40 -0
- package/src/i18n/index.ts +3 -0
- package/src/i18n/instance.ts +16 -0
- package/src/i18n/setup.ts +184 -0
- package/src/i18n/useTranslation.ts +42 -0
- package/src/index.ts +27 -0
- package/src/permissions/assets/android-dots-icon.svg +7 -0
- package/src/permissions/assets/android-settings-icon.svg +16 -0
- package/src/permissions/assets/android-toggle-icon.svg +20 -0
- package/src/permissions/assets/bank-card-icon.svg +14 -0
- package/src/permissions/assets/camera-icon.svg +12 -0
- package/src/permissions/assets/camera-ios.svg +53 -0
- package/src/permissions/assets/check-icon.svg +8 -0
- package/src/permissions/assets/chrome-icon.svg +43 -0
- package/src/permissions/assets/password-icon.svg +11 -0
- package/src/permissions/assets/permissions-img.svg +51 -0
- package/src/permissions/assets/safari-icon.svg +37 -0
- package/src/permissions/assets/settings-icon.svg +33 -0
- package/src/permissions/assets/toggle-icon.svg +19 -0
- package/src/permissions/assets/warning-icon.svg +6 -0
- package/src/permissions/boldWithArrow.css +9 -0
- package/src/permissions/boldWithArrow.tsx +41 -0
- package/src/permissions/denied.css +37 -0
- package/src/permissions/denied.tsx +29 -0
- package/src/permissions/deniedAndroid.tsx +56 -0
- package/src/permissions/deniedDesktop.css +9 -0
- package/src/permissions/deniedDesktop.tsx +64 -0
- package/src/permissions/deniedIOS.tsx +73 -0
- package/src/permissions/deniedInstructions.tsx +19 -0
- package/src/permissions/iconWrapper.css +9 -0
- package/src/permissions/iconWrapper.tsx +15 -0
- package/src/permissions/learnMore.css +37 -0
- package/src/permissions/learnMore.tsx +85 -0
- package/src/permissions/numberedStep.css +13 -0
- package/src/permissions/numberedStep.tsx +14 -0
- package/src/permissions/permissions.css +13 -0
- package/src/permissions/permissions.tsx +68 -0
- package/src/phone/phone.tsx +246 -0
- package/src/phone/phoneInput.test.tsx +275 -0
- package/src/phone/phoneInput.tsx +249 -0
- package/src/phone/styles.css +158 -0
- package/src/selfie/cameraButton.css +13 -0
- package/src/selfie/cameraButton.tsx +35 -0
- package/src/selfie/capture.css +57 -0
- package/src/selfie/capture.tsx +232 -0
- package/src/selfie/errorModal.tsx +218 -0
- package/src/selfie/errorModalContent.css +33 -0
- package/src/selfie/errorModalContent.tsx +44 -0
- package/src/selfie/faceOutline.css +5 -0
- package/src/selfie/faceOutline.tsx +22 -0
- package/src/selfie/loadingBorder.css +12 -0
- package/src/selfie/loadingBorder.tsx +77 -0
- package/src/selfie/manualCaptureButton.css +13 -0
- package/src/selfie/manualCaptureButton.tsx +35 -0
- package/src/selfie/noMoreAttemptsModal.tsx +44 -0
- package/src/selfie/notification.css +9 -0
- package/src/selfie/notification.tsx +36 -0
- package/src/selfie/retryErrorModal.tsx +56 -0
- package/src/selfie/selfie.test.tsx +458 -0
- package/src/selfie/selfie.tsx +83 -0
- package/src/selfie/selfieTutorial.json +2626 -0
- package/src/selfie/styles.css +1 -0
- package/src/selfie/tutorial.test.tsx +200 -0
- package/src/selfie/tutorial.tsx +43 -0
- package/src/setup.ts +33 -0
- package/src/shared/baseTutorial/baseTutorial.css +21 -0
- package/src/shared/baseTutorial/baseTutorial.test.tsx +184 -0
- package/src/shared/baseTutorial/baseTutorial.tsx +55 -0
- package/src/shared/baseTutorial/replaceBaseTutorial.test.ts +267 -0
- package/src/shared/baseTutorial/replaceBaseTutorial.ts +68 -0
- package/src/shared/button/button.css +55 -0
- package/src/shared/button/button.test.tsx +101 -0
- package/src/shared/button/button.tsx +47 -0
- package/src/shared/componentRoot/incodeComponent.tsx +12 -0
- package/src/shared/countries/countries.test.ts +75 -0
- package/src/shared/countries/countries.ts +139 -0
- package/src/shared/countries/index.ts +6 -0
- package/src/shared/icons/chevronDown.tsx +22 -0
- package/src/shared/icons/index.ts +2 -0
- package/src/shared/icons/successIcon.css +5 -0
- package/src/shared/icons/successIcon.test.tsx +40 -0
- package/src/shared/icons/successIcon.tsx +26 -0
- package/src/shared/loader/loadingIcon.css +28 -0
- package/src/shared/loader/loadingIcon.tsx +67 -0
- package/src/shared/lottie/lottie.tsx +108 -0
- package/src/shared/otpInput/otpInput.css +85 -0
- package/src/shared/otpInput/otpInput.test.tsx +356 -0
- package/src/shared/otpInput/otpInput.tsx +241 -0
- package/src/shared/page/incode-logo.svg +3 -0
- package/src/shared/page/page.css +47 -0
- package/src/shared/page/page.test.tsx +97 -0
- package/src/shared/page/page.tsx +91 -0
- package/src/shared/page/pageUiConfig.test.ts +112 -0
- package/src/shared/page/pageUiConfig.ts +64 -0
- package/src/shared/page/verifiedByIncode.css +5 -0
- package/src/shared/page/verifiedByIncode.tsx +75 -0
- package/src/shared/spacer/spacer.css +149 -0
- package/src/shared/spacer/spacer.test.tsx +143 -0
- package/src/shared/spacer/spacer.tsx +88 -0
- package/src/shared/spinner/index.ts +2 -0
- package/src/shared/spinner/spinner.css +28 -0
- package/src/shared/spinner/spinner.test.tsx +82 -0
- package/src/shared/spinner/spinner.tsx +65 -0
- package/src/shared/title/title.css +7 -0
- package/src/shared/title/title.tsx +12 -0
- package/src/shared/uiConfig/uiConfig.ts +36 -0
- package/src/shared/webComponent/incodeModule.ts +29 -0
- package/src/shared/webComponent/registerIncodeElement.ts +15 -0
- package/src/styles/__mocks__/fetchTheme.ts +19 -0
- package/src/styles/applyTheme.ts +37 -0
- package/src/styles/cn.test.tsx +57 -0
- package/src/styles/cn.tsx +21 -0
- package/src/styles/core.css +12 -0
- package/src/styles/fetchTheme.test.ts +390 -0
- package/src/styles/fetchTheme.ts +88 -0
- package/src/styles/generatePalette.ts +111 -0
- package/src/styles/reset.css +65 -0
- package/src/styles/resolveCssVariableToHex.ts +28 -0
- package/src/styles/tailwind.css +291 -0
- package/src/styles/themeTypes.ts +18 -0
- package/src/styles/tokens/colors.css +190 -0
- package/src/styles/tokens/components.css +174 -0
- package/src/styles/tokens/index.css +4 -0
- package/src/styles/tokens/primitives.css +129 -0
- package/src/styles/tokens/semantic.css +51 -0
- package/src/svg.d.ts +4 -0
- package/src/types/assets.d.ts +1 -0
- package/src/types/custom-elements.d.ts +104 -0
- package/tsconfig.json +22 -0
- package/vite.config.ts +260 -0
- package/vitest.config.ts +40 -0
- package/vitest.setup.ts +16 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import '../src/styles/tokens/index.css';
|
|
2
|
+
import '../src/selfie/selfie';
|
|
3
|
+
import { setup } from '@incodetech/core';
|
|
4
|
+
import type { FlowModuleConfig } from '@incodetech/core/flow';
|
|
5
|
+
import { createFlowManager } from '@incodetech/core/flow';
|
|
6
|
+
import { createSelfieManager } from '@incodetech/core/selfie';
|
|
7
|
+
import { render } from 'preact';
|
|
8
|
+
import type { FC } from 'preact/compat';
|
|
9
|
+
import { useEffect, useState } from 'preact/hooks';
|
|
10
|
+
import { useManager } from '../src/hooks';
|
|
11
|
+
import { getToken } from './getToken';
|
|
12
|
+
|
|
13
|
+
type SelfieModuleProps = {
|
|
14
|
+
config: FlowModuleConfig['SELFIE'];
|
|
15
|
+
onFinish: () => void;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const SelfieModule: FC<SelfieModuleProps> = ({ config, onFinish }) => {
|
|
19
|
+
const [state, selfieManager] = useManager(() =>
|
|
20
|
+
createSelfieManager({ config }),
|
|
21
|
+
);
|
|
22
|
+
const [tutorialLoading, setTutorialLoading] = useState(false);
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (state.status !== 'tutorial') {
|
|
26
|
+
setTutorialLoading(false);
|
|
27
|
+
}
|
|
28
|
+
}, [state.status]);
|
|
29
|
+
|
|
30
|
+
if (state.status === 'loading') {
|
|
31
|
+
return <p>Loading...</p>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (state.status === 'tutorial') {
|
|
35
|
+
return (
|
|
36
|
+
<div>
|
|
37
|
+
<h2>Selfie Tutorial</h2>
|
|
38
|
+
<p>Position your face in the center</p>
|
|
39
|
+
<button
|
|
40
|
+
type="button"
|
|
41
|
+
disabled={tutorialLoading}
|
|
42
|
+
onClick={() => {
|
|
43
|
+
setTutorialLoading(true);
|
|
44
|
+
selfieManager.nextStep();
|
|
45
|
+
}}
|
|
46
|
+
>
|
|
47
|
+
{tutorialLoading ? 'Loading...' : 'Continue'}
|
|
48
|
+
</button>
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
if (state.status === 'permissions') {
|
|
53
|
+
if (state.permissionStatus === 'idle') {
|
|
54
|
+
return (
|
|
55
|
+
<div>
|
|
56
|
+
<h2>Camera Permission</h2>
|
|
57
|
+
<p>We need access to your camera</p>
|
|
58
|
+
<button
|
|
59
|
+
type="button"
|
|
60
|
+
onClick={() => selfieManager.requestPermission()}
|
|
61
|
+
>
|
|
62
|
+
Allow Camera
|
|
63
|
+
</button>
|
|
64
|
+
<button type="button" onClick={() => selfieManager.goToLearnMore()}>
|
|
65
|
+
Learn More
|
|
66
|
+
</button>
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (state.permissionStatus === 'requesting') {
|
|
72
|
+
return <p>Requesting camera access...</p>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (state.permissionStatus === 'denied') {
|
|
76
|
+
return (
|
|
77
|
+
<div>
|
|
78
|
+
<h2>Permission Denied</h2>
|
|
79
|
+
<p>Camera access was denied</p>
|
|
80
|
+
<button type="button" onClick={() => window.location.reload()}>
|
|
81
|
+
Refresh Browser
|
|
82
|
+
</button>
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (state.permissionStatus === 'learnMore') {
|
|
88
|
+
return (
|
|
89
|
+
<div>
|
|
90
|
+
<h2>Why we need camera</h2>
|
|
91
|
+
<p>To verify your identity</p>
|
|
92
|
+
<button
|
|
93
|
+
type="button"
|
|
94
|
+
onClick={() => selfieManager.requestPermission()}
|
|
95
|
+
>
|
|
96
|
+
Allow Camera
|
|
97
|
+
</button>
|
|
98
|
+
<button type="button" onClick={() => selfieManager.back()}>
|
|
99
|
+
Back
|
|
100
|
+
</button>
|
|
101
|
+
<button type="button" onClick={() => selfieManager.close()}>
|
|
102
|
+
Quit
|
|
103
|
+
</button>
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (state.status === 'capture') {
|
|
110
|
+
if (state.captureStatus === 'initializing') {
|
|
111
|
+
return (
|
|
112
|
+
<div>
|
|
113
|
+
<h2>Initializing Camera</h2>
|
|
114
|
+
<p>Please wait...</p>
|
|
115
|
+
</div>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (state.captureStatus === 'detecting') {
|
|
120
|
+
return (
|
|
121
|
+
<div>
|
|
122
|
+
<h2>Face Detection</h2>
|
|
123
|
+
<p>
|
|
124
|
+
Status: <strong>{state.detectionStatus}</strong>
|
|
125
|
+
</p>
|
|
126
|
+
<p>Attempts remaining: {state.attemptsRemaining}</p>
|
|
127
|
+
{state.stream ? <Video stream={state.stream} /> : null}
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (state.captureStatus === 'capturing') {
|
|
133
|
+
return (
|
|
134
|
+
<div>
|
|
135
|
+
<h2>Capturing</h2>
|
|
136
|
+
<p>Taking photo...</p>
|
|
137
|
+
</div>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (state.captureStatus === 'uploading') {
|
|
142
|
+
return (
|
|
143
|
+
<div>
|
|
144
|
+
<h2>Uploading</h2>
|
|
145
|
+
<p>Sending selfie to server...</p>
|
|
146
|
+
</div>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (state.captureStatus === 'uploadError') {
|
|
151
|
+
return (
|
|
152
|
+
<div>
|
|
153
|
+
<h2>Upload Failed</h2>
|
|
154
|
+
<p>Error: {state.uploadError}</p>
|
|
155
|
+
<p>Attempts remaining: {state.attemptsRemaining}</p>
|
|
156
|
+
{state.attemptsRemaining > 0 ? (
|
|
157
|
+
<button type="button" onClick={() => selfieManager.retryCapture()}>
|
|
158
|
+
Retry
|
|
159
|
+
</button>
|
|
160
|
+
) : (
|
|
161
|
+
<>
|
|
162
|
+
<p>
|
|
163
|
+
<strong style={{ color: 'red' }}>
|
|
164
|
+
No more attempts available!
|
|
165
|
+
</strong>
|
|
166
|
+
</p>
|
|
167
|
+
<button type="button" onClick={onFinish}>
|
|
168
|
+
Continue
|
|
169
|
+
</button>
|
|
170
|
+
</>
|
|
171
|
+
)}
|
|
172
|
+
</div>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (state.captureStatus === 'success') {
|
|
177
|
+
return (
|
|
178
|
+
<div>
|
|
179
|
+
<h2>Success!</h2>
|
|
180
|
+
<p>Selfie captured successfully!</p>
|
|
181
|
+
</div>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (state.status === 'finished') {
|
|
187
|
+
onFinish();
|
|
188
|
+
return <p>Selfie complete!</p>;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (state.status === 'error') {
|
|
192
|
+
if (state.error === 'PERMISSION_REFRESH') {
|
|
193
|
+
window.location.reload();
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
return (
|
|
197
|
+
<div>
|
|
198
|
+
<h2>Process Cancelled</h2>
|
|
199
|
+
<p>You quit the selfie process</p>
|
|
200
|
+
<button type="button" onClick={() => selfieManager.reset()}>
|
|
201
|
+
Try Again
|
|
202
|
+
</button>
|
|
203
|
+
</div>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return null;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
type VideoProps = {
|
|
211
|
+
stream: MediaStream;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const Video: FC<VideoProps> = ({ stream }) => {
|
|
215
|
+
const [videoRef, setVideoRef] = useState<HTMLVideoElement | null>(null);
|
|
216
|
+
|
|
217
|
+
useEffect(() => {
|
|
218
|
+
if (videoRef && stream) {
|
|
219
|
+
videoRef.srcObject = stream;
|
|
220
|
+
}
|
|
221
|
+
}, [videoRef, stream]);
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<video
|
|
225
|
+
ref={setVideoRef}
|
|
226
|
+
autoplay
|
|
227
|
+
playsInline
|
|
228
|
+
muted
|
|
229
|
+
style={{
|
|
230
|
+
width: '320px',
|
|
231
|
+
height: '240px',
|
|
232
|
+
transform: 'scaleX(-1)',
|
|
233
|
+
display: 'block',
|
|
234
|
+
marginTop: '16px',
|
|
235
|
+
}}
|
|
236
|
+
/>
|
|
237
|
+
);
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const App: FC = () => {
|
|
241
|
+
const [state, flowManager] = useManager(() => createFlowManager(), {
|
|
242
|
+
autoLoad: false,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
useEffect(() => {
|
|
246
|
+
const init = async () => {
|
|
247
|
+
const { token } = await getToken();
|
|
248
|
+
setup({
|
|
249
|
+
apiURL: 'https://user-service-k8s.stage.incodetest.com/0',
|
|
250
|
+
token,
|
|
251
|
+
});
|
|
252
|
+
flowManager.load();
|
|
253
|
+
};
|
|
254
|
+
init();
|
|
255
|
+
}, [flowManager]);
|
|
256
|
+
|
|
257
|
+
if (state.status === 'loading') {
|
|
258
|
+
return <p>Loading flow...</p>;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (state.status === 'ready') {
|
|
262
|
+
const { currentStep, config } = state;
|
|
263
|
+
|
|
264
|
+
if (!currentStep) {
|
|
265
|
+
return <p>No current step</p>;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (currentStep === 'TUTORIAL_ID' || currentStep === 'ID') {
|
|
269
|
+
return (
|
|
270
|
+
<div>
|
|
271
|
+
<h1>This is {currentStep}</h1>
|
|
272
|
+
<p>Hi!</p>
|
|
273
|
+
<button type="button" onClick={() => flowManager.nextStep()}>
|
|
274
|
+
Continue
|
|
275
|
+
</button>
|
|
276
|
+
</div>
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (currentStep === 'SELFIE') {
|
|
281
|
+
const selfieConfig = config as FlowModuleConfig['SELFIE'];
|
|
282
|
+
return (
|
|
283
|
+
<div>
|
|
284
|
+
<h1>Selfie Module</h1>
|
|
285
|
+
<SelfieModule
|
|
286
|
+
config={selfieConfig}
|
|
287
|
+
onFinish={() => flowManager.nextStep()}
|
|
288
|
+
/>
|
|
289
|
+
</div>
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return (
|
|
294
|
+
<div>
|
|
295
|
+
<h1>Unknown step</h1>
|
|
296
|
+
<p>step: {currentStep}</p>
|
|
297
|
+
<button type="button" onClick={() => flowManager.nextStep()}>
|
|
298
|
+
Continue
|
|
299
|
+
</button>
|
|
300
|
+
</div>
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (state.status === 'finished') {
|
|
305
|
+
return (
|
|
306
|
+
<div>
|
|
307
|
+
<h1>Finished</h1>
|
|
308
|
+
<p>Hi!</p>
|
|
309
|
+
</div>
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (state.status === 'error') {
|
|
314
|
+
return <div>{state.error}</div>;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return null;
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
const root = document.getElementById('root');
|
|
321
|
+
if (root) {
|
|
322
|
+
render(<App />, root);
|
|
323
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createSession,
|
|
3
|
+
initializeWasmUtil,
|
|
4
|
+
setup,
|
|
5
|
+
setWasmConfig,
|
|
6
|
+
type WasmConfig,
|
|
7
|
+
} from '@incodetech/core';
|
|
8
|
+
import '../src/styles/tokens/index.css';
|
|
9
|
+
import '../src/styles/reset.css';
|
|
10
|
+
import '../src/flow/flow'; // Registers <incode-flow> web component
|
|
11
|
+
import '../src/flow/flowCompleted'; // Registers <incode-flow-completed> web component
|
|
12
|
+
import '../src/flow/styles.css';
|
|
13
|
+
import '../src/phone/styles.css';
|
|
14
|
+
import '../src/types/custom-elements'; // Enables typed DOM access for custom elements
|
|
15
|
+
import { getConfigIdFromUrl } from './getToken';
|
|
16
|
+
|
|
17
|
+
const API_URL = 'https://user-service-k8s.stage.incodetest.com/0';
|
|
18
|
+
const API_KEY = '5fbc0ab5a652b7808fbec42fffd8b4c9ec248413';
|
|
19
|
+
|
|
20
|
+
const WASM_CONFIG: WasmConfig = {
|
|
21
|
+
wasmPath:
|
|
22
|
+
'https://d3vv997wtl2myz.cloudfront.net/webcamera/onnx-backend-wasm/v2.12.47/webLib.wasm',
|
|
23
|
+
wasmSimdPath:
|
|
24
|
+
'https://d3vv997wtl2myz.cloudfront.net/webcamera/onnx-backend-wasm/v2.12.47/webLibSimd.wasm',
|
|
25
|
+
glueCodePath:
|
|
26
|
+
'https://d3vv997wtl2myz.cloudfront.net/webcamera/onnx-backend-wasm/v2.12.47/webLib.js',
|
|
27
|
+
modelsBasePath:
|
|
28
|
+
'https://d3vv997wtl2myz.cloudfront.net/webcamera/onnx-backend-wasm/v2.12.47/models',
|
|
29
|
+
// pipelines will be determined by flow analysis
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const root = document.getElementById('root');
|
|
33
|
+
if (root) {
|
|
34
|
+
const initializeFlow = async () => {
|
|
35
|
+
try {
|
|
36
|
+
// Setup SDK with API URL first (required before createSession)
|
|
37
|
+
await setup({ apiURL: API_URL });
|
|
38
|
+
|
|
39
|
+
// Create session externally
|
|
40
|
+
const configurationId = getConfigIdFromUrl();
|
|
41
|
+
const session = await createSession(API_KEY, {
|
|
42
|
+
configurationId,
|
|
43
|
+
language: 'en-US',
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Setup WASM
|
|
47
|
+
const wasmConfig = {
|
|
48
|
+
...WASM_CONFIG,
|
|
49
|
+
pipelines: ['selfie'],
|
|
50
|
+
} as WasmConfig;
|
|
51
|
+
console.log('WASM_CONFIG', wasmConfig);
|
|
52
|
+
setWasmConfig(wasmConfig);
|
|
53
|
+
await initializeWasmUtil(wasmConfig);
|
|
54
|
+
|
|
55
|
+
// Create element - automatically typed via HTMLElementTagNameMap augmentation
|
|
56
|
+
const flowElement = document.createElement('incode-flow');
|
|
57
|
+
flowElement.id = 'flow';
|
|
58
|
+
|
|
59
|
+
// Set all properties BEFORE appending to DOM
|
|
60
|
+
flowElement.apiURL = API_URL;
|
|
61
|
+
flowElement.token = session.token;
|
|
62
|
+
flowElement.wasmConfig = WASM_CONFIG;
|
|
63
|
+
flowElement.lang = 'en-US';
|
|
64
|
+
|
|
65
|
+
// Configure the loading spinner (optional customization)
|
|
66
|
+
// By default, IncodeFlow shows:
|
|
67
|
+
// - Initialization: "Setting Up"
|
|
68
|
+
// - Between modules: "Hang On" / "Validating"
|
|
69
|
+
// You can override with custom text:
|
|
70
|
+
// flowElement.spinnerConfig = {
|
|
71
|
+
// title: 'Verifying your identity',
|
|
72
|
+
// subtitle: 'This will only take a moment...',
|
|
73
|
+
// size: 'medium',
|
|
74
|
+
// };
|
|
75
|
+
|
|
76
|
+
const startTime = performance.now();
|
|
77
|
+
|
|
78
|
+
flowElement.onComplete = (result) => {
|
|
79
|
+
const totalTime = performance.now() - startTime;
|
|
80
|
+
console.log(
|
|
81
|
+
`[Lazy Load] Flow complete in ${totalTime.toFixed(0)}ms`,
|
|
82
|
+
result,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Remove the flow element and show the completed screen
|
|
86
|
+
flowElement.remove();
|
|
87
|
+
|
|
88
|
+
// Create and show the flow-completed component
|
|
89
|
+
const completedElement = document.createElement(
|
|
90
|
+
'incode-flow-completed',
|
|
91
|
+
);
|
|
92
|
+
completedElement.setAttribute('action', result.action);
|
|
93
|
+
completedElement.setAttribute('score-status', result.scoreStatus);
|
|
94
|
+
if (result.redirectionUrl) {
|
|
95
|
+
completedElement.setAttribute(
|
|
96
|
+
'redirection-url',
|
|
97
|
+
result.redirectionUrl,
|
|
98
|
+
);
|
|
99
|
+
completedElement.setAttribute('redirect-delay', '5000');
|
|
100
|
+
}
|
|
101
|
+
root.appendChild(completedElement);
|
|
102
|
+
};
|
|
103
|
+
flowElement.onError = (error) => {
|
|
104
|
+
console.error('[Lazy Load] Flow error:', error);
|
|
105
|
+
alert(`Flow error: ${error}`);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
console.log(
|
|
109
|
+
'[Lazy Load] Demo initialized - watch Network tab for chunk loading',
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
// Now append to DOM - component will mount with all props set
|
|
113
|
+
root.appendChild(flowElement);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.error('[Lazy Load] Failed to initialize:', error);
|
|
116
|
+
alert(
|
|
117
|
+
`Failed to initialize: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
initializeFlow();
|
|
123
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import '../src/styles/tokens/index.css';
|
|
2
|
+
import '../src/selfie/selfie';
|
|
3
|
+
import '../src/selfie/styles.css';
|
|
4
|
+
import '../src/phone/phone';
|
|
5
|
+
import '../src/phone/styles.css';
|
|
6
|
+
import '../src/email/email';
|
|
7
|
+
import '../src/email/styles.css';
|
|
8
|
+
import type { FlowModuleConfig } from '@incodetech/core/flow';
|
|
9
|
+
import { createFlowManager } from '@incodetech/core/flow';
|
|
10
|
+
import { render } from 'preact';
|
|
11
|
+
import type { FC } from 'preact/compat';
|
|
12
|
+
import { useEffect, useRef, useState } from 'preact/hooks';
|
|
13
|
+
import { useManager } from '../src/hooks';
|
|
14
|
+
import { setup } from '../src/setup';
|
|
15
|
+
import { getToken } from './getToken';
|
|
16
|
+
|
|
17
|
+
declare module 'preact' {
|
|
18
|
+
namespace JSX {
|
|
19
|
+
interface IntrinsicElements {
|
|
20
|
+
'incode-selfie': preact.JSX.HTMLAttributes<HTMLElement>;
|
|
21
|
+
'incode-phone': preact.JSX.HTMLAttributes<HTMLElement>;
|
|
22
|
+
'incode-email': preact.JSX.HTMLAttributes<HTMLElement>;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type SelfieElement = HTMLElement & {
|
|
28
|
+
config: FlowModuleConfig['SELFIE'];
|
|
29
|
+
onFinish: () => void;
|
|
30
|
+
onError: (error: string | undefined) => void;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type SelfieWebComponentProps = {
|
|
34
|
+
config: FlowModuleConfig['SELFIE'];
|
|
35
|
+
onFinish: () => void;
|
|
36
|
+
onError: (error: string | undefined) => void;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const SelfieWebComponent: FC<SelfieWebComponentProps> = ({
|
|
40
|
+
config,
|
|
41
|
+
onFinish,
|
|
42
|
+
onError,
|
|
43
|
+
}) => {
|
|
44
|
+
const ref = useRef<SelfieElement>(null);
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (ref.current) {
|
|
48
|
+
ref.current.config = config;
|
|
49
|
+
ref.current.onFinish = onFinish;
|
|
50
|
+
ref.current.onError = onError;
|
|
51
|
+
}
|
|
52
|
+
}, [config, onFinish, onError]);
|
|
53
|
+
|
|
54
|
+
return <incode-selfie ref={ref} />;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
type PhoneElement = HTMLElement & {
|
|
58
|
+
config: FlowModuleConfig['PHONE'];
|
|
59
|
+
onFinish: () => void;
|
|
60
|
+
onError: (error: string | undefined) => void;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const PhoneWebComponent: FC<{
|
|
64
|
+
config: FlowModuleConfig['PHONE'];
|
|
65
|
+
onFinish: () => void;
|
|
66
|
+
onError: (error: string | undefined) => void;
|
|
67
|
+
}> = ({ config, onFinish, onError }) => {
|
|
68
|
+
const ref = useRef<PhoneElement>(null);
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
if (ref.current) {
|
|
72
|
+
ref.current.config = config;
|
|
73
|
+
ref.current.onFinish = onFinish;
|
|
74
|
+
ref.current.onError = onError;
|
|
75
|
+
}
|
|
76
|
+
}, [config, onFinish, onError]);
|
|
77
|
+
|
|
78
|
+
return <incode-phone ref={ref} />;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
type EmailConfig = {
|
|
82
|
+
otpVerification: boolean;
|
|
83
|
+
otpExpirationInMinutes: number;
|
|
84
|
+
prefill: boolean;
|
|
85
|
+
maxOtpAttempts?: number;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
type EmailElement = HTMLElement & {
|
|
89
|
+
config: EmailConfig;
|
|
90
|
+
onFinish: () => void;
|
|
91
|
+
onError: (error: string | undefined) => void;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const EmailWebComponent: FC<{
|
|
95
|
+
config: FlowModuleConfig['EMAIL'];
|
|
96
|
+
onFinish: () => void;
|
|
97
|
+
onError: (error: string | undefined) => void;
|
|
98
|
+
}> = ({ config, onFinish, onError }) => {
|
|
99
|
+
const ref = useRef<EmailElement>(null);
|
|
100
|
+
const [isReady, setIsReady] = useState(false);
|
|
101
|
+
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
if (ref.current && !isReady) {
|
|
104
|
+
// Map FlowModuleConfig['EMAIL'] to full EmailConfig
|
|
105
|
+
ref.current.config = {
|
|
106
|
+
...config,
|
|
107
|
+
prefill: false,
|
|
108
|
+
maxOtpAttempts: 3,
|
|
109
|
+
};
|
|
110
|
+
ref.current.onFinish = onFinish;
|
|
111
|
+
ref.current.onError = onError;
|
|
112
|
+
// Trigger re-render to pick up the config
|
|
113
|
+
setIsReady(true);
|
|
114
|
+
}
|
|
115
|
+
}, [config, onFinish, onError, isReady]);
|
|
116
|
+
|
|
117
|
+
return <incode-email ref={ref} />;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const App: FC = () => {
|
|
121
|
+
const [state, flowManager] = useManager(() => createFlowManager(), {
|
|
122
|
+
autoLoad: false,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
const init = async () => {
|
|
127
|
+
const { token } = await getToken();
|
|
128
|
+
await setup({
|
|
129
|
+
apiURL: 'https://user-service-k8s.stage.incodetest.com/0',
|
|
130
|
+
token,
|
|
131
|
+
wasm: {
|
|
132
|
+
wasmPath:
|
|
133
|
+
'https://d3vv997wtl2myz.cloudfront.net/webcamera/onnx-backend-wasm/v2.12.47/webLib.wasm',
|
|
134
|
+
wasmSimdPath:
|
|
135
|
+
'https://d3vv997wtl2myz.cloudfront.net/webcamera/onnx-backend-wasm/v2.12.47/webLibSimd.wasm',
|
|
136
|
+
glueCodePath:
|
|
137
|
+
'https://d3vv997wtl2myz.cloudfront.net/webcamera/onnx-backend-wasm/v2.12.47/webLib.js',
|
|
138
|
+
modelsBasePath:
|
|
139
|
+
'https://d3vv997wtl2myz.cloudfront.net/webcamera/onnx-backend-wasm/v2.12.47/models',
|
|
140
|
+
pipelines: ['selfie'],
|
|
141
|
+
},
|
|
142
|
+
i18n: {
|
|
143
|
+
lang: 'en',
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
flowManager.load();
|
|
147
|
+
};
|
|
148
|
+
init();
|
|
149
|
+
}, [flowManager]);
|
|
150
|
+
|
|
151
|
+
if (state.status === 'loading') {
|
|
152
|
+
return <p>Loading flow...</p>;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (state.status === 'ready') {
|
|
156
|
+
const { currentStep, config } = state;
|
|
157
|
+
|
|
158
|
+
if (!currentStep) {
|
|
159
|
+
return <p>No current step</p>;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (currentStep === 'TUTORIAL_ID' || currentStep === 'ID') {
|
|
163
|
+
return (
|
|
164
|
+
<div>
|
|
165
|
+
<h1>This is {currentStep}</h1>
|
|
166
|
+
<p>Hi!</p>
|
|
167
|
+
<button type="button" onClick={() => flowManager.nextStep()}>
|
|
168
|
+
Continue
|
|
169
|
+
</button>
|
|
170
|
+
</div>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (currentStep === 'SELFIE') {
|
|
175
|
+
const selfieConfig = config as FlowModuleConfig['SELFIE'];
|
|
176
|
+
return (
|
|
177
|
+
<div style={{ width: '100%', height: '100%' }}>
|
|
178
|
+
<SelfieWebComponent
|
|
179
|
+
config={selfieConfig}
|
|
180
|
+
onFinish={() => flowManager.nextStep()}
|
|
181
|
+
onError={(error) => {
|
|
182
|
+
console.error('Selfie error:', error);
|
|
183
|
+
if (error === 'PERMISSION_REFRESH') {
|
|
184
|
+
window.location.reload();
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
alert(`Selfie error: ${error}`);
|
|
188
|
+
}}
|
|
189
|
+
/>
|
|
190
|
+
</div>
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (currentStep === 'PHONE') {
|
|
195
|
+
const phoneConfig = config as FlowModuleConfig['PHONE'];
|
|
196
|
+
return (
|
|
197
|
+
<div style={{ width: '100%', height: '100%' }}>
|
|
198
|
+
<PhoneWebComponent
|
|
199
|
+
config={phoneConfig}
|
|
200
|
+
onFinish={() => flowManager.nextStep()}
|
|
201
|
+
onError={(error) => {
|
|
202
|
+
console.error('Phone error:', error);
|
|
203
|
+
alert(`Phone error: ${error}`);
|
|
204
|
+
}}
|
|
205
|
+
/>
|
|
206
|
+
</div>
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (currentStep === 'EMAIL') {
|
|
211
|
+
const emailConfig = config as FlowModuleConfig['EMAIL'];
|
|
212
|
+
return (
|
|
213
|
+
<div style={{ width: '100%', height: '100%' }}>
|
|
214
|
+
<EmailWebComponent
|
|
215
|
+
config={emailConfig}
|
|
216
|
+
onFinish={() => flowManager.nextStep()}
|
|
217
|
+
onError={(error) => {
|
|
218
|
+
console.error('Email error:', error);
|
|
219
|
+
alert(`Email error: ${error}`);
|
|
220
|
+
}}
|
|
221
|
+
/>
|
|
222
|
+
</div>
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return (
|
|
227
|
+
<div>
|
|
228
|
+
<h1>Unknown step</h1>
|
|
229
|
+
<p>step: {currentStep}</p>
|
|
230
|
+
<button type="button" onClick={() => flowManager.nextStep()}>
|
|
231
|
+
Continue
|
|
232
|
+
</button>
|
|
233
|
+
</div>
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (state.status === 'finished') {
|
|
238
|
+
return (
|
|
239
|
+
<div>
|
|
240
|
+
<h1>Finished</h1>
|
|
241
|
+
<p>Hi!</p>
|
|
242
|
+
</div>
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (state.status === 'error') {
|
|
247
|
+
return <div>{state.error}</div>;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return null;
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const root = document.getElementById('root');
|
|
254
|
+
if (root) {
|
|
255
|
+
render(<App />, root);
|
|
256
|
+
}
|