Fix form keyboard issues and sticky footers in agent screens
This commit is contained in:
@@ -0,0 +1,563 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Alert,
|
||||
Pressable,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { NativeStackScreenProps } from '@react-navigation/native-stack';
|
||||
import Ionicons from '@react-native-vector-icons/ionicons';
|
||||
import LinearGradient from 'react-native-linear-gradient';
|
||||
|
||||
import { BottomSheet, Button, Card, Input, ScreenShell } from '../../../components';
|
||||
import { useAppSelector } from '../../../app/hooks';
|
||||
import type { HomeStackParamList } from '../../../navigation/types';
|
||||
import { useTheme } from '../../../theme';
|
||||
|
||||
type Props = NativeStackScreenProps<HomeStackParamList, 'ReKyc'>;
|
||||
type IconName = React.ComponentProps<typeof Ionicons>['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',
|
||||
'Select the fields that changed',
|
||||
'Submit the re-KYC request for review',
|
||||
];
|
||||
|
||||
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();
|
||||
const accounts = useAppSelector((state) => state.account.accounts);
|
||||
const profile = useAppSelector((state) => state.user.profile);
|
||||
const account = accounts.find((item) => item.id === route.params?.accountId) ?? 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 [otpSheetVisible, setOtpSheetVisible] = useState(false);
|
||||
const [otpValue, setOtpValue] = useState('');
|
||||
|
||||
if (!account) {
|
||||
return (
|
||||
<ScreenShell title=" Aadhaar" subtitle="Identity refresh">
|
||||
<Card style={styles.emptyCard}>
|
||||
<View
|
||||
style={[
|
||||
styles.emptyIcon,
|
||||
{ backgroundColor: `${theme.colors.danger}12` },
|
||||
]}
|
||||
>
|
||||
<Ionicons 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;
|
||||
}
|
||||
|
||||
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.type} account · A/c ${account.maskedNumber}`}
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
>
|
||||
<LinearGradient
|
||||
colors={['#F4F9FF', '#E8F2FE', '#F8FBFF']}
|
||||
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.colors.primary}12` },
|
||||
]}
|
||||
>
|
||||
<Ionicons 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.colors.primary}12` },
|
||||
]}
|
||||
>
|
||||
<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.colors.info}12` },
|
||||
]}
|
||||
>
|
||||
<Ionicons 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={<Ionicons 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.colors.warning}12` },
|
||||
]}
|
||||
>
|
||||
<Ionicons 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.colors.primary}12` : theme.colors.surfaceMuted,
|
||||
borderColor: isSelected ? `${theme.colors.primary}45` : theme.colors.cardBorder,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Ionicons
|
||||
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 },
|
||||
]}
|
||||
>
|
||||
<Ionicons
|
||||
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>
|
||||
|
||||
<Card style={styles.footerCard}>
|
||||
<View style={styles.footerHeader}>
|
||||
<Ionicons 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={<Ionicons 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,
|
||||
fontWeight: '800',
|
||||
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,
|
||||
fontWeight: '800',
|
||||
},
|
||||
stepText: {
|
||||
flex: 1,
|
||||
fontSize: 13,
|
||||
fontWeight: '600',
|
||||
},
|
||||
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,
|
||||
fontWeight: '800',
|
||||
letterSpacing: -0.3,
|
||||
},
|
||||
sectionSubtitle: {
|
||||
fontSize: 13,
|
||||
fontWeight: '500',
|
||||
},
|
||||
consentCard: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-start',
|
||||
gap: 12,
|
||||
borderWidth: 1,
|
||||
borderRadius: 16,
|
||||
padding: 16,
|
||||
},
|
||||
consentText: {
|
||||
flex: 1,
|
||||
fontSize: 13,
|
||||
lineHeight: 19,
|
||||
fontWeight: '500',
|
||||
},
|
||||
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,
|
||||
fontWeight: '700',
|
||||
},
|
||||
footerCard: {
|
||||
padding: 20,
|
||||
gap: 16,
|
||||
},
|
||||
footerHeader: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 10,
|
||||
},
|
||||
footerTitle: {
|
||||
fontSize: 16,
|
||||
fontWeight: '800',
|
||||
},
|
||||
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,
|
||||
fontWeight: '800',
|
||||
},
|
||||
emptyDescription: {
|
||||
fontSize: 14,
|
||||
textAlign: 'center',
|
||||
lineHeight: 20,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user