@myazahq/kyc-sdk-react-native 3.0.0 → 3.1.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myazahq/kyc-sdk-react-native",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "description": "Myaza KYC SDK for React Native (Expo) — ID verification, liveness detection, and document capture",
5
5
  "author": "Flitstack Technologies Inc.",
6
6
  "repository": {
@@ -54,7 +54,6 @@
54
54
  "expo-document-picker": ">=56",
55
55
  "expo-localization": ">=56",
56
56
  "expo-task-manager": ">=14",
57
- "react-native-webview": ">=13",
58
57
  "react": ">=19",
59
58
  "react-native": ">=0.83",
60
59
  "react-native-nitro-image": ">=0.15",
@@ -62,6 +61,7 @@
62
61
  "react-native-safe-area-context": ">=4",
63
62
  "react-native-svg": ">=15",
64
63
  "react-native-vision-camera-worklets": ">=5",
64
+ "react-native-webview": ">=13",
65
65
  "react-native-worklets": ">=0.8"
66
66
  },
67
67
  "peerDependenciesMeta": {
@@ -85,21 +85,22 @@
85
85
  }
86
86
  },
87
87
  "dependencies": {
88
+ "@hugeicons/core-free-icons": "4.3.5",
89
+ "@hugeicons/react-native": "1.0.16",
88
90
  "country-flag-icons": "^1.6.17",
89
- "libphonenumber-js": "^1.13.8",
90
- "expo-crypto": "~56.0.4",
91
91
  "expo-clipboard": "~56.0.0",
92
+ "expo-crypto": "~56.0.4",
93
+ "expo-file-system": "~56.0.0",
92
94
  "expo-font": "~56.0.5",
93
95
  "expo-glass-effect": "~56.0.4",
94
96
  "expo-image-manipulator": "~56.0.17",
95
97
  "expo-image-picker": "~56.0.16",
96
98
  "expo-location": "~56.0.24",
97
99
  "expo-speech": "~56.0.3",
98
- "lucide-react-native": "^1.17.0",
100
+ "libphonenumber-js": "^1.13.8",
99
101
  "react-native-compressor": "^1.19.0",
100
102
  "react-native-vision-camera": "^5.0.11",
101
- "zustand": "^5.0.2",
102
- "expo-file-system": "~56.0.0"
103
+ "zustand": "^5.0.2"
103
104
  },
