@myazahq/kyc-sdk-react-native 2.4.0 → 2.6.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/README.md +151 -5
- package/android/build.gradle +35 -3
- package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaFaceDetector.kt +77 -0
- package/app.plugin.js +89 -8
- package/ios/HybridMyazaFaceDetector.swift +16 -3
- 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 +14 -2
- package/src/MyazaBiometricAuth.tsx +119 -0
- package/src/MyazaKYC.tsx +12 -0
- package/src/components/CountryField.tsx +8 -0
- package/src/components/CountryRegionPicker.tsx +184 -0
- package/src/components/DashedBorder.tsx +55 -0
- package/src/components/DialCodePicker.tsx +53 -67
- package/src/components/DialCodeRow.tsx +102 -0
- package/src/components/FramedMapPicker.tsx +122 -0
- package/src/components/GeoBadge.tsx +34 -0
- package/src/components/Icon.tsx +2 -176
- package/src/components/KycFlow.tsx +60 -32
- package/src/components/LineSkeleton.tsx +100 -0
- package/src/components/MapChrome.tsx +67 -0
- package/src/components/MapPinMarker.tsx +54 -0
- package/src/components/MapPinPicker.tsx +179 -0
- package/src/components/MilestoneTrack.tsx +155 -0
- package/src/components/MultiIdProgress.tsx +107 -0
- package/src/components/MyazaInput.tsx +39 -4
- package/src/components/MyazaSelect.tsx +2 -1
- package/src/components/OptionRow.tsx +22 -3
- package/src/components/OwnershipSlider.tsx +102 -0
- package/src/components/PhoneNumberInput.tsx +45 -4
- package/src/components/PoweredBy.tsx +5 -5
- package/src/components/PresenceBlocks.tsx +153 -0
- package/src/components/StaggerIn.tsx +63 -0
- package/src/components/StepView.tsx +43 -0
- package/src/components/StickyActions.tsx +56 -0
- package/src/components/dialCodeRows.ts +64 -0
- package/src/components/glass/FloatingSheet.tsx +73 -21
- package/src/components/glass/GlassSheet.tsx +14 -2
- package/src/components/icon-map.ts +176 -0
- package/src/components/stepHeaderMeta.tsx +45 -5
- package/src/config/addressCollection.ts +125 -0
- package/src/config/biometricOptions.ts +100 -0
- package/src/config/business.ts +26 -7
- package/src/config/businessDetailsValidity.ts +52 -0
- package/src/config/businessPrefill.ts +72 -0
- package/src/config/businessSteps.ts +20 -3
- package/src/config/consentStep.ts +19 -0
- 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/proofOfAddress.ts +54 -11
- package/src/config/regions.ts +24 -0
- package/src/config/registrationHint.ts +1 -1
- package/src/config/stepOrder.ts +100 -6
- package/src/config/theme.ts +8 -2
- package/src/config/uploadLimits.ts +35 -0
- package/src/config/website.ts +36 -0
- package/src/config/workflowMerge.ts +16 -5
- 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/index.ts +54 -1
- package/src/lib/address-current-location.ts +171 -0
- package/src/lib/address-field-modes.ts +119 -0
- package/src/lib/address-flow.ts +172 -0
- package/src/lib/address-helpers.ts +48 -0
- package/src/lib/address-line.ts +76 -0
- package/src/lib/address-pin-move.ts +97 -0
- package/src/lib/address-step-recovery.ts +63 -0
- package/src/lib/authed-image.ts +78 -0
- package/src/lib/biometric-auth.ts +52 -0
- package/src/lib/biometric-copy.ts +51 -0
- package/src/lib/captureRing.ts +83 -0
- package/src/lib/contact-recovery.ts +44 -0
- package/src/lib/country-adoption.ts +89 -0
- package/src/lib/inferred-country.ts +65 -0
- package/src/lib/livenessLayout.ts +49 -0
- package/src/lib/map-frame.ts +159 -0
- package/src/lib/map-tiles.ts +143 -0
- package/src/lib/multi-id.ts +210 -0
- package/src/lib/poa-country-gate.ts +27 -0
- package/src/lib/resubmit.ts +130 -0
- package/src/lib/result-copy.ts +116 -0
- package/src/lib/result-wait.ts +53 -0
- package/src/lib/review-map-surface.ts +26 -0
- package/src/lib/scope.ts +31 -0
- package/src/lib/screen-corners.ts +50 -0
- package/src/lib/selfie-upload-wait.ts +79 -0
- package/src/lib/step-log.ts +24 -3
- package/src/lib/street-view-fov.ts +42 -0
- package/src/lib/use-multi-id-plan.ts +29 -0
- package/src/lib/webview-available.ts +47 -0
- package/src/liveness/useLiveness.ts +6 -1
- package/src/liveness/useModelReady.ts +65 -0
- package/src/liveness/visionCameraFaceDetector.ts +35 -0
- package/src/presence/background-math.ts +100 -0
- package/src/presence/background-store.ts +82 -0
- package/src/presence/background.ts +165 -0
- package/src/presence/foreground-service.ts +193 -0
- package/src/presence/fs.ts +51 -0
- package/src/presence/geofence.ts +41 -0
- package/src/presence/math.ts +44 -0
- package/src/presence/post.ts +40 -0
- package/src/presence/report.ts +87 -0
- package/src/presence/sampler.ts +110 -0
- package/src/presence/status.ts +92 -0
- package/src/presence/store.ts +94 -0
- package/src/presence/tier.ts +36 -0
- package/src/presence/watch-wait.ts +81 -0
- package/src/screens/AddressCountryControl.tsx +115 -0
- package/src/screens/ApplicantRoleStep.tsx +9 -5
- package/src/screens/BusinessCheckPanel.tsx +89 -0
- package/src/screens/BusinessDetailsFields.tsx +112 -0
- package/src/screens/BusinessDetailsStep.tsx +133 -145
- package/src/screens/BusinessDocumentSlot.tsx +5 -5
- package/src/screens/BusinessDocumentsStep.tsx +5 -56
- 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 +91 -51
- package/src/screens/ConsentStep.tsx +37 -17
- package/src/screens/ContactActions.tsx +54 -0
- package/src/screens/ContactDestinationField.tsx +5 -0
- package/src/screens/ContactEntryPanel.tsx +75 -0
- package/src/screens/ContactVerificationStep.tsx +43 -42
- package/src/screens/CountrySelectStep.tsx +13 -147
- 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/LivenessAvatar.tsx +15 -3
- package/src/screens/LivenessStep.tsx +172 -52
- package/src/screens/NfcStep.tsx +14 -0
- package/src/screens/PoaDocumentTypeList.tsx +66 -0
- package/src/screens/ProofOfAddressParts.tsx +151 -0
- package/src/screens/ProofOfAddressStep.tsx +58 -96
- package/src/screens/SubmittedBadge.tsx +25 -0
- package/src/screens/SubmittedError.tsx +64 -0
- package/src/screens/SubmittedResult.tsx +117 -0
- package/src/screens/SubmittedStep.tsx +82 -119
- package/src/screens/SubmittedSuccess.tsx +127 -0
- package/src/screens/SubmittedWaiting.tsx +45 -0
- package/src/screens/address/AddressEntranceStep.tsx +161 -0
- package/src/screens/address/AddressIntroGate.tsx +148 -0
- package/src/screens/address/AddressMapStub.tsx +50 -0
- package/src/screens/address/AddressPinStep.tsx +178 -0
- package/src/screens/address/AddressReviewStep.tsx +162 -0
- package/src/screens/address/AddressSandboxTabs.tsx +158 -0
- package/src/screens/address/AddressSearchField.tsx +115 -0
- package/src/screens/address/AddressSearchStep.tsx +94 -0
- package/src/screens/address/CurrentLocationRow.tsx +135 -0
- package/src/screens/address/DetailsSheet.tsx +175 -0
- package/src/screens/address/DetailsSheetFields.tsx +184 -0
- package/src/screens/address/EntranceDropzone.tsx +178 -0
- package/src/screens/address/EntranceFraming.tsx +66 -0
- package/src/screens/address/EntrancePills.tsx +60 -0
- package/src/screens/address/FramedStreetView.tsx +155 -0
- package/src/screens/address/IntroDisclosures.tsx +210 -0
- package/src/screens/address/LabelDecisionRow.tsx +113 -0
- package/src/screens/address/PinSummaryRow.tsx +105 -0
- package/src/screens/address/ReviewAddressBand.tsx +111 -0
- package/src/screens/address/ReviewEntranceThumbs.tsx +64 -0
- package/src/screens/address/ReviewMapPicture.tsx +106 -0
- package/src/screens/address/SearchResults.tsx +105 -0
- package/src/screens/address/SearchScreen.tsx +176 -0
- package/src/screens/address/SkipForNow.tsx +43 -0
- package/src/screens/address/StreetViewChrome.tsx +81 -0
- package/src/screens/address/detail-values.ts +22 -0
- package/src/screens/address/fix-source.ts +27 -0
- package/src/screens/address/index.ts +8 -0
- package/src/screens/address/meta.ts +44 -0
- package/src/screens/address/use-address-flow.ts +200 -0
- package/src/screens/address/use-label-pin.ts +80 -0
- package/src/screens/address/use-pin-actions.ts +192 -0
- package/src/screens/biometric/BiometricAuthFlow.tsx +170 -0
- package/src/screens/consent/model.ts +79 -10
- package/src/screens/liveness/CaptureRing.tsx +91 -0
- package/src/screens/liveness/LivenessCamera.tsx +63 -0
- package/src/screens/liveness/LivenessHandover.tsx +43 -0
- package/src/screens/liveness/LivenessOutcome.tsx +4 -3
- package/src/screens/liveness/SelfiePreview.tsx +13 -4
- package/src/screens/liveness/ShutterFlash.tsx +25 -0
- package/src/screens/liveness/index.ts +4 -0
- package/src/screens/liveness/useSelfieUpload.ts +23 -3
- package/src/screens/nfc/NfcReadProgress.tsx +1 -0
- package/src/screens/nfc/NfcScanIllustration.tsx +7 -18
- package/src/screens/useAddressPhotoAttach.ts +77 -0
- package/src/screens/useAwaitingPeople.ts +90 -0
- package/src/screens/useBusinessDocumentAttach.ts +93 -0
- package/src/screens/usePoaAttach.ts +12 -15
- package/src/services/api-biometric.ts +37 -0
- package/src/services/api-types-biometric.ts +41 -0
- package/src/services/api-types.ts +251 -3
- package/src/services/api-verify-types.ts +90 -1
- package/src/services/api.ts +237 -3
- package/src/services/deviceMetadata.ts +1 -1
- package/src/services/errors.ts +10 -2
- package/src/services/location.ts +234 -0
- package/src/services/uploadErrors.ts +30 -0
- package/src/services/workflowGate.ts +8 -0
- package/src/specs/MyazaFaceDetector.nitro.ts +29 -0
- package/src/store/address.ts +97 -0
- package/src/store/businessCheck.ts +113 -0
- package/src/store/derive.ts +44 -4
- package/src/store/kycStore.ts +352 -16
- package/src/store/serverConfig.ts +20 -0
- package/src/store/session.ts +227 -0
- package/src/store/state.ts +289 -2
- package/src/store/submit.ts +80 -13
- package/src/store/submitApplicant.ts +5 -11
- package/src/types/business.ts +23 -2
- package/src/types/config.ts +70 -2
- package/src/types/verification.ts +13 -1
- package/src/types/workflow.ts +112 -1
- package/src/screens/KeyPeopleInviteLinks.tsx +0 -239
|
@@ -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
|
}
|
|
@@ -1,27 +1,34 @@
|
|
|
1
|
-
import React, { useMemo } from 'react';
|
|
2
|
-
import { View } from 'react-native';
|
|
1
|
+
import React, { useMemo, useState } from 'react';
|
|
2
|
+
import { Pressable, View } from 'react-native';
|
|
3
3
|
|
|
4
|
-
import { spacing } from '../config/theme';
|
|
4
|
+
import { radius, spacing } from '../config/theme';
|
|
5
5
|
import { useTheme } from '../components/runtime';
|
|
6
6
|
import { MyazaText } from '../components/Typography';
|
|
7
7
|
import { MyazaInput } from '../components/MyazaInput';
|
|
8
|
-
import {
|
|
8
|
+
import { Icon } from '../components/Icon';
|
|
9
|
+
import { OwnershipSlider } from '../components/OwnershipSlider';
|
|
9
10
|
import { CountryField } from '../components/CountryField';
|
|
10
11
|
import type { DialCodeOption } from '../components/DialCodePicker';
|
|
11
12
|
import { ALL_REGION_CODES, regionCountryName } from '../config/regions';
|
|
12
13
|
import { isValidContactEmail } from '../config/contact';
|
|
14
|
+
import { rowNeedsEmail, type KeyPersonEntry } from '../config/keyPeople';
|
|
13
15
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
type
|
|
17
|
-
} from '../config/
|
|
16
|
+
primaryRole,
|
|
17
|
+
rolesOf,
|
|
18
|
+
type KeyPeopleSection as SectionKey,
|
|
19
|
+
} from '../config/keyPeopleSections';
|
|
18
20
|
import type { KeyPersonRole } from '../types/business';
|
|
21
|
+
import { KeyPersonKindToggle } from './KeyPersonKindToggle';
|
|
22
|
+
import { KeyPersonOwners } from './KeyPersonOwners';
|
|
23
|
+
import { KeyPersonRoleChips } from './KeyPersonRoleChips';
|
|
19
24
|
|
|
20
25
|
// ---------------------------------------------------------------------------
|
|
21
|
-
// The key-person FIELDS
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
26
|
+
// The key-person FIELDS, scoped by the SECTION that opened the sheet: the
|
|
27
|
+
// section already said what this person is, so no coarse role dropdown — the
|
|
28
|
+
// UBO form asks name/stake/country/email, the shareholder form adds the
|
|
29
|
+
// individual-or-company toggle, and the representative form picks between the
|
|
30
|
+
// real classifications (chips) with the human nuance ("CFO, Board Member")
|
|
31
|
+
// captured as a free-text title. Mirrors the web SDK's KeyPersonForm 1:1.
|
|
25
32
|
// ---------------------------------------------------------------------------
|
|
26
33
|
|
|
27
34
|
/** Whether the role is an ownership one (% is only meaningful then). */
|
|
@@ -40,12 +47,22 @@ function allCountryOptions(): DialCodeOption[] {
|
|
|
40
47
|
|
|
41
48
|
export function KeyPersonForm({
|
|
42
49
|
entry,
|
|
50
|
+
section,
|
|
51
|
+
corporateKyb = false,
|
|
43
52
|
onChange,
|
|
44
53
|
uboThreshold = 25,
|
|
45
54
|
combinedPctError = null,
|
|
55
|
+
emailRequiredFor,
|
|
46
56
|
}: {
|
|
47
57
|
entry: KeyPersonEntry;
|
|
58
|
+
/** The section whose add-tile or card opened the sheet. */
|
|
59
|
+
section: SectionKey;
|
|
60
|
+
/** The workflow sends corporate shareholders their own KYB application -
|
|
61
|
+
* the callout must tell that truth instead of the screening-only one. */
|
|
62
|
+
corporateKyb?: boolean;
|
|
48
63
|
onChange: (patch: Partial<KeyPersonEntry>) => void;
|
|
64
|
+
/** Roles whose email is mandatory (they are sent a verification link). */
|
|
65
|
+
emailRequiredFor?: ReadonlySet<KeyPersonRole>;
|
|
49
66
|
/** Ownership % at/above which the server treats a person as a beneficial
|
|
50
67
|
* owner (the workflow's `keyPeople.ownershipThreshold`, default 25). */
|
|
51
68
|
uboThreshold?: number;
|
|
@@ -62,47 +79,97 @@ export function KeyPersonForm({
|
|
|
62
79
|
|
|
63
80
|
const nameInvalid = entry.name !== '' && entry.name.trim().length < 2;
|
|
64
81
|
const emailInvalid = entry.email.trim() !== '' && !isValidContactEmail(entry.email.trim());
|
|
82
|
+
const needsEmail = rowNeedsEmail(entry, emailRequiredFor ?? new Set());
|
|
83
|
+
// COLLAPSED by default so the company sheet stays five fields; open when
|
|
84
|
+
// owners already exist (a restored draft, a look-through find). The
|
|
85
|
+
// capability is one tap away, not one field more. Mirrors the web SDK.
|
|
86
|
+
const [showOwners, setShowOwners] = useState((entry.owners?.length ?? 0) > 0);
|
|
65
87
|
const pct = entry.ownershipPct.trim();
|
|
66
88
|
const pctNum = Number(pct);
|
|
67
89
|
const pctInvalid = pct !== '' && (!Number.isFinite(pctNum) || pctNum < 0 || pctNum > 100);
|
|
68
90
|
// Surface the regulatory consequence as feedback: at/above the threshold the
|
|
69
91
|
// server escalates this person to a beneficial owner regardless of the role
|
|
70
92
|
// picked. Quiet when they already chose UBO — nothing new to say.
|
|
93
|
+
const corp = entry.isCorporate;
|
|
94
|
+
const roles = rolesOf(entry);
|
|
71
95
|
const uboHint =
|
|
72
|
-
!pctInvalid && pct !== '' && pctNum >= uboThreshold &&
|
|
96
|
+
!corp && !pctInvalid && pct !== '' && pctNum >= uboThreshold && !roles.includes('beneficial_owner');
|
|
97
|
+
const belowThresholdNote =
|
|
98
|
+
section === 'ubos' && !pctInvalid && pct !== '' && pctNum > 0 && pctNum < uboThreshold;
|
|
99
|
+
|
|
100
|
+
const setRoles = (next: KeyPersonRole[]): void =>
|
|
101
|
+
onChange({ roles: next, role: primaryRole(next) });
|
|
73
102
|
|
|
74
103
|
return (
|
|
75
104
|
<View>
|
|
105
|
+
{/* Only shareholders can be a company: a beneficial owner is a natural
|
|
106
|
+
person in every regime that defines one, and a representative form
|
|
107
|
+
is about the people who act. */}
|
|
108
|
+
{section === 'shareholders' ? (
|
|
109
|
+
<>
|
|
110
|
+
<KeyPersonKindToggle
|
|
111
|
+
isCorporate={corp}
|
|
112
|
+
onChange={(isCorporate) =>
|
|
113
|
+
// Beneficial ownership is a claim about a person, so switching
|
|
114
|
+
// to a company reads the role set down rather than leaving an
|
|
115
|
+
// impossible one.
|
|
116
|
+
onChange({
|
|
117
|
+
isCorporate,
|
|
118
|
+
...(isCorporate
|
|
119
|
+
? {
|
|
120
|
+
roles: roles.map((r) => (r === 'beneficial_owner' ? 'shareholder' : r)),
|
|
121
|
+
role: primaryRole(
|
|
122
|
+
roles.map((r) => (r === 'beneficial_owner' ? 'shareholder' : r)),
|
|
123
|
+
),
|
|
124
|
+
}
|
|
125
|
+
: { registrationNumber: '', owners: [] }),
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
/>
|
|
129
|
+
<View style={{ height: spacing.sm }} />
|
|
130
|
+
</>
|
|
131
|
+
) : null}
|
|
76
132
|
<MyazaInput
|
|
77
|
-
label=
|
|
133
|
+
label={corp ? 'Company name' : 'Full name'}
|
|
78
134
|
value={entry.name}
|
|
79
135
|
onChangeText={(name) => onChange({ name })}
|
|
80
|
-
placeholder=
|
|
81
|
-
// It's a
|
|
136
|
+
placeholder={corp ? 'e.g. Acme Holdings Ltd' : 'e.g. Bola Owner'}
|
|
137
|
+
// It's a name — start every word capitalized.
|
|
82
138
|
autoCapitalize="words"
|
|
83
|
-
error={
|
|
139
|
+
error={
|
|
140
|
+
nameInvalid
|
|
141
|
+
? `Enter the ${corp ? 'registered company name' : 'person’s full name'}.`
|
|
142
|
+
: null
|
|
143
|
+
}
|
|
84
144
|
/>
|
|
85
145
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
146
|
+
{section === 'representatives' ? (
|
|
147
|
+
<>
|
|
148
|
+
<View style={{ height: spacing.sm }} />
|
|
149
|
+
<KeyPersonRoleChips roles={roles} onRoles={setRoles} />
|
|
150
|
+
</>
|
|
151
|
+
) : null}
|
|
152
|
+
|
|
153
|
+
{!corp ? (
|
|
154
|
+
<>
|
|
155
|
+
<View style={{ height: spacing.sm }} />
|
|
156
|
+
<MyazaInput
|
|
157
|
+
label="Position or title (optional)"
|
|
158
|
+
value={entry.title}
|
|
159
|
+
onChangeText={(title) => onChange({ title })}
|
|
160
|
+
placeholder="e.g. CFO, Board Member"
|
|
161
|
+
autoCapitalize="words"
|
|
162
|
+
autoCorrect={false}
|
|
163
|
+
/>
|
|
164
|
+
</>
|
|
165
|
+
) : null}
|
|
99
166
|
|
|
100
167
|
<View style={{ height: spacing.sm }} />
|
|
101
168
|
<MyazaInput
|
|
102
169
|
label="Ownership % (optional)"
|
|
103
170
|
value={entry.ownershipPct}
|
|
104
171
|
onChangeText={(ownershipPct) => onChange({ ownershipPct })}
|
|
105
|
-
placeholder=
|
|
172
|
+
placeholder="0"
|
|
106
173
|
keyboardType="decimal-pad"
|
|
107
174
|
suffix={
|
|
108
175
|
<MyazaText variant="bodySmall" color={colors.textMuted}>
|
|
@@ -113,15 +180,37 @@ export function KeyPersonForm({
|
|
|
113
180
|
helper={
|
|
114
181
|
uboHint
|
|
115
182
|
? `At ${uboThreshold}% or more, this person counts as a beneficial owner.`
|
|
116
|
-
:
|
|
183
|
+
: belowThresholdNote
|
|
184
|
+
? `Below ${uboThreshold}% they will be recorded as a shareholder.`
|
|
185
|
+
: undefined
|
|
117
186
|
}
|
|
118
187
|
/>
|
|
188
|
+
<View style={{ height: spacing.xs }} />
|
|
189
|
+
{/* The slider rests at 0 for an undeclared stake; only a drag writes a
|
|
190
|
+
value, so an untouched slider still submits "not declared". */}
|
|
191
|
+
<OwnershipSlider
|
|
192
|
+
value={pct !== '' && Number.isFinite(Number(pct)) ? Number(pct) : 0}
|
|
193
|
+
onChange={(next) => onChange({ ownershipPct: String(next) })}
|
|
194
|
+
/>
|
|
195
|
+
|
|
196
|
+
{corp ? (
|
|
197
|
+
<>
|
|
198
|
+
<View style={{ height: spacing.sm }} />
|
|
199
|
+
<MyazaInput
|
|
200
|
+
label="Registration number (optional)"
|
|
201
|
+
value={entry.registrationNumber}
|
|
202
|
+
onChangeText={(registrationNumber) => onChange({ registrationNumber })}
|
|
203
|
+
placeholder="e.g. RC123456"
|
|
204
|
+
autoCapitalize="characters"
|
|
205
|
+
/>
|
|
206
|
+
</>
|
|
207
|
+
) : null}
|
|
119
208
|
|
|
120
209
|
<View style={{ height: spacing.sm }} />
|
|
121
|
-
<MyazaText variant="
|
|
210
|
+
<MyazaText variant="label" style={{ marginBottom: spacing.sm }}>
|
|
122
211
|
Country{' '}
|
|
123
|
-
<MyazaText variant="
|
|
124
|
-
(where their ID was issued)
|
|
212
|
+
<MyazaText variant="bodyMedium" color={colors.textSecondary}>
|
|
213
|
+
{corp ? '(of incorporation)' : '(where their ID was issued)'}
|
|
125
214
|
</MyazaText>
|
|
126
215
|
</MyazaText>
|
|
127
216
|
{/* The SAME sheet as the phone field's dial-code picker (keyboard-aware,
|
|
@@ -133,16 +222,86 @@ export function KeyPersonForm({
|
|
|
133
222
|
onChange={(country) => onChange({ country })}
|
|
134
223
|
/>
|
|
135
224
|
|
|
225
|
+
{corp && !showOwners ? (
|
|
226
|
+
<>
|
|
227
|
+
<View style={{ height: spacing.sm }} />
|
|
228
|
+
<Pressable
|
|
229
|
+
accessibilityRole="button"
|
|
230
|
+
onPress={() => setShowOwners(true)}
|
|
231
|
+
hitSlop={8}
|
|
232
|
+
style={{ flexDirection: 'row', alignItems: 'center', gap: 6, alignSelf: 'flex-start' }}
|
|
233
|
+
>
|
|
234
|
+
<Icon name="chevron-right" size={16} color={colors.textSecondary} />
|
|
235
|
+
<MyazaText
|
|
236
|
+
variant="bodyMedium"
|
|
237
|
+
color={colors.textSecondary}
|
|
238
|
+
style={{ fontWeight: '500' }}
|
|
239
|
+
>
|
|
240
|
+
Add the people who own it (optional)
|
|
241
|
+
</MyazaText>
|
|
242
|
+
</Pressable>
|
|
243
|
+
</>
|
|
244
|
+
) : null}
|
|
245
|
+
{corp && showOwners ? (
|
|
246
|
+
<>
|
|
247
|
+
<View style={{ height: spacing.sm }} />
|
|
248
|
+
<KeyPersonOwners
|
|
249
|
+
owners={entry.owners ?? []}
|
|
250
|
+
companyName={entry.name}
|
|
251
|
+
onChange={(owners) => onChange({ owners })}
|
|
252
|
+
/>
|
|
253
|
+
</>
|
|
254
|
+
) : null}
|
|
255
|
+
|
|
136
256
|
<View style={{ height: spacing.sm }} />
|
|
137
257
|
<MyazaInput
|
|
138
|
-
label=
|
|
258
|
+
label={
|
|
259
|
+
needsEmail
|
|
260
|
+
? 'Email *'
|
|
261
|
+
: corp
|
|
262
|
+
? 'Email (optional)'
|
|
263
|
+
: 'Email (optional, used to send their verification link)'
|
|
264
|
+
}
|
|
139
265
|
value={entry.email}
|
|
140
266
|
onChangeText={(email) => onChange({ email })}
|
|
141
267
|
placeholder="name@company.com"
|
|
142
268
|
keyboardType="email-address"
|
|
143
269
|
autoCapitalize="none"
|
|
144
270
|
error={emailInvalid ? 'Enter a valid email address.' : null}
|
|
271
|
+
helper={
|
|
272
|
+
needsEmail && entry.email.trim() === ''
|
|
273
|
+
? 'Required: this is how they receive their own verification link.'
|
|
274
|
+
: undefined
|
|
275
|
+
}
|
|
145
276
|
/>
|
|
277
|
+
|
|
278
|
+
{/* What actually happens to a company on this list, stated as a callout
|
|
279
|
+
rather than buried in a field helper, because it answers the question
|
|
280
|
+
every applicant has at this exact point ("is the company going to be
|
|
281
|
+
asked for a selfie?"). Deliberately OUR facts, not a promise of a
|
|
282
|
+
nested KYB we do not run. Mirrors the web SDK's callout. */}
|
|
283
|
+
{corp ? (
|
|
284
|
+
<View
|
|
285
|
+
style={{
|
|
286
|
+
marginTop: spacing.md,
|
|
287
|
+
flexDirection: 'row',
|
|
288
|
+
alignItems: 'flex-start',
|
|
289
|
+
gap: 10,
|
|
290
|
+
borderRadius: radius.sm,
|
|
291
|
+
backgroundColor: colors.primary50,
|
|
292
|
+
padding: 12,
|
|
293
|
+
}}
|
|
294
|
+
>
|
|
295
|
+
<View style={{ marginTop: 2 }}>
|
|
296
|
+
<Icon name="info" size={16} color={colors.primary} />
|
|
297
|
+
</View>
|
|
298
|
+
<MyazaText variant="bodyMedium" color={colors.textSecondary} style={{ flex: 1 }}>
|
|
299
|
+
{corporateKyb
|
|
300
|
+
? 'This company will need its own KYB verification: it receives a link to a business application of its own, where the people who own it are identified. We also screen it against sanctions lists.'
|
|
301
|
+
: 'A company is never asked to verify an identity. We check it against sanctions lists, and the people who own it are reviewed separately, so list its owners above if you know them.'}
|
|
302
|
+
</MyazaText>
|
|
303
|
+
</View>
|
|
304
|
+
) : null}
|
|
146
305
|
</View>
|
|
147
306
|
);
|
|
148
307
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Pressable, View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { radius, spacing } from '../config/theme';
|
|
5
|
+
import { useTheme } from '../components/runtime';
|
|
6
|
+
import { MyazaText } from '../components/Typography';
|
|
7
|
+
|
|
8
|
+
// Person or company.
|
|
9
|
+
//
|
|
10
|
+
// The first question on the form, because it changes what the rest of it asks:
|
|
11
|
+
// a company has a registration number rather than an ID country, and no
|
|
12
|
+
// identity of its own to verify. Left unasked, a limited company was collected
|
|
13
|
+
// as a person, escalated to beneficial owner (which by definition means a
|
|
14
|
+
// natural person), and sent a link to take a selfie.
|
|
15
|
+
|
|
16
|
+
export function KeyPersonKindToggle({
|
|
17
|
+
isCorporate,
|
|
18
|
+
onChange,
|
|
19
|
+
}: {
|
|
20
|
+
isCorporate: boolean;
|
|
21
|
+
onChange: (isCorporate: boolean) => void;
|
|
22
|
+
}): React.ReactElement {
|
|
23
|
+
const { colors } = useTheme();
|
|
24
|
+
const options: Array<{ value: boolean; label: string }> = [
|
|
25
|
+
{ value: false, label: 'A person' },
|
|
26
|
+
{ value: true, label: 'A company' },
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<View>
|
|
31
|
+
<MyazaText variant="bodySmall" style={{ fontWeight: '600', marginBottom: spacing.xs }}>
|
|
32
|
+
Who is this?
|
|
33
|
+
</MyazaText>
|
|
34
|
+
<View style={{ flexDirection: 'row', gap: spacing.sm }}>
|
|
35
|
+
{options.map(({ value, label }) => {
|
|
36
|
+
const selected = isCorporate === value;
|
|
37
|
+
return (
|
|
38
|
+
<Pressable
|
|
39
|
+
key={label}
|
|
40
|
+
accessibilityRole="radio"
|
|
41
|
+
accessibilityState={{ selected }}
|
|
42
|
+
accessibilityLabel={label}
|
|
43
|
+
onPress={() => onChange(value)}
|
|
44
|
+
style={{
|
|
45
|
+
flex: 1,
|
|
46
|
+
height: 44,
|
|
47
|
+
alignItems: 'center',
|
|
48
|
+
justifyContent: 'center',
|
|
49
|
+
borderRadius: radius.lg,
|
|
50
|
+
borderWidth: 1,
|
|
51
|
+
borderColor: selected ? colors.primary : colors.border,
|
|
52
|
+
backgroundColor: selected ? `${colors.primary}1A` : 'transparent',
|
|
53
|
+
}}
|
|
54
|
+
>
|
|
55
|
+
<MyazaText
|
|
56
|
+
variant="bodySmall"
|
|
57
|
+
style={{ fontWeight: '600' }}
|
|
58
|
+
color={selected ? colors.primary : colors.textSecondary}
|
|
59
|
+
>
|
|
60
|
+
{label}
|
|
61
|
+
</MyazaText>
|
|
62
|
+
</Pressable>
|
|
63
|
+
);
|
|
64
|
+
})}
|
|
65
|
+
</View>
|
|
66
|
+
</View>
|
|
67
|
+
);
|
|
68
|
+
}
|