- Positive Pay on the 12 Oct /pps contract (account number, not account ID): OTP enrollment, cheque submit, saved cheques, detail and stop payment. Full flow passed on the phone against mobile-banking-backend.lincpay. - Statement & Mini Statement guide v1.0 (14 Oct): mini statement JSON on Home and the Statement screen; dated PDF export with the password rule. - Onboarding slides and splash screen, Inter font, shared Icon and TypewriterText components, nearby branches via OpenStreetMap. - Every feature switched on; the 14 Oct switch set is kept in featureConfig.stage-2026-10-14.ts. Loans run on demo data until the backend has /loans routes. - npm run android wraps run-android for shells spawned from VS Code. - OpenProject work packages updated to 14-10-2026 status, with screenshots of Positive Pay, statement, loans and cards. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
786 lines
24 KiB
TypeScript
786 lines
24 KiB
TypeScript
import React, { useState } from 'react';
|
|
import {
|
|
Alert,
|
|
Pressable,
|
|
StyleSheet,
|
|
Text,
|
|
View,
|
|
} from 'react-native';
|
|
import { NativeStackScreenProps } from '@react-navigation/native-stack';
|
|
import { Icon } from '../../../components/Icon';
|
|
import LinearGradient from 'react-native-linear-gradient';
|
|
|
|
import { BottomSheet, Button, Card, Input, ScreenShell } from '../../../components';
|
|
import { useAccounts } from '../hooks/useAccounts';
|
|
import type { HomeStackParamList } from '../../../navigation/types';
|
|
import { useTheme, fontFamily } from '../../../theme';
|
|
|
|
type Props = NativeStackScreenProps<HomeStackParamList, 'ReKyc'>;
|
|
type IconName = React.ComponentProps<typeof Icon>['name'];
|
|
type ChangeField = 'name' | 'mobile' | 'email' | 'address';
|
|
|
|
type ChangeOption = {
|
|
id: ChangeField;
|
|
label: string;
|
|
icon: IconName;
|
|
};
|
|
|
|
const CHANGE_OPTIONS: ChangeOption[] = [
|
|
{ id: 'name', label: 'Name', icon: 'person-outline' },
|
|
{ id: 'mobile', label: 'Mobile', icon: 'call-outline' },
|
|
{ id: 'email', label: 'Email', icon: 'mail-outline' },
|
|
{ id: 'address', label: 'Address', icon: 'location-outline' },
|
|
];
|
|
|
|
const STEPS = [
|
|
'Enter your Aadhaar number',
|
|
'Upload documents & latest photograph',
|
|
'Accept the self-declaration and submit',
|
|
];
|
|
|
|
type DocOption = { id: string; label: string; hint: string; icon: IconName };
|
|
|
|
const DOCUMENT_OPTIONS: DocOption[] = [
|
|
{ id: 'identity', label: 'Identity proof', hint: 'Aadhaar / Passport / Voter ID', icon: 'id-card-outline' },
|
|
{ id: 'address', label: 'Address proof', hint: 'Utility bill / Rent agreement', icon: 'home-outline' },
|
|
{ id: 'pan', label: 'PAN card', hint: 'Required for tax records', icon: 'card-outline' },
|
|
];
|
|
|
|
const DECLARATIONS = [
|
|
'The information and documents provided are true and correct.',
|
|
'I will inform the bank of any future change in my KYC details.',
|
|
'I consent to the bank verifying my documents with the issuing authority.',
|
|
];
|
|
|
|
const formatAadhaar = (value: string) =>
|
|
value
|
|
.replace(/\D/g, '')
|
|
.slice(0, 12)
|
|
.replace(/(\d{4})(?=\d)/g, '$1 ');
|
|
|
|
const extractDigits = (value: string) => value.replace(/\D/g, '');
|
|
|
|
export default function ReKycScreen({ route, navigation }: Props) {
|
|
const theme = useTheme();
|
|
// Read from the bank on mount; nothing about accounts is cached locally.
|
|
// Re-KYC is raised against an operative account.
|
|
const { operableAccounts: accounts } = useAccounts();
|
|
const account =
|
|
accounts.find((item) => item.accountNumber === route.params?.accountNumber) ?? accounts[0];
|
|
|
|
const [aadhaarNumber, setAadhaarNumber] = useState('');
|
|
const [selectedFields, setSelectedFields] = useState<ChangeField[]>([]);
|
|
const [updatedName, setUpdatedName] = useState('');
|
|
const [updatedMobile, setUpdatedMobile] = useState('');
|
|
const [updatedEmail, setUpdatedEmail] = useState('');
|
|
const [updatedAddress, setUpdatedAddress] = useState('');
|
|
const [consentGiven, setConsentGiven] = useState(false);
|
|
const [uploadedDocs, setUploadedDocs] = useState<string[]>([]);
|
|
const [photoUploaded, setPhotoUploaded] = useState(false);
|
|
const [acceptedDeclarations, setAcceptedDeclarations] = useState<number[]>([]);
|
|
const [otpSheetVisible, setOtpSheetVisible] = useState(false);
|
|
const [otpValue, setOtpValue] = useState('');
|
|
|
|
const toggleDocument = (id: string) => {
|
|
setUploadedDocs((current) =>
|
|
current.includes(id) ? current.filter((item) => item !== id) : [...current, id],
|
|
);
|
|
};
|
|
|
|
const toggleDeclaration = (index: number) => {
|
|
setAcceptedDeclarations((current) =>
|
|
current.includes(index) ? current.filter((item) => item !== index) : [...current, index],
|
|
);
|
|
};
|
|
|
|
const allDeclarationsAccepted = acceptedDeclarations.length === DECLARATIONS.length;
|
|
|
|
if (!account) {
|
|
return (
|
|
<ScreenShell title=" Aadhaar" subtitle="Identity refresh">
|
|
<Card style={styles.emptyCard}>
|
|
<View
|
|
style={[
|
|
styles.emptyIcon,
|
|
{ backgroundColor: theme.tint(theme.colors.danger, 0.07) },
|
|
]}
|
|
>
|
|
<Icon name="alert-circle-outline" size={28} color={theme.colors.danger} />
|
|
</View>
|
|
<Text style={[styles.emptyTitle, { color: theme.colors.textPrimary }]}>
|
|
Account not found
|
|
</Text>
|
|
<Text style={[styles.emptyDescription, { color: theme.colors.textSecondary }]}>
|
|
We could not start re-KYC because the selected account is unavailable.
|
|
</Text>
|
|
</Card>
|
|
</ScreenShell>
|
|
);
|
|
}
|
|
|
|
const toggleField = (field: ChangeField) => {
|
|
setSelectedFields((currentValue) =>
|
|
currentValue.includes(field)
|
|
? currentValue.filter((item) => item !== field)
|
|
: [...currentValue, field],
|
|
);
|
|
};
|
|
|
|
const handleAadhaarChange = (value: string) => {
|
|
setAadhaarNumber(formatAadhaar(value));
|
|
};
|
|
|
|
const getMissingFieldLabel = () => {
|
|
if (selectedFields.includes('name') && updatedName.trim().length === 0) {
|
|
return 'updated name';
|
|
}
|
|
|
|
if (selectedFields.includes('mobile') && extractDigits(updatedMobile).length !== 10) {
|
|
return 'updated mobile number';
|
|
}
|
|
|
|
if (selectedFields.includes('email') && !updatedEmail.includes('@')) {
|
|
return 'updated email';
|
|
}
|
|
|
|
if (selectedFields.includes('address') && updatedAddress.trim().length < 8) {
|
|
return 'updated address';
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
const handleSubmit = () => {
|
|
const aadhaarDigits = extractDigits(aadhaarNumber);
|
|
|
|
if (aadhaarDigits.length !== 12) {
|
|
Alert.alert('Invalid Aadhaar', 'Enter a valid 12-digit Aadhaar number to continue.');
|
|
return;
|
|
}
|
|
|
|
if (selectedFields.length === 0 && !consentGiven) {
|
|
Alert.alert('Consent required', 'Please verify that there is no change on your Aadhaar details.');
|
|
return;
|
|
}
|
|
|
|
const missingFieldLabel = getMissingFieldLabel();
|
|
|
|
if (missingFieldLabel) {
|
|
Alert.alert('Incomplete details', `Please enter a valid ${missingFieldLabel}.`);
|
|
return;
|
|
}
|
|
|
|
if (!photoUploaded) {
|
|
Alert.alert('Photograph required', 'Please upload your latest photograph to continue.');
|
|
return;
|
|
}
|
|
|
|
if (!allDeclarationsAccepted) {
|
|
Alert.alert('Self declaration', 'Please accept all the self-declaration statements to continue.');
|
|
return;
|
|
}
|
|
|
|
setOtpSheetVisible(true);
|
|
};
|
|
|
|
const handleVerifyOtpAndSubmit = () => {
|
|
if (otpValue.length !== 6) {
|
|
Alert.alert('Invalid OTP', 'Please enter a valid 6-digit OTP.');
|
|
return;
|
|
}
|
|
|
|
setOtpSheetVisible(false);
|
|
|
|
setTimeout(() => {
|
|
Alert.alert(
|
|
'Re-KYC submitted',
|
|
'Your Aadhaar-based re-KYC request has been sent for review. We will update your records after validation.',
|
|
[
|
|
{
|
|
text: 'Back to account',
|
|
onPress: () => navigation.goBack(),
|
|
},
|
|
],
|
|
);
|
|
}, 300);
|
|
};
|
|
|
|
return (
|
|
<ScreenShell
|
|
title="Re-KYC via Aadhaar"
|
|
subtitle={`${account?.accountType ?? ""} account · A/c XXXX ${account?.accountNumber.slice(-4) ?? ""}`}
|
|
contentContainerStyle={styles.contentContainer}
|
|
>
|
|
<LinearGradient
|
|
colors={[theme.colors.surface, theme.colors.primarySurface, theme.colors.surface]}
|
|
start={{ x: 0, y: 0 }}
|
|
end={{ x: 1, y: 1 }}
|
|
style={[styles.heroCard, { borderColor: theme.colors.cardBorder }]}
|
|
>
|
|
<View style={styles.heroHeader}>
|
|
<View
|
|
style={[
|
|
styles.heroIcon,
|
|
{ backgroundColor: theme.tint(theme.colors.primary, 0.07) },
|
|
]}
|
|
>
|
|
<Icon name="shield-checkmark-outline" size={20} color={theme.colors.primary} />
|
|
</View>
|
|
<View style={styles.heroCopy}>
|
|
<Text style={[styles.heroTitle, { color: theme.colors.textPrimary }]}>
|
|
Update KYC when your records change
|
|
</Text>
|
|
<Text style={[styles.heroSubtitle, { color: theme.colors.textSecondary }]}>
|
|
Use Aadhaar re-verification if your name, mobile, email, or address has changed.
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
|
|
<View style={styles.stepList}>
|
|
{STEPS.map((step, index) => (
|
|
<View key={step} style={styles.stepRow}>
|
|
<View
|
|
style={[
|
|
styles.stepIndex,
|
|
{ backgroundColor: theme.tint(theme.colors.primary, 0.07) },
|
|
]}
|
|
>
|
|
<Text style={[styles.stepIndexText, { color: theme.colors.primary }]}>
|
|
{index + 1}
|
|
</Text>
|
|
</View>
|
|
<Text style={[styles.stepText, { color: theme.colors.textPrimary }]}>{step}</Text>
|
|
</View>
|
|
))}
|
|
</View>
|
|
</LinearGradient>
|
|
|
|
<Card style={styles.sectionCard}>
|
|
<View style={styles.sectionHeader}>
|
|
<View
|
|
style={[
|
|
styles.sectionIcon,
|
|
{ backgroundColor: theme.tint(theme.colors.info, 0.07) },
|
|
]}
|
|
>
|
|
<Icon name="card-outline" size={18} color={theme.colors.info} />
|
|
</View>
|
|
<View style={styles.sectionCopy}>
|
|
<Text style={[styles.sectionTitle, { color: theme.colors.textPrimary }]}>
|
|
Aadhaar details
|
|
</Text>
|
|
<Text style={[styles.sectionSubtitle, { color: theme.colors.textSecondary }]}>
|
|
Enter the Aadhaar linked to your updated identity details
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
|
|
<Input
|
|
label="Aadhaar number"
|
|
value={aadhaarNumber}
|
|
onChangeText={handleAadhaarChange}
|
|
keyboardType="number-pad"
|
|
maxLength={14}
|
|
placeholder="1234 5678 9012"
|
|
leftAccessory={<Icon name="finger-print-outline" size={18} color={theme.colors.textMuted} />}
|
|
/>
|
|
</Card>
|
|
|
|
<Card style={styles.sectionCard}>
|
|
<View style={styles.sectionHeader}>
|
|
<View
|
|
style={[
|
|
styles.sectionIcon,
|
|
{ backgroundColor: theme.tint(theme.colors.warning, 0.07) },
|
|
]}
|
|
>
|
|
<Icon name="create-outline" size={18} color={theme.colors.warning} />
|
|
</View>
|
|
<View style={styles.sectionCopy}>
|
|
<Text style={[styles.sectionTitle, { color: theme.colors.textPrimary }]}>
|
|
What changed?
|
|
</Text>
|
|
<Text style={[styles.sectionSubtitle, { color: theme.colors.textSecondary }]}>
|
|
Choose the fields that need to be updated
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
|
|
<View style={styles.chipGrid}>
|
|
{CHANGE_OPTIONS.map((option) => {
|
|
const isSelected = selectedFields.includes(option.id);
|
|
|
|
return (
|
|
<Pressable
|
|
key={option.id}
|
|
onPress={() => toggleField(option.id)}
|
|
style={[
|
|
styles.changeChip,
|
|
{
|
|
backgroundColor: isSelected ? theme.tint(theme.colors.primary, 0.07) : theme.colors.surfaceMuted,
|
|
borderColor: isSelected ? theme.tint(theme.colors.primary, 0.27) : theme.colors.cardBorder,
|
|
},
|
|
]}
|
|
>
|
|
<Icon
|
|
name={option.icon as any}
|
|
size={16}
|
|
color={isSelected ? theme.colors.primary : theme.colors.textSecondary}
|
|
/>
|
|
<Text
|
|
style={[
|
|
styles.changeChipText,
|
|
{ color: isSelected ? theme.colors.primary : theme.colors.textPrimary },
|
|
]}
|
|
>
|
|
{option.label}
|
|
</Text>
|
|
</Pressable>
|
|
);
|
|
})}
|
|
</View>
|
|
|
|
{selectedFields.length === 0 ? (
|
|
<Pressable
|
|
onPress={() => setConsentGiven((currentValue) => !currentValue)}
|
|
style={[
|
|
styles.consentCard,
|
|
{ backgroundColor: theme.colors.surfaceMuted, borderColor: theme.colors.cardBorder },
|
|
]}
|
|
>
|
|
<Icon
|
|
name={consentGiven ? 'checkmark-circle' : 'ellipse-outline'}
|
|
size={22}
|
|
color={consentGiven ? theme.colors.success : theme.colors.textMuted}
|
|
/>
|
|
<Text style={[styles.consentText, { color: theme.colors.textPrimary }]}>
|
|
I verify that there is no change on my Aadhaar details.
|
|
</Text>
|
|
</Pressable>
|
|
) : null}
|
|
|
|
{selectedFields.includes('name') ? (
|
|
<Input
|
|
label="Updated name"
|
|
value={updatedName}
|
|
onChangeText={setUpdatedName}
|
|
placeholder="Name as per Aadhaar"
|
|
autoCapitalize="words"
|
|
/>
|
|
) : null}
|
|
|
|
{selectedFields.includes('mobile') ? (
|
|
<Input
|
|
label="Updated mobile"
|
|
value={updatedMobile}
|
|
onChangeText={setUpdatedMobile}
|
|
keyboardType="phone-pad"
|
|
maxLength={10}
|
|
placeholder="10-digit mobile number"
|
|
/>
|
|
) : null}
|
|
|
|
{selectedFields.includes('email') ? (
|
|
<Input
|
|
label="Updated email"
|
|
value={updatedEmail}
|
|
onChangeText={setUpdatedEmail}
|
|
keyboardType="email-address"
|
|
autoCapitalize="none"
|
|
placeholder="name@example.com"
|
|
/>
|
|
) : null}
|
|
|
|
{selectedFields.includes('address') ? (
|
|
<Input
|
|
label="Updated address"
|
|
value={updatedAddress}
|
|
onChangeText={setUpdatedAddress}
|
|
placeholder="Flat, street, area, city"
|
|
/>
|
|
) : null}
|
|
</Card>
|
|
|
|
{/* Document upload */}
|
|
<Card style={styles.sectionCard}>
|
|
<View style={styles.sectionHeader}>
|
|
<View style={[styles.sectionIcon, { backgroundColor: theme.tint(theme.colors.success, 0.07) }]}>
|
|
<Icon name="cloud-upload-outline" size={18} color={theme.colors.success} />
|
|
</View>
|
|
<View style={styles.sectionCopy}>
|
|
<Text style={[styles.sectionTitle, { color: theme.colors.textPrimary }]}>Upload documents</Text>
|
|
<Text style={[styles.sectionSubtitle, { color: theme.colors.textSecondary }]}>
|
|
Attach the latest copies of your KYC documents
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
|
|
{DOCUMENT_OPTIONS.map((doc) => {
|
|
const isUploaded = uploadedDocs.includes(doc.id);
|
|
return (
|
|
<Pressable
|
|
key={doc.id}
|
|
onPress={() => toggleDocument(doc.id)}
|
|
style={[
|
|
styles.uploadRow,
|
|
{
|
|
backgroundColor: isUploaded ? theme.tint(theme.colors.success, 0.06) : theme.colors.surfaceMuted,
|
|
borderColor: isUploaded ? theme.tint(theme.colors.success, 0.27) : theme.colors.cardBorder,
|
|
},
|
|
]}
|
|
>
|
|
<View style={[styles.uploadIcon, { backgroundColor: theme.colors.surface }]}>
|
|
<Icon name={doc.icon} size={18} color={theme.colors.primary} />
|
|
</View>
|
|
<View style={styles.uploadCopy}>
|
|
<Text style={[styles.uploadLabel, { color: theme.colors.textPrimary }]}>{doc.label}</Text>
|
|
<Text style={[styles.uploadHint, { color: theme.colors.textSecondary }]}>
|
|
{isUploaded ? 'Uploaded · tap to remove' : doc.hint}
|
|
</Text>
|
|
</View>
|
|
<Icon
|
|
name={isUploaded ? 'checkmark-circle' : 'add-circle-outline'}
|
|
size={22}
|
|
color={isUploaded ? theme.colors.success : theme.colors.primary}
|
|
/>
|
|
</Pressable>
|
|
);
|
|
})}
|
|
</Card>
|
|
|
|
{/* Latest photograph */}
|
|
<Card style={styles.sectionCard}>
|
|
<View style={styles.sectionHeader}>
|
|
<View style={[styles.sectionIcon, { backgroundColor: theme.tint(theme.colors.info, 0.07) }]}>
|
|
<Icon name="camera-outline" size={18} color={theme.colors.info} />
|
|
</View>
|
|
<View style={styles.sectionCopy}>
|
|
<Text style={[styles.sectionTitle, { color: theme.colors.textPrimary }]}>Latest photograph</Text>
|
|
<Text style={[styles.sectionSubtitle, { color: theme.colors.textSecondary }]}>
|
|
Upload a clear, recent passport-style photo
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
|
|
<Pressable
|
|
onPress={() => setPhotoUploaded((value) => !value)}
|
|
style={[
|
|
styles.photoTile,
|
|
{
|
|
backgroundColor: photoUploaded ? theme.tint(theme.colors.success, 0.06) : theme.colors.surfaceMuted,
|
|
borderColor: photoUploaded ? theme.tint(theme.colors.success, 0.27) : theme.colors.cardBorder,
|
|
},
|
|
]}
|
|
>
|
|
<Icon
|
|
name={photoUploaded ? 'checkmark-circle' : 'person-circle-outline'}
|
|
size={48}
|
|
color={photoUploaded ? theme.colors.success : theme.colors.textMuted}
|
|
/>
|
|
<Text style={[styles.photoText, { color: photoUploaded ? theme.colors.success : theme.colors.textSecondary }]}>
|
|
{photoUploaded ? 'Photograph uploaded' : 'Tap to capture / upload photo'}
|
|
</Text>
|
|
</Pressable>
|
|
</Card>
|
|
|
|
{/* Self declaration */}
|
|
<Card style={styles.sectionCard}>
|
|
<View style={styles.sectionHeader}>
|
|
<View style={[styles.sectionIcon, { backgroundColor: theme.tint(theme.colors.warning, 0.07) }]}>
|
|
<Icon name="document-text-outline" size={18} color={theme.colors.warning} />
|
|
</View>
|
|
<View style={styles.sectionCopy}>
|
|
<Text style={[styles.sectionTitle, { color: theme.colors.textPrimary }]}>Self declaration</Text>
|
|
<Text style={[styles.sectionSubtitle, { color: theme.colors.textSecondary }]}>
|
|
Please read and accept all statements
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
|
|
{DECLARATIONS.map((statement, index) => {
|
|
const accepted = acceptedDeclarations.includes(index);
|
|
return (
|
|
<Pressable
|
|
key={statement}
|
|
onPress={() => toggleDeclaration(index)}
|
|
style={[
|
|
styles.consentCard,
|
|
{ backgroundColor: theme.colors.surfaceMuted, borderColor: theme.colors.cardBorder },
|
|
]}
|
|
>
|
|
<Icon
|
|
name={accepted ? 'checkmark-circle' : 'ellipse-outline'}
|
|
size={22}
|
|
color={accepted ? theme.colors.success : theme.colors.textMuted}
|
|
/>
|
|
<Text style={[styles.consentText, { color: theme.colors.textPrimary }]}>{statement}</Text>
|
|
</Pressable>
|
|
);
|
|
})}
|
|
</Card>
|
|
|
|
{/* Video KYC option */}
|
|
<Card style={styles.sectionCard}>
|
|
<View style={styles.sectionHeader}>
|
|
<View style={[styles.sectionIcon, { backgroundColor: theme.tint(theme.colors.primary, 0.07) }]}>
|
|
<Icon name="videocam-outline" size={18} color={theme.colors.primary} />
|
|
</View>
|
|
<View style={styles.sectionCopy}>
|
|
<Text style={[styles.sectionTitle, { color: theme.colors.textPrimary }]}>Prefer a video call?</Text>
|
|
<Text style={[styles.sectionSubtitle, { color: theme.colors.textSecondary }]}>
|
|
Complete re-KYC over a secure video call (V-CIP)
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
<Button
|
|
title="Start Video KYC"
|
|
variant="secondary"
|
|
onPress={() => account && navigation.navigate('VideoKyc', { accountNumber: account.accountNumber })}
|
|
leftAccessory={<Icon name="videocam-outline" size={18} color={theme.colors.textPrimary} />}
|
|
/>
|
|
</Card>
|
|
|
|
<Card style={styles.footerCard}>
|
|
<View style={styles.footerHeader}>
|
|
<Icon name="time-outline" size={18} color={theme.colors.primary} />
|
|
<Text style={[styles.footerTitle, { color: theme.colors.textPrimary }]}>
|
|
Processing note
|
|
</Text>
|
|
</View>
|
|
<Text style={[styles.footerText, { color: theme.colors.textSecondary }]}>
|
|
Re-KYC updates are usually reviewed within one working day after Aadhaar verification and profile checks.
|
|
</Text>
|
|
<Button
|
|
title="Submit re-KYC request"
|
|
onPress={handleSubmit}
|
|
leftAccessory={<Icon name="paper-plane-outline" size={18} color={theme.colors.white} />}
|
|
/>
|
|
</Card>
|
|
<View style={{ height: 60 }} />
|
|
|
|
<BottomSheet
|
|
visible={otpSheetVisible}
|
|
title="Verify your request"
|
|
onClose={() => setOtpSheetVisible(false)}
|
|
>
|
|
<Text style={{ color: theme.colors.textSecondary, marginBottom: 16, fontSize: 14, lineHeight: 20 }}>
|
|
Enter the 6-digit OTP sent to your registered mobile number to confirm this re-KYC request.
|
|
</Text>
|
|
<Input
|
|
label="Enter OTP"
|
|
value={otpValue}
|
|
onChangeText={setOtpValue}
|
|
keyboardType="number-pad"
|
|
maxLength={6}
|
|
placeholder="000000"
|
|
/>
|
|
<Button
|
|
title="Verify & Submit"
|
|
onPress={handleVerifyOtpAndSubmit}
|
|
style={{ marginTop: 8 }}
|
|
/>
|
|
</BottomSheet>
|
|
</ScreenShell>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
contentContainer: {
|
|
gap: 18,
|
|
},
|
|
heroCard: {
|
|
borderRadius: 24,
|
|
borderWidth: 1,
|
|
padding: 20,
|
|
gap: 18,
|
|
},
|
|
heroHeader: {
|
|
flexDirection: 'row',
|
|
gap: 12,
|
|
},
|
|
heroIcon: {
|
|
width: 44,
|
|
height: 44,
|
|
borderRadius: 14,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
heroCopy: {
|
|
flex: 1,
|
|
gap: 4,
|
|
},
|
|
heroTitle: {
|
|
fontSize: 20,
|
|
fontFamily: fontFamily.bold,
|
|
letterSpacing: -0.4,
|
|
},
|
|
heroSubtitle: {
|
|
fontSize: 13,
|
|
lineHeight: 19,
|
|
},
|
|
stepList: {
|
|
gap: 10,
|
|
},
|
|
stepRow: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 10,
|
|
},
|
|
stepIndex: {
|
|
width: 28,
|
|
height: 28,
|
|
borderRadius: 14,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
stepIndexText: {
|
|
fontSize: 12,
|
|
fontFamily: fontFamily.bold,
|
|
},
|
|
stepText: {
|
|
flex: 1,
|
|
fontSize: 13,
|
|
fontFamily: fontFamily.semibold,
|
|
},
|
|
sectionCard: {
|
|
padding: 20,
|
|
gap: 16,
|
|
},
|
|
sectionHeader: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 12,
|
|
},
|
|
sectionIcon: {
|
|
width: 40,
|
|
height: 40,
|
|
borderRadius: 14,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
sectionCopy: {
|
|
flex: 1,
|
|
gap: 4,
|
|
},
|
|
sectionTitle: {
|
|
fontSize: 18,
|
|
fontFamily: fontFamily.bold,
|
|
letterSpacing: -0.3,
|
|
},
|
|
sectionSubtitle: {
|
|
fontSize: 13,
|
|
fontFamily: fontFamily.medium,
|
|
},
|
|
consentCard: {
|
|
flexDirection: 'row',
|
|
alignItems: 'flex-start',
|
|
gap: 12,
|
|
borderWidth: 1,
|
|
borderRadius: 16,
|
|
padding: 16,
|
|
},
|
|
uploadRow: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 12,
|
|
borderWidth: 1,
|
|
borderRadius: 16,
|
|
padding: 14,
|
|
},
|
|
uploadIcon: {
|
|
width: 40,
|
|
height: 40,
|
|
borderRadius: 12,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
uploadCopy: {
|
|
flex: 1,
|
|
gap: 2,
|
|
},
|
|
uploadLabel: {
|
|
fontSize: 14,
|
|
fontFamily: fontFamily.bold,
|
|
},
|
|
uploadHint: {
|
|
fontSize: 12,
|
|
fontFamily: fontFamily.medium,
|
|
},
|
|
photoTile: {
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
gap: 10,
|
|
borderWidth: 1,
|
|
borderStyle: 'dashed',
|
|
borderRadius: 16,
|
|
paddingVertical: 28,
|
|
},
|
|
photoText: {
|
|
fontSize: 13,
|
|
fontFamily: fontFamily.bold,
|
|
},
|
|
consentText: {
|
|
flex: 1,
|
|
fontSize: 13,
|
|
lineHeight: 19,
|
|
fontFamily: fontFamily.medium,
|
|
},
|
|
chipGrid: {
|
|
flexDirection: 'row',
|
|
flexWrap: 'wrap',
|
|
gap: 12,
|
|
},
|
|
changeChip: {
|
|
minWidth: '47%',
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 8,
|
|
borderWidth: 1,
|
|
borderRadius: 16,
|
|
paddingHorizontal: 14,
|
|
paddingVertical: 12,
|
|
},
|
|
changeChipText: {
|
|
fontSize: 13,
|
|
fontFamily: fontFamily.bold,
|
|
},
|
|
footerCard: {
|
|
padding: 20,
|
|
gap: 16,
|
|
},
|
|
footerHeader: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 10,
|
|
},
|
|
footerTitle: {
|
|
fontSize: 16,
|
|
fontFamily: fontFamily.bold,
|
|
},
|
|
footerText: {
|
|
fontSize: 13,
|
|
lineHeight: 19,
|
|
},
|
|
emptyCard: {
|
|
alignItems: 'center',
|
|
paddingVertical: 32,
|
|
gap: 12,
|
|
},
|
|
emptyIcon: {
|
|
width: 56,
|
|
height: 56,
|
|
borderRadius: 18,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
emptyTitle: {
|
|
fontSize: 18,
|
|
fontFamily: fontFamily.bold,
|
|
},
|
|
emptyDescription: {
|
|
fontSize: 14,
|
|
textAlign: 'center',
|
|
lineHeight: 20,
|
|
},
|
|
});
|