@incodetech/prizma-core-screens 0.1.2 → 0.2.3
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/DemoScreensConfigScreen.d.ts +36 -0
- package/dist/DemoScreensConfigScreen.js +30 -0
- package/dist/DemoScreensConfigSheetScreen.d.ts +39 -0
- package/dist/DemoScreensConfigSheetScreen.js +33 -0
- package/dist/DemoScreensEditSheetScreen.d.ts +30 -0
- package/dist/DemoScreensEditSheetScreen.js +28 -0
- package/dist/DemoScreensEndScreen.d.ts +9 -21
- package/dist/DemoScreensEndScreen.js +9 -14
- package/dist/DemoScreensLaunchScreen.d.ts +28 -0
- package/dist/DemoScreensLaunchScreen.js +26 -0
- package/dist/DemoScreensLayout.css +542 -0
- package/dist/DemoScreensRedirectScreen.d.ts +50 -0
- package/dist/DemoScreensRedirectScreen.js +38 -0
- package/dist/DemoScreensResultScreen.d.ts +52 -0
- package/dist/DemoScreensResultScreen.js +46 -0
- package/dist/DemoScreensRunningScreen.d.ts +22 -0
- package/dist/DemoScreensRunningScreen.js +16 -0
- package/dist/DemoScreensSettingsScreen.d.ts +37 -0
- package/dist/DemoScreensSettingsScreen.js +40 -0
- package/dist/DemoScreensSplashScreen.d.ts +27 -0
- package/dist/DemoScreensSplashScreen.js +25 -0
- package/dist/DemoScreensStartScreen.d.ts +38 -0
- package/dist/DemoScreensStartScreen.js +37 -0
- package/dist/DemoScreensStrings.d.ts +192 -0
- package/dist/DemoScreensStrings.js +11 -11
- package/dist/DemoScreensTermsScreen.d.ts +28 -0
- package/dist/DemoScreensTermsScreen.js +26 -0
- package/dist/EkybAddressDetailsScreen.js +9 -5
- package/dist/EkybBusinessDetailsScreen.js +6 -3
- package/dist/EkybOwnersScreen.d.ts +4 -0
- package/dist/EkybOwnersScreen.js +13 -5
- package/dist/EkybStrings.d.ts +12 -1
- package/dist/EkycCountryCatalog.d.ts +46 -0
- package/dist/EkycCountryCatalog.js +139 -0
- package/dist/EkycCountryFormScreen.d.ts +36 -0
- package/dist/EkycCountryFormScreen.js +94 -0
- package/dist/EkycDetailsScreen.js +11 -4
- package/dist/EkycLayout.css +23 -0
- package/dist/EkycLicenseScreen.js +7 -3
- package/dist/EkycStrings.d.ts +26 -0
- package/dist/EmailInputStrings.d.ts +11 -1
- package/dist/FaceMatchComparisonScreen.d.ts +4 -2
- package/dist/FaceMatchComparisonScreen.js +3 -1
- package/dist/FaceMatchLayout.css +53 -3
- package/dist/FormScreen.css +74 -0
- package/dist/FormScreen.d.ts +17 -0
- package/dist/FormScreen.js +12 -1
- package/dist/PhoneInputStrings.d.ts +10 -1
- package/dist/index.d.ts +15 -2
- package/dist/index.js +13 -0
- package/dist/styles.css +1 -0
- package/package.json +62 -1
- package/src/DemoScreensConfigScreen.tsx +101 -0
- package/src/DemoScreensConfigSheetScreen.tsx +120 -0
- package/src/DemoScreensEditSheetScreen.tsx +103 -0
- package/src/DemoScreensEndScreen.tsx +14 -38
- package/src/DemoScreensLaunchScreen.tsx +96 -0
- package/src/DemoScreensLayout.css +542 -0
- package/src/DemoScreensRedirectScreen.tsx +190 -0
- package/src/DemoScreensResultScreen.tsx +115 -0
- package/src/DemoScreensRunningScreen.tsx +45 -0
- package/src/DemoScreensSettingsScreen.tsx +101 -0
- package/src/DemoScreensSplashScreen.tsx +47 -0
- package/src/DemoScreensStartScreen.tsx +105 -0
- package/src/DemoScreensStrings.ts +204 -11
- package/src/DemoScreensTermsScreen.tsx +82 -0
- package/src/EkybAddressDetailsScreen.tsx +7 -1
- package/src/EkybBusinessDetailsScreen.tsx +6 -1
- package/src/EkybOwnersScreen.tsx +46 -2
- package/src/EkybStrings.ts +12 -0
- package/src/EkycCountryCatalog.ts +199 -0
- package/src/EkycCountryFormScreen.tsx +184 -0
- package/src/EkycDetailsScreen.tsx +29 -20
- package/src/EkycLayout.css +23 -0
- package/src/EkycLicenseScreen.tsx +22 -16
- package/src/EkycStrings.ts +25 -0
- package/src/EmailInputStrings.ts +11 -1
- package/src/FaceMatchComparisonScreen.tsx +11 -3
- package/src/FaceMatchLayout.css +53 -3
- package/src/FormScreen.css +74 -0
- package/src/FormScreen.tsx +27 -1
- package/src/PhoneInputStrings.ts +10 -1
- package/src/index.ts +65 -1
- package/src/styles.css +1 -0
|
@@ -4,7 +4,7 @@ import { forwardRef } from 'react';
|
|
|
4
4
|
import { PrizmaTextField } from '@incodetech/prizma-core-web/text-field';
|
|
5
5
|
import { PrizmaButton } from '@incodetech/prizma-core-web/button';
|
|
6
6
|
import { PrizmaStepper } from '@incodetech/prizma-core-web/stepper';
|
|
7
|
-
import { PrizmaFormScreen } from './FormScreen';
|
|
7
|
+
import { PrizmaFormScreen, PrizmaFormRow } from './FormScreen';
|
|
8
8
|
import './ScreenLayout.css';
|
|
9
9
|
/**
|
|
10
10
|
* eKYB — step 1 of the wizard, the company's identity, built on the Form
|
|
@@ -45,13 +45,16 @@ import './ScreenLayout.css';
|
|
|
45
45
|
*/
|
|
46
46
|
export const EkybBusinessDetailsScreen = forwardRef(({ strings, state = 'empty', values, screenId = `ekyb-business-${state}`, overrides, onAction, ...shell }, ref) => {
|
|
47
47
|
const copy = strings.business;
|
|
48
|
-
return (
|
|
48
|
+
return (_jsx(PrizmaFormScreen, { ref: ref,
|
|
49
49
|
/* Step 1 of 3. `showIcons={false}` because the eKYB frames draw the bar
|
|
50
50
|
as a bare 5px rail with no glyph row. */
|
|
51
51
|
progress: _jsx(PrizmaStepper, { steps: 3, currentState: "Start", showIcons: false, "aria-label": strings.stepperLabel }), title: copy.title, subtitle: copy.subtitle, ctaLabel: copy.ctaLabel, linkContent: _jsx(PrizmaButton, { variant: "secondary", size: "L", onClick: () => onAction?.({ type: 'secondary', screenId }), children: strings.backLabel }), verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId,
|
|
52
52
|
/* Left-aligned title block, the alignment every eKYB frame draws; a host
|
|
53
53
|
`align` still wins (#GEO-2). */
|
|
54
|
-
overrides: { align: overrides?.align ?? 'left', logo: overrides?.logo }, onAction: onAction,
|
|
54
|
+
overrides: { align: overrides?.align ?? 'left', logo: overrides?.logo }, onAction: onAction,
|
|
55
|
+
/* Exploration ekyb-desktop: on a tablet or desktop frame the two
|
|
56
|
+
fields share a row; on the phone nothing changes. */
|
|
57
|
+
responsive: true, ...shell, children: _jsxs(PrizmaFormRow, { children: [_jsx(PrizmaTextField, { label: copy.businessName.label, placeholder: copy.businessName.placeholder, required: true, requiredHint: strings.requiredHint, autoComplete: "off", defaultValue: values?.businessName ?? '' }), _jsx(PrizmaTextField, { label: copy.taxId.label, placeholder: copy.taxId.placeholder, required: true, requiredHint: strings.requiredHint, autoComplete: "off", defaultValue: values?.taxId ?? '' })] }) }));
|
|
55
58
|
});
|
|
56
59
|
EkybBusinessDetailsScreen.displayName = 'EkybBusinessDetailsScreen';
|
|
57
60
|
export default EkybBusinessDetailsScreen;
|
|
@@ -31,6 +31,10 @@ export interface EkybOwnersScreenProps extends Omit<PrizmaFormScreenProps, 'titl
|
|
|
31
31
|
* - `uboFilled` 5707:49608 — UBO block only, both fields carrying a value
|
|
32
32
|
* - `directorEmpty` 5655:56032 — UBO block + Director block, all four blank
|
|
33
33
|
* - `directorFilled` 5707:49782 — UBO block + Director block, all four filled
|
|
34
|
+
* - `uboAdded` (no frame) — `directorFilled` after "Add another UBO": a
|
|
35
|
+
* second, blank UBO pair under the first with its own "Remove UBO" link,
|
|
36
|
+
* then the same "Add another UBO" link and the Director block. Elena asked
|
|
37
|
+
* for the screen the link leads to (2026-09-24); the frames stop at the link.
|
|
34
38
|
* - `loading` 5663:94390 — `directorFilled` with the submission in
|
|
35
39
|
* flight: every field, both "Add another" links and "Back" go inert, and the
|
|
36
40
|
* spinner rides the CTA, never a field (the Phone Input lesson, #PI-5)
|
package/dist/EkybOwnersScreen.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx,
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { forwardRef } from 'react';
|
|
4
4
|
import { PrizmaTextField } from '@incodetech/prizma-core-web/text-field';
|
|
5
5
|
import { PrizmaTitle } from '@incodetech/prizma-core-web/title';
|
|
6
6
|
import { PrizmaButton } from '@incodetech/prizma-core-web/button';
|
|
7
7
|
import { PrizmaStepper } from '@incodetech/prizma-core-web/stepper';
|
|
8
|
-
import { PrizmaFormScreen } from './FormScreen';
|
|
8
|
+
import { PrizmaFormScreen, PrizmaFormRow } from './FormScreen';
|
|
9
9
|
import './ScreenLayout.css';
|
|
10
10
|
/**
|
|
11
11
|
* eKYB — step 3 of the wizard, the people behind the company, built on the Form
|
|
@@ -23,6 +23,10 @@ import './ScreenLayout.css';
|
|
|
23
23
|
* - `uboFilled` 5707:49608 — UBO block only, both fields carrying a value
|
|
24
24
|
* - `directorEmpty` 5655:56032 — UBO block + Director block, all four blank
|
|
25
25
|
* - `directorFilled` 5707:49782 — UBO block + Director block, all four filled
|
|
26
|
+
* - `uboAdded` (no frame) — `directorFilled` after "Add another UBO": a
|
|
27
|
+
* second, blank UBO pair under the first with its own "Remove UBO" link,
|
|
28
|
+
* then the same "Add another UBO" link and the Director block. Elena asked
|
|
29
|
+
* for the screen the link leads to (2026-09-24); the frames stop at the link.
|
|
26
30
|
* - `loading` 5663:94390 — `directorFilled` with the submission in
|
|
27
31
|
* flight: every field, both "Add another" links and "Back" go inert, and the
|
|
28
32
|
* spinner rides the CTA, never a field (the Phone Input lesson, #PI-5)
|
|
@@ -64,7 +68,8 @@ import './ScreenLayout.css';
|
|
|
64
68
|
export const EkybOwnersScreen = forwardRef(({ strings, state = 'uboEmpty', values, screenId = `ekyb-owners-${state}`, className, overrides, onAction, ...shell }, ref) => {
|
|
65
69
|
const copy = strings.owners;
|
|
66
70
|
const busy = state === 'loading';
|
|
67
|
-
const
|
|
71
|
+
const secondUbo = state === 'uboAdded';
|
|
72
|
+
const showDirector = state === 'directorEmpty' || state === 'directorFilled' || secondUbo || busy;
|
|
68
73
|
return (_jsxs(PrizmaFormScreen, { ref: ref, className: ['ekyb-owners', className].filter(Boolean).join(' '),
|
|
69
74
|
/* The archetype's own scrolling field stack — two titled blocks, four
|
|
70
75
|
fields and two links do not fit a 844 frame. No module CSS. */
|
|
@@ -74,11 +79,14 @@ export const EkybOwnersScreen = forwardRef(({ strings, state = 'uboEmpty', value
|
|
|
74
79
|
/* `loading` swaps the CTA label for Core's designed spinner. */
|
|
75
80
|
ctaLoading: busy, ctaLoadingLabel: copy.ctaLoadingLabel, linkContent: _jsx(PrizmaButton, { variant: "secondary", size: "L",
|
|
76
81
|
/* 5663:94390 greys "Back" while the submission runs. */
|
|
77
|
-
disabled: busy, onClick: () => onAction?.({ type: 'secondary', screenId }), children: strings.backLabel }), verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId, overrides: { align: overrides?.align ?? 'left', logo: overrides?.logo }, onAction: onAction,
|
|
82
|
+
disabled: busy, onClick: () => onAction?.({ type: 'secondary', screenId }), children: strings.backLabel }), verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId, overrides: { align: overrides?.align ?? 'left', logo: overrides?.logo }, onAction: onAction,
|
|
83
|
+
/* Exploration ekyb-desktop: name + surname share a row per block on a
|
|
84
|
+
wide frame; on the phone nothing changes. */
|
|
85
|
+
responsive: true, ...shell, children: [_jsxs(PrizmaFormRow, { children: [_jsx(PrizmaTextField, { label: copy.uboName.label, placeholder: copy.uboName.placeholder, required: true, requiredHint: strings.requiredHint, autoComplete: "off", defaultValue: values?.uboName ?? '', disabled: busy }), _jsx(PrizmaTextField, { label: copy.uboSurname.label, placeholder: copy.uboSurname.placeholder, autoComplete: "off", defaultValue: values?.uboSurname ?? '', disabled: busy })] }), secondUbo ? (_jsxs(_Fragment, { children: [_jsxs(PrizmaFormRow, { children: [_jsx(PrizmaTextField, { label: copy.uboName.label, placeholder: copy.uboName.placeholder, required: true, requiredHint: strings.requiredHint, autoComplete: "off", defaultValue: values?.secondUboName ?? '' }), _jsx(PrizmaTextField, { label: copy.uboSurname.label, placeholder: copy.uboSurname.placeholder, autoComplete: "off", defaultValue: values?.secondUboSurname ?? '' })] }), copy.removeUboLabel ? (_jsx(PrizmaButton, { variant: "tertiary-link", size: "L", onClick: () => onAction?.({ type: 'tertiary', screenId: `${screenId}-remove-ubo` }), children: copy.removeUboLabel })) : null] })) : null, _jsx(PrizmaButton, { variant: "tertiary-link", size: "L", disabled: busy,
|
|
78
86
|
/* Action-qualified id: the two "Add another…" links are different
|
|
79
87
|
flows and must stay distinguishable (#PI-10), but the action
|
|
80
88
|
contract has no per-action discriminator yet. */
|
|
81
|
-
onClick: () => onAction?.({ type: 'tertiary', screenId: `${screenId}-add-ubo` }), children: copy.addUboLabel }), showDirector ? (_jsxs(_Fragment, { children: [_jsx(PrizmaTitle, { variant: "left", title: copy.directorTitle, showSubtitle: Boolean(copy.directorSubtitle), subtitle: copy.directorSubtitle }), _jsx(PrizmaTextField, { label: copy.directorName.label, placeholder: copy.directorName.placeholder, autoComplete: "off", defaultValue: values?.directorName ?? '', disabled: busy }), _jsx(PrizmaTextField, { label: copy.directorSurname.label, placeholder: copy.directorSurname.placeholder, autoComplete: "off", defaultValue: values?.directorSurname ?? '', disabled: busy }), _jsx(PrizmaButton, { variant: "tertiary-link", size: "L", disabled: busy, onClick: () => onAction?.({
|
|
89
|
+
onClick: () => onAction?.({ type: 'tertiary', screenId: `${screenId}-add-ubo` }), children: copy.addUboLabel }), showDirector ? (_jsxs(_Fragment, { children: [_jsx(PrizmaTitle, { variant: "left", title: copy.directorTitle, showSubtitle: Boolean(copy.directorSubtitle), subtitle: copy.directorSubtitle }), _jsxs(PrizmaFormRow, { children: [_jsx(PrizmaTextField, { label: copy.directorName.label, placeholder: copy.directorName.placeholder, autoComplete: "off", defaultValue: values?.directorName ?? '', disabled: busy }), _jsx(PrizmaTextField, { label: copy.directorSurname.label, placeholder: copy.directorSurname.placeholder, autoComplete: "off", defaultValue: values?.directorSurname ?? '', disabled: busy })] }), _jsx(PrizmaButton, { variant: "tertiary-link", size: "L", disabled: busy, onClick: () => onAction?.({
|
|
82
90
|
type: 'tertiary',
|
|
83
91
|
screenId: `${screenId}-add-director`,
|
|
84
92
|
}), children: copy.addDirectorLabel })] })) : null] }));
|
package/dist/EkybStrings.d.ts
CHANGED
|
@@ -25,12 +25,16 @@ export type EkybAddressState = 'empty' | 'filled';
|
|
|
25
25
|
* - `directorEmpty` — `eKYB.director.empty` (5655:56032): UBO block plus the
|
|
26
26
|
* Director block, every field blank
|
|
27
27
|
* - `directorFilled`— `eKYB.directorFilled` (5707:49782): all four filled
|
|
28
|
+
* - `uboAdded` — what "Add another UBO" does: a second UBO name + surname
|
|
29
|
+
* pair opens under the first, blank, with a "Remove" link of its own, and the
|
|
30
|
+
* Director block below as before. Not a Figma frame — the frames draw the
|
|
31
|
+
* link and stop there; Elena asked for the screen it leads to (2026-09-24).
|
|
28
32
|
* - `loading` — `eKYB.directorloading` (5663:94390): the submission is in
|
|
29
33
|
* flight, so EVERY field, both "Add another" links and "Back" go inert and
|
|
30
34
|
* the spinner rides the CTA, never a field (the Phone Input lesson,
|
|
31
35
|
* DEVIATIONS #PI-5)
|
|
32
36
|
*/
|
|
33
|
-
export type EkybOwnersState = 'uboEmpty' | 'uboFilled' | 'directorEmpty' | 'directorFilled' | 'loading';
|
|
37
|
+
export type EkybOwnersState = 'uboEmpty' | 'uboFilled' | 'directorEmpty' | 'directorFilled' | 'uboAdded' | 'loading';
|
|
34
38
|
/**
|
|
35
39
|
* One selectable row in the country / state menus. Runtime data, not copy — the
|
|
36
40
|
* host owns the catalogue (an ISO country list is ~250 entries and a state list
|
|
@@ -67,6 +71,9 @@ export interface EkybAddressValues {
|
|
|
67
71
|
export interface EkybOwnersValues {
|
|
68
72
|
uboName?: string;
|
|
69
73
|
uboSurname?: string;
|
|
74
|
+
/** The second UBO, present only in `uboAdded`. */
|
|
75
|
+
secondUboName?: string;
|
|
76
|
+
secondUboSurname?: string;
|
|
70
77
|
directorName?: string;
|
|
71
78
|
directorSurname?: string;
|
|
72
79
|
}
|
|
@@ -167,6 +174,10 @@ export interface EkybStrings {
|
|
|
167
174
|
uboSurname: EkybFieldStrings;
|
|
168
175
|
/** The tertiary link under the UBO block ("Add another UBO (Optional)"). */
|
|
169
176
|
addUboLabel: string;
|
|
177
|
+
/** The tertiary link under a second UBO's pair ("Remove UBO"), `uboAdded`
|
|
178
|
+
* only. Optional so existing hosts keep compiling; without it the second
|
|
179
|
+
* pair cannot be taken away again. */
|
|
180
|
+
removeUboLabel?: string;
|
|
170
181
|
/** The Director block's own heading + description. Rendered by Core's
|
|
171
182
|
* `PrizmaTitle` inside the field stack; see the screen's docstring and the
|
|
172
183
|
* module's reported deviation about the second heading. */
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export type EkycFieldKind = 'text' | 'date' | 'phone' | 'dropdown' | 'radio';
|
|
2
|
+
export type EkycFieldKey = 'firstName' | 'middleName' | 'middleNameOptional' | 'lastName' | 'surname' | 'maternalSurname' | 'dateOfBirth' | 'email' | 'phone' | 'ssnLast4' | 'ssnOrItin' | 'nationality' | 'cpf' | 'nationalId' | 'taxId' | 'gender' | 'licenseNumber' | 'licenseState' | 'licenseExpiration' | 'street' | 'houseNumber' | 'city' | 'state' | 'stateCode' | 'zipCode' | 'postalCode';
|
|
3
|
+
export interface EkycField {
|
|
4
|
+
key: EkycFieldKey;
|
|
5
|
+
kind: EkycFieldKind;
|
|
6
|
+
/** Draws the `*` after the label, as the US driver's licence frame does. */
|
|
7
|
+
required?: boolean;
|
|
8
|
+
/** Placeholder key into `EkycStrings.country.placeholders`, when the frame draws one. */
|
|
9
|
+
placeholder?: string;
|
|
10
|
+
/** For `phone`: the ISO code of the dial code the frame shows (+1, +52, +34…). */
|
|
11
|
+
dialCountry?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Desktop and tablet pairing (DEVIATIONS #EKC-5). Two ADJACENT fields with
|
|
14
|
+
* the same `row` share a `PrizmaFormRow`: two lanes of 202 on a wide frame,
|
|
15
|
+
* the ordinary stack on the phone (the archetype's container query, 420 and
|
|
16
|
+
* up, decides; nothing here re-declares a width). A field without a `row`
|
|
17
|
+
* spans the column. The rule mirrors eKYB's frames: a name pair shares
|
|
18
|
+
* (first | last, first | surname, surname | maternal surname), nationality |
|
|
19
|
+
* CPF share, State | Postal or Zip code share; email, date of birth, the
|
|
20
|
+
* phone (a Combined field never shares a row, #EKC-7), every identifier
|
|
21
|
+
* (SSN, ITIN, national ID, tax ID, CPF alone), the gender pair and street,
|
|
22
|
+
* house number, city span.
|
|
23
|
+
*/
|
|
24
|
+
row?: number;
|
|
25
|
+
}
|
|
26
|
+
export interface EkycCountryStep {
|
|
27
|
+
/** Figma frame id. */
|
|
28
|
+
node: string;
|
|
29
|
+
/** A section heading under the title ("Address details", "Driver's License details"), when drawn. */
|
|
30
|
+
section?: 'address' | 'license';
|
|
31
|
+
fields: EkycField[];
|
|
32
|
+
}
|
|
33
|
+
export interface EkycCountryVariant {
|
|
34
|
+
/** Stable id; screen ids are `ekyc-<id>-step<n>`. */
|
|
35
|
+
id: string;
|
|
36
|
+
/** ISO 3166-1 alpha-2 of the country; `XX` for a country-agnostic provider (IPQS). */
|
|
37
|
+
country: string;
|
|
38
|
+
/** Country name and provider, as the frame names them. */
|
|
39
|
+
label: string;
|
|
40
|
+
provider: string;
|
|
41
|
+
/** Segments the frame's stepper draws; 0 when it draws none. */
|
|
42
|
+
stepper: number;
|
|
43
|
+
steps: EkycCountryStep[];
|
|
44
|
+
}
|
|
45
|
+
export declare const EKYC_COUNTRY_CATALOG: EkycCountryVariant[];
|
|
46
|
+
export declare const ekycCountryVariant: (id: string) => EkycCountryVariant | undefined;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// eKYC — the field catalogue by country and data provider.
|
|
2
|
+
//
|
|
3
|
+
// Figma: `👁️ eKYC Initial screen options` (`TWgYZVhurQCizfC4tfLEut`), section
|
|
4
|
+
// `2098:20867` "1-1-1-eKYC-empty (all options)", read on 2026-09-24. Fifteen
|
|
5
|
+
// step-1 frames, one per country and provider, eight of them with a step 2 and
|
|
6
|
+
// US Telco 1 with a step 3. Every frame is archetype E on the same skeleton;
|
|
7
|
+
// what changes is the LIST OF FIELDS per step. So a country is data, not a
|
|
8
|
+
// screen: `EkycCountryFormScreen` renders any entry of this catalogue.
|
|
9
|
+
//
|
|
10
|
+
// Labels are copy and live in `EkycStrings.country.fields`, keyed by
|
|
11
|
+
// `EkycFieldKey`, so a locale pack translates them once. The catalogue only
|
|
12
|
+
// says WHICH fields, in WHICH order, with WHICH kind of Core input.
|
|
13
|
+
//
|
|
14
|
+
// Node ids per variant are the frame ids in that section; a step's id is the
|
|
15
|
+
// frame that draws it. Nothing here is invented: a field appears because the
|
|
16
|
+
// frame draws it, with the label the frame draws (DEVIATIONS #EKC-1). Two
|
|
17
|
+
// things the frames draw unevenly were ALIGNED by the owner (Elena,
|
|
18
|
+
// 2026-09-24): every step uses the module's "Enter your details" title and
|
|
19
|
+
// subtitle (the US licence frame's copy) rather than a bare "eKYC
|
|
20
|
+
// Verification", and no variant draws a close in the nav; steps after the
|
|
21
|
+
// first get the archetype's Back button instead of a back arrow (#EKC-2).
|
|
22
|
+
const t = (key, extra = {}) => ({ key, kind: 'text', ...extra });
|
|
23
|
+
const date = (key = 'dateOfBirth', extra = {}) => ({ key, kind: 'date', ...extra });
|
|
24
|
+
// Required fields carry Core's asterisk (`required` + `requiredHint`), the
|
|
25
|
+
// owner's rule (Elena, 2026-09-24, DEVIATIONS #EKC-6): first name, last name or
|
|
26
|
+
// surname, email and phone on every variant. Middle name (plain or "Optional")
|
|
27
|
+
// and maternal surname stay optional; date of birth, identifiers, address and
|
|
28
|
+
// licence fields keep what their frame draws (only the US licence step 1 draws
|
|
29
|
+
// asterisks on them). `showErrors` flags required empty fields only.
|
|
30
|
+
const phone = (dialCountry, extra = {}) => ({ key: 'phone', kind: 'phone', dialCountry, required: true, ...extra });
|
|
31
|
+
const email = (extra = {}) => t('email', { required: true, ...extra });
|
|
32
|
+
// The wide-frame pairs (`row`, DEVIATIONS #EKC-5). A row number is only a
|
|
33
|
+
// grouping id, local to its step; 1 is the name pair, 2 Brazil's nationality |
|
|
34
|
+
// CPF, 3 the State | Postal pair of an address. The phone field NEVER shares a
|
|
35
|
+
// row: Core's Combined (dial code + number) collapses to a three-digit stub in
|
|
36
|
+
// a 202 lane (Elena, 2026-09-24, #EKC-7), and date of birth spans with it.
|
|
37
|
+
const NAME_PAIR = (last = 'lastName') => [t('firstName', { required: true, row: 1 }), t(last, { required: true, row: 1 })];
|
|
38
|
+
const DOB_PHONE = (dialCountry) => [date(), phone(dialCountry)];
|
|
39
|
+
const US_ADDRESS = [t('street'), t('houseNumber'), t('city'), { key: 'state', kind: 'dropdown', placeholder: 'selectState', row: 3 }, t('zipCode', { row: 3 })];
|
|
40
|
+
const CODE_ADDRESS = (placeholder) => [t('street'), t('houseNumber'), t('city'), t('stateCode', { row: 3, ...(placeholder ? { placeholder } : {}) }), t('postalCode', { row: 3 })];
|
|
41
|
+
const LICENSE = [t('licenseNumber'), { key: 'licenseState', kind: 'dropdown', placeholder: 'selectState', row: 1 }, date('licenseExpiration', { row: 1 })];
|
|
42
|
+
export const EKYC_COUNTRY_CATALOG = [
|
|
43
|
+
{
|
|
44
|
+
id: 'us-drivers-license', country: 'US', label: 'United States', provider: "Driver's license", stepper: 2,
|
|
45
|
+
steps: [
|
|
46
|
+
/* First | Last share the wide row, so Middle name follows the pair here
|
|
47
|
+
and in `EkycDetailsScreen` (the same frame). On the phone the stack
|
|
48
|
+
reads First, Last, Middle, Date of birth — the one order change the
|
|
49
|
+
desktop pairing costs (#EKC-5). */
|
|
50
|
+
{ node: '2101:25871', fields: [t('firstName', { required: true, row: 1 }), t('lastName', { required: true, row: 1 }), t('middleName'), date('dateOfBirth', { required: true })] },
|
|
51
|
+
{ node: '5849:73280', section: 'license', fields: LICENSE },
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: 'us-telco-1', country: 'US', label: 'United States', provider: 'Telco 1', stepper: 3,
|
|
56
|
+
steps: [
|
|
57
|
+
{ node: '2098:9575', fields: [...NAME_PAIR(), email(), ...DOB_PHONE('US'), t('ssnLast4')] },
|
|
58
|
+
{ node: '5854:76222', section: 'license', fields: [t('licenseNumber'), { key: 'licenseState', kind: 'dropdown', placeholder: 'selectState' }] },
|
|
59
|
+
{ node: '5854:76221', section: 'address', fields: US_ADDRESS },
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: 'us-telco-2', country: 'US', label: 'United States', provider: 'Telco 2', stepper: 2,
|
|
64
|
+
steps: [
|
|
65
|
+
{ node: '2098:9578', fields: [...NAME_PAIR(), ...DOB_PHONE('US')] },
|
|
66
|
+
{ node: '5857:33074', section: 'address', fields: US_ADDRESS },
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: 'us-credit-bureau', country: 'US', label: 'United States', provider: 'Credit bureau', stepper: 2,
|
|
71
|
+
steps: [
|
|
72
|
+
/* Same First | Last pair as the licence variant; Middle name (Optional)
|
|
73
|
+
follows it and spans (#EKC-5). */
|
|
74
|
+
{ node: '2098:9577', fields: [...NAME_PAIR(), t('middleNameOptional'), ...DOB_PHONE('US'), email(), t('ssnOrItin')] },
|
|
75
|
+
{ node: '5857:34094', section: 'address', fields: US_ADDRESS },
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: 'mx-consumer', country: 'MX', label: 'Mexico', provider: 'Consumer', stepper: 0,
|
|
80
|
+
/* Mexico pairs the two surnames, so First name spans on its own. */
|
|
81
|
+
steps: [{ node: '2098:9572', fields: [t('firstName', { required: true }), t('surname', { required: true, row: 1 }), t('maternalSurname', { row: 1 }), phone('MX'), email()] }],
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: 'br-govt', country: 'BR', label: 'Brazil', provider: 'Government', stepper: 2,
|
|
85
|
+
steps: [
|
|
86
|
+
{ node: '2098:9574', fields: [...NAME_PAIR('surname'), email(), date(), { key: 'nationality', kind: 'dropdown', row: 2 }, t('cpf', { row: 2 })] },
|
|
87
|
+
{ node: '5857:34935', section: 'address', fields: CODE_ADDRESS('stateCodeBr') },
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: 'ca-res-credit', country: 'CA', label: 'Canada', provider: 'Residential credit', stepper: 2,
|
|
92
|
+
steps: [
|
|
93
|
+
{ node: '2098:9570', fields: [...NAME_PAIR(), email(), ...DOB_PHONE('CA'), t('ssnOrItin')] },
|
|
94
|
+
{ node: '5857:36450', section: 'address', fields: CODE_ADDRESS('stateCodeCa') },
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
id: 'uk-credit-bureau', country: 'GB', label: 'United Kingdom', provider: 'Credit bureau', stepper: 2,
|
|
99
|
+
steps: [
|
|
100
|
+
{ node: '2098:9569', fields: [...NAME_PAIR('surname'), date()] },
|
|
101
|
+
{ node: '5857:36888', section: 'address', fields: CODE_ADDRESS() },
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: 'es-telephone-register', country: 'ES', label: 'Spain', provider: 'Telephone register', stepper: 2,
|
|
106
|
+
steps: [
|
|
107
|
+
{ node: '2098:9573', fields: [...NAME_PAIR('surname'), ...DOB_PHONE('ES'), t('nationalId')] },
|
|
108
|
+
{ node: '5857:37341', section: 'address', fields: CODE_ADDRESS() },
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: 'cl-official-census', country: 'CL', label: 'Chile', provider: 'Official census', stepper: 0,
|
|
113
|
+
steps: [{ node: '2098:9571', fields: [...NAME_PAIR('surname'), date(), t('nationalId')] }],
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: 'ar-civil-register', country: 'AR', label: 'Argentina', provider: 'Official civil register', stepper: 2,
|
|
117
|
+
steps: [
|
|
118
|
+
{ node: '2098:9568', fields: [...NAME_PAIR('surname'), { key: 'gender', kind: 'radio' }, email(), date(), t('nationalId'), t('taxId')] },
|
|
119
|
+
{ node: '5857:38015', section: 'address', fields: [t('street'), t('houseNumber'), t('city'), { key: 'state', kind: 'dropdown', placeholder: 'selectState', row: 3 }, t('postalCode', { row: 3 })] },
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: 'gt', country: 'GT', label: 'Guatemala', provider: 'Registry', stepper: 0,
|
|
124
|
+
steps: [{ node: '2098:9566', fields: [...NAME_PAIR('surname'), date(), t('nationalId')] }],
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
id: 'co', country: 'CO', label: 'Colombia', provider: 'Registry', stepper: 0,
|
|
128
|
+
steps: [{ node: '2098:9567', fields: [...NAME_PAIR('surname'), t('nationalId')] }],
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
id: 'cr', country: 'CR', label: 'Costa Rica', provider: 'Registry', stepper: 0,
|
|
132
|
+
steps: [{ node: '2098:9565', fields: [...NAME_PAIR('surname'), t('nationalId')] }],
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
id: 'ipqs', country: 'XX', label: 'Any country', provider: 'IPQS', stepper: 0,
|
|
136
|
+
steps: [{ node: '2098:9564', fields: [email(), phone('US')] }],
|
|
137
|
+
},
|
|
138
|
+
];
|
|
139
|
+
export const ekycCountryVariant = (id) => EKYC_COUNTRY_CATALOG.find(v => v.id === id);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type PrizmaCountry } from '@incodetech/prizma-core-web/combined';
|
|
3
|
+
import { type PrizmaFormScreenProps } from './FormScreen';
|
|
4
|
+
import type { PrizmaScreenActionHandler, PrizmaScreenOverrides } from './types';
|
|
5
|
+
import type { EkycCountryValues, EkycStrings } from './EkycStrings';
|
|
6
|
+
import { type EkycCountryVariant } from './EkycCountryCatalog';
|
|
7
|
+
import './ScreenLayout.css';
|
|
8
|
+
import './EkycLayout.css';
|
|
9
|
+
export interface EkycCountryFormScreenProps extends Omit<PrizmaFormScreenProps, 'title' | 'subtitle' | 'ctaLabel' | 'ctaDisabled' | 'verifiedLabel' | 'children' | 'navLabel' | 'progress' | 'scrollFields' | 'linkContent'> {
|
|
10
|
+
strings: EkycStrings;
|
|
11
|
+
/** A catalogue id (`us-telco-1`) or the variant itself. */
|
|
12
|
+
variant: string | EkycCountryVariant;
|
|
13
|
+
/** 1-based step of the variant. Default 1. */
|
|
14
|
+
step?: number;
|
|
15
|
+
/** Starting values by field key. Empty renders the resting empty field. */
|
|
16
|
+
values?: EkycCountryValues;
|
|
17
|
+
/**
|
|
18
|
+
* The state after Continue was pressed with fields still empty: every empty
|
|
19
|
+
* field shows Core's error state and `country.requiredError` under it.
|
|
20
|
+
* Continue itself is ALWAYS enabled (Elena, 2026-09-24): validation happens
|
|
21
|
+
* on press, not by greying the button.
|
|
22
|
+
*/
|
|
23
|
+
showErrors?: boolean;
|
|
24
|
+
/** Dial codes the phone field offers. Runtime data, never copy. */
|
|
25
|
+
phoneCountries?: PrizmaCountry[];
|
|
26
|
+
/** Options for the dropdown fields, keyed by field key. Runtime data. */
|
|
27
|
+
options?: Record<string, {
|
|
28
|
+
value: string;
|
|
29
|
+
label: string;
|
|
30
|
+
}[]>;
|
|
31
|
+
screenId?: string;
|
|
32
|
+
overrides?: PrizmaScreenOverrides;
|
|
33
|
+
onAction?: PrizmaScreenActionHandler;
|
|
34
|
+
}
|
|
35
|
+
export declare const EkycCountryFormScreen: React.ForwardRefExoticComponent<EkycCountryFormScreenProps & React.RefAttributes<HTMLDivElement>>;
|
|
36
|
+
export default EkycCountryFormScreen;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef } from 'react';
|
|
4
|
+
import { PrizmaTextField } from '@incodetech/prizma-core-web/text-field';
|
|
5
|
+
import { PrizmaDateField } from '@incodetech/prizma-core-web/date-field';
|
|
6
|
+
import { PrizmaDropdown } from '@incodetech/prizma-core-web/dropdown';
|
|
7
|
+
import { PrizmaCombined } from '@incodetech/prizma-core-web/combined';
|
|
8
|
+
import { PrizmaRadioGroup } from '@incodetech/prizma-core-web/radio-group';
|
|
9
|
+
import { PrizmaRadio } from '@incodetech/prizma-core-web/radio';
|
|
10
|
+
import { PrizmaStepper } from '@incodetech/prizma-core-web/stepper';
|
|
11
|
+
import { PrizmaButton } from '@incodetech/prizma-core-web/button';
|
|
12
|
+
import { PrizmaFormScreen, PrizmaFormRow } from './FormScreen';
|
|
13
|
+
import { ekycCountryVariant } from './EkycCountryCatalog';
|
|
14
|
+
import './ScreenLayout.css';
|
|
15
|
+
import './EkycLayout.css';
|
|
16
|
+
/**
|
|
17
|
+
* eKYC — one step of one by-country variant, built on the Form archetype.
|
|
18
|
+
*
|
|
19
|
+
* The section `2098:20867` draws fifteen variants on the same skeleton (nav →
|
|
20
|
+
* optional stepper → title → fields → Continue → tag) and differs only in the
|
|
21
|
+
* list of fields. This screen reads that list from `EkycCountryCatalog.ts` and
|
|
22
|
+
* composes Core's own inputs for it — text, date, phone (`PrizmaCombined`),
|
|
23
|
+
* dropdown, radio pair — so a new country is a catalogue entry, never a file.
|
|
24
|
+
*
|
|
25
|
+
* Deliberate, per frame and per owner:
|
|
26
|
+
* - Continue is ALWAYS enabled (Elena, 2026-09-24, overriding the grey button
|
|
27
|
+
* most frames draw): pressing it with empty fields is the `showErrors` state,
|
|
28
|
+
* Core's error ring and message under each empty field. The radio pair has
|
|
29
|
+
* no error state in Core, so an empty gender shows none (DEVIATIONS #EKC-4).
|
|
30
|
+
* - ONE title per step (Elena, 2026-09-24): the personal-data step is "Enter
|
|
31
|
+
* your details" with its subtitle; a licence step is titled "Driver's
|
|
32
|
+
* License details" and an address step "Address details", with nothing
|
|
33
|
+
* above them. The frames drew "eKYC Verification" over a section heading;
|
|
34
|
+
* the owner aligned the copy to one H2 per form.
|
|
35
|
+
* - No close in the nav (Core's nav has none, GUIDELINES §3, and the owner
|
|
36
|
+
* asked for none). A step after the first gets the archetype's Back button,
|
|
37
|
+
* as eKYB does, instead of the back arrow four frames drew. DEVIATIONS #EKC-2.
|
|
38
|
+
* - Desktop and tablet take the eKYB shape (`responsive`, exploration
|
|
39
|
+
* `ekyb-desktop`): two adjacent catalogue fields with the same `row` share a
|
|
40
|
+
* `PrizmaFormRow`, so on a wide frame they sit in two lanes and on the phone
|
|
41
|
+
* they stack exactly as before. Which fields pair is the catalogue's call,
|
|
42
|
+
* never this screen's (DEVIATIONS #EKC-5).
|
|
43
|
+
*/
|
|
44
|
+
/** Adjacent fields sharing a `row` become one group; the rest stand alone. */
|
|
45
|
+
const groupRows = (fields) => {
|
|
46
|
+
const groups = [];
|
|
47
|
+
for (const f of fields) {
|
|
48
|
+
const last = groups[groups.length - 1];
|
|
49
|
+
if (f.row != null && last && last[0].row === f.row)
|
|
50
|
+
last.push(f);
|
|
51
|
+
else
|
|
52
|
+
groups.push([f]);
|
|
53
|
+
}
|
|
54
|
+
return groups;
|
|
55
|
+
};
|
|
56
|
+
export const EkycCountryFormScreen = forwardRef(({ strings, variant, step = 1, values, showErrors = false, phoneCountries, options, screenId, overrides, onAction, ...shell }, ref) => {
|
|
57
|
+
const v = typeof variant === 'string' ? ekycCountryVariant(variant) : variant;
|
|
58
|
+
if (!v)
|
|
59
|
+
return null;
|
|
60
|
+
const s = v.steps[Math.min(Math.max(step, 1), v.steps.length) - 1];
|
|
61
|
+
const c = strings.country;
|
|
62
|
+
const stepperState = step <= 1 ? 'Start' : Math.min(step, 6);
|
|
63
|
+
const label = (f) => c.fields[f.key] ?? f.key;
|
|
64
|
+
const placeholder = (f) => (f.placeholder ? c.placeholders[f.placeholder] : c.placeholders[f.key]);
|
|
65
|
+
const renderField = (f) => {
|
|
66
|
+
// Only a REQUIRED empty field is an error on press (Elena, 2026-09-24,
|
|
67
|
+
// DEVIATIONS #EKC-6): an optional field left blank shows nothing.
|
|
68
|
+
const missing = showErrors && Boolean(f.required) && !values?.[f.key];
|
|
69
|
+
const common = {
|
|
70
|
+
key: f.key, label: label(f), required: f.required, requiredHint: strings.details.requiredHint,
|
|
71
|
+
error: missing || undefined, errorText: missing ? c.requiredError : undefined,
|
|
72
|
+
};
|
|
73
|
+
switch (f.kind) {
|
|
74
|
+
case 'date':
|
|
75
|
+
return _jsx(PrizmaDateField, { ...common, placeholder: placeholder(f), defaultValue: values?.[f.key] ?? '' });
|
|
76
|
+
case 'phone': {
|
|
77
|
+
const country = phoneCountries?.find(p => p.code === f.dialCountry) ?? phoneCountries?.[0];
|
|
78
|
+
return (_jsx(PrizmaCombined, { ...common, countries: phoneCountries, defaultCountry: country, countryListLabel: c.countryListLabel, placeholder: placeholder(f), defaultValue: values?.[f.key] ?? '', autoComplete: "off" }));
|
|
79
|
+
}
|
|
80
|
+
case 'dropdown':
|
|
81
|
+
return (_jsx(PrizmaDropdown, { ...common, placeholder: placeholder(f), options: options?.[f.key] ?? [], defaultValue: values?.[f.key] ?? null }));
|
|
82
|
+
case 'radio':
|
|
83
|
+
return (_jsx(PrizmaRadioGroup, { name: f.key, legend: label(f), defaultValue: values?.[f.key], className: "ekyc-country__radios", children: c.genderOptions.map(o => (_jsx(PrizmaRadio, { value: o.value, textSize: "m", showText: true, text: o.label }, o.value))) }, f.key));
|
|
84
|
+
default:
|
|
85
|
+
return _jsx(PrizmaTextField, { ...common, placeholder: placeholder(f), autoComplete: "off", defaultValue: values?.[f.key] ?? '' });
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
return (_jsx(PrizmaFormScreen, { ref: ref, scrollFields: true, progress: v.stepper > 0 ? (_jsx(PrizmaStepper, { steps: v.stepper, currentState: stepperState, showIcons: false, "aria-label": strings.stepperLabel })) : undefined, title: s.section === 'address' ? c.addressSection : s.section === 'license' ? c.licenseSection : strings.details.title, subtitle: s.section ? undefined : strings.details.subtitle, linkContent: step > 1 ? (_jsx(PrizmaButton, { variant: "secondary", size: "L", onClick: () => onAction?.({ type: 'secondary', screenId }), children: c.backLabel })) : undefined, ctaLabel: c.ctaLabel, verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId, overrides: { align: overrides?.align ?? 'left', logo: overrides?.logo }, onAction: onAction,
|
|
89
|
+
/* The eKYB desktop shape: catalogue pairs become two lanes at 420 and
|
|
90
|
+
up, Back | Continue share a row. The phone stack is untouched. */
|
|
91
|
+
responsive: true, ...shell, children: groupRows(s.fields).map(group => group.length > 1 ? (_jsx(PrizmaFormRow, { children: group.map(renderField) }, group.map(f => f.key).join('+'))) : (renderField(group[0]))) }));
|
|
92
|
+
});
|
|
93
|
+
EkycCountryFormScreen.displayName = 'EkycCountryFormScreen';
|
|
94
|
+
export default EkycCountryFormScreen;
|
|
@@ -4,7 +4,7 @@ import { forwardRef } from 'react';
|
|
|
4
4
|
import { PrizmaTextField } from '@incodetech/prizma-core-web/text-field';
|
|
5
5
|
import { PrizmaDateField } from '@incodetech/prizma-core-web/date-field';
|
|
6
6
|
import { PrizmaStepper } from '@incodetech/prizma-core-web/stepper';
|
|
7
|
-
import { PrizmaFormScreen } from './FormScreen';
|
|
7
|
+
import { PrizmaFormScreen, PrizmaFormRow } from './FormScreen';
|
|
8
8
|
import './ScreenLayout.css';
|
|
9
9
|
import './EkycLayout.css';
|
|
10
10
|
/**
|
|
@@ -73,9 +73,16 @@ export const EkycDetailsScreen = forwardRef(({ strings, state = 'default', value
|
|
|
73
73
|
defaults to centred. The module supplies its designed alignment through
|
|
74
74
|
the sanctioned override rather than declaring any layout, and a host
|
|
75
75
|
that passes its own `align` still wins. */
|
|
76
|
-
overrides: { align: overrides?.align ?? 'left', logo: overrides?.logo }, onAction: onAction,
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
overrides: { align: overrides?.align ?? 'left', logo: overrides?.logo }, onAction: onAction,
|
|
77
|
+
/* Exploration ekyb-desktop, applied to eKYC: on a tablet or desktop
|
|
78
|
+
frame First name | Last name share a row; Middle name and Date of
|
|
79
|
+
birth span the column. So that the pair is adjacent, Middle name
|
|
80
|
+
now FOLLOWS Last name, on the phone too (First, Last, Middle, Date of
|
|
81
|
+
birth), the one order change the pairing costs: the frames drew
|
|
82
|
+
First, Middle, Last. DEVIATIONS #EKC-5. */
|
|
83
|
+
responsive: true, ...shell, children: [_jsxs(PrizmaFormRow, { children: [_jsx(PrizmaTextField, { className: focused ? 'prizma-input-textfield--focused' : undefined, label: strings.details.firstNameLabel, required: true, requiredHint: strings.details.requiredHint, defaultValue: values?.firstName ?? '', autoComplete: "off",
|
|
84
|
+
/* Only 2101:8159 draws the ✕, and only in this field. */
|
|
85
|
+
clearable: focused, clearLabel: strings.details.clearLabel }), _jsx(PrizmaTextField, { label: strings.details.lastNameLabel, required: true, requiredHint: strings.details.requiredHint, defaultValue: values?.lastName ?? '', autoComplete: "off", clearable: false })] }), _jsx(PrizmaTextField, { label: strings.details.middleNameLabel, defaultValue: values?.middleName ?? '', autoComplete: "off", clearable: false }), _jsx(PrizmaDateField, { label: strings.details.dateOfBirthLabel, required: true, requiredHint: strings.details.requiredHint, defaultValue: values?.dateOfBirth ?? '', autoComplete: "off" })] }));
|
|
79
86
|
});
|
|
80
87
|
EkycDetailsScreen.displayName = 'EkycDetailsScreen';
|
|
81
88
|
export default EkycDetailsScreen;
|
package/dist/EkycLayout.css
CHANGED
|
@@ -37,3 +37,26 @@
|
|
|
37
37
|
.prizma-input-textfield__clear--show {
|
|
38
38
|
display: inline-flex;
|
|
39
39
|
}
|
|
40
|
+
|
|
41
|
+
/* ── The gender pair sits on one row ──────────────────────────────────────────
|
|
42
|
+
* Argentina's frame (2098:9568) draws "Female Male" side by side under the
|
|
43
|
+
* "Gender" label. Core's `PrizmaRadioGroup` stacks its radios in a column and
|
|
44
|
+
* has no row option, so this releases that one direction for the pair: the
|
|
45
|
+
* legend keeps the whole first line, the two radios share the next one at
|
|
46
|
+
* Spacing/24. Core's radio, its label, its size and colours are untouched.
|
|
47
|
+
* CORE REQUEST: a `direction="row"` on `PrizmaRadioGroup` would retire this
|
|
48
|
+
* rule (DEVIATIONS #EKC-3). */
|
|
49
|
+
.ekyc-country__radios {
|
|
50
|
+
flex-direction: row;
|
|
51
|
+
flex-wrap: wrap;
|
|
52
|
+
column-gap: var(--prizma-spacing-24);
|
|
53
|
+
}
|
|
54
|
+
.ekyc-country__radios .prizma-radio-group__legend {
|
|
55
|
+
flex-basis: 100%;
|
|
56
|
+
}
|
|
57
|
+
/* Core's radio is FILL by design (radio.md §3); on the row each one hugs its
|
|
58
|
+
* label so the pair reads "Female Male" as drawn. Same release, same request. */
|
|
59
|
+
.ekyc-country__radios .prizma-radio {
|
|
60
|
+
width: auto;
|
|
61
|
+
flex: 0 0 auto;
|
|
62
|
+
}
|
|
@@ -5,7 +5,7 @@ import { PrizmaTextField } from '@incodetech/prizma-core-web/text-field';
|
|
|
5
5
|
import { PrizmaDateField } from '@incodetech/prizma-core-web/date-field';
|
|
6
6
|
import { PrizmaDropdown } from '@incodetech/prizma-core-web/dropdown';
|
|
7
7
|
import { PrizmaStepper } from '@incodetech/prizma-core-web/stepper';
|
|
8
|
-
import { PrizmaFormScreen } from './FormScreen';
|
|
8
|
+
import { PrizmaFormScreen, PrizmaFormRow } from './FormScreen';
|
|
9
9
|
import './ScreenLayout.css';
|
|
10
10
|
import './EkycLayout.css';
|
|
11
11
|
/**
|
|
@@ -60,9 +60,13 @@ export const EkycLicenseScreen = forwardRef(({ strings, state = 'default', value
|
|
|
60
60
|
/* 5849:71925 swaps the CTA label for Core's designed spinner. */
|
|
61
61
|
ctaLoading: busy, ctaLoadingLabel: strings.license.ctaLoadingLabel, verifiedLabel: strings.verifiedLabel, navLabel: strings.navLabel, screenId: screenId,
|
|
62
62
|
/* Left-aligned title block, same as page 1; a host `align` still wins. */
|
|
63
|
-
overrides: { align: overrides?.align ?? 'left', logo: overrides?.logo }, onAction: onAction,
|
|
63
|
+
overrides: { align: overrides?.align ?? 'left', logo: overrides?.logo }, onAction: onAction,
|
|
64
|
+
/* Exploration ekyb-desktop, applied to eKYC: on a wide frame the
|
|
65
|
+
licence number spans and State | Expiration date share a row; the
|
|
66
|
+
phone stack is untouched. DEVIATIONS #EKC-5. */
|
|
67
|
+
responsive: true, ...shell, children: [_jsx(PrizmaTextField, { label: strings.license.numberLabel, defaultValue: values?.number ?? '', autoComplete: "off", spellCheck: false, disabled: busy,
|
|
64
68
|
/* No section on this page draws the clear ✕. */
|
|
65
|
-
clearable: false }), _jsx(PrizmaDropdown, { label: strings.license.stateLabel, options: stateOptions, defaultValue: values?.state ?? null, disabled: busy }), _jsx(PrizmaDateField, { label: strings.license.expirationLabel, defaultValue: values?.expirationDate ?? '', autoComplete: "off", disabled: busy })] }));
|
|
69
|
+
clearable: false }), _jsxs(PrizmaFormRow, { children: [_jsx(PrizmaDropdown, { label: strings.license.stateLabel, options: stateOptions, defaultValue: values?.state ?? null, disabled: busy }), _jsx(PrizmaDateField, { label: strings.license.expirationLabel, defaultValue: values?.expirationDate ?? '', autoComplete: "off", disabled: busy })] })] }));
|
|
66
70
|
});
|
|
67
71
|
EkycLicenseScreen.displayName = 'EkycLicenseScreen';
|
|
68
72
|
export default EkycLicenseScreen;
|
package/dist/EkycStrings.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ export type EkycDetailsState = 'default' | 'focused' | 'filled';
|
|
|
21
21
|
* a field — the standing #PI-5 ruling)
|
|
22
22
|
*/
|
|
23
23
|
export type EkycLicenseState = 'default' | 'filled' | 'loading';
|
|
24
|
+
/** Values for a by-country step, keyed by `EkycFieldKey`. Empty renders the resting empty field. */
|
|
25
|
+
export type EkycCountryValues = Partial<Record<string, string>>;
|
|
24
26
|
/** One row of the licence-state chooser. Runtime data, never copy — the host
|
|
25
27
|
* supplies the jurisdictions its tenant accepts, exactly as Phone Input's
|
|
26
28
|
* `PhoneInputCountry` supplies dial codes. */
|
|
@@ -96,6 +98,30 @@ export interface EkycStrings {
|
|
|
96
98
|
/** Visible/accessible copy while the submit is in flight. */
|
|
97
99
|
ctaLoadingLabel: string;
|
|
98
100
|
};
|
|
101
|
+
/**
|
|
102
|
+
* The by-country variants (`EkycCountryCatalog.ts`, Figma section
|
|
103
|
+
* 2098:20867). Field labels keyed by `EkycFieldKey`, placeholders by the
|
|
104
|
+
* catalogue's `placeholder` keys; one map, translated once.
|
|
105
|
+
*/
|
|
106
|
+
country: {
|
|
107
|
+
/** Section headings drawn under the title on a step 2 / 3. */
|
|
108
|
+
addressSection: string;
|
|
109
|
+
licenseSection: string;
|
|
110
|
+
/** Accessible name of the phone field's dial-code list. */
|
|
111
|
+
countryListLabel: string;
|
|
112
|
+
/** The gender radio pair, Argentina. */
|
|
113
|
+
genderOptions: {
|
|
114
|
+
value: string;
|
|
115
|
+
label: string;
|
|
116
|
+
}[];
|
|
117
|
+
fields: Record<string, string>;
|
|
118
|
+
placeholders: Record<string, string>;
|
|
119
|
+
ctaLabel: string;
|
|
120
|
+
/** The secondary button on a step after the first. */
|
|
121
|
+
backLabel: string;
|
|
122
|
+
/** Under every empty field once Continue was pressed (`showErrors`). */
|
|
123
|
+
requiredError: string;
|
|
124
|
+
};
|
|
99
125
|
/** `eKYC.processing` — spinner over the status line, no button. */
|
|
100
126
|
processing: {
|
|
101
127
|
title: string;
|
|
@@ -31,7 +31,17 @@ export interface EmailInputStrings {
|
|
|
31
31
|
verify: {
|
|
32
32
|
/** H2 ("Verify your email"). */
|
|
33
33
|
title: string;
|
|
34
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* H5 ("We'll use it to verify it's yours").
|
|
36
|
+
*
|
|
37
|
+
* DELIBERATELY SILENT ABOUT THE CODE, for the same reason as Phone Input's
|
|
38
|
+
* twin line. This module ships in TWO configurations and the `Without a
|
|
39
|
+
* one-time code` one never asks for a code at all (see the `noOtp` variant
|
|
40
|
+
* in `flow/graphs/email-input.ts`: the spinner runs straight into the
|
|
41
|
+
* verdict). One screen serves both, and which configuration the session
|
|
42
|
+
* wants is not something this line can know. It answers "why do you want my
|
|
43
|
+
* address?" instead, which holds in both.
|
|
44
|
+
*/
|
|
35
45
|
subtitle: string;
|
|
36
46
|
/** Field placeholder ("Email"). */
|
|
37
47
|
placeholder: string;
|
|
@@ -7,8 +7,10 @@ import './FaceMatchLayout.css';
|
|
|
7
7
|
/** Which captured image fills a circle. The host owns the pixels (same
|
|
8
8
|
* contract as `camera` on the capture screens) — Face Match never captures. */
|
|
9
9
|
export type FaceMatchPhotoRole = 'selfie' | 'id' | 'nfc' | 'secondId';
|
|
10
|
-
/** Host-supplied portraits, one per role the chosen variant asks for.
|
|
11
|
-
* left out renders
|
|
10
|
+
/** Host-supplied portraits, one per role the chosen variant asks for. A role
|
|
11
|
+
* left out renders the empty state: a Brand/50 disc, marked with
|
|
12
|
+
* `.face-match-cluster__photo--empty`. Interim treatment (2026-09-25) while
|
|
13
|
+
* the designed no-photo state is drawn; see FaceMatchLayout.css. */
|
|
12
14
|
export type FaceMatchComparisonPhotos = Partial<Record<FaceMatchPhotoRole, ReactNode>>;
|
|
13
15
|
/**
|
|
14
16
|
* Where the comparison is in its own choreography — the module MOVING rather
|