@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,204 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Pressable, Share, View } from 'react-native';
|
|
3
|
+
import * as Clipboard from 'expo-clipboard';
|
|
4
|
+
|
|
5
|
+
import { radius, spacing } from '../config/theme';
|
|
6
|
+
import { useTheme } from '../components/runtime';
|
|
7
|
+
import { MyazaText } from '../components/Typography';
|
|
8
|
+
import { Icon } from '../components/Icon';
|
|
9
|
+
import { CountryFlag } from '../components/CountryFlag';
|
|
10
|
+
import { regionCountryName } from '../config/regions';
|
|
11
|
+
import { KEY_PERSON_ROLE_LABELS } from '../config/keyPeople';
|
|
12
|
+
import type { AwaitRow } from './KeyPeopleAwaitList';
|
|
13
|
+
|
|
14
|
+
// One person's card on the KYB "awaiting users" list — split from
|
|
15
|
+
// KeyPeopleAwaitList per the 200-line rule.
|
|
16
|
+
|
|
17
|
+
const ROLE_LABELS: Record<string, string> = {
|
|
18
|
+
...KEY_PERSON_ROLE_LABELS,
|
|
19
|
+
authorized_representative: 'Authorized representative',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function statusView(
|
|
23
|
+
row: AwaitRow,
|
|
24
|
+
colors: ReturnType<typeof useTheme>['colors'],
|
|
25
|
+
): { label: string; bg: string; fg: string } {
|
|
26
|
+
switch (row.status) {
|
|
27
|
+
case 'verified':
|
|
28
|
+
return { label: 'VERIFIED', bg: colors.successBg, fg: colors.success };
|
|
29
|
+
case 'submitted':
|
|
30
|
+
return { label: 'SUBMITTED', bg: colors.successBg, fg: colors.success };
|
|
31
|
+
case 'failed':
|
|
32
|
+
return { label: 'CHECK FAILED', bg: colors.errorBg, fg: colors.error };
|
|
33
|
+
case 'not_needed':
|
|
34
|
+
return { label: 'NOT NEEDED', bg: colors.border, fg: colors.textMuted };
|
|
35
|
+
default:
|
|
36
|
+
return {
|
|
37
|
+
label: row.isCorporate ? 'KYB PENDING' : 'KYC PENDING',
|
|
38
|
+
bg: colors.primary100,
|
|
39
|
+
fg: colors.primary,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function AwaitCard({ row }: { row: AwaitRow }): React.ReactElement {
|
|
45
|
+
const { colors } = useTheme();
|
|
46
|
+
const [copied, setCopied] = useState(false);
|
|
47
|
+
const status = statusView(row, colors);
|
|
48
|
+
|
|
49
|
+
const share = (): void => {
|
|
50
|
+
if (!row.inviteUrl) return;
|
|
51
|
+
Share.share({ message: row.inviteUrl }).catch(() => undefined);
|
|
52
|
+
};
|
|
53
|
+
const copy = (): void => {
|
|
54
|
+
if (!row.inviteUrl) return;
|
|
55
|
+
Clipboard.setStringAsync(row.inviteUrl)
|
|
56
|
+
.then(() => {
|
|
57
|
+
setCopied(true);
|
|
58
|
+
setTimeout(() => setCopied(false), 2000);
|
|
59
|
+
})
|
|
60
|
+
.catch(() => undefined);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<View
|
|
65
|
+
style={{
|
|
66
|
+
backgroundColor: colors.backgroundSecondary,
|
|
67
|
+
borderRadius: radius.md,
|
|
68
|
+
padding: spacing.md,
|
|
69
|
+
marginBottom: spacing.sm,
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
<View style={{ flexDirection: 'row', alignItems: 'flex-start' }}>
|
|
73
|
+
<View style={{ flex: 1, minWidth: 0, marginRight: spacing.sm }}>
|
|
74
|
+
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
|
75
|
+
<MyazaText
|
|
76
|
+
variant="bodyMedium"
|
|
77
|
+
numberOfLines={1}
|
|
78
|
+
style={{ fontWeight: '700', flexShrink: 1 }}
|
|
79
|
+
>
|
|
80
|
+
{row.name}
|
|
81
|
+
{row.isApplicant ? (
|
|
82
|
+
<MyazaText variant="bodySmall" color={colors.textMuted}>
|
|
83
|
+
{' (you)'}
|
|
84
|
+
</MyazaText>
|
|
85
|
+
) : null}
|
|
86
|
+
</MyazaText>
|
|
87
|
+
{row.isCorporate ? (
|
|
88
|
+
// Readability over subtlety: foreground text on a bordered chip.
|
|
89
|
+
// The muted-on-muted version disappeared on dark org themes.
|
|
90
|
+
<View
|
|
91
|
+
style={{
|
|
92
|
+
flexDirection: 'row',
|
|
93
|
+
alignItems: 'center',
|
|
94
|
+
borderWidth: 1,
|
|
95
|
+
borderColor: colors.border,
|
|
96
|
+
backgroundColor: colors.background,
|
|
97
|
+
borderRadius: radius.full,
|
|
98
|
+
paddingHorizontal: spacing.xs + 4,
|
|
99
|
+
paddingVertical: 2,
|
|
100
|
+
marginLeft: spacing.xs + 2,
|
|
101
|
+
}}
|
|
102
|
+
>
|
|
103
|
+
<Icon name="building-2" size={12} color={colors.textDark} />
|
|
104
|
+
<MyazaText
|
|
105
|
+
variant="bodySmall"
|
|
106
|
+
color={colors.textDark}
|
|
107
|
+
style={{ fontSize: 12, fontWeight: '600', marginLeft: 4 }}
|
|
108
|
+
>
|
|
109
|
+
Company
|
|
110
|
+
</MyazaText>
|
|
111
|
+
</View>
|
|
112
|
+
) : null}
|
|
113
|
+
</View>
|
|
114
|
+
<View
|
|
115
|
+
style={{ flexDirection: 'row', alignItems: 'center', flexWrap: 'wrap', marginTop: 2 }}
|
|
116
|
+
>
|
|
117
|
+
<MyazaText variant="bodySmall" color={colors.textMuted}>
|
|
118
|
+
{ROLE_LABELS[row.role] ?? 'Key person'}
|
|
119
|
+
{row.pct ? ` · ${row.pct}%` : ''}
|
|
120
|
+
{row.country ? ' · ' : ''}
|
|
121
|
+
</MyazaText>
|
|
122
|
+
{row.country ? (
|
|
123
|
+
<>
|
|
124
|
+
<CountryFlag country={row.country} size={14} />
|
|
125
|
+
<MyazaText variant="bodySmall" color={colors.textMuted}>
|
|
126
|
+
{` ${regionCountryName(row.country)}`}
|
|
127
|
+
</MyazaText>
|
|
128
|
+
</>
|
|
129
|
+
) : null}
|
|
130
|
+
</View>
|
|
131
|
+
</View>
|
|
132
|
+
<View
|
|
133
|
+
style={{
|
|
134
|
+
borderRadius: radius.full,
|
|
135
|
+
paddingHorizontal: spacing.sm + 4,
|
|
136
|
+
paddingVertical: spacing.xs,
|
|
137
|
+
backgroundColor: status.bg,
|
|
138
|
+
}}
|
|
139
|
+
>
|
|
140
|
+
<MyazaText
|
|
141
|
+
variant="bodySmall"
|
|
142
|
+
color={status.fg}
|
|
143
|
+
style={{ fontWeight: '700', fontSize: 11, letterSpacing: 0.6 }}
|
|
144
|
+
>
|
|
145
|
+
{status.label}
|
|
146
|
+
</MyazaText>
|
|
147
|
+
</View>
|
|
148
|
+
</View>
|
|
149
|
+
|
|
150
|
+
{/* A failed check keeps its link — that is the retry. */}
|
|
151
|
+
{row.inviteUrl ? (
|
|
152
|
+
<View style={{ flexDirection: 'row', marginTop: spacing.sm + 4 }}>
|
|
153
|
+
<Pressable
|
|
154
|
+
onPress={copy}
|
|
155
|
+
accessibilityRole="button"
|
|
156
|
+
accessibilityLabel={`Copy ${row.name}'s verification link`}
|
|
157
|
+
style={{
|
|
158
|
+
flex: 1,
|
|
159
|
+
flexDirection: 'row',
|
|
160
|
+
alignItems: 'center',
|
|
161
|
+
justifyContent: 'center',
|
|
162
|
+
backgroundColor: colors.primary100,
|
|
163
|
+
borderRadius: radius.full,
|
|
164
|
+
paddingVertical: spacing.sm + 2,
|
|
165
|
+
marginRight: spacing.sm,
|
|
166
|
+
}}
|
|
167
|
+
>
|
|
168
|
+
<Icon name={copied ? 'check' : 'copy'} size={15} color={colors.primary} />
|
|
169
|
+
<MyazaText
|
|
170
|
+
variant="bodySmall"
|
|
171
|
+
color={colors.primary}
|
|
172
|
+
style={{ fontWeight: '600', marginLeft: 6 }}
|
|
173
|
+
>
|
|
174
|
+
{copied ? 'Copied' : 'Copy link'}
|
|
175
|
+
</MyazaText>
|
|
176
|
+
</Pressable>
|
|
177
|
+
<Pressable
|
|
178
|
+
onPress={share}
|
|
179
|
+
accessibilityRole="button"
|
|
180
|
+
accessibilityLabel={`Share ${row.name}'s verification link`}
|
|
181
|
+
style={{
|
|
182
|
+
flex: 1,
|
|
183
|
+
flexDirection: 'row',
|
|
184
|
+
alignItems: 'center',
|
|
185
|
+
justifyContent: 'center',
|
|
186
|
+
backgroundColor: colors.primary100,
|
|
187
|
+
borderRadius: radius.full,
|
|
188
|
+
paddingVertical: spacing.sm + 2,
|
|
189
|
+
}}
|
|
190
|
+
>
|
|
191
|
+
<Icon name="share" size={15} color={colors.primary} />
|
|
192
|
+
<MyazaText
|
|
193
|
+
variant="bodySmall"
|
|
194
|
+
color={colors.primary}
|
|
195
|
+
style={{ fontWeight: '600', marginLeft: 6 }}
|
|
196
|
+
>
|
|
197
|
+
Share
|
|
198
|
+
</MyazaText>
|
|
199
|
+
</Pressable>
|
|
200
|
+
</View>
|
|
201
|
+
) : null}
|
|
202
|
+
</View>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { spacing } from '../config/theme';
|
|
5
|
+
import { useTheme } from '../components/runtime';
|
|
6
|
+
import { MyazaText } from '../components/Typography';
|
|
7
|
+
import { AwaitCard } from './KeyPeopleAwaitCard';
|
|
8
|
+
import { StaggerIn } from '../components/StaggerIn';
|
|
9
|
+
import type { AwaitingPersonPayload } from '../services/api-types';
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// "Awaiting users" — the KYB success screen's people section, rendered from
|
|
13
|
+
// the SERVER's reconciled list (mirrors the web SDK's KeyPeopleAwaitList).
|
|
14
|
+
// Registry discovery can add people the applicant never listed, so the rows
|
|
15
|
+
// here come from the session summary once it settles — never from what was
|
|
16
|
+
// typed. Statuses stay live: the people named go and verify after this screen
|
|
17
|
+
// is first shown.
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
export interface AwaitRow {
|
|
21
|
+
name: string;
|
|
22
|
+
role: string;
|
|
23
|
+
pct: string | null;
|
|
24
|
+
country: string | null;
|
|
25
|
+
status: AwaitingPersonPayload['status'];
|
|
26
|
+
inviteUrl?: string;
|
|
27
|
+
isApplicant?: boolean;
|
|
28
|
+
/** A company on the list completes a KYB application, not a KYC. It gets a
|
|
29
|
+
* Company tag and its pending pill reads "KYB PENDING". */
|
|
30
|
+
isCorporate?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Ownership as the list renders it: a whole number where that is honest. */
|
|
34
|
+
function formatPct(pct: number | null): string | null {
|
|
35
|
+
if (pct === null || !Number.isFinite(pct)) return null;
|
|
36
|
+
return Number.isInteger(pct) ? String(pct) : pct.toFixed(2).replace(/\.?0+$/, '');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Server payload → display rows. */
|
|
40
|
+
export function rowsFromServer(people: AwaitingPersonPayload[]): AwaitRow[] {
|
|
41
|
+
return people.map((p) => ({
|
|
42
|
+
name: p.name,
|
|
43
|
+
role: p.role,
|
|
44
|
+
pct: formatPct(p.ownershipPct),
|
|
45
|
+
country: p.country,
|
|
46
|
+
status: p.status,
|
|
47
|
+
...(p.inviteUrl ? { inviteUrl: p.inviteUrl } : {}),
|
|
48
|
+
isApplicant: p.isApplicant,
|
|
49
|
+
isCorporate: p.isCorporate ?? false,
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const SECTIONS: Array<{ role: string; label: string }> = [
|
|
54
|
+
{ role: 'beneficial_owner', label: 'UBOS' },
|
|
55
|
+
{ role: 'director', label: 'DIRECTORS' },
|
|
56
|
+
{ role: 'signatory', label: 'SIGNATORIES' },
|
|
57
|
+
{ role: 'shareholder', label: 'SHAREHOLDERS' },
|
|
58
|
+
{ role: 'authorized_representative', label: 'REPRESENTATIVES' },
|
|
59
|
+
];
|
|
60
|
+
const KNOWN_ROLES = new Set(SECTIONS.map((s) => s.role));
|
|
61
|
+
|
|
62
|
+
export function KeyPeopleAwaitList({ rows }: { rows: AwaitRow[] }): React.ReactElement | null {
|
|
63
|
+
const { colors } = useTheme();
|
|
64
|
+
if (rows.length === 0) return null;
|
|
65
|
+
|
|
66
|
+
// Anything whose role we have no section for still has to appear — quietly
|
|
67
|
+
// dropping people from a "who still owes a check" list is the worst failure
|
|
68
|
+
// it could have.
|
|
69
|
+
const groups = SECTIONS.map(({ role, label }) => ({
|
|
70
|
+
label,
|
|
71
|
+
rows: rows.filter((r) => r.role === role),
|
|
72
|
+
})).filter((g) => g.rows.length > 0);
|
|
73
|
+
const others = rows.filter((r) => !KNOWN_ROLES.has(r.role));
|
|
74
|
+
if (others.length > 0) groups.push({ label: 'OTHER PEOPLE', rows: others });
|
|
75
|
+
|
|
76
|
+
const outstanding = rows.filter((r) => r.status === 'pending' || r.status === 'failed').length;
|
|
77
|
+
|
|
78
|
+
// Entrance stagger, mirroring the web SDK: cards rise in reading order,
|
|
79
|
+
// ~45ms apart, capped so a long roster is not a slow reveal. Together with
|
|
80
|
+
// the skeleton this list replaces (drawn at the same geometry), the
|
|
81
|
+
// hand-off reads as the ghost roster resolving into the real one.
|
|
82
|
+
let entranceIndex = 0;
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<View style={{ width: '100%', marginTop: spacing.lg }}>
|
|
86
|
+
<MyazaText
|
|
87
|
+
variant="bodySmall"
|
|
88
|
+
color={colors.textMuted}
|
|
89
|
+
style={{ textAlign: 'center', marginBottom: spacing.md }}
|
|
90
|
+
>
|
|
91
|
+
{outstanding === 0
|
|
92
|
+
? 'Everyone on this application has completed their identity check.'
|
|
93
|
+
: 'To complete the review, the people below must verify their identity with a KYC check. Anyone with an email on file has already been sent their link.'}
|
|
94
|
+
</MyazaText>
|
|
95
|
+
|
|
96
|
+
{groups.map((group) => (
|
|
97
|
+
<View key={group.label} style={{ marginBottom: spacing.sm }}>
|
|
98
|
+
<MyazaText
|
|
99
|
+
variant="bodySmall"
|
|
100
|
+
color={colors.textMuted}
|
|
101
|
+
style={{ fontWeight: '600', letterSpacing: 1.2, marginBottom: spacing.xs + 2 }}
|
|
102
|
+
>
|
|
103
|
+
{group.label}
|
|
104
|
+
</MyazaText>
|
|
105
|
+
{group.rows.map((row, i) => (
|
|
106
|
+
<StaggerIn key={`${group.label}-${i}`} delayMs={Math.min(entranceIndex++, 8) * 45}>
|
|
107
|
+
<AwaitCard row={row} />
|
|
108
|
+
</StaggerIn>
|
|
109
|
+
))}
|
|
110
|
+
</View>
|
|
111
|
+
))}
|
|
112
|
+
|
|
113
|
+
{outstanding > 0 ? (
|
|
114
|
+
<MyazaText
|
|
115
|
+
variant="bodySmall"
|
|
116
|
+
color={colors.textMuted}
|
|
117
|
+
// Clear air below: this caption is the list's last element and the
|
|
118
|
+
// Done button renders immediately after it in the parent.
|
|
119
|
+
style={{ textAlign: 'center', marginTop: spacing.xs, marginBottom: spacing.lg }}
|
|
120
|
+
>
|
|
121
|
+
Links are valid for 14 days.
|
|
122
|
+
</MyazaText>
|
|
123
|
+
) : null}
|
|
124
|
+
</View>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from 'react';
|
|
2
|
+
import { ActivityIndicator, Animated, 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
|
+
import { useReduceMotion } from '../components/StaggerIn';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Held while the register is reconciled against what the applicant typed.
|
|
11
|
+
*
|
|
12
|
+
* A SKELETON of the roster it becomes, not a spinner line (mirrors the web
|
|
13
|
+
* SDK's KeyPeoplePending): ghost cards drawn at the real cards' geometry
|
|
14
|
+
* reserve the space so the answer lands in place, and the shape itself says
|
|
15
|
+
* "a list of people is coming" where a spinner only says "busy". A blank here
|
|
16
|
+
* reads as "nobody needs to verify", the opposite of true. The status line is
|
|
17
|
+
* a polite live region; every shimmer respects the OS reduce-motion setting.
|
|
18
|
+
* Seconds, normally; the hook behind it gives up rather than spinning forever.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** One shimmering placeholder bar. Static at 70% opacity under reduced motion. */
|
|
22
|
+
function GhostBar({
|
|
23
|
+
pulse,
|
|
24
|
+
width,
|
|
25
|
+
height,
|
|
26
|
+
color,
|
|
27
|
+
round,
|
|
28
|
+
style,
|
|
29
|
+
}: {
|
|
30
|
+
pulse: Animated.Value | null;
|
|
31
|
+
width: number | `${number}%`;
|
|
32
|
+
height: number;
|
|
33
|
+
color: string;
|
|
34
|
+
round?: boolean;
|
|
35
|
+
style?: object;
|
|
36
|
+
}): React.ReactElement {
|
|
37
|
+
return (
|
|
38
|
+
<Animated.View
|
|
39
|
+
style={{
|
|
40
|
+
width,
|
|
41
|
+
height,
|
|
42
|
+
backgroundColor: color,
|
|
43
|
+
borderRadius: round ? radius.full : 4,
|
|
44
|
+
opacity: pulse ?? 0.7,
|
|
45
|
+
...style,
|
|
46
|
+
}}
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function GhostCard({
|
|
52
|
+
pulse,
|
|
53
|
+
withLink,
|
|
54
|
+
}: {
|
|
55
|
+
pulse: Animated.Value | null;
|
|
56
|
+
withLink?: boolean;
|
|
57
|
+
}): React.ReactElement {
|
|
58
|
+
const { colors } = useTheme();
|
|
59
|
+
return (
|
|
60
|
+
<View
|
|
61
|
+
style={{
|
|
62
|
+
backgroundColor: colors.backgroundSecondary,
|
|
63
|
+
borderRadius: radius.md,
|
|
64
|
+
padding: spacing.md,
|
|
65
|
+
marginBottom: spacing.sm,
|
|
66
|
+
}}
|
|
67
|
+
>
|
|
68
|
+
<View style={{ flexDirection: 'row', alignItems: 'flex-start' }}>
|
|
69
|
+
<View style={{ flex: 1, marginRight: spacing.sm }}>
|
|
70
|
+
<GhostBar pulse={pulse} width="42%" height={14} color={colors.border} />
|
|
71
|
+
<GhostBar
|
|
72
|
+
pulse={pulse}
|
|
73
|
+
width="64%"
|
|
74
|
+
height={10}
|
|
75
|
+
color={colors.border}
|
|
76
|
+
style={{ marginTop: spacing.xs + 2 }}
|
|
77
|
+
/>
|
|
78
|
+
</View>
|
|
79
|
+
<GhostBar pulse={pulse} width={76} height={22} color={colors.border} round />
|
|
80
|
+
</View>
|
|
81
|
+
{withLink ? (
|
|
82
|
+
<GhostBar
|
|
83
|
+
pulse={pulse}
|
|
84
|
+
width="100%"
|
|
85
|
+
height={38}
|
|
86
|
+
color={colors.primary100}
|
|
87
|
+
round
|
|
88
|
+
style={{ marginTop: spacing.sm + 4 }}
|
|
89
|
+
/>
|
|
90
|
+
) : null}
|
|
91
|
+
</View>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function KeyPeoplePending(): React.ReactElement {
|
|
96
|
+
const { colors } = useTheme();
|
|
97
|
+
const reduceMotion = useReduceMotion();
|
|
98
|
+
const pulseValue = useRef(new Animated.Value(1)).current;
|
|
99
|
+
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
if (reduceMotion) return undefined;
|
|
102
|
+
const loop = Animated.loop(
|
|
103
|
+
Animated.sequence([
|
|
104
|
+
Animated.timing(pulseValue, { toValue: 0.45, duration: 600, useNativeDriver: true }),
|
|
105
|
+
Animated.timing(pulseValue, { toValue: 1, duration: 600, useNativeDriver: true }),
|
|
106
|
+
]),
|
|
107
|
+
);
|
|
108
|
+
loop.start();
|
|
109
|
+
return () => loop.stop();
|
|
110
|
+
}, [pulseValue, reduceMotion]);
|
|
111
|
+
|
|
112
|
+
const pulse = reduceMotion ? null : pulseValue;
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<View style={{ width: '100%', marginTop: spacing.lg }}>
|
|
116
|
+
<View
|
|
117
|
+
accessible
|
|
118
|
+
accessibilityLiveRegion="polite"
|
|
119
|
+
accessibilityLabel="Working out who else needs to verify. We are checking the official register for the company's directors and owners."
|
|
120
|
+
style={{ alignItems: 'center', marginBottom: spacing.md }}
|
|
121
|
+
>
|
|
122
|
+
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
|
123
|
+
<ActivityIndicator size="small" color={colors.primary} />
|
|
124
|
+
<MyazaText variant="bodyMedium" style={{ fontWeight: '600', marginLeft: spacing.sm }}>
|
|
125
|
+
Working out who else needs to verify
|
|
126
|
+
</MyazaText>
|
|
127
|
+
</View>
|
|
128
|
+
{/* Naming the authority is the reassurance: the pause is the official
|
|
129
|
+
register being consulted, not the app hanging. */}
|
|
130
|
+
<MyazaText
|
|
131
|
+
variant="bodySmall"
|
|
132
|
+
color={colors.textMuted}
|
|
133
|
+
style={{ textAlign: 'center', marginTop: spacing.xs }}
|
|
134
|
+
>
|
|
135
|
+
We are checking the official register for the company's directors and owners.
|
|
136
|
+
</MyazaText>
|
|
137
|
+
</View>
|
|
138
|
+
|
|
139
|
+
{/* The ghost roster: geometry, not information — hidden from assistive
|
|
140
|
+
tech, the live region above already carries the message. */}
|
|
141
|
+
<View importantForAccessibility="no-hide-descendants" accessibilityElementsHidden>
|
|
142
|
+
<GhostBar
|
|
143
|
+
pulse={pulse}
|
|
144
|
+
width={64}
|
|
145
|
+
height={10}
|
|
146
|
+
color={colors.border}
|
|
147
|
+
style={{ marginBottom: spacing.xs + 2 }}
|
|
148
|
+
/>
|
|
149
|
+
<GhostCard pulse={pulse} withLink />
|
|
150
|
+
<GhostCard pulse={pulse} />
|
|
151
|
+
</View>
|
|
152
|
+
</View>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
@@ -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
|
+
}
|