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;