@incodetech/prizma-core-screens 0.2.4 → 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
|
@@ -2,7 +2,7 @@ import React, { forwardRef } from 'react';
|
|
|
2
2
|
import { PrizmaTextField } from '@incodetech/prizma-core-web/text-field';
|
|
3
3
|
import { PrizmaButton } from '@incodetech/prizma-core-web/button';
|
|
4
4
|
import { PrizmaStepper } from '@incodetech/prizma-core-web/stepper';
|
|
5
|
-
import { PrizmaFormScreen,
|
|
5
|
+
import { PrizmaFormScreen, type PrizmaFormScreenProps } from './FormScreen';
|
|
6
6
|
import type { PrizmaScreenActionHandler, PrizmaScreenOverrides } from './types';
|
|
7
7
|
import type { EkybBusinessState, EkybBusinessValues, EkybStrings } from './EkybStrings';
|
|
8
8
|
import './ScreenLayout.css';
|
|
@@ -69,6 +69,12 @@ export interface EkybBusinessDetailsScreenProps
|
|
|
69
69
|
* bar is at `currentState="Start"`: segment 1 filled, segments 2 and 3 grey,
|
|
70
70
|
* exactly what 5919:68599 renders. Both offsets around it live in
|
|
71
71
|
* `ScreenLayout.css`, never here.
|
|
72
|
+
*
|
|
73
|
+
* `emptyError` is Continue pressed with the fields still empty (Elena,
|
|
74
|
+
* 2026-09-30, GUIDELINES §6-E empty-submit rule; precedent `FormsFieldScreen`'s
|
|
75
|
+
* `emptyError`): both REQUIRED fields render empty in Core's `error` state with
|
|
76
|
+
* `requiredError` under them, and Continue is disabled. `empty` keeps Continue
|
|
77
|
+
* enabled so the press can happen.
|
|
72
78
|
*/
|
|
73
79
|
export const EkybBusinessDetailsScreen = forwardRef<
|
|
74
80
|
HTMLDivElement,
|
|
@@ -78,8 +84,9 @@ export const EkybBusinessDetailsScreen = forwardRef<
|
|
|
78
84
|
{
|
|
79
85
|
strings,
|
|
80
86
|
state = 'empty',
|
|
81
|
-
values,
|
|
87
|
+
values: valuesProp,
|
|
82
88
|
screenId = `ekyb-business-${state}`,
|
|
89
|
+
ctaDisabled,
|
|
83
90
|
overrides,
|
|
84
91
|
onAction,
|
|
85
92
|
...shell
|
|
@@ -87,6 +94,13 @@ export const EkybBusinessDetailsScreen = forwardRef<
|
|
|
87
94
|
ref,
|
|
88
95
|
) => {
|
|
89
96
|
const copy = strings.business;
|
|
97
|
+
const emptyError = state === 'emptyError';
|
|
98
|
+
const values = emptyError ? undefined : valuesProp;
|
|
99
|
+
// Core's own error treatment, spread onto the REQUIRED fields only; the
|
|
100
|
+
// error state shows no placeholder (the `FormsFieldScreen` precedent).
|
|
101
|
+
const missing = emptyError
|
|
102
|
+
? { error: true, errorText: strings.requiredError, placeholder: undefined }
|
|
103
|
+
: {};
|
|
90
104
|
|
|
91
105
|
return (
|
|
92
106
|
<PrizmaFormScreen
|
|
@@ -104,6 +118,8 @@ export const EkybBusinessDetailsScreen = forwardRef<
|
|
|
104
118
|
title={copy.title}
|
|
105
119
|
subtitle={copy.subtitle}
|
|
106
120
|
ctaLabel={copy.ctaLabel}
|
|
121
|
+
/* Disabled only after the empty submit; `empty` keeps it enabled. */
|
|
122
|
+
ctaDisabled={emptyError || ctaDisabled}
|
|
107
123
|
linkContent={
|
|
108
124
|
<PrizmaButton
|
|
109
125
|
variant="secondary"
|
|
@@ -121,16 +137,18 @@ export const EkybBusinessDetailsScreen = forwardRef<
|
|
|
121
137
|
overrides={{ align: overrides?.align ?? 'left', logo: overrides?.logo }}
|
|
122
138
|
onAction={onAction}
|
|
123
139
|
/* Exploration ekyb-desktop: on a tablet or desktop frame the two
|
|
124
|
-
fields
|
|
140
|
+
fields stay on two rows, each spanning the column (Elena,
|
|
141
|
+
2026-09-28); `responsive` still pairs Continue and Back. On the
|
|
142
|
+
phone nothing changes. */
|
|
125
143
|
responsive
|
|
126
144
|
{...shell}
|
|
127
145
|
>
|
|
128
|
-
<PrizmaFormRow>
|
|
129
146
|
<PrizmaTextField
|
|
130
147
|
label={copy.businessName.label}
|
|
131
148
|
placeholder={copy.businessName.placeholder}
|
|
132
149
|
required
|
|
133
150
|
requiredHint={strings.requiredHint}
|
|
151
|
+
{...missing}
|
|
134
152
|
autoComplete="off"
|
|
135
153
|
defaultValue={values?.businessName ?? ''}
|
|
136
154
|
/>
|
|
@@ -139,10 +157,10 @@ export const EkybBusinessDetailsScreen = forwardRef<
|
|
|
139
157
|
placeholder={copy.taxId.placeholder}
|
|
140
158
|
required
|
|
141
159
|
requiredHint={strings.requiredHint}
|
|
160
|
+
{...missing}
|
|
142
161
|
autoComplete="off"
|
|
143
162
|
defaultValue={values?.taxId ?? ''}
|
|
144
163
|
/>
|
|
145
|
-
</PrizmaFormRow>
|
|
146
164
|
</PrizmaFormScreen>
|
|
147
165
|
);
|
|
148
166
|
},
|
|
@@ -55,14 +55,22 @@ export interface EkybCountryScreenProps
|
|
|
55
55
|
* live interaction) — the same mechanism Phone Input's `countryCode` and
|
|
56
56
|
* CURP's `Gene.dropdown` sections use. The search bar Figma draws inside the
|
|
57
57
|
* menu is Core's own `searchable`.
|
|
58
|
-
* 3.
|
|
59
|
-
*
|
|
60
|
-
* "
|
|
61
|
-
*
|
|
62
|
-
* because Core still ships no country-flag primitive
|
|
58
|
+
* 3. Menu rows and the closed field read the same "🇺🇸 United States": flag
|
|
59
|
+
* and country name, no dial code. Figma 5707:54174 draws the rows with
|
|
60
|
+
* Phone Input's "+1" prefixes, dropped by the owner (Elena, 2026-09-28)
|
|
61
|
+
* because a country of registration has no phone prefix. The flag travels
|
|
62
|
+
* inside the label text because Core still ships no country-flag primitive
|
|
63
|
+
* (#PI-3).
|
|
63
64
|
*
|
|
64
65
|
* This page is the only eKYB form page with NO "Back" secondary and NO Stepper —
|
|
65
66
|
* it is step 0, before the three-step wizard starts.
|
|
67
|
+
*
|
|
68
|
+
* `emptyError` is Continue pressed with no country chosen (Elena, 2026-09-30,
|
|
69
|
+
* GUIDELINES §6-E empty-submit rule; precedent `FormsFieldScreen`'s
|
|
70
|
+
* `emptyError`): the dropdown renders empty, with no placeholder, in Core's
|
|
71
|
+
* `error` state with `requiredError` under it, and Continue is disabled. The
|
|
72
|
+
* field draws no `*`, but it is the page's only input and the flow cannot go on
|
|
73
|
+
* without it, so it is treated as required. `empty` keeps Continue enabled.
|
|
66
74
|
*/
|
|
67
75
|
export const EkybCountryScreen = forwardRef<HTMLDivElement, EkybCountryScreenProps>(
|
|
68
76
|
(
|
|
@@ -72,6 +80,7 @@ export const EkybCountryScreen = forwardRef<HTMLDivElement, EkybCountryScreenPro
|
|
|
72
80
|
countries = [],
|
|
73
81
|
value,
|
|
74
82
|
screenId = `ekyb-country-${state}`,
|
|
83
|
+
ctaDisabled,
|
|
75
84
|
overrides,
|
|
76
85
|
onAction,
|
|
77
86
|
...shell
|
|
@@ -81,7 +90,9 @@ export const EkybCountryScreen = forwardRef<HTMLDivElement, EkybCountryScreenPro
|
|
|
81
90
|
const copy = strings.country;
|
|
82
91
|
// `empty` is the only section drawn on the placeholder; both other sections
|
|
83
92
|
// draw a chosen country in the closed field.
|
|
84
|
-
const
|
|
93
|
+
const emptyError = state === 'emptyError';
|
|
94
|
+
const selected =
|
|
95
|
+
state === 'empty' || emptyError ? null : (value ?? countries[0]?.value ?? null);
|
|
85
96
|
|
|
86
97
|
return (
|
|
87
98
|
<PrizmaFormScreen
|
|
@@ -89,6 +100,8 @@ export const EkybCountryScreen = forwardRef<HTMLDivElement, EkybCountryScreenPro
|
|
|
89
100
|
title={copy.title}
|
|
90
101
|
subtitle={copy.subtitle}
|
|
91
102
|
ctaLabel={copy.ctaLabel}
|
|
103
|
+
/* Disabled only after the empty submit; `empty` keeps it enabled. */
|
|
104
|
+
ctaDisabled={emptyError || ctaDisabled}
|
|
92
105
|
verifiedLabel={strings.verifiedLabel}
|
|
93
106
|
navLabel={strings.navLabel}
|
|
94
107
|
screenId={screenId}
|
|
@@ -102,7 +115,15 @@ export const EkybCountryScreen = forwardRef<HTMLDivElement, EkybCountryScreenPro
|
|
|
102
115
|
>
|
|
103
116
|
<PrizmaDropdown
|
|
104
117
|
label={copy.field.label}
|
|
105
|
-
|
|
118
|
+
/* Required, but UNMARKED: a single-input screen shows no asterisk
|
|
119
|
+
and no legend (Core pattern "Optimistic submission", single-input
|
|
120
|
+
screens; Elena 2026-09-30). The requirement surfaces on submit,
|
|
121
|
+
through the empty-submit error. */
|
|
122
|
+
/* The error state draws the field with nothing in it, not even the
|
|
123
|
+
placeholder (the `FormsFieldScreen` precedent). */
|
|
124
|
+
placeholder={emptyError ? undefined : copy.field.placeholder}
|
|
125
|
+
error={emptyError}
|
|
126
|
+
errorText={emptyError ? strings.requiredError : undefined}
|
|
106
127
|
options={countries}
|
|
107
128
|
/* Uncontrolled: a screen is one static spec state, so no `onChange` is
|
|
108
129
|
needed. Core's `readOnly`/`disabled` are NOT used to freeze it —
|
package/src/EkybOwnersScreen.tsx
CHANGED
|
@@ -59,6 +59,14 @@ export interface EkybOwnersScreenProps
|
|
|
59
59
|
* - `loading` 5663:94390 — `directorFilled` with the submission in
|
|
60
60
|
* flight: every field, both "Add another" links and "Back" go inert, and the
|
|
61
61
|
* spinner rides the CTA, never a field (the Phone Input lesson, #PI-5)
|
|
62
|
+
* - `uboEmptyError` / `directorEmptyError` (no frame) — `uboEmpty` /
|
|
63
|
+
* `directorEmpty` after Continue was pressed with the fields still empty
|
|
64
|
+
* (Elena, 2026-09-30, GUIDELINES §6-E empty-submit rule; precedent
|
|
65
|
+
* `FormsFieldScreen`'s `emptyError`): every field renders empty, the two
|
|
66
|
+
* REQUIRED UBO fields in Core's `error` state with `requiredError` and no
|
|
67
|
+
* placeholder, the optional Director fields unflagged, and Continue
|
|
68
|
+
* disabled. The `*Empty` states keep Continue enabled so the press can
|
|
69
|
+
* happen.
|
|
62
70
|
*
|
|
63
71
|
* Four things are deliberate:
|
|
64
72
|
*
|
|
@@ -99,8 +107,9 @@ export const EkybOwnersScreen = forwardRef<HTMLDivElement, EkybOwnersScreenProps
|
|
|
99
107
|
{
|
|
100
108
|
strings,
|
|
101
109
|
state = 'uboEmpty',
|
|
102
|
-
values,
|
|
110
|
+
values: valuesProp,
|
|
103
111
|
screenId = `ekyb-owners-${state}`,
|
|
112
|
+
ctaDisabled,
|
|
104
113
|
className,
|
|
105
114
|
overrides,
|
|
106
115
|
onAction,
|
|
@@ -111,8 +120,20 @@ export const EkybOwnersScreen = forwardRef<HTMLDivElement, EkybOwnersScreenProps
|
|
|
111
120
|
const copy = strings.owners;
|
|
112
121
|
const busy = state === 'loading';
|
|
113
122
|
const secondUbo = state === 'uboAdded';
|
|
123
|
+
const emptyError = state === 'uboEmptyError' || state === 'directorEmptyError';
|
|
124
|
+
const values = emptyError ? undefined : valuesProp;
|
|
114
125
|
const showDirector =
|
|
115
|
-
state === 'directorEmpty' ||
|
|
126
|
+
state === 'directorEmpty' ||
|
|
127
|
+
state === 'directorFilled' ||
|
|
128
|
+
state === 'directorEmptyError' ||
|
|
129
|
+
secondUbo ||
|
|
130
|
+
busy;
|
|
131
|
+
// Core's own error treatment, spread AFTER each required UBO field's own
|
|
132
|
+
// placeholder so the error state shows none (the `FormsFieldScreen`
|
|
133
|
+
// precedent). The Director fields are optional and never receive it.
|
|
134
|
+
const missing = emptyError
|
|
135
|
+
? { error: true, errorText: strings.requiredError, placeholder: undefined }
|
|
136
|
+
: {};
|
|
116
137
|
|
|
117
138
|
return (
|
|
118
139
|
<PrizmaFormScreen
|
|
@@ -134,6 +155,9 @@ export const EkybOwnersScreen = forwardRef<HTMLDivElement, EkybOwnersScreenProps
|
|
|
134
155
|
title={copy.title}
|
|
135
156
|
subtitle={copy.subtitle}
|
|
136
157
|
ctaLabel={copy.ctaLabel}
|
|
158
|
+
/* Disabled only after the empty submit; the `*Empty` states keep it
|
|
159
|
+
enabled. */
|
|
160
|
+
ctaDisabled={emptyError || ctaDisabled}
|
|
137
161
|
/* `loading` swaps the CTA label for Core's designed spinner. */
|
|
138
162
|
ctaLoading={busy}
|
|
139
163
|
ctaLoadingLabel={copy.ctaLoadingLabel}
|
|
@@ -164,14 +188,19 @@ export const EkybOwnersScreen = forwardRef<HTMLDivElement, EkybOwnersScreenProps
|
|
|
164
188
|
placeholder={copy.uboName.placeholder}
|
|
165
189
|
required
|
|
166
190
|
requiredHint={strings.requiredHint}
|
|
191
|
+
{...missing}
|
|
167
192
|
autoComplete="off"
|
|
168
193
|
defaultValue={values?.uboName ?? ''}
|
|
169
194
|
disabled={busy}
|
|
170
195
|
/>
|
|
171
|
-
{/*
|
|
196
|
+
{/* Required since 2026-09-28 (Elena): the frames drew no asterisk on the
|
|
197
|
+
surname, but a beneficial owner needs both names. */}
|
|
172
198
|
<PrizmaTextField
|
|
173
199
|
label={copy.uboSurname.label}
|
|
174
200
|
placeholder={copy.uboSurname.placeholder}
|
|
201
|
+
required
|
|
202
|
+
requiredHint={strings.requiredHint}
|
|
203
|
+
{...missing}
|
|
175
204
|
autoComplete="off"
|
|
176
205
|
defaultValue={values?.uboSurname ?? ''}
|
|
177
206
|
disabled={busy}
|
|
@@ -192,6 +221,8 @@ export const EkybOwnersScreen = forwardRef<HTMLDivElement, EkybOwnersScreenProps
|
|
|
192
221
|
<PrizmaTextField
|
|
193
222
|
label={copy.uboSurname.label}
|
|
194
223
|
placeholder={copy.uboSurname.placeholder}
|
|
224
|
+
required
|
|
225
|
+
requiredHint={strings.requiredHint}
|
|
195
226
|
autoComplete="off"
|
|
196
227
|
defaultValue={values?.secondUboSurname ?? ''}
|
|
197
228
|
/>
|
package/src/EkybStrings.ts
CHANGED
|
@@ -34,15 +34,21 @@
|
|
|
34
34
|
* - `dropdown` — the same selection with the MENU OPEN over the form: search
|
|
35
35
|
* bar plus the country rows (Core's documentation-only `forceOpen`, since a
|
|
36
36
|
* screen is one static spec state and never a live interaction)
|
|
37
|
+
* - `emptyError` — Continue was pressed with no country chosen (GUIDELINES
|
|
38
|
+
* §6-E empty-submit rule, Elena 2026-09-30): the dropdown in Core's error
|
|
39
|
+
* state with `requiredError`, Continue disabled. Not a Figma frame.
|
|
37
40
|
*/
|
|
38
|
-
export type EkybCountryState = 'empty' | 'filled' | 'dropdown';
|
|
41
|
+
export type EkybCountryState = 'empty' | 'filled' | 'dropdown' | 'emptyError';
|
|
39
42
|
|
|
40
43
|
/** The two `eKYB.Bdetails` (business identity) sections: every field on its
|
|
41
|
-
* resting empty surface, or every field carrying a value
|
|
42
|
-
|
|
44
|
+
* resting empty surface, or every field carrying a value — plus `emptyError`,
|
|
45
|
+
* Continue pressed with the fields empty (empty-submit rule, 2026-09-30):
|
|
46
|
+
* every required field in Core's error state, Continue disabled. */
|
|
47
|
+
export type EkybBusinessState = 'empty' | 'filled' | 'emptyError';
|
|
43
48
|
|
|
44
|
-
/** The two address sections (the SECOND `eKYB.Bdetails` parent). Same axis
|
|
45
|
-
|
|
49
|
+
/** The two address sections (the SECOND `eKYB.Bdetails` parent). Same axis,
|
|
50
|
+
* same `emptyError` (the optional Address line 2 shows no error). */
|
|
51
|
+
export type EkybAddressState = 'empty' | 'filled' | 'emptyError';
|
|
46
52
|
|
|
47
53
|
/**
|
|
48
54
|
* The `eKYB.UBO.*` and `eKYB.director.*` sections — one screen family, because
|
|
@@ -63,6 +69,11 @@ export type EkybAddressState = 'empty' | 'filled';
|
|
|
63
69
|
* flight, so EVERY field, both "Add another" links and "Back" go inert and
|
|
64
70
|
* the spinner rides the CTA, never a field (the Phone Input lesson,
|
|
65
71
|
* DEVIATIONS #PI-5)
|
|
72
|
+
* - `uboEmptyError` / `directorEmptyError` — `uboEmpty` / `directorEmpty` after
|
|
73
|
+
* Continue was pressed with the fields empty (GUIDELINES §6-E empty-submit
|
|
74
|
+
* rule, Elena 2026-09-30): the two REQUIRED UBO fields in Core's error state
|
|
75
|
+
* with `requiredError`, the optional Director fields unflagged, Continue
|
|
76
|
+
* disabled. Not Figma frames.
|
|
66
77
|
*/
|
|
67
78
|
export type EkybOwnersState =
|
|
68
79
|
| 'uboEmpty'
|
|
@@ -70,7 +81,9 @@ export type EkybOwnersState =
|
|
|
70
81
|
| 'directorEmpty'
|
|
71
82
|
| 'directorFilled'
|
|
72
83
|
| 'uboAdded'
|
|
73
|
-
| 'loading'
|
|
84
|
+
| 'loading'
|
|
85
|
+
| 'uboEmptyError'
|
|
86
|
+
| 'directorEmptyError';
|
|
74
87
|
|
|
75
88
|
/**
|
|
76
89
|
* One selectable row in the country / state menus. Runtime data, not copy — the
|
|
@@ -84,9 +97,8 @@ export interface EkybOption {
|
|
|
84
97
|
/** The row label as drawn in the open menu. */
|
|
85
98
|
label: string;
|
|
86
99
|
/** Compact face for the CLOSED field when it differs from the menu row —
|
|
87
|
-
* Core's own `PrizmaDropdownOption.fieldLabel`.
|
|
88
|
-
*
|
|
89
|
-
* field. */
|
|
100
|
+
* Core's own `PrizmaDropdownOption.fieldLabel`. The eKYB country list
|
|
101
|
+
* does not need it: rows and field both read "🇺🇸 United States". */
|
|
90
102
|
fieldLabel?: string;
|
|
91
103
|
}
|
|
92
104
|
|
|
@@ -139,6 +151,13 @@ export interface EkybStrings {
|
|
|
139
151
|
* localizable and never hardcoded in a screen.
|
|
140
152
|
*/
|
|
141
153
|
requiredHint?: string;
|
|
154
|
+
/**
|
|
155
|
+
* Under every empty REQUIRED field once the primary was pressed with it
|
|
156
|
+
* still empty — the `emptyError` states (GUIDELINES §6-E empty-submit rule,
|
|
157
|
+
* Elena 2026-09-30). Core's own `errorText` slot; SDK key
|
|
158
|
+
* `dynamicForms.fieldRequired`.
|
|
159
|
+
*/
|
|
160
|
+
requiredError: string;
|
|
142
161
|
/** Shared "Back" secondary action drawn under "Continue" on every page except
|
|
143
162
|
* `country`. Emits `secondary`. */
|
|
144
163
|
backLabel: string;
|
|
@@ -217,7 +236,7 @@ export interface EkybStrings {
|
|
|
217
236
|
title: string;
|
|
218
237
|
subtitle?: string;
|
|
219
238
|
uboName: EkybFieldStrings;
|
|
220
|
-
/**
|
|
239
|
+
/** Required, like the name (Elena, 2026-09-28); the frames drew no `*`. */
|
|
221
240
|
uboSurname: EkybFieldStrings;
|
|
222
241
|
/** The tertiary link under the UBO block ("Add another UBO (Optional)"). */
|
|
223
242
|
addUboLabel: string;
|
|
@@ -20,7 +20,10 @@
|
|
|
20
20
|
// Verification", and no variant draws a close in the nav; steps after the
|
|
21
21
|
// first get the archetype's Back button instead of a back arrow (#EKC-2).
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
/** `password`: Core's `PrizmaPassword`, the field with the eye that hides the
|
|
24
|
+
* typed value. For the identifiers a stranger must not read over a shoulder:
|
|
25
|
+
* the SSN / ITIN fields (Elena, 2026-09-28). No requirement checklist. */
|
|
26
|
+
export type EkycFieldKind = 'text' | 'date' | 'phone' | 'dropdown' | 'radio' | 'password';
|
|
24
27
|
|
|
25
28
|
export type EkycFieldKey =
|
|
26
29
|
| 'firstName' | 'middleName' | 'middleNameOptional' | 'lastName' | 'surname' | 'maternalSurname'
|
|
@@ -75,6 +78,8 @@ export interface EkycCountryVariant {
|
|
|
75
78
|
}
|
|
76
79
|
|
|
77
80
|
const t = (key: EkycFieldKey, extra: Partial<EkycField> = {}): EkycField => ({ key, kind: 'text', ...extra });
|
|
81
|
+
/** A hidden-by-default identifier, with the eye to reveal it. */
|
|
82
|
+
const secret = (key: EkycFieldKey, extra: Partial<EkycField> = {}): EkycField => ({ key, kind: 'password', ...extra });
|
|
78
83
|
const date = (key: EkycFieldKey = 'dateOfBirth', extra: Partial<EkycField> = {}): EkycField => ({ key, kind: 'date', ...extra });
|
|
79
84
|
// Required fields carry Core's asterisk (`required` + `requiredHint`), the
|
|
80
85
|
// owner's rule (Elena, 2026-09-24, DEVIATIONS #EKC-6): first name, last name or
|
|
@@ -112,7 +117,7 @@ export const EKYC_COUNTRY_CATALOG: EkycCountryVariant[] = [
|
|
|
112
117
|
{
|
|
113
118
|
id: 'us-telco-1', country: 'US', label: 'United States', provider: 'Telco 1', stepper: 3,
|
|
114
119
|
steps: [
|
|
115
|
-
{ node: '2098:9575', fields: [...NAME_PAIR(), email(), ...DOB_PHONE('US'),
|
|
120
|
+
{ node: '2098:9575', fields: [...NAME_PAIR(), email(), ...DOB_PHONE('US'), secret('ssnLast4')] },
|
|
116
121
|
{ node: '5854:76222', section: 'license', fields: [t('licenseNumber'), { key: 'licenseState', kind: 'dropdown', placeholder: 'selectState' }] },
|
|
117
122
|
{ node: '5854:76221', section: 'address', fields: US_ADDRESS },
|
|
118
123
|
],
|
|
@@ -129,7 +134,7 @@ export const EKYC_COUNTRY_CATALOG: EkycCountryVariant[] = [
|
|
|
129
134
|
steps: [
|
|
130
135
|
/* Same First | Last pair as the licence variant; Middle name (Optional)
|
|
131
136
|
follows it and spans (#EKC-5). */
|
|
132
|
-
{ node: '2098:9577', fields: [...NAME_PAIR(), t('middleNameOptional'), ...DOB_PHONE('US'), email(),
|
|
137
|
+
{ node: '2098:9577', fields: [...NAME_PAIR(), t('middleNameOptional'), ...DOB_PHONE('US'), email(), secret('ssnOrItin')] },
|
|
133
138
|
{ node: '5857:34094', section: 'address', fields: US_ADDRESS },
|
|
134
139
|
],
|
|
135
140
|
},
|
|
@@ -148,7 +153,7 @@ export const EKYC_COUNTRY_CATALOG: EkycCountryVariant[] = [
|
|
|
148
153
|
{
|
|
149
154
|
id: 'ca-res-credit', country: 'CA', label: 'Canada', provider: 'Residential credit', stepper: 2,
|
|
150
155
|
steps: [
|
|
151
|
-
{ node: '2098:9570', fields: [...NAME_PAIR(), email(), ...DOB_PHONE('CA'),
|
|
156
|
+
{ node: '2098:9570', fields: [...NAME_PAIR(), email(), ...DOB_PHONE('CA'), secret('ssnOrItin')] },
|
|
152
157
|
{ node: '5857:36450', section: 'address', fields: CODE_ADDRESS('stateCodeCa') },
|
|
153
158
|
],
|
|
154
159
|
},
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { forwardRef } from 'react';
|
|
2
2
|
import { PrizmaTextField } from '@incodetech/prizma-core-web/text-field';
|
|
3
|
+
import { PrizmaPassword } from '@incodetech/prizma-core-web/password';
|
|
3
4
|
import { PrizmaDateField } from '@incodetech/prizma-core-web/date-field';
|
|
4
5
|
import { PrizmaDropdown } from '@incodetech/prizma-core-web/dropdown';
|
|
5
6
|
import { PrizmaCombined, type PrizmaCountry } from '@incodetech/prizma-core-web/combined';
|
|
@@ -28,11 +29,20 @@ export interface EkycCountryFormScreenProps
|
|
|
28
29
|
values?: EkycCountryValues;
|
|
29
30
|
/**
|
|
30
31
|
* The state after Continue was pressed with fields still empty: every empty
|
|
31
|
-
* field shows Core's error state and `country.requiredError` under
|
|
32
|
-
* Continue
|
|
33
|
-
*
|
|
32
|
+
* REQUIRED field shows Core's error state and `country.requiredError` under
|
|
33
|
+
* it, and Continue is disabled (GUIDELINES §6-E empty-submit rule, Elena
|
|
34
|
+
* 2026-09-30). Without it Continue stays enabled: validation happens on
|
|
35
|
+
* press, the resting page never greys the button.
|
|
34
36
|
*/
|
|
35
37
|
showErrors?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Field keys whose typed value failed the format check on Continue (an
|
|
40
|
+
* invalid CPF, say). Each keeps its value, shows Core's error state and
|
|
41
|
+
* `country.invalidErrors[key]` under it; Continue stays enabled so the user
|
|
42
|
+
* corrects it and presses again, the CURP precedent (`CurpEnterScreen`'s
|
|
43
|
+
* `invalid`). A key with no message in `invalidErrors` shows no error.
|
|
44
|
+
*/
|
|
45
|
+
invalidFields?: string[];
|
|
36
46
|
/** Dial codes the phone field offers. Runtime data, never copy. */
|
|
37
47
|
phoneCountries?: PrizmaCountry[];
|
|
38
48
|
/** Options for the dropdown fields, keyed by field key. Runtime data. */
|
|
@@ -52,10 +62,13 @@ export interface EkycCountryFormScreenProps
|
|
|
52
62
|
* dropdown, radio pair — so a new country is a catalogue entry, never a file.
|
|
53
63
|
*
|
|
54
64
|
* Deliberate, per frame and per owner:
|
|
55
|
-
* - Continue is
|
|
56
|
-
* most frames draw): pressing it with empty fields is the
|
|
57
|
-
* Core's error ring and message under each empty
|
|
58
|
-
*
|
|
65
|
+
* - Continue is enabled on the resting page (Elena, 2026-09-24, overriding the
|
|
66
|
+
* grey button most frames draw): pressing it with empty fields is the
|
|
67
|
+
* `showErrors` state, Core's error ring and message under each empty
|
|
68
|
+
* required field, and THEN Continue is disabled. The "always enabled" half
|
|
69
|
+
* of DEVIATIONS #EKC-4 is superseded by the 2026-09-30 empty-submit rule
|
|
70
|
+
* (GUIDELINES §6-E). The radio pair has no error state in Core, so an empty
|
|
71
|
+
* gender still shows none.
|
|
59
72
|
* - ONE title per step (Elena, 2026-09-24): the personal-data step is "Enter
|
|
60
73
|
* your details" with its subtitle; a licence step is titled "Driver's
|
|
61
74
|
* License details" and an address step "Address details", with nothing
|
|
@@ -82,7 +95,7 @@ const groupRows = (fields: EkycField[]): EkycField[][] => {
|
|
|
82
95
|
return groups;
|
|
83
96
|
};
|
|
84
97
|
export const EkycCountryFormScreen = forwardRef<HTMLDivElement, EkycCountryFormScreenProps>(
|
|
85
|
-
({ strings, variant, step = 1, values, showErrors = false, phoneCountries, options, screenId, overrides, onAction, ...shell }, ref) => {
|
|
98
|
+
({ strings, variant, step = 1, values, showErrors = false, invalidFields, phoneCountries, options, screenId, overrides, onAction, ...shell }, ref) => {
|
|
86
99
|
const v = typeof variant === 'string' ? ekycCountryVariant(variant) : variant;
|
|
87
100
|
if (!v) return null;
|
|
88
101
|
const s = v.steps[Math.min(Math.max(step, 1), v.steps.length) - 1];
|
|
@@ -90,15 +103,23 @@ export const EkycCountryFormScreen = forwardRef<HTMLDivElement, EkycCountryFormS
|
|
|
90
103
|
const stepperState: PrizmaStepperState = step <= 1 ? 'Start' : (Math.min(step, 6) as PrizmaStepperState);
|
|
91
104
|
|
|
92
105
|
const label = (f: EkycField) => c.fields[f.key] ?? f.key;
|
|
93
|
-
|
|
106
|
+
// A lone field in its empty-submit error drops its placeholder, as the
|
|
107
|
+
// single-input screens do (Phone, Email and CURP's `emptyError`).
|
|
108
|
+
const lone = s.fields.length === 1;
|
|
109
|
+
const placeholder = (f: EkycField) =>
|
|
110
|
+
lone && showErrors && !values?.[f.key] ? undefined : f.placeholder ? c.placeholders[f.placeholder] : c.placeholders[f.key];
|
|
94
111
|
|
|
95
112
|
const renderField = (f: EkycField) => {
|
|
96
113
|
// Only a REQUIRED empty field is an error on press (Elena, 2026-09-24,
|
|
97
|
-
// DEVIATIONS #EKC-6): an optional field left blank shows nothing.
|
|
98
|
-
|
|
114
|
+
// DEVIATIONS #EKC-6): an optional field left blank shows nothing. A
|
|
115
|
+
// step's lone field is required by definition, asterisk or not (the
|
|
116
|
+
// single-input rule, as Phone and Email Input: Elena, 2026-10-02).
|
|
117
|
+
const required = Boolean(f.required) || lone;
|
|
118
|
+
const missing = showErrors && required && !values?.[f.key];
|
|
119
|
+
const invalidText = invalidFields?.includes(f.key) ? c.invalidErrors?.[f.key] : undefined;
|
|
99
120
|
const common = {
|
|
100
121
|
key: f.key, label: label(f), required: f.required, requiredHint: strings.details.requiredHint,
|
|
101
|
-
error: missing || undefined, errorText: missing ? c.requiredError :
|
|
122
|
+
error: missing || Boolean(invalidText) || undefined, errorText: missing ? c.requiredError : invalidText,
|
|
102
123
|
} as const;
|
|
103
124
|
switch (f.kind) {
|
|
104
125
|
case 'date':
|
|
@@ -126,6 +147,22 @@ export const EkycCountryFormScreen = forwardRef<HTMLDivElement, EkycCountryFormS
|
|
|
126
147
|
defaultValue={values?.[f.key] ?? null}
|
|
127
148
|
/>
|
|
128
149
|
);
|
|
150
|
+
case 'password':
|
|
151
|
+
/* The SSN / ITIN fields: Core's Password field, hidden by default, the
|
|
152
|
+
eye reveals it. No requirement checklist — this is an identifier,
|
|
153
|
+
not a password to compose (Elena, 2026-09-28). */
|
|
154
|
+
return (
|
|
155
|
+
<PrizmaPassword
|
|
156
|
+
{...common}
|
|
157
|
+
placeholder={placeholder(f)}
|
|
158
|
+
autoComplete="off"
|
|
159
|
+
defaultValue={values?.[f.key] ?? ''}
|
|
160
|
+
showPasswordLabel={c.showValueLabel}
|
|
161
|
+
hidePasswordLabel={c.hideValueLabel}
|
|
162
|
+
showHelper={false}
|
|
163
|
+
showRequirements={false}
|
|
164
|
+
/>
|
|
165
|
+
);
|
|
129
166
|
case 'radio':
|
|
130
167
|
return (
|
|
131
168
|
<PrizmaRadioGroup key={f.key} name={f.key} legend={label(f)} defaultValue={values?.[f.key]} className="ekyc-country__radios">
|
|
@@ -158,6 +195,8 @@ export const EkycCountryFormScreen = forwardRef<HTMLDivElement, EkycCountryFormS
|
|
|
158
195
|
) : undefined
|
|
159
196
|
}
|
|
160
197
|
ctaLabel={c.ctaLabel}
|
|
198
|
+
/* Greyed only after the empty submit (2026-09-30 empty-submit rule). */
|
|
199
|
+
ctaDisabled={showErrors}
|
|
161
200
|
verifiedLabel={strings.verifiedLabel}
|
|
162
201
|
navLabel={strings.navLabel}
|
|
163
202
|
screenId={screenId}
|
|
@@ -71,6 +71,13 @@ export interface EkycDetailsScreenProps
|
|
|
71
71
|
* the label row Figma draws as "First name *" — so Middle name, the one
|
|
72
72
|
* optional field, correctly renders none.
|
|
73
73
|
*
|
|
74
|
+
* `emptyError` is the state after Continue was pressed with the fields still
|
|
75
|
+
* empty (Elena, 2026-09-30, GUIDELINES §6-E empty-submit rule; drawn precedent
|
|
76
|
+
* `FormsFieldScreen`'s `emptyError`): every field renders empty, the three
|
|
77
|
+
* REQUIRED ones carry Core's `error` + `country.requiredError`, Middle name
|
|
78
|
+
* stays unflagged (DEVIATIONS #EKC-6), and Continue is disabled. The resting
|
|
79
|
+
* `default` keeps Continue enabled so the press can happen at all.
|
|
80
|
+
*
|
|
74
81
|
* Figma draws a two-segment journey bar between the nav and the title on every
|
|
75
82
|
* page-1 frame, and it is the only chrome that separates page 1 from page 2.
|
|
76
83
|
* Archetype E had no slot above the title; the gap is now CLOSED in the
|
|
@@ -85,8 +92,9 @@ export const EkycDetailsScreen = forwardRef<HTMLDivElement, EkycDetailsScreenPro
|
|
|
85
92
|
{
|
|
86
93
|
strings,
|
|
87
94
|
state = 'default',
|
|
88
|
-
values,
|
|
95
|
+
values: valuesProp,
|
|
89
96
|
screenId = `ekyc-details-${state}`,
|
|
97
|
+
ctaDisabled,
|
|
90
98
|
className,
|
|
91
99
|
overrides,
|
|
92
100
|
onAction,
|
|
@@ -98,6 +106,13 @@ export const EkycDetailsScreen = forwardRef<HTMLDivElement, EkycDetailsScreenPro
|
|
|
98
106
|
// rings First name blue and shows its clear ✕; the other two draw every
|
|
99
107
|
// field resting.
|
|
100
108
|
const focused = state === 'focused';
|
|
109
|
+
// The empty-submit state: every field empty, required ones in error.
|
|
110
|
+
const emptyError = state === 'emptyError';
|
|
111
|
+
const values = emptyError ? undefined : valuesProp;
|
|
112
|
+
// Core's own error treatment, spread onto the REQUIRED fields only.
|
|
113
|
+
const missing = emptyError
|
|
114
|
+
? { error: true, errorText: strings.country.requiredError }
|
|
115
|
+
: {};
|
|
101
116
|
|
|
102
117
|
return (
|
|
103
118
|
<PrizmaFormScreen
|
|
@@ -122,6 +137,9 @@ export const EkycDetailsScreen = forwardRef<HTMLDivElement, EkycDetailsScreenPro
|
|
|
122
137
|
title={strings.details.title}
|
|
123
138
|
subtitle={strings.details.subtitle}
|
|
124
139
|
ctaLabel={strings.details.ctaLabel}
|
|
140
|
+
/* Disabled only after the empty submit; the resting page keeps it
|
|
141
|
+
enabled so the press can happen. */
|
|
142
|
+
ctaDisabled={emptyError || ctaDisabled}
|
|
125
143
|
verifiedLabel={strings.verifiedLabel}
|
|
126
144
|
navLabel={strings.navLabel}
|
|
127
145
|
screenId={screenId}
|
|
@@ -146,6 +164,7 @@ export const EkycDetailsScreen = forwardRef<HTMLDivElement, EkycDetailsScreenPro
|
|
|
146
164
|
label={strings.details.firstNameLabel}
|
|
147
165
|
required
|
|
148
166
|
requiredHint={strings.details.requiredHint}
|
|
167
|
+
{...missing}
|
|
149
168
|
defaultValue={values?.firstName ?? ''}
|
|
150
169
|
autoComplete="off"
|
|
151
170
|
/* Only 2101:8159 draws the ✕, and only in this field. */
|
|
@@ -156,6 +175,7 @@ export const EkycDetailsScreen = forwardRef<HTMLDivElement, EkycDetailsScreenPro
|
|
|
156
175
|
label={strings.details.lastNameLabel}
|
|
157
176
|
required
|
|
158
177
|
requiredHint={strings.details.requiredHint}
|
|
178
|
+
{...missing}
|
|
159
179
|
defaultValue={values?.lastName ?? ''}
|
|
160
180
|
autoComplete="off"
|
|
161
181
|
clearable={false}
|
|
@@ -171,6 +191,7 @@ export const EkycDetailsScreen = forwardRef<HTMLDivElement, EkycDetailsScreenPro
|
|
|
171
191
|
label={strings.details.dateOfBirthLabel}
|
|
172
192
|
required
|
|
173
193
|
requiredHint={strings.details.requiredHint}
|
|
194
|
+
{...missing}
|
|
174
195
|
defaultValue={values?.dateOfBirth ?? ''}
|
|
175
196
|
autoComplete="off"
|
|
176
197
|
/* No `onCalendarClick`: a screen is a static spec state with no
|
package/src/EkycStrings.ts
CHANGED
|
@@ -43,8 +43,12 @@
|
|
|
43
43
|
* the clear ✕ (2101:8159 draws the OS keyboard over the CTA; the
|
|
44
44
|
* keyboard is OS chrome, never a screen element)
|
|
45
45
|
* - `filled` — all four fields resting with their values
|
|
46
|
+
* - `emptyError` — Continue was pressed with the fields empty (GUIDELINES §6-E
|
|
47
|
+
* empty-submit rule, Elena 2026-09-30): the three REQUIRED fields
|
|
48
|
+
* in Core's error state with `country.requiredError`, Middle name
|
|
49
|
+
* (optional) unflagged, and Continue disabled. Not a Figma frame.
|
|
46
50
|
*/
|
|
47
|
-
export type EkycDetailsState = 'default' | 'focused' | 'filled';
|
|
51
|
+
export type EkycDetailsState = 'default' | 'focused' | 'filled' | 'emptyError';
|
|
48
52
|
|
|
49
53
|
/**
|
|
50
54
|
* The page-2 sections (`eKYC.default` / `eKYC.filled` / `eKYC.loading`), same
|
|
@@ -153,6 +157,10 @@ export interface EkycStrings {
|
|
|
153
157
|
licenseSection: string;
|
|
154
158
|
/** Accessible name of the phone field's dial-code list. */
|
|
155
159
|
countryListLabel: string;
|
|
160
|
+
/** Accessible names for the eye on a `password` field (the SSN / ITIN
|
|
161
|
+
* fields): while the value is hidden, and while it is shown. */
|
|
162
|
+
showValueLabel: string;
|
|
163
|
+
hideValueLabel: string;
|
|
156
164
|
/** The gender radio pair, Argentina. */
|
|
157
165
|
genderOptions: { value: string; label: string }[];
|
|
158
166
|
fields: Record<string, string>;
|
|
@@ -160,8 +168,12 @@ export interface EkycStrings {
|
|
|
160
168
|
ctaLabel: string;
|
|
161
169
|
/** The secondary button on a step after the first. */
|
|
162
170
|
backLabel: string;
|
|
163
|
-
/** Under every empty field once Continue was pressed
|
|
171
|
+
/** Under every empty REQUIRED field once Continue was pressed: the
|
|
172
|
+
* by-country `showErrors` state and page 1's `emptyError` both read it. */
|
|
164
173
|
requiredError: string;
|
|
174
|
+
/** Under a field whose value failed the format check on Continue, by
|
|
175
|
+
* field key (`invalidFields`). `cpf` is SDK key `dynamicForms.invalidCpf`. */
|
|
176
|
+
invalidErrors?: Record<string, string>;
|
|
165
177
|
};
|
|
166
178
|
|
|
167
179
|
/** `eKYC.processing` — spinner over the status line, no button. */
|
|
@@ -47,6 +47,12 @@
|
|
|
47
47
|
border: var(--prizma-border-width-regular) solid var(--prizma-border-neutral-100);
|
|
48
48
|
border-radius: var(--prizma-border-radius-small);
|
|
49
49
|
text-align: start;
|
|
50
|
+
/* Air between the frame and the CTA band. Archetype G's list middle carries
|
|
51
|
+
no bottom gap (its content normally ends well above the band), so a frame
|
|
52
|
+
that fills the middle sat flush against Continue (owner-reported
|
|
53
|
+
2026-09-29). Spacing/16 is the archetype's own scrolling-middle → band gap
|
|
54
|
+
(`.prizma-screen-layout__list--scroll`, ScreenLayout.css). */
|
|
55
|
+
margin-bottom: var(--prizma-spacing-16);
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
/* The agreement's own typography — Body/M Regular on Text/Body/500, the pair
|
package/src/EmailInputStrings.ts
CHANGED
|
@@ -27,13 +27,18 @@
|
|
|
27
27
|
* - `filled` — focused field with a complete address
|
|
28
28
|
* - `error` — negative field + clear ✕ + the invalid-format row
|
|
29
29
|
* - `loading` — field greyed while the code is requested, spinner in the CTA
|
|
30
|
+
* - `emptyError` — "Continue" pressed with the field still empty: the field
|
|
31
|
+
* stays empty (no placeholder) in Core's error state with `requiredError`,
|
|
32
|
+
* and the CTA is disabled. GUIDELINES §6-E empty-submit rule (Elena,
|
|
33
|
+
* 2026-09-30).
|
|
30
34
|
*/
|
|
31
35
|
export type EmailInputVerifyState =
|
|
32
36
|
| 'default'
|
|
33
37
|
| 'typing'
|
|
34
38
|
| 'filled'
|
|
35
39
|
| 'error'
|
|
36
|
-
| 'loading'
|
|
40
|
+
| 'loading'
|
|
41
|
+
| 'emptyError';
|
|
37
42
|
|
|
38
43
|
/**
|
|
39
44
|
* The `email.OTP.*` sections — same layout, different code-field state:
|
|
@@ -58,6 +63,12 @@ export interface EmailInputStrings {
|
|
|
58
63
|
verifiedLabel: string;
|
|
59
64
|
/** Accessible name for the logo nav row. */
|
|
60
65
|
navLabel?: string;
|
|
66
|
+
/**
|
|
67
|
+
* Error row under the required field when the primary CTA is pressed with it
|
|
68
|
+
* still empty (the verify screen's `emptyError` state): "This field is
|
|
69
|
+
* required". SDK key `dynamicForms.fieldRequired`.
|
|
70
|
+
*/
|
|
71
|
+
requiredError: string;
|
|
61
72
|
|
|
62
73
|
/** email.verifyEmail.* */
|
|
63
74
|
verify: {
|