374 lines
12 KiB
TypeScript
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;
|