@incodetech/prizma-core-screens 0.2.3 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CurpEnterScreen.d.ts +7 -0
- package/dist/CurpEnterScreen.js +17 -3
- package/dist/CurpGenerateScreen.d.ts +8 -0
- package/dist/CurpGenerateScreen.js +28 -13
- package/dist/CurpLayout.css +8 -34
- package/dist/CurpStrings.d.ts +16 -2
- package/dist/EkybAddressDetailsScreen.d.ts +7 -0
- package/dist/EkybAddressDetailsScreen.js +25 -7
- package/dist/EkybBusinessDetailsScreen.d.ts +6 -0
- package/dist/EkybBusinessDetailsScreen.js +23 -6
- package/dist/EkybCountryScreen.d.ts +13 -5
- package/dist/EkybCountryScreen.js +27 -9
- package/dist/EkybOwnersScreen.d.ts +8 -0
- package/dist/EkybOwnersScreen.js +26 -3
- package/dist/EkybStrings.d.ts +27 -10
- package/dist/EkycCountryCatalog.d.ts +4 -1
- package/dist/EkycCountryCatalog.js +5 -3
- package/dist/EkycCountryFormScreen.d.ts +12 -3
- package/dist/EkycCountryFormScreen.js +28 -10
- package/dist/EkycDetailsScreen.d.ts +7 -0
- package/dist/EkycDetailsScreen.js +21 -4
- package/dist/EkycStrings.d.ts +14 -2
- package/dist/ElectronicSignatureLayout.css +6 -0
- package/dist/EmailInputStrings.d.ts +11 -1
- package/dist/EmailInputVerifyScreen.d.ts +7 -0
- package/dist/EmailInputVerifyScreen.js +17 -3
- package/dist/FaceMatchComparisonScreen.d.ts +3 -3
- package/dist/FaceMatchLayout.css +21 -12
- package/dist/FormScreen.css +16 -0
- package/dist/FormsLongFormScreen.d.ts +8 -0
- package/dist/FormsLongFormScreen.js +34 -13
- package/dist/FormsStrings.d.ts +11 -1
- package/dist/GeolocationBrowserStepsScreen.d.ts +6 -2
- package/dist/GeolocationBrowserStepsScreen.js +7 -3
- package/dist/GeolocationStrings.d.ts +3 -0
- package/dist/IdCaptureLayout.css +40 -0
- package/dist/ListScreen.d.ts +2 -0
- package/dist/ListScreen.js +1 -1
- package/dist/PhoneInputLayout.css +10 -9
- package/dist/PhoneInputStrings.d.ts +11 -1
- package/dist/PhoneInputVerifyScreen.d.ts +8 -0
- package/dist/PhoneInputVerifyScreen.js +19 -6
- package/dist/QualifiedSignatureAcceptScreen.js +6 -1
- package/dist/QualifiedSignatureDocumentScreen.d.ts +13 -9
- package/dist/QualifiedSignatureDocumentScreen.js +18 -16
- package/dist/QualifiedSignatureLayout.css +76 -0
- package/dist/QualifiedSignatureStrings.d.ts +10 -0
- package/dist/ScreenNav.css +8 -0
- package/dist/ScreenNav.d.ts +15 -2
- package/dist/ScreenNav.js +10 -3
- package/dist/VideoSelfieIdCaptureScreen.d.ts +10 -3
- package/dist/VideoSelfieIdCaptureScreen.js +44 -13
- package/dist/VideoSelfieLayout.css +164 -33
- package/dist/VideoSelfieSelfieCaptureScreen.d.ts +44 -27
- package/dist/VideoSelfieSelfieCaptureScreen.js +65 -39
- package/dist/VideoSelfieSpeakScreen.js +18 -1
- package/dist/VideoSelfieStrings.d.ts +78 -12
- package/dist/VideoSelfieTurnHeadScreen.d.ts +51 -0
- package/dist/VideoSelfieTurnHeadScreen.js +63 -0
- package/dist/VideoSelfieTutorialScreen.js +13 -5
- package/dist/VideoSelfieWaveScreen.d.ts +53 -0
- package/dist/VideoSelfieWaveScreen.js +62 -0
- package/dist/WatchlistBusinessFormScreen.d.ts +11 -7
- package/dist/WatchlistBusinessFormScreen.js +27 -13
- package/dist/WatchlistBusinessStrings.d.ts +21 -7
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -0
- package/dist/types.d.ts +3 -1
- package/package.json +14 -3
- package/samples/video-selfie-room.png +0 -0
- package/src/CurpEnterScreen.tsx +21 -4
- package/src/CurpGenerateScreen.tsx +46 -22
- package/src/CurpLayout.css +8 -34
- package/src/CurpStrings.ts +22 -2
- package/src/EkybAddressDetailsScreen.tsx +32 -4
- package/src/EkybBusinessDetailsScreen.tsx +23 -5
- package/src/EkybCountryScreen.tsx +28 -7
- package/src/EkybOwnersScreen.tsx +34 -3
- package/src/EkybStrings.ts +29 -10
- package/src/EkycCountryCatalog.ts +9 -4
- package/src/EkycCountryFormScreen.tsx +51 -12
- package/src/EkycDetailsScreen.tsx +22 -1
- package/src/EkycStrings.ts +14 -2
- package/src/ElectronicSignatureLayout.css +6 -0
- package/src/EmailInputStrings.ts +12 -1
- package/src/EmailInputVerifyScreen.tsx +21 -4
- package/src/FaceMatchComparisonScreen.tsx +3 -3
- package/src/FaceMatchLayout.css +21 -12
- package/src/FormScreen.css +16 -0
- package/src/FormsLongFormScreen.tsx +41 -13
- package/src/FormsStrings.ts +12 -1
- package/src/GeolocationBrowserStepsScreen.tsx +6 -3
- package/src/GeolocationStrings.ts +3 -0
- package/src/IdCaptureLayout.css +40 -0
- package/src/ListScreen.tsx +10 -1
- package/src/PhoneInputLayout.css +10 -9
- package/src/PhoneInputStrings.ts +12 -1
- package/src/PhoneInputVerifyScreen.tsx +21 -5
- package/src/QualifiedSignatureAcceptScreen.tsx +12 -1
- package/src/QualifiedSignatureDocumentScreen.tsx +27 -17
- package/src/QualifiedSignatureLayout.css +76 -0
- package/src/QualifiedSignatureStrings.ts +10 -0
- package/src/ScreenNav.css +8 -0
- package/src/ScreenNav.tsx +35 -5
- package/src/VideoSelfieIdCaptureScreen.tsx +116 -30
- package/src/VideoSelfieLayout.css +164 -33
- package/src/VideoSelfieSelfieCaptureScreen.tsx +104 -59
- package/src/VideoSelfieSpeakScreen.tsx +25 -14
- package/src/VideoSelfieStrings.ts +85 -12
- package/src/VideoSelfieTurnHeadScreen.tsx +151 -0
- package/src/VideoSelfieTutorialScreen.tsx +34 -23
- package/src/VideoSelfieWaveScreen.tsx +135 -0
- package/src/WatchlistBusinessFormScreen.tsx +35 -16
- package/src/WatchlistBusinessStrings.ts +21 -7
- package/src/index.ts +14 -0
- package/src/types.ts +3 -1
|
@@ -60,11 +60,13 @@ export interface QualifiedSignatureDocumentScreenProps
|
|
|
60
60
|
* overflow constraint, declaring no spacing or size, the same idiom as CURP's
|
|
61
61
|
* taller-than-844 form (DEVIATIONS #CURP-1).
|
|
62
62
|
*
|
|
63
|
-
* THREE deltas from the frames
|
|
64
|
-
* 1. Both sections draw a back chevron at the nav's left
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
63
|
+
* THREE deltas from the frames:
|
|
64
|
+
* 1. Both sections draw a back chevron at the nav's left. It is BUILT (owner
|
|
65
|
+
* call 2026-09-29): these are viewers the user opened from the accept
|
|
66
|
+
* screen, so they need a way back. It is Core's chevron through the opt-in
|
|
67
|
+
* `PrizmaScreenNav.backLabel`, emitting `back`; the logo stays centred.
|
|
68
|
+
* `terms` replacing the logo row with a chevron + heading bar is still not
|
|
69
|
+
* followed — the title stays the archetype's H2 below the nav.
|
|
68
70
|
* 2. `viewdoc` draws NO heading. GUIDELINES §4 is explicit that every screen has
|
|
69
71
|
* exactly one H2, and archetype G has no title-less path, so the screen
|
|
70
72
|
* carries one and the Figma frame is the side to change. (An archetype
|
|
@@ -73,10 +75,12 @@ export interface QualifiedSignatureDocumentScreenProps
|
|
|
73
75
|
* for attribution chrome (#PI-8, #F-30, #F-34), so both variants keep the
|
|
74
76
|
* header and the footer and nothing shifts between states.
|
|
75
77
|
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* an
|
|
78
|
+
* Both variants frame the page in the box Electronic Signature's review screen
|
|
79
|
+
* uses, so the title is CENTRED on both, like that screen (owner call
|
|
80
|
+
* 2026-09-29, "take reference from other screens"). Figma's `QSign.T&C` draws
|
|
81
|
+
* the title left over an unframed wall of text; once the text sits in its own
|
|
82
|
+
* left-aligned frame the centred title no longer reads as a mistake. An
|
|
83
|
+
* explicit `overrides.align` from the host still wins.
|
|
80
84
|
*/
|
|
81
85
|
export const QualifiedSignatureDocumentScreen = forwardRef<
|
|
82
86
|
HTMLDivElement,
|
|
@@ -96,12 +100,6 @@ export const QualifiedSignatureDocumentScreen = forwardRef<
|
|
|
96
100
|
) => {
|
|
97
101
|
const copy = variant === 'terms' ? strings.document.terms : strings.document.document;
|
|
98
102
|
|
|
99
|
-
// The legal wall is left-aligned in Figma; the contract page keeps the
|
|
100
|
-
// archetype's centred default. An explicit `overrides.align` still wins.
|
|
101
|
-
const alignedOverrides: PrizmaScreenOverrides = {
|
|
102
|
-
...overrides,
|
|
103
|
-
align: overrides?.align ?? (variant === 'terms' ? 'left' : 'centered'),
|
|
104
|
-
};
|
|
105
103
|
|
|
106
104
|
return (
|
|
107
105
|
<PrizmaListScreen
|
|
@@ -109,12 +107,24 @@ export const QualifiedSignatureDocumentScreen = forwardRef<
|
|
|
109
107
|
title={copy.title}
|
|
110
108
|
verifiedLabel={strings.verifiedLabel}
|
|
111
109
|
navLabel={strings.navLabel}
|
|
110
|
+
backLabel={strings.document.backLabel}
|
|
112
111
|
screenId={screenId}
|
|
113
|
-
overrides={
|
|
112
|
+
overrides={overrides}
|
|
114
113
|
onAction={onAction}
|
|
115
114
|
{...shell}
|
|
116
115
|
>
|
|
117
|
-
<div
|
|
116
|
+
<div
|
|
117
|
+
className={[
|
|
118
|
+
'qualified-signature-document',
|
|
119
|
+
'qualified-signature-document--framed',
|
|
120
|
+
`qualified-signature-document--${variant}`,
|
|
121
|
+
].join(' ')}
|
|
122
|
+
role="region"
|
|
123
|
+
aria-label={copy.title}
|
|
124
|
+
tabIndex={0}
|
|
125
|
+
>
|
|
126
|
+
{body}
|
|
127
|
+
</div>
|
|
118
128
|
</PrizmaListScreen>
|
|
119
129
|
);
|
|
120
130
|
},
|
|
@@ -57,6 +57,26 @@
|
|
|
57
57
|
display: flex;
|
|
58
58
|
flex-direction: column;
|
|
59
59
|
gap: var(--prizma-spacing-4);
|
|
60
|
+
/* The clauses sit at the BOTTOM of the column, on the primary button, not
|
|
61
|
+
* under the document row (Elena, 2026-09-30). The fields are a flex column,
|
|
62
|
+
* so the auto margin takes the free space; when the clauses outgrow the frame
|
|
63
|
+
* (the five-clause reading) there is none and the stack scrolls as before. */
|
|
64
|
+
margin-top: auto;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* The required legend leading the group. Core ships no caption block outside a
|
|
68
|
+
* field, so the line binds the clause copy's own Size-S tokens in the secondary
|
|
69
|
+
* ink — no new size, no new colour. Spacing/8 more below it than the clauses
|
|
70
|
+
* keep between themselves, so it reads as the group's header. */
|
|
71
|
+
.qualified-signature-terms__legend {
|
|
72
|
+
margin: 0 0 var(--prizma-spacing-8);
|
|
73
|
+
font-family: var(--prizma-checkbox-radio-family);
|
|
74
|
+
font-weight: var(--prizma-checkbox-radio-s-font-weight);
|
|
75
|
+
font-size: var(--prizma-checkbox-radio-s-font-size);
|
|
76
|
+
line-height: var(--prizma-checkbox-radio-s-line-height);
|
|
77
|
+
letter-spacing: var(--prizma-checkbox-radio-s-letter-spacing);
|
|
78
|
+
color: var(--prizma-text-body-500-secondary);
|
|
79
|
+
text-align: left;
|
|
60
80
|
}
|
|
61
81
|
|
|
62
82
|
/* ── (c) Uploaded-file preview card ──────────────────────────────────────────
|
|
@@ -181,6 +201,62 @@
|
|
|
181
201
|
overscroll-behavior: contain;
|
|
182
202
|
}
|
|
183
203
|
|
|
204
|
+
/* The document FRAME — the two full-surface document screens draw the page
|
|
205
|
+
* inside the same box Electronic Signature's review screen uses
|
|
206
|
+
* (`.electronic-signature-document`): `Border/Neutral/100` hairline,
|
|
207
|
+
* `Border/Radius/small`, `Surface/Neutral/0`, clipped so the page scrolls INSIDE
|
|
208
|
+
* it. Without it the contract image floated loose on the screen and the legal
|
|
209
|
+
* text ran edge to edge as one grey wall (owner-reported 2026-09-29: "take
|
|
210
|
+
* reference from other screens"). A modifier, not the base class, because the
|
|
211
|
+
* gated-form exploration reuses `.qualified-signature-document` inside its own
|
|
212
|
+
* popup, which already carries a frame.
|
|
213
|
+
*
|
|
214
|
+
* Spacing/16 bottom margin: the gap Electronic Signature's frame keeps above the
|
|
215
|
+
* CTA band, here kept above the anchored tag so the frame never touches it.
|
|
216
|
+
*/
|
|
217
|
+
.qualified-signature-document--framed {
|
|
218
|
+
box-sizing: border-box;
|
|
219
|
+
background-color: var(--prizma-surface-neutral-0);
|
|
220
|
+
border: var(--prizma-border-width-regular) solid var(--prizma-border-neutral-100);
|
|
221
|
+
border-radius: var(--prizma-border-radius-small);
|
|
222
|
+
margin-bottom: var(--prizma-spacing-16);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/* `terms` — a TEXT agreement needs a gutter inside the frame (Spacing/16) and
|
|
226
|
+
* the paragraph rhythm Electronic Signature uses (Spacing/12 between blocks). */
|
|
227
|
+
.qualified-signature-document--terms {
|
|
228
|
+
padding: var(--prizma-spacing-16);
|
|
229
|
+
text-align: start;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.qualified-signature-document--terms > * {
|
|
233
|
+
margin: 0;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.qualified-signature-document.qualified-signature-document--terms > * + * {
|
|
237
|
+
/* Doubled class: `__prose` resets `margin: 0` later in this file at the
|
|
238
|
+
same specificity, which would otherwise swallow the rhythm. */
|
|
239
|
+
margin-top: var(--prizma-spacing-12);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/* Section headings inside the form — Headline/H5 on Text/Body/800, the same
|
|
243
|
+
* pair as `.electronic-signature-document h4`. */
|
|
244
|
+
.qualified-signature-document--terms h4 {
|
|
245
|
+
font-family: var(--prizma-headline-family);
|
|
246
|
+
font-weight: var(--prizma-headline-h5-font-weight);
|
|
247
|
+
font-size: var(--prizma-headline-h5-font-size);
|
|
248
|
+
line-height: var(--prizma-headline-h5-line-height);
|
|
249
|
+
letter-spacing: var(--prizma-headline-h5-letter-spacing);
|
|
250
|
+
color: var(--prizma-text-body-800-primary);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/* Inside the frame the prose drops to Text/Body/500, the secondary ink every
|
|
254
|
+
* block of Core prose uses (`CardList.css`, `Card.css`) — the heading carries
|
|
255
|
+
* the hierarchy, as in Electronic Signature. */
|
|
256
|
+
.qualified-signature-document--terms .qualified-signature-document__prose {
|
|
257
|
+
color: var(--prizma-text-body-500-secondary);
|
|
258
|
+
}
|
|
259
|
+
|
|
184
260
|
/* The contract page is host-supplied media (an <img> / <canvas> of the rendered
|
|
185
261
|
* PDF, GUIDELINES §10's `cameraSurface` idiom) — it fills the column width and
|
|
186
262
|
* keeps its own aspect. */
|
|
@@ -169,6 +169,12 @@ export interface QualifiedSignatureStrings {
|
|
|
169
169
|
viewLabel: string;
|
|
170
170
|
/** Accessible name for the consent stack. */
|
|
171
171
|
termsLabel?: string;
|
|
172
|
+
/** Suffix appended to every clause, all of them required ("*"). Localizable,
|
|
173
|
+
* never baked into the clause copy — Data Sharing Consent's `requiredHint`. */
|
|
174
|
+
requiredHint?: string;
|
|
175
|
+
/** The line that leads the clause group and explains the marker ("All terms
|
|
176
|
+
* marked with * are required."). Only drawn alongside `requiredHint`. */
|
|
177
|
+
requiredLegend?: string;
|
|
172
178
|
/** The generic five-clause reading. */
|
|
173
179
|
accept: QualifiedSignatureAcceptCopy;
|
|
174
180
|
/** The partner two-clause reading. */
|
|
@@ -186,6 +192,10 @@ export interface QualifiedSignatureStrings {
|
|
|
186
192
|
document: { title: string };
|
|
187
193
|
/** QSign.T&C ("Certificate Application Form"), left-aligned in Figma. */
|
|
188
194
|
terms: { title: string };
|
|
195
|
+
/** Accessible name of the nav's back chevron on both document screens
|
|
196
|
+
* ("Back") — they are opened from the accept screen, so they carry a way
|
|
197
|
+
* back to it. Emits `back`. Omit to render the logo-only nav. */
|
|
198
|
+
backLabel?: string;
|
|
189
199
|
};
|
|
190
200
|
|
|
191
201
|
/** The two in-flight screens. */
|
package/src/ScreenNav.css
CHANGED
|
@@ -28,6 +28,14 @@
|
|
|
28
28
|
width: 100%;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/* With the opt-in back chevron the nav sits on the content column's 24px
|
|
32
|
+
* gutter, so the chevron lines up with the title and content below it. Core's
|
|
33
|
+
* nav already pads its row by Spacing/2, hence the subtraction. A logo-only nav
|
|
34
|
+
* is centred and needs no gutter, so nothing else moves. */
|
|
35
|
+
.prizma-screen-nav--back {
|
|
36
|
+
padding-inline: calc(var(--prizma-spacing-24) - var(--prizma-spacing-2));
|
|
37
|
+
}
|
|
38
|
+
|
|
31
39
|
.prizma-screen-nav__custom-logo {
|
|
32
40
|
display: flex;
|
|
33
41
|
align-items: center;
|
package/src/ScreenNav.tsx
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {
|
|
2
|
+
forwardRef,
|
|
3
|
+
type ComponentPropsWithoutRef,
|
|
4
|
+
type MouseEventHandler,
|
|
5
|
+
type ReactNode,
|
|
6
|
+
} from 'react';
|
|
2
7
|
import { PrizmaNavigation } from '@incodetech/prizma-core-web/navigation';
|
|
3
8
|
import './ScreenNav.css';
|
|
4
9
|
|
|
@@ -18,25 +23,50 @@ export interface PrizmaScreenNavProps extends ComponentPropsWithoutRef<'header'>
|
|
|
18
23
|
* only the logo disappears, and the row leaves the a11y tree.
|
|
19
24
|
*/
|
|
20
25
|
reserved?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Opt-in back chevron at the nav's LEFT — Core's own
|
|
28
|
+
* (`PrizmaNavigation.showLeftIcon`, IconChevronLeft). Passing the label turns
|
|
29
|
+
* it on; it is the button's accessible name (i18n). Off by default, so every
|
|
30
|
+
* existing screen keeps the logo-only nav. For a screen the user OPENED from
|
|
31
|
+
* another one (Qualified Signature's document viewers, reached from "View"
|
|
32
|
+
* and from the application-form link) — a way back to where they came from,
|
|
33
|
+
* not a dismiss of the flow, which stays out (#PI-9). Owner call 2026-09-29.
|
|
34
|
+
*/
|
|
35
|
+
backLabel?: string;
|
|
36
|
+
/** Click handler for the back chevron. */
|
|
37
|
+
onBack?: MouseEventHandler<HTMLButtonElement>;
|
|
21
38
|
}
|
|
22
39
|
|
|
23
40
|
/**
|
|
24
41
|
* ScreenNav — the logo header of every SDK screen.
|
|
25
42
|
* Sits 12px below the top of the shell (the host frame owns the OS status
|
|
26
|
-
* bar), logo centered, no
|
|
43
|
+
* bar), logo centered, no close button (GUIDELINES.md §3). A back chevron is
|
|
44
|
+
* opt-in through `backLabel`, for opened sub-screens only.
|
|
27
45
|
*/
|
|
28
46
|
export const PrizmaScreenNav = forwardRef<HTMLElement, PrizmaScreenNavProps>(
|
|
29
|
-
({ logo, navLabel, reserved = false, className, ...rest }, ref) => (
|
|
47
|
+
({ logo, navLabel, reserved = false, backLabel, onBack, className, ...rest }, ref) => (
|
|
30
48
|
<header
|
|
31
49
|
ref={ref}
|
|
32
50
|
aria-hidden={reserved || undefined}
|
|
33
|
-
className={[
|
|
51
|
+
className={[
|
|
52
|
+
'prizma-screen-nav',
|
|
53
|
+
backLabel && !reserved ? 'prizma-screen-nav--back' : '',
|
|
54
|
+
className ?? '',
|
|
55
|
+
]
|
|
56
|
+
.filter(Boolean)
|
|
57
|
+
.join(' ')}
|
|
34
58
|
{...rest}
|
|
35
59
|
>
|
|
36
60
|
{reserved ? null : logo ? (
|
|
37
61
|
<span className="prizma-screen-nav__custom-logo">{logo}</span>
|
|
38
62
|
) : (
|
|
39
|
-
<PrizmaNavigation
|
|
63
|
+
<PrizmaNavigation
|
|
64
|
+
showIncodeLogo
|
|
65
|
+
aria-label={navLabel}
|
|
66
|
+
showLeftIcon={Boolean(backLabel)}
|
|
67
|
+
backLabel={backLabel}
|
|
68
|
+
onLeftIconClick={onBack}
|
|
69
|
+
/>
|
|
40
70
|
)}
|
|
41
71
|
</header>
|
|
42
72
|
),
|
|
@@ -3,15 +3,18 @@ import {
|
|
|
3
3
|
PrizmaIdCapture,
|
|
4
4
|
type PrizmaIdCaptureState,
|
|
5
5
|
} from '@incodetech/prizma-core-web/id-capture';
|
|
6
|
+
import { PrizmaCaptureFeedback } from '@incodetech/prizma-core-web/capture-feedback';
|
|
6
7
|
import { PrizmaLoadingSpinner } from '@incodetech/prizma-core-web/loading-spinner';
|
|
7
|
-
import { PrizmaSnackbar } from '@incodetech/prizma-core-web/snackbar';
|
|
8
8
|
import { PrizmaStepper } from '@incodetech/prizma-core-web/stepper';
|
|
9
9
|
import { PrizmaTag } from '@incodetech/prizma-core-web/tag';
|
|
10
|
+
import { PrizmaTitle } from '@incodetech/prizma-core-web/title';
|
|
10
11
|
import { PrizmaScreenShell, type PrizmaScreenShellProps } from './ScreenShell';
|
|
11
12
|
import type { PrizmaScreenActionHandler, PrizmaScreenOverrides } from './types';
|
|
12
13
|
import type {
|
|
13
14
|
VideoSelfieDocumentType,
|
|
14
|
-
|
|
15
|
+
VideoSelfieIdCondition,
|
|
16
|
+
VideoSelfieIdErrorReason,
|
|
17
|
+
VideoSelfieIdErrorSeverity,
|
|
15
18
|
VideoSelfieIdSide,
|
|
16
19
|
VideoSelfieStrings,
|
|
17
20
|
} from './VideoSelfieStrings';
|
|
@@ -19,13 +22,52 @@ import './ScreenLayout.css';
|
|
|
19
22
|
import './ViewfinderScreen.css';
|
|
20
23
|
import './VideoSelfieLayout.css';
|
|
21
24
|
|
|
22
|
-
/** Which purple stage section to render (groups 3, 5 and 6)
|
|
25
|
+
/** Which purple stage section to render (groups 3, 5 and 6), plus the live
|
|
26
|
+
* coaching beat the full-flow frames draw between them. */
|
|
23
27
|
export type VideoSelfieIdCaptureState =
|
|
24
28
|
| 'default'
|
|
25
29
|
| 'shown'
|
|
26
30
|
| 'processing'
|
|
27
31
|
| 'allGood'
|
|
28
|
-
| 'error'
|
|
32
|
+
| 'error'
|
|
33
|
+
| 'condition';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Condition → the Core treatment that already exists for it.
|
|
37
|
+
*
|
|
38
|
+
* Four are `PrizmaIdCapture`'s own error states. `blurry` is the plain error
|
|
39
|
+
* border: blur is a DETECTION result, not a framing mistake, and the owner's
|
|
40
|
+
* 2026-07-28 call is that those pair the plain border with the feedback
|
|
41
|
+
* bubble rather than inventing a semantic overlay (`IdCapture.tsx`, the note
|
|
42
|
+
* above `error`). Either way the message is the bubble's, never the frame's.
|
|
43
|
+
*/
|
|
44
|
+
const CONDITION_STATE: Record<VideoSelfieIdCondition, PrizmaIdCaptureState> = {
|
|
45
|
+
wrongSide: 'error-wrong-side',
|
|
46
|
+
notAligned: 'error-not-aligned',
|
|
47
|
+
glare: 'error-glare',
|
|
48
|
+
tooDark: 'error-too-dark',
|
|
49
|
+
blurry: 'error',
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/** Condition → the bubble's own variant. All six of Core's are used: the
|
|
53
|
+
* wrong-side bubble names the face the user SHOULD be showing, so it flips
|
|
54
|
+
* with the leg — `show-front` on the front, `show-back` on the back. */
|
|
55
|
+
const CONDITION_FEEDBACK: Record<
|
|
56
|
+
VideoSelfieIdCondition,
|
|
57
|
+
'not-aligned' | 'blurry' | 'glare' | 'too-dark'
|
|
58
|
+
> = {
|
|
59
|
+
wrongSide: 'not-aligned', // unused — the wrong-side variant is side-keyed below
|
|
60
|
+
notAligned: 'not-aligned',
|
|
61
|
+
blurry: 'blurry',
|
|
62
|
+
glare: 'glare',
|
|
63
|
+
tooDark: 'too-dark',
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** The wrong-side bubble, per leg. */
|
|
67
|
+
const WRONG_SIDE_FEEDBACK: Record<VideoSelfieIdSide, 'show-front' | 'show-back'> = {
|
|
68
|
+
front: 'show-front',
|
|
69
|
+
back: 'show-back',
|
|
70
|
+
};
|
|
29
71
|
|
|
30
72
|
/** Purple stage state → Core `PrizmaIdCapture` state. The frame border, the
|
|
31
73
|
* analyzing sweep, the scrim and the encrypted row are ALL Core's. */
|
|
@@ -35,6 +77,8 @@ const CAPTURE_STATE: Record<VideoSelfieIdCaptureState, PrizmaIdCaptureState> = {
|
|
|
35
77
|
processing: 'analyzing',
|
|
36
78
|
allGood: 'success',
|
|
37
79
|
error: 'error',
|
|
80
|
+
// Replaced per-condition below — the map needs an entry for every key.
|
|
81
|
+
condition: 'error',
|
|
38
82
|
};
|
|
39
83
|
|
|
40
84
|
/** Which beat of the seven-step journey each side occupies — front is step 2
|
|
@@ -56,6 +100,12 @@ export interface VideoSelfieIdCaptureScreenProps
|
|
|
56
100
|
/** Whether the rejection invites a retry (`6.1`/`6.3`) or is terminal
|
|
57
101
|
* (`6.2`/`6.4`). Read only when `state="error"`. */
|
|
58
102
|
errorSeverity?: VideoSelfieIdErrorSeverity;
|
|
103
|
+
/** WHY the read was rejected — the `.1`–`.4` suffix of the group 6 section.
|
|
104
|
+
* Read only when `state="error"`. Default `scan`. */
|
|
105
|
+
errorReason?: VideoSelfieIdErrorReason;
|
|
106
|
+
/** Which live condition to coach the user out of. Read only when
|
|
107
|
+
* `state="condition"`. Default `notAligned`. */
|
|
108
|
+
condition?: VideoSelfieIdCondition;
|
|
59
109
|
/** Live camera feed (<video>) or sample still (<img>) behind the frame. */
|
|
60
110
|
camera?: ReactNode;
|
|
61
111
|
/** The captured document still, for the states Core draws one in. */
|
|
@@ -118,6 +168,8 @@ export const VideoSelfieIdCaptureScreen = forwardRef<
|
|
|
118
168
|
documentType = 'id',
|
|
119
169
|
state = 'default',
|
|
120
170
|
errorSeverity = 'tryAgain',
|
|
171
|
+
errorReason = 'scan',
|
|
172
|
+
condition = 'notAligned',
|
|
121
173
|
camera,
|
|
122
174
|
still,
|
|
123
175
|
screenId = `video-selfie-id-capture-${side}-${state}`,
|
|
@@ -128,7 +180,15 @@ export const VideoSelfieIdCaptureScreen = forwardRef<
|
|
|
128
180
|
},
|
|
129
181
|
ref,
|
|
130
182
|
) => {
|
|
131
|
-
const errorLabel = strings.idCapture.errors[errorSeverity];
|
|
183
|
+
const errorLabel = strings.idCapture.errors[errorReason][errorSeverity];
|
|
184
|
+
/* Only the wrong-side bubble is keyed by side — it is the one that names
|
|
185
|
+
the face to show. The other four describe the shot. */
|
|
186
|
+
const conditionCopy =
|
|
187
|
+
condition === 'wrongSide'
|
|
188
|
+
? strings.idCapture.conditions.wrongSide[side]
|
|
189
|
+
: strings.idCapture.conditions[condition];
|
|
190
|
+
const conditionFeedback =
|
|
191
|
+
condition === 'wrongSide' ? WRONG_SIDE_FEEDBACK[side] : CONDITION_FEEDBACK[condition];
|
|
132
192
|
|
|
133
193
|
return (
|
|
134
194
|
<PrizmaScreenShell ref={ref} dark className={className} {...shell}>
|
|
@@ -149,50 +209,76 @@ export const VideoSelfieIdCaptureScreen = forwardRef<
|
|
|
149
209
|
.filter(Boolean)
|
|
150
210
|
.join(' ')}
|
|
151
211
|
>
|
|
152
|
-
|
|
212
|
+
{/* The frame is CENTRED in the air between the stepper and the tag,
|
|
213
|
+
not bottom-anchored: Figma seats `ID Capture Frame` at y 172–627
|
|
214
|
+
of a 91–708 content area, dead centre. One flexible band either
|
|
215
|
+
side is the whole geometry — no offset is declared. */}
|
|
216
|
+
{/* The coaching bubble sits in the band ABOVE the frame, which is
|
|
217
|
+
where the five condition frames draw it — Core's own
|
|
218
|
+
`PrizmaCaptureFeedback`, the same piece ID Capture's stage uses
|
|
219
|
+
for the same job (`IdCaptureStageScreen`). The frame underneath
|
|
220
|
+
takes the matching red treatment, so the two say one thing:
|
|
221
|
+
what is wrong, and what to do about it. */}
|
|
222
|
+
<div className="video-selfie-band">
|
|
223
|
+
{state === 'condition' ? (
|
|
224
|
+
<div className="video-selfie-feedback">
|
|
225
|
+
<PrizmaCaptureFeedback
|
|
226
|
+
feedback={conditionFeedback}
|
|
227
|
+
title={conditionCopy.title}
|
|
228
|
+
subtitle={conditionCopy.subtitle}
|
|
229
|
+
/>
|
|
230
|
+
</div>
|
|
231
|
+
) : null}
|
|
232
|
+
</div>
|
|
153
233
|
<PrizmaIdCapture
|
|
234
|
+
className="video-selfie-stage"
|
|
154
235
|
type={documentType}
|
|
155
|
-
state={CAPTURE_STATE[state]}
|
|
236
|
+
state={state === 'condition' ? CONDITION_STATE[condition] : CAPTURE_STATE[state]}
|
|
156
237
|
isVertical
|
|
157
238
|
mode="overlay"
|
|
158
239
|
scrim
|
|
159
240
|
showEncryptedLabel={false}
|
|
160
241
|
still={still}
|
|
161
242
|
/>
|
|
243
|
+
<div className="video-selfie-band" />
|
|
162
244
|
{/* Centred ON the frame, not below it — `3.2`/`5.2` put the spinner
|
|
163
245
|
and its caption over the document (DEVIATIONS #VS-7). */}
|
|
164
246
|
{state === 'processing' ? (
|
|
165
247
|
<div className="video-selfie-stage-status">
|
|
166
|
-
<
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
248
|
+
<div className="video-selfie-stage-status__card">
|
|
249
|
+
<PrizmaLoadingSpinner
|
|
250
|
+
showTitle
|
|
251
|
+
titleText={strings.idCapture.processingLabel}
|
|
252
|
+
loadingLabel={strings.idCapture.processingLabel}
|
|
253
|
+
/>
|
|
254
|
+
</div>
|
|
171
255
|
</div>
|
|
172
256
|
) : null}
|
|
173
257
|
{/* `3.3`/`5.3` and every `6.x` draw the verdict in the SAME dark card,
|
|
174
258
|
centred on the document, as `3.2`/`5.2` draw the spinner — three
|
|
175
259
|
identical Figma treatments had become two different ones once the
|
|
176
|
-
spinner moved (owner QA 2026-08-19). DEVIATIONS #VS-13.
|
|
260
|
+
spinner moved (owner QA 2026-08-19). DEVIATIONS #VS-13.
|
|
261
|
+
|
|
262
|
+
The card's CONTENT is Core's `PrizmaTitle` with its status icon,
|
|
263
|
+
not a Snackbar: Figma stacks the mark ABOVE the label, centred,
|
|
264
|
+
with the label free to wrap onto two or three lines ("ID photo
|
|
265
|
+
verification failed, try again"). A Snackbar is a one-line ROW —
|
|
266
|
+
icon beside text — so it printed the copy across the full column
|
|
267
|
+
and had to be told, by a prop, whether its own text would wrap
|
|
268
|
+
(#VS-9). The stacked status block is what Core already ships for
|
|
269
|
+
this shape, and it is the same piece the liveness ring uses on
|
|
270
|
+
the same beat. DEVIATIONS #VS-19. */}
|
|
177
271
|
{state === 'allGood' || state === 'error' ? (
|
|
178
272
|
<div className="video-selfie-stage-status">
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
(owner-reported 2026-08-19: "the text is not even aligned to
|
|
189
|
-
the icon"). CORE REQUEST: have the Snackbar follow actual
|
|
190
|
-
wrapping rather than a prop the caller has to predict.
|
|
191
|
-
DEVIATIONS #VS-9. */
|
|
192
|
-
<PrizmaSnackbar state="error" role="alert">
|
|
193
|
-
{errorLabel}
|
|
194
|
-
</PrizmaSnackbar>
|
|
195
|
-
)}
|
|
273
|
+
<div
|
|
274
|
+
className="video-selfie-stage-status__card"
|
|
275
|
+
role={state === 'error' ? 'alert' : 'status'}
|
|
276
|
+
>
|
|
277
|
+
<PrizmaTitle
|
|
278
|
+
title={state === 'allGood' ? strings.idCapture.successLabel : errorLabel}
|
|
279
|
+
icon={{ type: 'status', status: state === 'allGood' ? 'success' : 'error' }}
|
|
280
|
+
/>
|
|
281
|
+
</div>
|
|
196
282
|
</div>
|
|
197
283
|
) : null}
|
|
198
284
|
</div>
|