104
105
  "devDependencies": {
105
106
  "@types/jest": "^29.5.12",
@@ -17,8 +17,6 @@ export interface GlassIconButtonProps {
17
17
  size?: number;
18
18
  iconSize?: number;
19
19
  disabled?: boolean;
20
- /** Render the glyph solid/filled (e.g. the theme toggle). */
21
- solid?: boolean;
22
20
  /**
23
21
  * Skip this button's own glass surface — used when it sits inside a
24
22
  * {@link GlassGroup} that already provides one shared glass capsule, so the
@@ -35,7 +33,6 @@ export function GlassIconButton({
35
33
  size = 40,
36
34
  iconSize = 20,
37
35
  disabled = false,
38
- solid = false,
39
36
  plain = false,
40
37
  accessibilityLabel,
41
38
  }: GlassIconButtonProps): React.ReactElement {
@@ -54,7 +51,7 @@ export function GlassIconButton({
54
51
  overflow: 'hidden',
55
52
  }}
56
53
  >
57
- <Icon name={icon} size={iconSize} color={iconColor} solid={solid} />
54
+ <Icon name={icon} size={iconSize} color={iconColor} />
58
55
  </View>
59
56
  );
60
57
 
@@ -71,7 +68,7 @@ export function GlassIconButton({
71
68
  justifyContent: 'center',
72
69
  }}
73
70
  >
74
- <Icon name={icon} size={iconSize} color={iconColor} solid={solid} />
71
+ <Icon name={icon} size={iconSize} color={iconColor} />
75
72
  </GlassSurface>
76
73
  ) : (
77
74
  glyph
@@ -1,26 +1,29 @@
1
1
  import React from 'react';
2
+ import { HugeiconsIcon } from '@hugeicons/react-native';
2
3
 
3
- import { ICONS, type IconName } from './icon-map';
4
+ import { ICONS, type IconName } from './icons';
4
5
 
5
- export type { IconName } from './icon-map';
6
+ export type { IconName } from './icons';
6
7
 
7
8
  export interface IconProps {
8
9
  name: IconName;
9
10
  size?: number;
10
11
  color: string;
11
12
  strokeWidth?: number;
12
- /** Render a solid/filled glyph (used for the theme toggle, matching Flutter's filled moon/sun). */
13
- solid?: boolean;
14
13
  }
15
14
 
16
- export function Icon({ name, size = 20, color, strokeWidth = 2, solid = false }: IconProps): React.ReactElement {
17
- const Glyph = ICONS[name];
18
- // The theme toggle's filled moon/sun (Flutter's dark_mode/light_mode). Lucide
19
- // ships no filled variants, so the glyph's own body takes the colour: the
20
- // moon's crescent and the sun's disc fill, the sun's rays stay strokes. This
21
- // replaced an Ionicons import that was the SDK's ONLY use of
22
- // @expo/vector-icons, a package whose icon fonts still reached every
23
- // integrator's app for those two glyphs (1.35 MB on a real release APK).
24
- const fill = solid && (name === 'moon' || name === 'sun') ? color : 'none';
25
- return <Glyph size={size} color={color} strokeWidth={strokeWidth} fill={fill} />;
15
+ /**
16
+ * Every icon this SDK draws goes through here.
17
+ *
18
+ * The stroke is 1.6 to match the web SDK and the dashboard, which draw the
19
+ * same set at that weight. It was 2 while this SDK drew Lucide, whose glyphs
20
+ * are built for it.
21
+ *
22
+ * There is deliberately no `solid` prop. It existed to fill the theme toggle's
23
+ * moon and sun, which worked because Lucide's glyph bodies take a fill.
24
+ * Hugeicons' free pack is Stroke Rounded only - the solid styles are Pro - so
25
+ * the toggle now draws stroked, which is what Flutter has always done.
26
+ */
27
+ export function Icon({ name, size = 20, color, strokeWidth = 1.6 }: IconProps): React.ReactElement {
28
+ return <HugeiconsIcon icon={ICONS[name]} size={size} color={color} strokeWidth={strokeWidth} />;
26
29
  }
@@ -109,6 +109,7 @@ export function KycFlow({
109
109
  addressIntroPending,
110
110
  addressEntranceFraming: state.addressEntranceFraming,
111
111
  poaDocumentType: state.poaDocumentType,
112
+ multiIdSlots: state.multiIdSlots,
112
113
  }),
113
114
  [
114
115
  currentStep,
@@ -212,7 +212,6 @@ export function KycSheet({
212
212
  <GlassIconButton
213
213
  icon={mode === "dark" ? "sun" : "moon"}
214
214
  onPress={toggle}
215
- solid
216
215
  plain={showClose}
217
216
  accessibilityLabel="Toggle theme"
218
217
  />
@@ -11,6 +11,7 @@ import { LivenessStep } from '../screens/LivenessStep';
11
11
  import { SubmittedStep } from '../screens/SubmittedStep';
12
12
  import { QuestionnaireStep } from '../screens/QuestionnaireStep';
13
13
  import { ContactVerificationStep } from '../screens/ContactVerificationStep';
14
+ import { SupportingDocumentsStep } from '../screens/SupportingDocumentsStep';
14
15
  import { ProofOfAddressStep } from '../screens/ProofOfAddressStep';
15
16
  import {
16
17
  AddressEntranceStep,
@@ -84,6 +85,8 @@ function stepScreen(step: KYCStep, onClose: () => void): React.ReactElement {
84
85
  return <ContactVerificationStep key="contact-phone" channel="phone" />;
85
86
  case 'proof-of-address':
86
87
  return <ProofOfAddressStep />;
88
+ case 'supporting-documents':
89
+ return <SupportingDocumentsStep />;
87
90
  // The address flow, in order: find it, confirm it, show it, commit it.
88
91
  // 'address-collection' is the PIN step and keeps that wire name so older
89
92
  // session progress restores cleanly.
@@ -0,0 +1,35 @@
1
+ import type { IconSvgElement } from '@hugeicons/react-native';
2
+
3
+ /**
4
+ * Glyphs Hugeicons does not carry, drawn in the set's own stroke language.
5
+ *
6
+ * Kept to the strict minimum: every other icon comes from the set, and this
7
+ * file exists only where the set has no equivalent at all. It MIRRORS the web
8
+ * SDK's `components/icons/glyphs.ts` - same geometry, same stroke attributes -
9
+ * so the two platforms draw one arrow rather than two near-misses.
10
+ */
11
+
12
+ const STROKE = {
13
+ stroke: 'currentColor',
14
+ strokeLinecap: 'round',
15
+ strokeLinejoin: 'round',
16
+ strokeWidth: '1.5',
17
+ } as const;
18
+
19
+ /**
20
+ * A LONG left arrow - a full-width shaft with a small head.
21
+ *
22
+ * Hugeicons has no such glyph. Its longest one-way left arrow is
23
+ * `ArrowLeft02Icon`, whose shaft spans 13.5 of the 24-unit viewBox; the rest
24
+ * are shorter, bare chevrons, or arrows into a vertical bar. This SDK's back
25
+ * control drew Lucide's `MoveLeft` before the migration, whose shaft is 20
26
+ * units, so moving to the set would visibly shorten it.
27
+ *
28
+ * The geometry is Lucide's, inset to sit on the same optical margins as its
29
+ * Hugeicons siblings (they start at ~3.5 rather than Lucide's 2), and the
30
+ * stroke attributes are copied from the set so weight and joins match exactly.
31
+ */
32
+ export const LONG_ARROW_LEFT: IconSvgElement = [
33
+ ['path', { d: 'M20.5 12H3.5', ...STROKE, key: '0' }],
34
+ ['path', { d: 'M8 7.5L3.5 12L8 16.5', ...STROKE, key: '1' }],
35
+ ];
@@ -0,0 +1,3 @@
1
+ export type { IconName } from './names';
2
+ export { ICONS } from './map';
3
+ export { LONG_ARROW_LEFT } from './glyphs';
@@ -0,0 +1,165 @@
1
+ import type { IconSvgElement } from '@hugeicons/react-native';
2
+
3
+ import Add01Icon from '@hugeicons/core-free-icons/Add01Icon';
4
+ import AlertCircleIcon from '@hugeicons/core-free-icons/AlertCircleIcon';
5
+ import BadgeCheckIcon from '@hugeicons/core-free-icons/BadgeCheckIcon';
6
+ import BellRingIcon from '@hugeicons/core-free-icons/BellRingIcon';
7
+ import BookUserIcon from '@hugeicons/core-free-icons/BookUserIcon';
8
+ import BubbleChatIcon from '@hugeicons/core-free-icons/BubbleChatIcon';
9
+ import Building02Icon from '@hugeicons/core-free-icons/Building02Icon';
10
+ import BulbIcon from '@hugeicons/core-free-icons/BulbIcon';
11
+ import Calendar01Icon from '@hugeicons/core-free-icons/Calendar01Icon';
12
+ import Camera01Icon from '@hugeicons/core-free-icons/Camera01Icon';
13
+ import CameraOff01Icon from '@hugeicons/core-free-icons/CameraOff01Icon';
14
+ import Cancel01Icon from '@hugeicons/core-free-icons/Cancel01Icon';
15
+ import CancelCircleIcon from '@hugeicons/core-free-icons/CancelCircleIcon';
16
+ import Car01Icon from '@hugeicons/core-free-icons/Car01Icon';
17
+ import CheckIcon from '@hugeicons/core-free-icons/CheckIcon';
18
+ import CheckmarkCircle02Icon from '@hugeicons/core-free-icons/CheckmarkCircle02Icon';
19
+ import ChevronDownIcon from '@hugeicons/core-free-icons/ChevronDownIcon';
20
+ import ChevronLeftIcon from '@hugeicons/core-free-icons/ChevronLeftIcon';
21
+ import ChevronRightIcon from '@hugeicons/core-free-icons/ChevronRightIcon';
22
+ import CircleDashedIcon from '@hugeicons/core-free-icons/CircleDashedIcon';
23
+ import ContactIcon from '@hugeicons/core-free-icons/ContactIcon';
24
+ import CopyIcon from '@hugeicons/core-free-icons/CopyIcon';
25
+ import CreditCardIcon from '@hugeicons/core-free-icons/CreditCardIcon';
26
+ import ExpandIcon from '@hugeicons/core-free-icons/ExpandIcon';
27
+ import FaceIdIcon from '@hugeicons/core-free-icons/FaceIdIcon';
28
+ import File02Icon from '@hugeicons/core-free-icons/File02Icon';
29
+ import FilterIcon from '@hugeicons/core-free-icons/FilterIcon';
30
+ import FingerPrintIcon from '@hugeicons/core-free-icons/FingerPrintIcon';
31
+ import FlashOffIcon from '@hugeicons/core-free-icons/FlashOffIcon';
32
+ import FlaskConicalIcon from '@hugeicons/core-free-icons/FlaskConicalIcon';
33
+ import GlobeIcon from '@hugeicons/core-free-icons/GlobeIcon';
34
+ import Gps02Icon from '@hugeicons/core-free-icons/Gps02Icon';
35
+ import HelpCircleIcon from '@hugeicons/core-free-icons/HelpCircleIcon';
36
+ import Home01Icon from '@hugeicons/core-free-icons/Home01Icon';
37
+ import IdentityCardIcon from '@hugeicons/core-free-icons/IdentityCardIcon';
38
+ import Image01Icon from '@hugeicons/core-free-icons/Image01Icon';
39
+ import InformationCircleIcon from '@hugeicons/core-free-icons/InformationCircleIcon';
40
+ import LandmarkIcon from '@hugeicons/core-free-icons/LandmarkIcon';
41
+ import Link02Icon from '@hugeicons/core-free-icons/Link02Icon';
42
+ import Location02Icon from '@hugeicons/core-free-icons/Location02Icon';
43
+ import LocationCheck01Icon from '@hugeicons/core-free-icons/LocationCheck01Icon';
44
+ import LockIcon from '@hugeicons/core-free-icons/LockIcon';
45
+ import Mail01Icon from '@hugeicons/core-free-icons/Mail01Icon';
46
+ import MapPinHouseIcon from '@hugeicons/core-free-icons/MapPinHouseIcon';
47
+ import MapPinIcon from '@hugeicons/core-free-icons/MapPinIcon';
48
+ import Message01Icon from '@hugeicons/core-free-icons/Message01Icon';
49
+ import MinusSignIcon from '@hugeicons/core-free-icons/MinusSignIcon';
50
+ import MoonIcon from '@hugeicons/core-free-icons/MoonIcon';
51
+ import NfcIcon from '@hugeicons/core-free-icons/NfcIcon';
52
+ import PencilEdit01Icon from '@hugeicons/core-free-icons/PencilEdit01Icon';
53
+ import PencilIcon from '@hugeicons/core-free-icons/PencilIcon';
54
+ import Radar01Icon from '@hugeicons/core-free-icons/Radar01Icon';
55
+ import RefreshCwIcon from '@hugeicons/core-free-icons/RefreshCwIcon';
56
+ import RefreshIcon from '@hugeicons/core-free-icons/RefreshIcon';
57
+ import ScanIcon from '@hugeicons/core-free-icons/ScanIcon';
58
+ import Search01Icon from '@hugeicons/core-free-icons/Search01Icon';
59
+ import SecurityCheckIcon from '@hugeicons/core-free-icons/SecurityCheckIcon';
60
+ import Share01Icon from '@hugeicons/core-free-icons/Share01Icon';
61
+ import SlidersHorizontalIcon from '@hugeicons/core-free-icons/SlidersHorizontalIcon';
62
+ import SmartPhone01Icon from '@hugeicons/core-free-icons/SmartPhone01Icon';
63
+ import StampIcon from '@hugeicons/core-free-icons/StampIcon';
64
+ import Sun01Icon from '@hugeicons/core-free-icons/Sun01Icon';
65
+ import Timer01Icon from '@hugeicons/core-free-icons/Timer01Icon';
66
+ import Upload01Icon from '@hugeicons/core-free-icons/Upload01Icon';
67
+ import UserAdd01Icon from '@hugeicons/core-free-icons/UserAdd01Icon';
68
+ import UserCircleIcon from '@hugeicons/core-free-icons/UserCircleIcon';
69
+ import UserGroupIcon from '@hugeicons/core-free-icons/UserGroupIcon';
70
+ import VideoOffIcon from '@hugeicons/core-free-icons/VideoOffIcon';
71
+ import VoteIcon from '@hugeicons/core-free-icons/VoteIcon';
72
+ import ZapIcon from '@hugeicons/core-free-icons/ZapIcon';
73
+
74
+ import { LONG_ARROW_LEFT } from './glyphs';
75
+ import type { IconName } from './names';
76
+
77
+ /**
78
+ * Each role, and the glyph that draws it.
79
+ *
80
+ * Imports are DEEP - one module per icon - and must stay that way. Importing
81
+ * from the package index instead pulls the whole 6,025-icon pack into the
82
+ * bundle: measured at +8.33 MB for five icons against +7 KB for the same five
83
+ * imported this way. The pack declares a `./*` export for exactly this, and
84
+ * iconImports.test.ts fails the build on a barrel import.
85
+ *
86
+ * The glyphs are the ones the web SDK and Flutter already chose for these
87
+ * names, so the three SDKs draw one set. Where neither had a choice to copy,
88
+ * it was made against the catalogue by meaning and is noted below.
89
+ */
90
+ export const ICONS: Record<IconName, IconSvgElement> = {
91
+ close: Cancel01Icon,
92
+ back: LONG_ARROW_LEFT,
93
+ moon: MoonIcon,
94
+ sun: Sun01Icon,
95
+ check: CheckIcon,
96
+ lock: LockIcon,
97
+ alert: AlertCircleIcon,
98
+ // The environment banner's mark, shared with the web SDK (same lucide glyph)
99
+ // and mirrored by Flutter's Icons.science_outlined.
100
+ flask: FlaskConicalIcon,
101
+ refresh: RefreshCwIcon,
102
+ 'refresh-ccw': RefreshIcon,
103
+ maximize: ExpandIcon,
104
+ calendar: Calendar01Icon,
105
+ image: Image01Icon,
106
+ 'chevron-left': ChevronLeftIcon,
107
+ 'chevron-right': ChevronRightIcon,
108
+ 'chevron-down': ChevronDownIcon,
109
+ shield: SecurityCheckIcon,
110
+ 'badge-check': BadgeCheckIcon,
111
+ 'circle-dashed': CircleDashedIcon,
112
+ 'circle-check': CheckmarkCircle02Icon,
113
+ 'circle-help': HelpCircleIcon,
114
+ 'circle-x': CancelCircleIcon,
115
+ 'building-2': Building02Icon,
116
+ stamp: StampIcon,
117
+ user: UserCircleIcon,
118
+ info: InformationCircleIcon,
119
+ 'user-plus': UserAdd01Icon,
120
+ plus: Add01Icon,
121
+ link: Link02Icon,
122
+ copy: CopyIcon,
123
+ share: Share01Icon,
124
+ users: UserGroupIcon,
125
+ 'scan-line': ScanIcon,
126
+ 'scan-face': FaceIdIcon,
127
+ nfc: NfcIcon,
128
+ filter: FilterIcon,
129
+ pencil: PencilIcon,
130
+ 'pencil-line': PencilEdit01Icon,
131
+ search: Search01Icon,
132
+ zap: ZapIcon,
133
+ 'zap-off': FlashOffIcon,
134
+ timer: Timer01Icon,
135
+ fingerprint: FingerPrintIcon,
136
+ 'credit-card': CreditCardIcon,
137
+ globe: GlobeIcon,
138
+ car: Car01Icon,
139
+ vote: VoteIcon,
140
+ 'file-text': File02Icon,
141
+ house: Home01Icon,
142
+ landmark: LandmarkIcon,
143
+ 'map-pin': MapPinIcon,
144
+ 'map-pin-house': MapPinHouseIcon,
145
+ 'map-pin-check': LocationCheck01Icon,
146
+ 'map-pinned': Location02Icon,
147
+ radar: Radar01Icon,
148
+ 'bell-ring': BellRingIcon,
149
+ sliders: SlidersHorizontalIcon,
150
+ locate: Gps02Icon,
151
+ minus: MinusSignIcon,
152
+ 'id-card': IdentityCardIcon,
153
+ contact: ContactIcon,
154
+ passport: BookUserIcon,
155
+ camera: Camera01Icon,
156
+ 'camera-off': CameraOff01Icon,
157
+ 'video-off': VideoOffIcon,
158
+ upload: Upload01Icon,
159
+ x: Cancel01Icon,
160
+ lightbulb: BulbIcon,
161
+ mail: Mail01Icon,
162
+ smartphone: SmartPhone01Icon,
163
+ 'message-square': Message01Icon,
164
+ 'message-circle': BubbleChatIcon,
165
+ };
@@ -0,0 +1,86 @@
1
+ /**
2
+ * The icon roles this SDK draws, by MEANING rather than by glyph name.
3
+ *
4
+ * This is the boundary that lets the set underneath change without touching a
5
+ * call site: a screen asks for 'close', and `icons/map.ts` decides what that
6
+ * looks like. Adding a role here means adding its glyph there.
7
+ */
8
+
9
+ export type IconName =
10
+ // chrome
11
+ | 'close'
12
+ | 'back'
13
+ | 'moon'
14
+ | 'sun'
15
+ | 'check'
16
+ | 'lock'
17
+ | 'alert'
18
+ | 'flask'
19
+ | 'refresh'
20
+ | 'refresh-ccw'
21
+ | 'maximize'
22
+ | 'calendar'
23
+ | 'image'
24
+ | 'chevron-left'
25
+ | 'chevron-right'
26
+ | 'chevron-down'
27
+ // consent hero + process steps
28
+ | 'shield'
29
+ | 'badge-check'
30
+ | 'circle-dashed'
31
+ | 'circle-check'
32
+ | 'circle-help'
33
+ | 'circle-x'
34
+ | 'building-2'
35
+ | 'stamp'
36
+ | 'user'
37
+ | 'user-plus'
38
+ | 'plus'
39
+ | 'info'
40
+ | 'users'
41
+ | 'scan-line'
42
+ | 'scan-face'
43
+ | 'nfc'
44
+ | 'filter'
45
+ | 'pencil'
46
+ | 'pencil-line'
47
+ | 'search'
48
+ | 'zap'
49
+ | 'link'
50
+ | 'copy'
51
+ | 'share'
52
+ | 'zap-off'
53
+ | 'timer'
54
+ // id-type glyphs
55
+ | 'fingerprint'
56
+ | 'credit-card'
57
+ | 'globe'
58
+ | 'car'
59
+ | 'vote'
60
+ | 'file-text'
61
+ | 'house'
62
+ | 'landmark'
63
+ | 'id-card'
64
+ | 'contact'
65
+ | 'passport'
66
+ | 'camera'
67
+ | 'camera-off'
68
+ | 'video-off'
69
+ | 'upload'
70
+ | 'x'
71
+ // address collection (map picker + the presence milestone track)
72
+ | 'map-pin'
73
+ | 'map-pin-house'
74
+ | 'map-pin-check'
75
+ | 'map-pinned'
76
+ | 'radar'
77
+ | 'bell-ring'
78
+ | 'sliders'
79
+ | 'locate'
80
+ | 'minus'
81
+ | 'lightbulb'
82
+ // contact verification (OTP channels + step footer)
83
+ | 'mail'
84
+ | 'smartphone'
85
+ | 'message-square'
86
+ | 'message-circle';
@@ -10,6 +10,8 @@ import { addressStepMeta } from '../screens/address';
10
10
  import { countrySelectMeta } from '../screens/CountrySelectStep';
11
11
  import { businessDetailsMeta } from '../screens/BusinessDetailsStep';
12
12
  import { businessKeyPeopleMeta } from '../screens/BusinessKeyPeopleStep';
13
+ import { supportingDocumentsMeta } from '../screens/SupportingDocumentsStep';
14
+ import { resolveSupportingDocuments, verifiedIdsFor } from '../config/supportingDocuments';
13
15
  import { businessDocumentsMeta } from '../screens/BusinessDocumentsStep';
14
16
  import { applicantRoleMeta } from '../screens/ApplicantRoleStep';
15
17
  import { nfcMeta } from '../screens/NfcStep';
@@ -49,6 +51,9 @@ export interface StepHeaderContext {
49
51
  /** The proof-of-address kind the applicant has picked, so the header can ask
50
52
  * for what the workflow's name rule wants on THAT document. */
51
53
  poaDocumentType?: string | null;
54
+ /** The committed multi-ID slots, so the supporting-documents header resolves
55
+ * the SAME list the screen shows. */
56
+ multiIdSlots?: Array<{ idType: string }>;
52
57
  }
53
58
 
54
59
  export function stepHeaderMeta(
@@ -62,6 +67,7 @@ export function stepHeaderMeta(
62
67
  addressIntroPending,
63
68
  addressEntranceFraming,
64
69
  poaDocumentType,
70
+ multiIdSlots,
65
71
  }: StepHeaderContext,
66
72
  ): { title: string; description: string | null } {
67
73
  const label = labelFor(country, selectedIdType);
@@ -121,6 +127,15 @@ export function stepHeaderMeta(
121
127
  return businessKeyPeopleMeta;
122
128
  case 'business-documents':
123
129
  return businessDocumentsMeta;
130
+ case 'supporting-documents':
131
+ // Resolved exactly as the screen resolves its own slots, or the header
132
+ // could name a number the body does not show.
133
+ return supportingDocumentsMeta(
134
+ resolveSupportingDocuments(
135
+ config.supportingDocuments,
136
+ verifiedIdsFor({ country, idType: selectedIdType, multiIdSlots }),
137
+ ),
138
+ );
124
139
  case 'applicant-role':
125
140
  return applicantRoleMeta;
126
141
  case 'nfc':
@@ -44,6 +44,14 @@ export interface StepOrderOptions {
44
44
  hasEmailVerification: boolean;
45
45
  hasPhoneVerification: boolean;
46
46
  hasPoa: boolean;
47
+ /**
48
+ * Whether the supporting-documents step has anything to ask for on THIS
49
+ * attempt. Resolved by the caller (config/supportingDocuments.ts) because
50
+ * the list depends on the ID the person actually picked — a document scoped
51
+ * to one ID is not asked of somebody who used another, and a step with an
52
+ * empty list must not appear at all.
53
+ */
54
+ hasSupportingDocuments: boolean;
47
55
  hasAddressCollection: boolean;
48
56
  /**
49
57
  * Which address screens the flow has, from the ONE `addressFlowFor` call.
@@ -120,11 +128,15 @@ function fullStepOrder(o: StepOrderOptions): KYCStep[] {
120
128
  if (o.scope === 'address') {
121
129
  const steps: KYCStep[] = [...openingSteps(o), ...contactSteps(o)];
122
130
  if (o.hasPoa) steps.push('proof-of-address');
123
- steps.push(
124
- ...addressFlowSteps(
125
- o.addressFlow ?? { searchAvailable: false, photoMode: 'optional', streetViewOffered: false },
126
- ),
127
- );
131
+ // Gated like the full flow, and like Flutter has always done it: an
132
+ // address flow that asks only for proof of address never opens a map.
133
+ if (o.hasAddressCollection) {
134
+ steps.push(
135
+ ...addressFlowSteps(
136
+ o.addressFlow ?? { searchAvailable: false, photoMode: 'optional', streetViewOffered: false },
137
+ ),
138
+ );
139
+ }
128
140
  if (o.hasQuestionnaire) steps.push('questionnaire');
129
141
  steps.push('submitted');
130
142
  return steps;
@@ -143,6 +155,9 @@ function fullStepOrder(o: StepOrderOptions): KYCStep[] {
143
155
  }
144
156
 
145
157
  const middle: KYCStep[] = [...captureLeg(o)];
158
+ // Paperwork the org files comes BEFORE the address evidence the
159
+ // verification is judged on (user decision 2026-09-22).
160
+ if (o.hasSupportingDocuments) middle.push('supporting-documents');
146
161
  if (o.hasPoa) middle.push('proof-of-address');
147
162
  // The address flow is FOUR real steps on an individual flow (find it, confirm
148
163
  // it, show it, commit it), so the progress bar advances through them and back