Fix form keyboard issues and sticky footers in agent screens

This commit is contained in:
Abin Toppo
2026-05-26 16:34:05 +05:30
commit 9ac5e95d52
410 changed files with 86650 additions and 0 deletions
+563
View File
@@ -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,
},
});