Update CBS Mobile App codebase

This commit is contained in:
Abin Toppo
2026-09-21 13:21:25 +05:30
parent 58af541536
commit 2dcb10775c
339 changed files with 26750 additions and 16767 deletions
+239 -17
View File
@@ -11,9 +11,9 @@ 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 { useAccounts } from '../hooks/useAccounts';
import type { HomeStackParamList } from '../../../navigation/types';
import { useTheme } from '../../../theme';
import { useTheme, fontFamily } from '../../../theme';
type Props = NativeStackScreenProps<HomeStackParamList, 'ReKyc'>;
type IconName = React.ComponentProps<typeof Ionicons>['name'];
@@ -34,8 +34,22 @@ const CHANGE_OPTIONS: ChangeOption[] = [
const STEPS = [
'Enter your Aadhaar number',
'Select the fields that changed',
'Submit the re-KYC request for review',
'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) =>
@@ -48,9 +62,11 @@ 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];
// 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[]>([]);
@@ -59,9 +75,26 @@ export default function ReKycScreen({ route, navigation }: Props) {
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">
@@ -137,6 +170,16 @@ export default function ReKycScreen({ route, navigation }: Props) {
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);
};
@@ -165,7 +208,7 @@ export default function ReKycScreen({ route, navigation }: Props) {
return (
<ScreenShell
title="Re-KYC via Aadhaar"
subtitle={`${account.type} account · A/c ${account.maskedNumber}`}
subtitle={`${account?.accountType ?? ""} account · A/c XXXX ${account?.accountNumber.slice(-4) ?? ""}`}
contentContainerStyle={styles.contentContainer}
>
<LinearGradient
@@ -358,6 +401,145 @@ export default function ReKycScreen({ route, navigation }: Props) {
) : null}
</Card>
{/* Document upload */}
<Card style={styles.sectionCard}>
<View style={styles.sectionHeader}>
<View style={[styles.sectionIcon, { backgroundColor: `${theme.colors.success}12` }]}>
<Ionicons 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.colors.success}10` : theme.colors.surfaceMuted,
borderColor: isUploaded ? `${theme.colors.success}45` : theme.colors.cardBorder,
},
]}
>
<View style={[styles.uploadIcon, { backgroundColor: theme.colors.surface }]}>
<Ionicons 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>
<Ionicons
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.colors.info}12` }]}>
<Ionicons 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.colors.success}10` : theme.colors.surfaceMuted,
borderColor: photoUploaded ? `${theme.colors.success}45` : theme.colors.cardBorder,
},
]}
>
<Ionicons
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.colors.warning}12` }]}>
<Ionicons 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 },
]}
>
<Ionicons
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.colors.primary}12` }]}>
<Ionicons 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={<Ionicons name="videocam-outline" size={18} color={theme.colors.textPrimary} />}
/>
</Card>
<Card style={styles.footerCard}>
<View style={styles.footerHeader}>
<Ionicons name="time-outline" size={18} color={theme.colors.primary} />
@@ -429,7 +611,7 @@ const styles = StyleSheet.create({
},
heroTitle: {
fontSize: 20,
fontWeight: '800',
fontFamily: fontFamily.bold,
letterSpacing: -0.4,
},
heroSubtitle: {
@@ -453,12 +635,12 @@ const styles = StyleSheet.create({
},
stepIndexText: {
fontSize: 12,
fontWeight: '800',
fontFamily: fontFamily.bold,
},
stepText: {
flex: 1,
fontSize: 13,
fontWeight: '600',
fontFamily: fontFamily.semibold,
},
sectionCard: {
padding: 20,
@@ -482,12 +664,12 @@ const styles = StyleSheet.create({
},
sectionTitle: {
fontSize: 18,
fontWeight: '800',
fontFamily: fontFamily.bold,
letterSpacing: -0.3,
},
sectionSubtitle: {
fontSize: 13,
fontWeight: '500',
fontFamily: fontFamily.medium,
},
consentCard: {
flexDirection: 'row',
@@ -497,11 +679,51 @@ const styles = StyleSheet.create({
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,
fontWeight: '500',
fontFamily: fontFamily.medium,
},
chipGrid: {
flexDirection: 'row',
@@ -520,7 +742,7 @@ const styles = StyleSheet.create({
},
changeChipText: {
fontSize: 13,
fontWeight: '700',
fontFamily: fontFamily.bold,
},
footerCard: {
padding: 20,
@@ -533,7 +755,7 @@ const styles = StyleSheet.create({
},
footerTitle: {
fontSize: 16,
fontWeight: '800',
fontFamily: fontFamily.bold,
},
footerText: {
fontSize: 13,
@@ -553,7 +775,7 @@ const styles = StyleSheet.create({
},
emptyTitle: {
fontSize: 18,
fontWeight: '800',
fontFamily: fontFamily.bold,
},
emptyDescription: {
fontSize: 14,