356 lines
10 KiB
TypeScript
356 lines
10 KiB
TypeScript
import React, { useState } from 'react';
|
|
import {
|
|
View,
|
|
Text,
|
|
ScrollView,
|
|
TouchableOpacity,
|
|
Alert,
|
|
ActivityIndicator,
|
|
} from 'react-native';
|
|
import { useAppTheme } from '@theme';
|
|
import { ScreenHeader, KycDocumentCard, LoadingOverlay } from '@components';
|
|
import { useNavigation, useFocusEffect } from '@react-navigation/native';
|
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
|
import { OnBoardingParamList } from '@navigation/navigationTypes';
|
|
import { RouteNames } from '@utils/constants';
|
|
import Svg, { Path } from 'react-native-svg';
|
|
import { useAppDispatch, useAppSelector } from '@store';
|
|
import { uploadKyc, fetchMyKyc } from './thunk';
|
|
import { getStyles } from './kycScreen.styles';
|
|
|
|
// Interface for Document state matching KycDocumentCard structure
|
|
interface KycDocument {
|
|
id: string;
|
|
name: string;
|
|
idNumber: string;
|
|
fileName: string | null;
|
|
fileUri: string | null;
|
|
fileType: string | null;
|
|
status?: string;
|
|
}
|
|
|
|
// Inline Cloud Upload SVG representation for the top banner card
|
|
const CloudUploadIcon = ({ size = 22, color = '#666' }) => (
|
|
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
|
|
<Path
|
|
d="M17.5 19A5.5 5.5 0 0 0 18 8h-1.26A8 8 0 1 0 3 16.28M12 12v9m0-9l-3 3m3-3l3 3"
|
|
stroke={color}
|
|
strokeWidth="2"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
/>
|
|
</Svg>
|
|
);
|
|
|
|
// Inline Plus icon for adding a new document
|
|
const PlusIcon = ({ size = 16, color = '#333' }) => (
|
|
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
|
|
<Path
|
|
d="M12 5v14M5 12h14"
|
|
stroke={color}
|
|
strokeWidth="2.5"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
/>
|
|
</Svg>
|
|
);
|
|
|
|
// Inline Submit/Disk icon for the submit button
|
|
const SubmitIcon = ({ size = 18, color = '#fff' }) => (
|
|
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
|
|
<Path
|
|
d="M19 21H5a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"
|
|
stroke={color}
|
|
strokeWidth="2"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
/>
|
|
<Path
|
|
d="M17 21v-8H7v8M7 3v5h8"
|
|
stroke={color}
|
|
strokeWidth="2"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
/>
|
|
</Svg>
|
|
);
|
|
|
|
export const KycScreen: React.FC = () => {
|
|
const { colors } = useAppTheme();
|
|
const styles = getStyles(colors);
|
|
const navigation =
|
|
useNavigation<NativeStackNavigationProp<OnBoardingParamList>>();
|
|
const dispatch = useAppDispatch();
|
|
|
|
// Get upload loading status from KYC store slice
|
|
const { isLoading, myKycResponse } = useAppSelector(state => state.kyc);
|
|
|
|
const [documents, setDocuments] = useState<KycDocument[]>([
|
|
{
|
|
id: '1',
|
|
name: '',
|
|
idNumber: '',
|
|
fileName: null,
|
|
fileUri: null,
|
|
fileType: null,
|
|
},
|
|
]);
|
|
|
|
// Fetch current KYC verification status when screen is focused
|
|
useFocusEffect(
|
|
React.useCallback(() => {
|
|
dispatch(fetchMyKyc());
|
|
}, [dispatch]),
|
|
);
|
|
|
|
const handleAddDocument = () => {
|
|
setDocuments([
|
|
...documents,
|
|
{
|
|
id: Date.now().toString(),
|
|
name: '',
|
|
idNumber: '',
|
|
fileName: null,
|
|
fileUri: null,
|
|
fileType: null,
|
|
},
|
|
]);
|
|
};
|
|
|
|
const handleRemoveDocument = (id: string) => {
|
|
setDocuments(documents.filter(doc => doc.id !== id));
|
|
};
|
|
|
|
const handleUpdateDocument = (
|
|
id: string,
|
|
updatedFields: Partial<KycDocument>,
|
|
) => {
|
|
setDocuments(
|
|
documents.map(doc =>
|
|
doc.id === id ? { ...doc, ...updatedFields } : doc,
|
|
),
|
|
);
|
|
};
|
|
|
|
const handleSubmit = () => {
|
|
// Validation
|
|
for (let i = 0; i < documents.length; i++) {
|
|
const doc = documents[i];
|
|
if (!doc.name.trim()) {
|
|
Alert.alert(
|
|
'Required Field',
|
|
`Please fill in the Document Name/Type for Document ${i + 1}.`,
|
|
);
|
|
return;
|
|
}
|
|
if (!doc.idNumber.trim()) {
|
|
Alert.alert(
|
|
'Required Field',
|
|
`Please fill in the Document Identification Number for Document ${
|
|
i + 1
|
|
}.`,
|
|
);
|
|
return;
|
|
}
|
|
if (!doc.fileName || !doc.fileUri) {
|
|
Alert.alert(
|
|
'Required Field',
|
|
`Please select and attach a document file for Document ${i + 1}.`,
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Build files and metadata array payloads
|
|
const payloadFiles = documents.map(doc => ({
|
|
uri: doc.fileUri || '',
|
|
name: doc.fileName || '',
|
|
type: doc.fileName?.endsWith('.pdf') ? 'application/pdf' : 'image/png',
|
|
}));
|
|
|
|
const payloadMetadata = documents.map(doc => ({
|
|
docName: doc.name,
|
|
docNumber: doc.idNumber,
|
|
}));
|
|
|
|
dispatch(
|
|
uploadKyc({
|
|
files: payloadFiles,
|
|
metadata: payloadMetadata,
|
|
}),
|
|
)
|
|
.unwrap()
|
|
.then(() => {
|
|
Alert.alert('Success', 'KYC Documents submitted successfully!', [
|
|
{
|
|
text: 'OK',
|
|
onPress: () => {
|
|
dispatch(fetchMyKyc());
|
|
navigation.navigate(RouteNames.OnboardingComplete);
|
|
},
|
|
},
|
|
]);
|
|
})
|
|
.catch(err => {
|
|
Alert.alert(
|
|
'Upload Failed',
|
|
err || 'Failed to submit KYC documents. Please try again.',
|
|
);
|
|
});
|
|
};
|
|
|
|
// Determine if documents exist to render in read-only mode
|
|
const hasSubmittedDocs = !!(
|
|
myKycResponse &&
|
|
myKycResponse.documents &&
|
|
myKycResponse.documents.length > 0
|
|
);
|
|
|
|
// Map backend documents if submitted, else use local state
|
|
const displayedDocuments: KycDocument[] = hasSubmittedDocs
|
|
? myKycResponse.documents.map(doc => ({
|
|
id: doc.id,
|
|
name: doc.documentType,
|
|
idNumber: doc.documentNumber,
|
|
fileName: doc.fileName,
|
|
fileUri: doc.fileUrl,
|
|
fileType: doc.fileName.endsWith('.pdf')
|
|
? 'application/pdf'
|
|
: 'image/png',
|
|
status: doc.status,
|
|
}))
|
|
: documents;
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
<ScreenHeader
|
|
title="KYC Verification"
|
|
showBack={true}
|
|
onBack={() => navigation.goBack()}
|
|
/>
|
|
|
|
<ScrollView
|
|
contentContainerStyle={styles.scrollContainer}
|
|
showsVerticalScrollIndicator={false}
|
|
>
|
|
{/* Compliance Check Required Banner */}
|
|
<View style={styles.bannerCard}>
|
|
<View style={styles.bannerIconWrapper}>
|
|
<CloudUploadIcon size={22} color={colors.primary} />
|
|
</View>
|
|
<View style={styles.bannerTextContainer}>
|
|
<Text style={styles.bannerTitle}>
|
|
KYC Compliance Check Required
|
|
</Text>
|
|
<Text style={styles.bannerSubtitle}>
|
|
Please upload store identity documents to configure payouts,
|
|
deliveries, and operational scopes.
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
|
|
{/* Upload KYC verification documents main card header info */}
|
|
<View style={styles.mainCardHeader}>
|
|
<Text style={styles.mainCardTitle}>
|
|
{hasSubmittedDocs
|
|
? 'Submitted KYC Documents'
|
|
: 'Upload KYC Verification Documents'}
|
|
</Text>
|
|
<Text style={styles.mainCardSubtitle}>
|
|
{hasSubmittedDocs
|
|
? 'Below are the KYC documents submitted for verification.'
|
|
: 'Provide details and attachments to initiate compliance verification.'}
|
|
</Text>
|
|
<View style={styles.divider} />
|
|
</View>
|
|
|
|
{/* Render each document card */}
|
|
{displayedDocuments.map((doc, idx) => (
|
|
<KycDocumentCard
|
|
key={doc.id}
|
|
document={doc}
|
|
index={idx}
|
|
onUpdate={
|
|
!hasSubmittedDocs
|
|
? fields => handleUpdateDocument(doc.id, fields)
|
|
: undefined
|
|
}
|
|
onRemove={
|
|
!hasSubmittedDocs && idx > 0
|
|
? () => handleRemoveDocument(doc.id)
|
|
: undefined
|
|
}
|
|
readOnly={hasSubmittedDocs}
|
|
/>
|
|
))}
|
|
|
|
{!hasSubmittedDocs && (
|
|
<>
|
|
{/* Add another document button */}
|
|
<View style={styles.addButtonContainer}>
|
|
<TouchableOpacity
|
|
style={styles.addBtn}
|
|
onPress={handleAddDocument}
|
|
activeOpacity={0.7}
|
|
>
|
|
<PlusIcon size={14} color={colors.text} />
|
|
<Text style={styles.addBtnText}>Add Another Document</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
{/* Upload & Submit KYC Action Button */}
|
|
<TouchableOpacity
|
|
style={[
|
|
styles.submitBtn,
|
|
isLoading ? styles.submitBtnDisabled : null,
|
|
]}
|
|
onPress={handleSubmit}
|
|
disabled={isLoading}
|
|
activeOpacity={0.8}
|
|
>
|
|
{isLoading ? (
|
|
<ActivityIndicator color={colors.white} />
|
|
) : (
|
|
<>
|
|
<SubmitIcon size={18} color={colors.white} />
|
|
<Text style={styles.submitBtnText}>Upload & Submit KYC</Text>
|
|
</>
|
|
)}
|
|
</TouchableOpacity>
|
|
</>
|
|
)}
|
|
{hasSubmittedDocs && (
|
|
<TouchableOpacity
|
|
style={[
|
|
styles.submitBtn,
|
|
isLoading ? styles.submitBtnDisabled : null,
|
|
]}
|
|
onPress={() => {
|
|
navigation.navigate(RouteNames.OnboardingComplete);
|
|
}}
|
|
disabled={isLoading}
|
|
activeOpacity={0.8}
|
|
>
|
|
{isLoading ? (
|
|
<ActivityIndicator color={colors.white} />
|
|
) : (
|
|
<>
|
|
<SubmitIcon size={18} color={colors.white} />
|
|
<Text style={styles.submitBtnText}>Next Page</Text>
|
|
</>
|
|
)}
|
|
</TouchableOpacity>
|
|
)}
|
|
</ScrollView>
|
|
|
|
{/* Loading Overlay for the focus fetch check */}
|
|
<LoadingOverlay
|
|
visible={isLoading && !myKycResponse}
|
|
message="Checking KYC status..."
|
|
/>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
export default KycScreen;
|