Amba-Agent-App/app/(root)/(screens)/kyc.tsx
2026-01-16 00:22:35 +03:00

463 lines
15 KiB
TypeScript

import React, { useState, useRef, useEffect } from "react";
import {
View,
Text,
ScrollView,
TouchableOpacity,
Alert,
ActivityIndicator,
} from "react-native";
import ScreenWrapper from "~/components/ui/ScreenWrapper";
import { Button } from "~/components/ui/button";
import { Input } from "~/components/ui/input";
import { UploadCloud } from "lucide-react-native";
import Dropdown, { type DropdownOption } from "~/components/ui/dropdown";
import ModalToast from "~/components/ui/toast";
import BackButton from "~/components/ui/backButton";
import { useAuthWithProfile } from "~/lib/hooks/useAuthWithProfile";
import AuthService from "~/lib/services/authServices";
import { uploadKycDocument } from "~/lib/services/kycDocumentService";
import * as ImagePicker from "expo-image-picker";
export default function KycScreen() {
const { user, refreshProfile } = useAuthWithProfile();
const [activeTab, setActiveTab] = useState<"personal" | "business">(
"personal"
);
const [fanNumber, setFanNumber] = useState("");
const [tin, setTin] = useState("");
const [businessType, setBusinessType] = useState("");
const [nationalIdUri, setNationalIdUri] = useState<string | null>(null);
const [nationalIdLabel, setNationalIdLabel] = useState("");
const [businessLicenseUri, setBusinessLicenseUri] = useState<string | null>(
null
);
const [businessLicenseLabel, setBusinessLicenseLabel] = useState("");
const [pickingNationalId, setPickingNationalId] = useState(false);
const [pickingBusinessLicense, setPickingBusinessLicense] = useState(false);
const [submitting, setSubmitting] = useState(false);
const [toastVisible, setToastVisible] = useState(false);
const [toastTitle, setToastTitle] = useState("");
const [toastDescription, setToastDescription] = useState<string | undefined>(
undefined
);
const [toastVariant, setToastVariant] = useState<
"success" | "error" | "warning" | "info"
>("info");
const toastTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const businessTypeOptions: DropdownOption[] = [
{ label: "Retail & E-commerce", value: "Retail & E-commerce" },
{ label: "Food & Beverage", value: "Food & Beverage" },
{
label: "Transportation & Logistics",
value: "Transportation & Logistics",
},
{ label: "Finance & Fintech", value: "Finance & Fintech" },
{
label: "Healthcare & Pharmacy",
value: "Healthcare & Pharmacy",
},
{ label: "Education & Training", value: "Education & Training" },
{
label: "Construction & Real Estate",
value: "Construction & Real Estate",
},
{
label: "Technology & Software",
value: "Technology & Software",
},
{ label: "Entertainment & Events", value: "Entertainment & Events" },
{ label: "Agriculture & Farming", value: "Agriculture & Farming" },
{
label: "Freelancer / Sole Proprietor",
value: "Freelancer / Sole Proprietor",
},
{ label: "Other", value: "Other" },
];
const showToast = (
title: string,
description?: string,
variant: "success" | "error" | "warning" | "info" = "info"
) => {
if (toastTimeoutRef.current) {
clearTimeout(toastTimeoutRef.current);
}
setToastTitle(title);
setToastDescription(description);
setToastVariant(variant);
setToastVisible(true);
toastTimeoutRef.current = setTimeout(() => {
setToastVisible(false);
toastTimeoutRef.current = null;
}, 2500);
};
useEffect(() => {
return () => {
if (toastTimeoutRef.current) {
clearTimeout(toastTimeoutRef.current);
}
};
}, []);
const handlePrimary = async () => {
if (!user?.uid) {
showToast("Error", "You must be logged in to save KYC.", "error");
return;
}
try {
setSubmitting(true);
const updateData: any = {};
if (activeTab === "personal") {
if (!fanNumber.trim()) {
showToast("Error", "Please enter your FAN Number.", "error");
setSubmitting(false);
return;
}
updateData.fanNumber = fanNumber.trim();
if (nationalIdUri) {
try {
const url = await uploadKycDocument(
user.uid,
nationalIdUri,
"personal-id"
);
updateData.nationalIdUrl = url;
} catch (e) {
console.error("[KYC] Failed to upload national ID", e);
showToast("Error", "Failed to upload National ID.", "error");
setSubmitting(false);
return;
}
}
} else {
if (!tin.trim()) {
showToast("Error", "Please enter your EIN / TIN.", "error");
setSubmitting(false);
return;
}
if (!businessType.trim()) {
showToast("Error", "Please enter your business type.", "error");
setSubmitting(false);
return;
}
updateData.tin = tin.trim();
updateData.businessType = businessType.trim();
if (businessLicenseUri) {
try {
const url = await uploadKycDocument(
user.uid,
businessLicenseUri,
"business-license"
);
updateData.businessLicenseUrl = url;
} catch (e) {
console.error("[KYC] Failed to upload business license", e);
showToast("Error", "Failed to upload Business License.", "error");
setSubmitting(false);
return;
}
}
}
const result = await AuthService.updateUserProfile(user.uid, updateData);
if (!result.success) {
console.error("[KYC] updateUserProfile failed", result.error);
showToast("Error", result.error || "Failed to save KYC.", "error");
return;
}
try {
await refreshProfile();
} catch (e) {
console.warn("[KYC] Failed to refresh profile after KYC save", e);
}
// Successful save: clear all KYC fields
setFanNumber("");
setTin("");
setBusinessType("");
setNationalIdUri(null);
setNationalIdLabel("");
setBusinessLicenseUri(null);
setBusinessLicenseLabel("");
showToast("Success", "KYC information saved.", "success");
} catch (e) {
console.error("[KYC] Unexpected error while saving KYC", e);
showToast("Error", "Unexpected error while saving KYC.", "error");
} finally {
setSubmitting(false);
}
};
const handlePickNationalId = async () => {
if (pickingNationalId) return;
try {
setPickingNationalId(true);
const permissionResult =
await ImagePicker.requestMediaLibraryPermissionsAsync();
if (!permissionResult.granted) {
Alert.alert(
"Permission Required",
"Please allow access to your photo library to upload your ID."
);
return;
}
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
quality: 0.8,
});
if (!result.canceled && result.assets[0]) {
const uri = result.assets[0].uri;
setNationalIdUri(uri);
const name = uri.split("/").pop() || "Selected file";
setNationalIdLabel(name);
}
} catch (e) {
console.error("[KYC] Error while selecting National ID", e);
showToast("Error", "Failed to select National ID.", "error");
} finally {
setPickingNationalId(false);
}
};
const handlePickBusinessLicense = async () => {
if (pickingBusinessLicense) return;
try {
setPickingBusinessLicense(true);
const permissionResult =
await ImagePicker.requestMediaLibraryPermissionsAsync();
if (!permissionResult.granted) {
Alert.alert(
"Permission Required",
"Please allow access to your photo library to upload your Business License."
);
return;
}
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
quality: 0.8,
});
if (!result.canceled && result.assets[0]) {
const uri = result.assets[0].uri;
setBusinessLicenseUri(uri);
const name = uri.split("/").pop() || "Selected file";
setBusinessLicenseLabel(name);
}
} catch (e) {
console.error("[KYC] Error while selecting Business License", e);
showToast("Error", "Failed to select Business License.", "error");
} finally {
setPickingBusinessLicense(false);
}
};
return (
<ScreenWrapper edges={[]}>
<BackButton />
<ScrollView
className="flex-1 bg-white"
showsVerticalScrollIndicator={false}
>
<View className="px-5 pb-8">
<Text className="text-3xl font-dmsans-bold text-gray-900 mb-2">
Information
</Text>
<Text className="text-lg font-dmsans-bold text-[#0F7B4A] mb-1">
KYC
</Text>
<Text className="text-base font-dmsans text-gray-500 mb-5">
Fill out the information below to add more limits to your account
</Text>
{/* tab */}
<View className="flex-row mb-5 bg-[#F3F4F6] rounded-full p-1">
<TouchableOpacity
onPress={() => setActiveTab("personal")}
className={`flex-1 items-center py-2 rounded-full ${
activeTab === "personal" ? "bg-white" : "bg-transparent"
}`}
activeOpacity={0.8}
>
<Text
className={`text-base font-dmsans-medium ${
activeTab === "personal" ? "text-[#0F7B4A]" : "text-gray-500"
}`}
>
Personal
</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() => setActiveTab("business")}
className={`flex-1 items-center py-2 rounded-full ${
activeTab === "business" ? "bg-white" : "bg-transparent"
}`}
activeOpacity={0.8}
>
<Text
className={`text-base font-dmsans-medium ${
activeTab === "business" ? "text-[#0F7B4A]" : "text-gray-500"
}`}
>
Business
</Text>
</TouchableOpacity>
</View>
{activeTab === "personal" ? (
<View className="mt-2">
<Text className="text-base font-dmsans text-black mb-2">
FAN Number
</Text>
<Input
placeholderText="FAN Number"
value={fanNumber}
onChangeText={setFanNumber}
containerClassName="w-full mb-4"
borderClassName="border-[#E5E7EB] bg-white rounded-[4px]"
placeholderColor="#9CA3AF"
textClassName="text-[#111827] text-sm"
/>
<Text className="text-base font-dmsans text-black mb-2">
National ID Upload
</Text>
<TouchableOpacity
activeOpacity={0.8}
onPress={handlePickNationalId}
disabled={pickingNationalId}
>
<Input
placeholderText={
pickingNationalId
? "Opening picker..."
: "Upload National ID"
}
value={nationalIdLabel}
containerClassName="w-full"
borderClassName="border-[#E5E7EB] bg-white rounded-[4px]"
placeholderColor="#9CA3AF"
textClassName="text-[#111827] text-sm"
editable={false}
rightIcon={
pickingNationalId ? (
<ActivityIndicator size="small" color="#111827" />
) : (
<UploadCloud size={18} color="#111827" />
)
}
/>
</TouchableOpacity>
</View>
) : (
<View className="mt-2">
<Text className="text-base font-dmsans text-black mb-2">
EIN / TIN
</Text>
<Input
placeholderText="EIN / TIN"
value={tin}
onChangeText={setTin}
containerClassName="w-full mb-4"
borderClassName="border-[#E5E7EB] bg-white rounded-[4px]"
placeholderColor="#9CA3AF"
textClassName="text-[#111827] text-sm"
/>
<Text className="text-base font-dmsans text-black mb-2">
Type of Business
</Text>
<View className="w-full mb-4">
<Dropdown
value={businessType || null}
options={businessTypeOptions}
onSelect={(value) => setBusinessType(value)}
placeholder="Select type of business"
/>
</View>
<Text className="text-base font-dmsans text-black mb-2">
Business License
</Text>
<TouchableOpacity
activeOpacity={0.8}
onPress={handlePickBusinessLicense}
disabled={pickingBusinessLicense}
>
<Input
placeholderText={
pickingBusinessLicense
? "Opening picker..."
: "In-cooperation Document"
}
value={businessLicenseLabel}
containerClassName="w-full"
borderClassName="border-[#E5E7EB] bg-white rounded-[4px]"
placeholderColor="#9CA3AF"
textClassName="text-[#111827] text-sm"
editable={false}
rightIcon={
pickingBusinessLicense ? (
<ActivityIndicator size="small" color="#111827" />
) : (
<UploadCloud size={18} color="#111827" />
)
}
/>
</TouchableOpacity>
</View>
)}
</View>
</ScrollView>
<View className="w-full px-5 pb-8 bg-white">
<Button
className="h-13 rounded-full bg-[#0F7B4A]"
onPress={handlePrimary}
disabled={submitting}
>
<Text className="text-white font-dmsans-bold text-base">
{submitting
? "Saving..."
: activeTab === "personal"
? "Done"
: "Add"}
</Text>
</Button>
</View>
<ModalToast
visible={toastVisible}
title={toastTitle}
description={toastDescription}
variant={toastVariant}
/>
</ScreenWrapper>
);
}