374 lines
12 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import {
View,
Text,
ScrollView,
TextInput,
TouchableOpacity,
Switch,
ActivityIndicator,
Alert,
Image,
} from 'react-native';
import { useAppTheme } from '@theme';
import { ScreenHeader } from '@components';
import { ClipboardIcon, WalletIcon } from '@icons';
import { useAppDispatch, useAppSelector } from '@store';
import { useNavigation } from '@react-navigation/native';
import { pick, types, errorCodes } from '@react-native-documents/picker';
import { AddBankDetailsPayload } from '@interfaces';
import { addBankAccount } from './thunk';
import { resetAddBankState } from './reducer';
import { getStyles } from './addBankDetailsScreen.styles';
export const AddBankDetailsScreen: React.FC = () => {
const { colors } = useAppTheme();
const styles = getStyles(colors);
const dispatch = useAppDispatch();
const navigation = useNavigation();
const { isLoading, error, success } = useAppSelector(
state => state.addBankDetails,
);
const [accountHolderName, setAccountHolderName] = useState('');
const [bankName, setBankName] = useState('');
const [accountNumber, setAccountNumber] = useState('');
const [ifscCode, setIfscCode] = useState('');
const [branchName, setBranchName] = useState('');
const [isDefault, setIsDefault] = useState(true);
const [passbookImage, setPassbookImage] = useState<{
uri: string;
name: string;
type: string;
} | null>(null);
const [errors, setErrors] = useState<Record<string, string>>({});
useEffect(() => {
return () => {
dispatch(resetAddBankState());
};
}, [dispatch]);
useEffect(() => {
if (success) {
Alert.alert(
'Bank Account Registered',
'Your bank account has been added successfully.',
[
{
text: 'OK',
onPress: () => {
dispatch(resetAddBankState());
navigation.goBack();
},
},
],
);
}
}, [success, dispatch, navigation]);
const handlePickPassbook = async () => {
try {
const [result] = await pick({
type: [types.images, types.pdf],
allowMultiSelection: false,
});
if (result) {
setPassbookImage({
uri: result.uri,
name: result.name ?? `passbook_${Date.now()}.jpg`,
type: result.type ?? 'image/jpeg',
});
}
} catch (err: any) {
if (err?.code === errorCodes.OPERATION_CANCELED) return;
Alert.alert('File Picker Error', err?.message ?? 'Failed to pick file.');
}
};
const handleRemovePassbook = () => {
setPassbookImage(null);
};
const handleSubmit = () => {
const newErrors: Record<string, string> = {};
if (!accountHolderName.trim()) {
newErrors.accountHolderName = 'Account holder name is required';
}
if (!bankName.trim()) {
newErrors.bankName = 'Bank name is required';
}
if (!accountNumber.trim()) {
newErrors.accountNumber = 'Account number is required';
} else if (accountNumber.trim().length < 8) {
newErrors.accountNumber = 'Enter a valid account number (min 8 digits)';
}
if (!ifscCode.trim()) {
newErrors.ifscCode = 'IFSC code is required';
} else if (ifscCode.trim().length < 4) {
newErrors.ifscCode = 'Enter a valid IFSC code';
}
setErrors(newErrors);
if (Object.keys(newErrors).length > 0) {
return;
}
const payload: AddBankDetailsPayload = {
accountHolderName: accountHolderName.trim(),
bankName: bankName.trim(),
accountNumber: accountNumber.trim(),
ifscCode: ifscCode.trim().toUpperCase(),
branchName: branchName.trim() || undefined,
isDefault,
passbookImage: passbookImage ? (passbookImage as any) : undefined,
};
dispatch(addBankAccount(payload));
};
return (
<View style={styles.container}>
<ScreenHeader
title="Add Bank Account"
showBack={true}
onBack={() => navigation.goBack()}
/>
<ScrollView
contentContainerStyle={styles.scrollContainer}
showsVerticalScrollIndicator={false}
>
{/* Info Header Banner */}
<View style={styles.infoBanner}>
<View style={styles.infoIconContainer}>
<WalletIcon size={22} color={colors.white} />
</View>
<View style={styles.infoTextContainer}>
<Text style={styles.infoTitle}>Payout Account Details</Text>
<Text style={styles.infoSub}>
Enter your official bank details to receive weekly delivery earnings and automated payouts.
</Text>
</View>
</View>
{/* Error Banner */}
{error && (
<View style={styles.errorBanner}>
<Text style={styles.errorBannerText}>{error}</Text>
</View>
)}
{/* Form Fields Card */}
<View style={styles.formCard}>
{/* Account Holder Name */}
<View style={styles.fieldContainer}>
<View style={styles.labelRow}>
<Text style={styles.label}>Account Holder Name</Text>
<Text style={styles.requiredStar}>*</Text>
</View>
<TextInput
style={[
styles.input,
errors.accountHolderName ? styles.inputError : null,
]}
placeholder="Name as per bank account (e.g. John Doe)"
placeholderTextColor={colors.placeholder}
value={accountHolderName}
onChangeText={text => {
setAccountHolderName(text);
if (errors.accountHolderName) {
setErrors(prev => ({ ...prev, accountHolderName: '' }));
}
}}
autoCapitalize="words"
/>
{errors.accountHolderName && (
<Text style={styles.errorText}>{errors.accountHolderName}</Text>
)}
</View>
{/* Bank Name */}
<View style={styles.fieldContainer}>
<View style={styles.labelRow}>
<Text style={styles.label}>Bank Name</Text>
<Text style={styles.requiredStar}>*</Text>
</View>
<TextInput
style={[styles.input, errors.bankName ? styles.inputError : null]}
placeholder="e.g. HDFC Bank / ICICI Bank"
placeholderTextColor={colors.placeholder}
value={bankName}
onChangeText={text => {
setBankName(text);
if (errors.bankName) {
setErrors(prev => ({ ...prev, bankName: '' }));
}
}}
autoCapitalize="words"
/>
{errors.bankName && (
<Text style={styles.errorText}>{errors.bankName}</Text>
)}
</View>
{/* Account Number */}
<View style={styles.fieldContainer}>
<View style={styles.labelRow}>
<Text style={styles.label}>Account Number</Text>
<Text style={styles.requiredStar}>*</Text>
</View>
<TextInput
style={[
styles.input,
errors.accountNumber ? styles.inputError : null,
]}
placeholder="e.g. 50100432845612"
placeholderTextColor={colors.placeholder}
value={accountNumber}
onChangeText={text => {
setAccountNumber(text);
if (errors.accountNumber) {
setErrors(prev => ({ ...prev, accountNumber: '' }));
}
}}
keyboardType="number-pad"
/>
{errors.accountNumber && (
<Text style={styles.errorText}>{errors.accountNumber}</Text>
)}
</View>
{/* IFSC Code */}
<View style={styles.fieldContainer}>
<View style={styles.labelRow}>
<Text style={styles.label}>IFSC Code</Text>
<Text style={styles.requiredStar}>*</Text>
</View>
<TextInput
style={[styles.input, errors.ifscCode ? styles.inputError : null]}
placeholder="e.g. HDFC0000012"
placeholderTextColor={colors.placeholder}
value={ifscCode}
onChangeText={text => {
setIfscCode(text.toUpperCase());
if (errors.ifscCode) {
setErrors(prev => ({ ...prev, ifscCode: '' }));
}
}}
autoCapitalize="characters"
/>
{errors.ifscCode && (
<Text style={styles.errorText}>{errors.ifscCode}</Text>
)}
</View>
{/* Branch Name (Optional) */}
<View style={styles.fieldContainer}>
<View style={styles.labelRow}>
<Text style={styles.label}>Branch Name (Optional)</Text>
</View>
<TextInput
style={styles.input}
placeholder="e.g. Sector 62 Noida"
placeholderTextColor={colors.placeholder}
value={branchName}
onChangeText={setBranchName}
autoCapitalize="sentences"
/>
</View>
{/* Default Account Toggle */}
<View style={styles.toggleRow}>
<View style={styles.toggleTextContainer}>
<Text style={styles.toggleTitle}>Default Payout Account</Text>
<Text style={styles.toggleSub}>
Mark as default bank account for payouts
</Text>
</View>
<Switch
value={isDefault}
onValueChange={setIsDefault}
trackColor={{ false: colors.border, true: colors.primary }}
thumbColor={colors.white}
/>
</View>
{/* Passbook / Cancelled Cheque Image (Optional) */}
<View style={styles.fieldContainer}>
<View style={styles.labelRow}>
<Text style={styles.label}>
Passbook / Cancelled Cheque (Optional)
</Text>
</View>
<TouchableOpacity
style={[
styles.uploadBox,
passbookImage ? styles.uploadBoxActive : null,
]}
onPress={handlePickPassbook}
activeOpacity={0.8}
>
{passbookImage ? (
<View style={styles.filePreviewRow}>
<View style={styles.fileInfo}>
{passbookImage.type.startsWith('image/') && (
<Image
source={{ uri: passbookImage.uri }}
style={styles.fileThumbnail}
/>
)}
<View style={{ flex: 1 }}>
<Text style={styles.fileName} numberOfLines={1}>
{passbookImage.name}
</Text>
<Text style={styles.fileSize}>Attached document</Text>
</View>
</View>
<TouchableOpacity
style={styles.removeBtn}
onPress={handleRemovePassbook}
activeOpacity={0.7}
>
<Text style={styles.removeBtnText}>Remove</Text>
</TouchableOpacity>
</View>
) : (
<>
<View style={styles.uploadIconCircle}>
<ClipboardIcon size={20} color={colors.primary} />
</View>
<Text style={styles.uploadTitle}>Choose Passbook / Cheque File</Text>
<Text style={styles.uploadSub}>Supports JPG, PNG, or PDF format</Text>
</>
)}
</TouchableOpacity>
</View>
{/* Submit Button */}
<TouchableOpacity
style={[styles.submitBtn, isLoading ? styles.submitBtnDisabled : null]}
onPress={handleSubmit}
disabled={isLoading}
activeOpacity={0.8}
>
{isLoading ? (
<ActivityIndicator color={colors.white} size="small" />
) : (
<Text style={styles.submitBtnText}>Save Bank Account</Text>
)}
</TouchableOpacity>
</View>
</ScrollView>
</View>
);
};
export default AddBankDetailsScreen;