@myazahq/kyc-sdk-react-native 2.4.0 → 2.5.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/android/build.gradle +35 -3
- package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaFaceDetector.kt +75 -0
- package/ios/HybridMyazaFaceDetector.swift +8 -0
- package/nitrogen/generated/android/c++/JHybridMyazaFaceDetectorSpec.cpp +23 -0
- package/nitrogen/generated/android/c++/JHybridMyazaFaceDetectorSpec.hpp +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaFaceDetectorSpec.kt +9 -0
- package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Bridge.cpp +8 -0
- package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Bridge.hpp +43 -0
- package/nitrogen/generated/ios/c++/HybridMyazaFaceDetectorSpecSwift.hpp +17 -0
- package/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridMyazaFaceDetectorSpec.swift +2 -0
- package/nitrogen/generated/ios/swift/HybridMyazaFaceDetectorSpec_cxx.swift +31 -0
- package/nitrogen/generated/shared/c++/HybridMyazaFaceDetectorSpec.cpp +2 -0
- package/nitrogen/generated/shared/c++/HybridMyazaFaceDetectorSpec.hpp +3 -0
- package/package.json +1 -1
- package/src/MyazaKYC.tsx +12 -0
- package/src/components/DashedBorder.tsx +55 -0
- package/src/components/DialCodePicker.tsx +14 -7
- package/src/components/Icon.tsx +9 -0
- package/src/components/MultiIdProgress.tsx +107 -0
- package/src/components/MyazaInput.tsx +23 -4
- package/src/components/MyazaSelect.tsx +2 -1
- package/src/components/OwnershipSlider.tsx +102 -0
- package/src/components/PhoneNumberInput.tsx +37 -4
- package/src/components/StaggerIn.tsx +63 -0
- package/src/components/StepView.tsx +26 -0
- package/src/components/glass/FloatingSheet.tsx +73 -21
- package/src/components/glass/GlassSheet.tsx +14 -2
- package/src/components/stepHeaderMeta.tsx +3 -1
- package/src/config/business.ts +6 -6
- package/src/config/businessDetailsValidity.ts +52 -0
- package/src/config/businessPrefill.ts +72 -0
- package/src/config/businessSteps.ts +14 -3
- package/src/config/keyPeople.ts +111 -7
- package/src/config/keyPeopleCorporate.ts +19 -0
- package/src/config/keyPeopleOwnerTypes.ts +12 -0
- package/src/config/keyPeopleOwners.ts +26 -0
- package/src/config/keyPeoplePrefill.ts +138 -0
- package/src/config/keyPeopleSectionDefs.ts +65 -0
- package/src/config/keyPeopleSections.ts +167 -0
- package/src/config/registrationHint.ts +1 -1
- package/src/config/stepOrder.ts +24 -4
- package/src/config/theme.ts +8 -2
- package/src/config/website.ts +36 -0
- package/src/config/workflowMerge.ts +5 -0
- package/src/emrtd/activeAuth.ts +100 -0
- package/src/emrtd/extras.ts +76 -0
- package/src/emrtd/files.ts +5 -0
- package/src/emrtd/index.ts +1 -0
- package/src/emrtd/read.ts +8 -1
- package/src/emrtd/session.ts +41 -0
- package/src/emrtd/stages.ts +11 -0
- package/src/lib/contact-recovery.ts +44 -0
- package/src/lib/multi-id.ts +210 -0
- package/src/lib/resubmit.ts +130 -0
- package/src/lib/screen-corners.ts +50 -0
- package/src/lib/step-log.ts +24 -3
- package/src/lib/use-multi-id-plan.ts +29 -0
- package/src/liveness/useModelReady.ts +65 -0
- package/src/liveness/visionCameraFaceDetector.ts +35 -0
- package/src/screens/ApplicantRoleStep.tsx +9 -5
- package/src/screens/BusinessCheckPanel.tsx +89 -0
- package/src/screens/BusinessDetailsFields.tsx +109 -0
- package/src/screens/BusinessDetailsStep.tsx +131 -145
- package/src/screens/BusinessDocumentSlot.tsx +2 -3
- package/src/screens/BusinessDocumentsStep.tsx +1 -1
- package/src/screens/BusinessKeyPeopleStep.tsx +103 -71
- package/src/screens/BusinessPickedCard.tsx +86 -0
- package/src/screens/BusinessPickedSection.tsx +51 -0
- package/src/screens/BusinessRegistryPickers.tsx +67 -0
- package/src/screens/BusinessSandboxToggle.tsx +114 -0
- package/src/screens/BusinessSearch.tsx +199 -0
- package/src/screens/BusinessSearchResults.tsx +115 -0
- package/src/screens/CompanyInfoFields.tsx +86 -51
- package/src/screens/ConsentStep.tsx +31 -0
- package/src/screens/ContactDestinationField.tsx +1 -0
- package/src/screens/ContactVerificationStep.tsx +34 -3
- package/src/screens/IdTypeStep.tsx +12 -5
- package/src/screens/KeepLinksSheet.tsx +88 -0
- package/src/screens/KeyPeopleAwaitCard.tsx +204 -0
- package/src/screens/KeyPeopleAwaitList.tsx +126 -0
- package/src/screens/KeyPeoplePending.tsx +154 -0
- package/src/screens/KeyPeopleSection.tsx +162 -0
- package/src/screens/KeyPeopleSectionsList.tsx +103 -0
- package/src/screens/KeyPeopleUboExemption.tsx +72 -0
- package/src/screens/KeyPersonCard.tsx +36 -4
- package/src/screens/KeyPersonForm.tsx +195 -36
- package/src/screens/KeyPersonKindToggle.tsx +68 -0
- package/src/screens/KeyPersonOwners.tsx +109 -0
- package/src/screens/KeyPersonRoleChips.tsx +68 -0
- package/src/screens/KeyPersonSheet.tsx +92 -108
- package/src/screens/LivenessStep.tsx +18 -0
- package/src/screens/NfcStep.tsx +14 -0
- package/src/screens/ProofOfAddressStep.tsx +6 -3
- package/src/screens/SubmittedStep.tsx +66 -10
- package/src/screens/nfc/NfcReadProgress.tsx +1 -0
- package/src/screens/useAwaitingPeople.ts +90 -0
- package/src/services/api-types.ts +158 -2
- package/src/services/api-verify-types.ts +53 -1
- package/src/services/api.ts +104 -0
- package/src/services/deviceMetadata.ts +1 -1
- package/src/services/errors.ts +10 -2
- package/src/services/workflowGate.ts +1 -0
- package/src/specs/MyazaFaceDetector.nitro.ts +29 -0
- package/src/store/businessCheck.ts +113 -0
- package/src/store/derive.ts +15 -0
- package/src/store/kycStore.ts +241 -14
- package/src/store/serverConfig.ts +2 -0
- package/src/store/session.ts +208 -0
- package/src/store/state.ts +155 -2
- package/src/store/submit.ts +63 -13
- package/src/store/submitApplicant.ts +5 -11
- package/src/types/business.ts +23 -2
- package/src/types/config.ts +22 -1
- package/src/types/verification.ts +1 -1
- package/src/types/workflow.ts +15 -0
- package/src/screens/KeyPeopleInviteLinks.tsx +0 -239
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Pressable, View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { DashedBorder } from '../components/DashedBorder';
|
|
5
|
+
import { Icon } from '../components/Icon';
|
|
6
|
+
import { MyazaText } from '../components/Typography';
|
|
7
|
+
import { useTheme } from '../components/runtime';
|
|
8
|
+
import { radius, spacing } from '../config/theme';
|
|
9
|
+
import { KEY_PERSON_ROLE_LABELS, type KeyPersonEntry } from '../config/keyPeople';
|
|
10
|
+
import { SECTION_ROLE, type KeyPeopleSection as SectionKey } from '../config/keyPeopleSections';
|
|
11
|
+
import { KeyPersonCard } from './KeyPersonCard';
|
|
12
|
+
import type { KeyPersonRole } from '../types/business';
|
|
13
|
+
|
|
14
|
+
// One section of the key-people step — heading, plain-language definition,
|
|
15
|
+
// member cards, quick-add chips for people already entered elsewhere, and the
|
|
16
|
+
// dashed add-tile. Sections are VIEWS over one shared list
|
|
17
|
+
// (keyPeopleSections.ts). Mirrors the web SDK's KeyPeopleSection 1:1.
|
|
18
|
+
|
|
19
|
+
export function KeyPeopleSection({
|
|
20
|
+
section,
|
|
21
|
+
title,
|
|
22
|
+
description,
|
|
23
|
+
rows,
|
|
24
|
+
members,
|
|
25
|
+
quickAdd,
|
|
26
|
+
emailRequiredFor,
|
|
27
|
+
addLabel,
|
|
28
|
+
canAdd,
|
|
29
|
+
onAdd,
|
|
30
|
+
onEdit,
|
|
31
|
+
onRemove,
|
|
32
|
+
onQuickAdd,
|
|
33
|
+
children,
|
|
34
|
+
}: {
|
|
35
|
+
section: SectionKey;
|
|
36
|
+
title: string;
|
|
37
|
+
description: string;
|
|
38
|
+
rows: KeyPersonEntry[];
|
|
39
|
+
members: number[];
|
|
40
|
+
quickAdd: number[];
|
|
41
|
+
emailRequiredFor: ReadonlySet<KeyPersonRole>;
|
|
42
|
+
addLabel: string;
|
|
43
|
+
canAdd: boolean;
|
|
44
|
+
onAdd: () => void;
|
|
45
|
+
onEdit: (index: number) => void;
|
|
46
|
+
onRemove: (index: number) => void;
|
|
47
|
+
onQuickAdd: (index: number) => void;
|
|
48
|
+
children?: React.ReactNode;
|
|
49
|
+
}): React.ReactElement {
|
|
50
|
+
const { colors } = useTheme();
|
|
51
|
+
return (
|
|
52
|
+
<View>
|
|
53
|
+
<MyazaText variant="heading3">{title}</MyazaText>
|
|
54
|
+
<MyazaText variant="bodyMedium" color={colors.textSecondary} style={{ marginTop: 2 }}>
|
|
55
|
+
{description}
|
|
56
|
+
</MyazaText>
|
|
57
|
+
|
|
58
|
+
{members.length > 0 ? (
|
|
59
|
+
<View style={{ marginTop: spacing.sm + 4 }}>
|
|
60
|
+
{members.map((index) => (
|
|
61
|
+
<KeyPersonCard
|
|
62
|
+
key={index}
|
|
63
|
+
entry={rows[index]!}
|
|
64
|
+
emailRequiredFor={emailRequiredFor}
|
|
65
|
+
// The card names the hat THIS section is about, so the same
|
|
66
|
+
// person reads "Beneficial owner" here and "Director" there.
|
|
67
|
+
roleLabel={KEY_PERSON_ROLE_LABELS[SECTION_ROLE[section]]}
|
|
68
|
+
onPress={() => onEdit(index)}
|
|
69
|
+
onRemove={() => onRemove(index)}
|
|
70
|
+
/>
|
|
71
|
+
))}
|
|
72
|
+
</View>
|
|
73
|
+
) : null}
|
|
74
|
+
|
|
75
|
+
{quickAdd.length > 0 && canAdd ? (
|
|
76
|
+
<View style={{ marginTop: spacing.sm + 4 }}>
|
|
77
|
+
<MyazaText
|
|
78
|
+
variant="bodySmall"
|
|
79
|
+
color={colors.textSecondary}
|
|
80
|
+
style={{ textTransform: 'uppercase', letterSpacing: 0.6, fontWeight: '600' }}
|
|
81
|
+
>
|
|
82
|
+
Quick add from people you already entered
|
|
83
|
+
</MyazaText>
|
|
84
|
+
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 8, marginTop: spacing.sm }}>
|
|
85
|
+
{quickAdd.map((index) => {
|
|
86
|
+
const row = rows[index]!;
|
|
87
|
+
return (
|
|
88
|
+
<Pressable
|
|
89
|
+
key={index}
|
|
90
|
+
accessibilityRole="button"
|
|
91
|
+
onPress={() => onQuickAdd(index)}
|
|
92
|
+
style={{
|
|
93
|
+
flexDirection: 'row',
|
|
94
|
+
alignItems: 'center',
|
|
95
|
+
gap: 8,
|
|
96
|
+
borderWidth: 1,
|
|
97
|
+
borderColor: colors.border,
|
|
98
|
+
borderRadius: 999,
|
|
99
|
+
paddingVertical: 6,
|
|
100
|
+
paddingLeft: 12,
|
|
101
|
+
paddingRight: 8,
|
|
102
|
+
}}
|
|
103
|
+
>
|
|
104
|
+
<Icon
|
|
105
|
+
name={row.isCorporate ? 'building-2' : 'user'}
|
|
106
|
+
size={16}
|
|
107
|
+
color={colors.textSecondary}
|
|
108
|
+
/>
|
|
109
|
+
<MyazaText
|
|
110
|
+
variant="bodyMedium"
|
|
111
|
+
color={colors.textDark}
|
|
112
|
+
numberOfLines={1}
|
|
113
|
+
style={{ fontWeight: '500', maxWidth: 160 }}
|
|
114
|
+
>
|
|
115
|
+
{row.name.trim()}
|
|
116
|
+
</MyazaText>
|
|
117
|
+
<View
|
|
118
|
+
style={{
|
|
119
|
+
width: 20,
|
|
120
|
+
height: 20,
|
|
121
|
+
borderRadius: 10,
|
|
122
|
+
backgroundColor: colors.primary100,
|
|
123
|
+
alignItems: 'center',
|
|
124
|
+
justifyContent: 'center',
|
|
125
|
+
}}
|
|
126
|
+
>
|
|
127
|
+
<Icon name="plus" size={12} color={colors.primary} />
|
|
128
|
+
</View>
|
|
129
|
+
</Pressable>
|
|
130
|
+
);
|
|
131
|
+
})}
|
|
132
|
+
</View>
|
|
133
|
+
</View>
|
|
134
|
+
) : null}
|
|
135
|
+
|
|
136
|
+
{canAdd ? (
|
|
137
|
+
<Pressable
|
|
138
|
+
accessibilityRole="button"
|
|
139
|
+
onPress={onAdd}
|
|
140
|
+
style={{
|
|
141
|
+
marginTop: spacing.sm + 4,
|
|
142
|
+
borderRadius: radius.sm,
|
|
143
|
+
paddingVertical: 14,
|
|
144
|
+
alignItems: 'center',
|
|
145
|
+
justifyContent: 'center',
|
|
146
|
+
flexDirection: 'row',
|
|
147
|
+
gap: 6,
|
|
148
|
+
}}
|
|
149
|
+
>
|
|
150
|
+
{/* The same tight dashed language as the document slots. */}
|
|
151
|
+
<DashedBorder color={colors.border} radius={radius.sm} />
|
|
152
|
+
<Icon name="plus" size={16} color={colors.primary} />
|
|
153
|
+
<MyazaText variant="bodyMedium" color={colors.primary} style={{ fontWeight: '600' }}>
|
|
154
|
+
{addLabel}
|
|
155
|
+
</MyazaText>
|
|
156
|
+
</Pressable>
|
|
157
|
+
) : null}
|
|
158
|
+
|
|
159
|
+
{children}
|
|
160
|
+
</View>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { useTheme } from '../components/runtime';
|
|
5
|
+
import { spacing } from '../config/theme';
|
|
6
|
+
import {
|
|
7
|
+
grantRole,
|
|
8
|
+
quickAddCandidates,
|
|
9
|
+
sectionMembers,
|
|
10
|
+
withoutSection,
|
|
11
|
+
type KeyPeopleSection as SectionKey,
|
|
12
|
+
} from '../config/keyPeopleSections';
|
|
13
|
+
import type { KeyPeopleSectionDef } from '../config/keyPeopleSectionDefs';
|
|
14
|
+
import { KeyPeopleSection } from './KeyPeopleSection';
|
|
15
|
+
import { KeyPeopleUboExemption } from './KeyPeopleUboExemption';
|
|
16
|
+
import type { KeyPersonEntry } from '../config/keyPeople';
|
|
17
|
+
import type { KeyPersonRole } from '../types/business';
|
|
18
|
+
|
|
19
|
+
// The stacked sections with their shared-list plumbing: quick-add grants an
|
|
20
|
+
// existing person the section's hat, and the card's X takes them out of that
|
|
21
|
+
// section — dropping just the hat when membership rested on it, or the whole
|
|
22
|
+
// person when a declared stake (or a last remaining hat) is what holds them
|
|
23
|
+
// there. Split from BusinessKeyPeopleStep (200-line rule); mirrors the web
|
|
24
|
+
// SDK's KeyPeopleSectionsList 1:1.
|
|
25
|
+
|
|
26
|
+
export function KeyPeopleSectionsList({
|
|
27
|
+
sections,
|
|
28
|
+
rows,
|
|
29
|
+
threshold,
|
|
30
|
+
emailRequiredFor,
|
|
31
|
+
uboUnidentifiable,
|
|
32
|
+
canAdd,
|
|
33
|
+
onRows,
|
|
34
|
+
onSheet,
|
|
35
|
+
onExemption,
|
|
36
|
+
}: {
|
|
37
|
+
sections: KeyPeopleSectionDef[];
|
|
38
|
+
rows: KeyPersonEntry[];
|
|
39
|
+
threshold: number;
|
|
40
|
+
emailRequiredFor: ReadonlySet<KeyPersonRole>;
|
|
41
|
+
uboUnidentifiable: boolean;
|
|
42
|
+
canAdd: boolean;
|
|
43
|
+
onRows: (next: KeyPersonEntry[]) => void;
|
|
44
|
+
onSheet: (sheet: { mode: 'add' | 'edit'; section: SectionKey; index?: number }) => void;
|
|
45
|
+
onExemption: (next: boolean) => void;
|
|
46
|
+
}): React.ReactElement {
|
|
47
|
+
const { colors } = useTheme();
|
|
48
|
+
const members = sectionMembers(rows, threshold);
|
|
49
|
+
|
|
50
|
+
const removeFromSection = (section: SectionKey, index: number): void => {
|
|
51
|
+
const next = withoutSection(rows[index]!, section, threshold);
|
|
52
|
+
onRows(
|
|
53
|
+
next === null
|
|
54
|
+
? rows.filter((_, i) => i !== index)
|
|
55
|
+
: rows.map((row, i) => (i === index ? next : row)),
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<View>
|
|
61
|
+
{sections.map(({ key, title, description, addLabel }, i) => (
|
|
62
|
+
<React.Fragment key={key}>
|
|
63
|
+
{i > 0 ? (
|
|
64
|
+
<View
|
|
65
|
+
style={{
|
|
66
|
+
borderTopWidth: 1,
|
|
67
|
+
borderTopColor: colors.border,
|
|
68
|
+
marginVertical: spacing.lg,
|
|
69
|
+
}}
|
|
70
|
+
/>
|
|
71
|
+
) : null}
|
|
72
|
+
<KeyPeopleSection
|
|
73
|
+
section={key}
|
|
74
|
+
title={title}
|
|
75
|
+
description={description}
|
|
76
|
+
rows={rows}
|
|
77
|
+
members={members[key]}
|
|
78
|
+
quickAdd={
|
|
79
|
+
key === 'ubos' && uboUnidentifiable ? [] : quickAddCandidates(rows, key, threshold)
|
|
80
|
+
}
|
|
81
|
+
emailRequiredFor={emailRequiredFor}
|
|
82
|
+
addLabel={addLabel}
|
|
83
|
+
canAdd={canAdd && !(key === 'ubos' && uboUnidentifiable)}
|
|
84
|
+
onAdd={() => onSheet({ mode: 'add', section: key })}
|
|
85
|
+
onEdit={(index) => onSheet({ mode: 'edit', section: key, index })}
|
|
86
|
+
onRemove={(index) => removeFromSection(key, index)}
|
|
87
|
+
onQuickAdd={(index) =>
|
|
88
|
+
onRows(rows.map((row, i2) => (i2 === index ? grantRole(row, key) : row)))
|
|
89
|
+
}
|
|
90
|
+
>
|
|
91
|
+
{key === 'ubos' ? (
|
|
92
|
+
<KeyPeopleUboExemption
|
|
93
|
+
checked={uboUnidentifiable}
|
|
94
|
+
hasUbos={members.ubos.length > 0}
|
|
95
|
+
onChange={onExemption}
|
|
96
|
+
/>
|
|
97
|
+
) : null}
|
|
98
|
+
</KeyPeopleSection>
|
|
99
|
+
</React.Fragment>
|
|
100
|
+
))}
|
|
101
|
+
</View>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Pressable, View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { Icon } from '../components/Icon';
|
|
5
|
+
import { MyazaText } from '../components/Typography';
|
|
6
|
+
import { useTheme } from '../components/runtime';
|
|
7
|
+
import { spacing } from '../config/theme';
|
|
8
|
+
|
|
9
|
+
// The FATF fallback, attested: some companies genuinely have no natural
|
|
10
|
+
// person who qualifies as a UBO (listed companies, complex trusts, nominee
|
|
11
|
+
// arrangements), and without this box the applicant's only moves were to
|
|
12
|
+
// stall or to invent one. It is an attestation the org can branch on, never a
|
|
13
|
+
// verdict — and it is disabled the moment a UBO is listed, because the two
|
|
14
|
+
// claims contradict. Mirrors the web SDK's KeyPeopleUboExemption.
|
|
15
|
+
|
|
16
|
+
export function KeyPeopleUboExemption({
|
|
17
|
+
checked,
|
|
18
|
+
hasUbos,
|
|
19
|
+
onChange,
|
|
20
|
+
}: {
|
|
21
|
+
checked: boolean;
|
|
22
|
+
hasUbos: boolean;
|
|
23
|
+
onChange: (checked: boolean) => void;
|
|
24
|
+
}): React.ReactElement {
|
|
25
|
+
const { colors } = useTheme();
|
|
26
|
+
return (
|
|
27
|
+
<View style={{ marginTop: spacing.sm + 4 }}>
|
|
28
|
+
<Pressable
|
|
29
|
+
accessibilityRole="checkbox"
|
|
30
|
+
accessibilityState={{ checked, disabled: hasUbos }}
|
|
31
|
+
disabled={hasUbos}
|
|
32
|
+
onPress={() => onChange(!checked)}
|
|
33
|
+
style={{
|
|
34
|
+
flexDirection: 'row',
|
|
35
|
+
alignItems: 'flex-start',
|
|
36
|
+
gap: 12,
|
|
37
|
+
opacity: hasUbos ? 0.5 : 1,
|
|
38
|
+
}}
|
|
39
|
+
>
|
|
40
|
+
<View
|
|
41
|
+
style={{
|
|
42
|
+
width: 20,
|
|
43
|
+
height: 20,
|
|
44
|
+
marginTop: 1,
|
|
45
|
+
borderRadius: 6,
|
|
46
|
+
borderWidth: 1.5,
|
|
47
|
+
borderColor: checked ? colors.primary : colors.border,
|
|
48
|
+
backgroundColor: checked ? colors.primary : 'transparent',
|
|
49
|
+
alignItems: 'center',
|
|
50
|
+
justifyContent: 'center',
|
|
51
|
+
}}
|
|
52
|
+
>
|
|
53
|
+
{checked ? <Icon name="check" size={13} color={colors.onPrimary} /> : null}
|
|
54
|
+
</View>
|
|
55
|
+
<MyazaText variant="bodyMedium" color={colors.textSecondary} style={{ flex: 1 }}>
|
|
56
|
+
UBOs cannot be identified due to public share structures, complex trusts or nominee
|
|
57
|
+
arrangements.
|
|
58
|
+
</MyazaText>
|
|
59
|
+
</Pressable>
|
|
60
|
+
{checked && !hasUbos ? (
|
|
61
|
+
<MyazaText
|
|
62
|
+
variant="bodySmall"
|
|
63
|
+
color={colors.textSecondary}
|
|
64
|
+
style={{ marginTop: 6, paddingLeft: 32 }}
|
|
65
|
+
>
|
|
66
|
+
We will record this with the application; a senior person is still identified through
|
|
67
|
+
the applicant's own verification.
|
|
68
|
+
</MyazaText>
|
|
69
|
+
) : null}
|
|
70
|
+
</View>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
@@ -7,12 +7,14 @@ import { MyazaText } from '../components/Typography';
|
|
|
7
7
|
import { Icon } from '../components/Icon';
|
|
8
8
|
import { CountryFlag } from '../components/CountryFlag';
|
|
9
9
|
import {
|
|
10
|
+
rowNeedsEmail,
|
|
10
11
|
KEY_PERSON_ROLE_LABELS,
|
|
11
12
|
initialsOf,
|
|
12
13
|
isKeyPersonRowValid,
|
|
13
14
|
type KeyPersonEntry,
|
|
14
15
|
} from '../config/keyPeople';
|
|
15
16
|
import { regionCountryName } from '../config/regions';
|
|
17
|
+
import type { KeyPersonRole } from '../types/business';
|
|
16
18
|
|
|
17
19
|
// ---------------------------------------------------------------------------
|
|
18
20
|
// One saved key person, summarised — monogram avatar with their ID-issuing
|
|
@@ -27,20 +29,39 @@ import { regionCountryName } from '../config/regions';
|
|
|
27
29
|
|
|
28
30
|
export function KeyPersonCard({
|
|
29
31
|
entry,
|
|
32
|
+
emailRequiredFor,
|
|
30
33
|
onPress,
|
|
34
|
+
roleLabel,
|
|
35
|
+
onRemove,
|
|
31
36
|
}: {
|
|
32
37
|
entry: KeyPersonEntry;
|
|
38
|
+
emailRequiredFor?: ReadonlySet<KeyPersonRole>;
|
|
33
39
|
onPress: () => void;
|
|
40
|
+
/** Names the hat the HOSTING section is about (sectioned step). */
|
|
41
|
+
roleLabel?: string;
|
|
42
|
+
/** The sectioned step's X: take this person out of that section. */
|
|
43
|
+
onRemove?: () => void;
|
|
34
44
|
}): React.ReactElement {
|
|
35
45
|
const { colors } = useTheme();
|
|
36
46
|
const name = entry.name.trim() || 'Unnamed person';
|
|
37
47
|
const country = entry.country.trim() ? entry.country.trim().toUpperCase() : null;
|
|
38
48
|
const pct = entry.ownershipPct.trim();
|
|
39
49
|
// A row persisted by the old inline UI (or interrupted mid-edit) may be
|
|
40
|
-
// incomplete — the card says so instead of silently blocking Continue.
|
|
41
|
-
|
|
50
|
+
// incomplete — the card says so instead of silently blocking Continue. A
|
|
51
|
+
// missing REQUIRED email gets named specifically: "incomplete" on a row
|
|
52
|
+
// whose name, role and country are all filled reads as a bug.
|
|
53
|
+
const required = emailRequiredFor ?? new Set<KeyPersonRole>();
|
|
54
|
+
const incomplete = !isKeyPersonRowValid(entry, required);
|
|
55
|
+
const problem =
|
|
56
|
+
rowNeedsEmail(entry, required) && entry.email.trim() === '' && entry.name.trim().length >= 2
|
|
57
|
+
? 'Email required, tap to add'
|
|
58
|
+
: 'Incomplete, tap to finish';
|
|
42
59
|
|
|
43
|
-
const meta = [
|
|
60
|
+
const meta = [
|
|
61
|
+
roleLabel ?? KEY_PERSON_ROLE_LABELS[entry.role],
|
|
62
|
+
entry.title.trim() || null,
|
|
63
|
+
pct ? `${pct}% ownership` : null,
|
|
64
|
+
]
|
|
44
65
|
.filter(Boolean)
|
|
45
66
|
.join(' · ');
|
|
46
67
|
// The flag alone doesn't say WHICH country — spell it out, alongside the
|
|
@@ -104,7 +125,7 @@ export function KeyPersonCard({
|
|
|
104
125
|
</MyazaText>
|
|
105
126
|
{incomplete ? (
|
|
106
127
|
<MyazaText variant="bodySmall" color={colors.error} numberOfLines={1} style={{ marginTop: 1 }}>
|
|
107
|
-
|
|
128
|
+
{problem}
|
|
108
129
|
</MyazaText>
|
|
109
130
|
) : detail !== '' ? (
|
|
110
131
|
<MyazaText variant="bodySmall" color={colors.textMuted} numberOfLines={1} style={{ marginTop: 1 }}>
|
|
@@ -114,6 +135,17 @@ export function KeyPersonCard({
|
|
|
114
135
|
</View>
|
|
115
136
|
|
|
116
137
|
<Icon name="pencil" size={16} color={colors.textMuted} />
|
|
138
|
+
{onRemove ? (
|
|
139
|
+
<Pressable
|
|
140
|
+
accessibilityRole="button"
|
|
141
|
+
accessibilityLabel={`Remove ${name} from this section`}
|
|
142
|
+
hitSlop={8}
|
|
143
|
+
onPress={onRemove}
|
|
144
|
+
style={{ marginLeft: 4, padding: 4 }}
|
|
145
|
+
>
|
|
146
|
+
<Icon name="close" size={16} color={colors.textMuted} />
|
|
147
|
+
</Pressable>
|
|
148
|
+
) : null}
|
|
117
149
|
</Pressable>
|
|
118
150
|
);
|
|
119
151
|
}
|