Update CBS Mobile App codebase
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user