888 lines
30 KiB
TypeScript
888 lines
30 KiB
TypeScript
import React, { useEffect, useState } from "react";
|
|
import {
|
|
View,
|
|
Pressable,
|
|
TextInput,
|
|
StyleSheet,
|
|
ActivityIndicator,
|
|
Switch,
|
|
Platform,
|
|
} from "react-native";
|
|
import { useColorScheme } from "nativewind";
|
|
import { Text } from "@/components/ui/text";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
ArrowLeft,
|
|
Calendar,
|
|
ChevronDown,
|
|
DollarSign,
|
|
Send,
|
|
CalendarSearch,
|
|
Clock,
|
|
User,
|
|
Phone,
|
|
Building2,
|
|
Hash,
|
|
Banknote,
|
|
Upload,
|
|
} from "@/lib/icons";
|
|
import { ScreenWrapper } from "@/components/ScreenWrapper";
|
|
import { useSirouRouter } from "@sirou/react-native";
|
|
import { AppRoutes } from "@/lib/routes";
|
|
import { api, BASE_URL } from "@/lib/api";
|
|
import { toast } from "@/lib/toast-store";
|
|
import { useAuthStore } from "@/lib/auth-store";
|
|
import * as ImagePicker from "expo-image-picker";
|
|
import { PickerModal, SelectOption } from "@/components/PickerModal";
|
|
import { CalendarGrid } from "@/components/CalendarGrid";
|
|
import { TimerPickerModal } from "react-native-timer-picker";
|
|
import { LinearGradient } from "expo-linear-gradient";
|
|
import { getPlaceholderColor } from "@/lib/colors";
|
|
import { getScanData } from "@/lib/scan-cache";
|
|
import { FormFlow } from "@/components/FormFlow";
|
|
|
|
const S = StyleSheet.create({
|
|
input: {
|
|
paddingHorizontal: 12,
|
|
paddingVertical: 12,
|
|
fontSize: 12,
|
|
fontWeight: "500",
|
|
borderRadius: 6,
|
|
borderWidth: 1,
|
|
textAlignVertical: "center",
|
|
},
|
|
inputCenter: {
|
|
paddingHorizontal: 12,
|
|
paddingVertical: 10,
|
|
fontSize: 14,
|
|
fontWeight: "500",
|
|
borderRadius: 6,
|
|
borderWidth: 1,
|
|
textAlign: "center",
|
|
textAlignVertical: "center",
|
|
},
|
|
});
|
|
|
|
function useInputColors() {
|
|
const { colorScheme } = useColorScheme();
|
|
const dark = colorScheme === "dark";
|
|
return {
|
|
bg: dark ? "rgba(30,30,30,0.8)" : "rgba(241,245,249,0.2)",
|
|
border: dark ? "rgba(255,255,255,0.08)" : "rgba(203,213,225,0.6)",
|
|
text: dark ? "#f1f5f9" : "#0f172a",
|
|
placeholder: "rgba(100,116,139,0.45)",
|
|
};
|
|
}
|
|
|
|
function Field({
|
|
label,
|
|
value,
|
|
onChangeText,
|
|
placeholder,
|
|
numeric = false,
|
|
center = false,
|
|
flex,
|
|
multiline = false,
|
|
}: {
|
|
label: string;
|
|
value: string;
|
|
onChangeText: (v: string) => void;
|
|
placeholder: string;
|
|
numeric?: boolean;
|
|
center?: boolean;
|
|
flex?: number;
|
|
multiline?: boolean;
|
|
}) {
|
|
const c = useInputColors();
|
|
return (
|
|
<View style={flex != null ? { flex } : undefined}>
|
|
<Text className="text-[14px] font-sans-bold mb-1.5 ml-1 text-foreground">
|
|
{label}
|
|
</Text>
|
|
<TextInput
|
|
style={[
|
|
center ? S.inputCenter : S.input,
|
|
{ backgroundColor: c.bg, borderColor: c.border, color: c.text },
|
|
multiline
|
|
? { height: 80, paddingTop: 10, textAlignVertical: "top" }
|
|
: {},
|
|
]}
|
|
placeholder={placeholder}
|
|
placeholderTextColor={c.placeholder}
|
|
value={value}
|
|
onChangeText={onChangeText}
|
|
keyboardType={numeric ? "numeric" : "default"}
|
|
multiline={multiline}
|
|
autoCorrect={false}
|
|
autoCapitalize="none"
|
|
/>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
function Label({ children }: { children: string }) {
|
|
return (
|
|
<Text className="text-[18px] font-sans-bold text-foreground tracking-tight">
|
|
{children}
|
|
</Text>
|
|
);
|
|
}
|
|
|
|
const currencies = ["ETB", "USD", "EUR", "GBP", "KES", "ZAR"];
|
|
const paymentMethods = [
|
|
"Telebirr",
|
|
"CBE",
|
|
"Dashen",
|
|
"DECSI",
|
|
"Bank Transfer",
|
|
"Cash",
|
|
"Other",
|
|
];
|
|
const providers = ["telebirr", "cbe", "dashen", "decsi", "other"];
|
|
|
|
const STEPS = [
|
|
{ key: "payment", label: "Payment Details" },
|
|
{ key: "transaction", label: "Transaction" },
|
|
{ key: "merchant", label: "Merchant" },
|
|
{ key: "sender", label: "Sender" },
|
|
{ key: "verification", label: "Verification" },
|
|
{ key: "summary", label: "Summary" },
|
|
];
|
|
|
|
export default function AddReceiptScreen() {
|
|
const nav = useSirouRouter<AppRoutes>();
|
|
const [step, setStep] = useState(0);
|
|
const [submitting, setSubmitting] = useState(false);
|
|
const [scanning, setScanning] = useState(false);
|
|
const [scanFailures, setScanFailures] = useState(0);
|
|
const token = useAuthStore((s) => s.token);
|
|
|
|
const [amount, setAmount] = useState("");
|
|
const [currency, setCurrency] = useState("ETB");
|
|
const [paymentDate, setPaymentDate] = useState(
|
|
new Date().toISOString().split("T")[0],
|
|
);
|
|
const [paymentTime, setPaymentTime] = useState(
|
|
new Date().toLocaleTimeString("en-US", {
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
hour12: false,
|
|
}),
|
|
);
|
|
const [paymentMethod, setPaymentMethod] = useState("Telebirr");
|
|
const [transactionId, setTransactionId] = useState("");
|
|
const [referenceNumber, setReferenceNumber] = useState("");
|
|
const [merchantName, setMerchantName] = useState("");
|
|
const [merchantId, setMerchantId] = useState("");
|
|
const [provider, setProvider] = useState("telebirr");
|
|
const [senderName, setSenderName] = useState("");
|
|
const [senderPhone, setSenderPhone] = useState("");
|
|
const [verifyWithProvider, setVerifyWithProvider] = useState(false);
|
|
const [verifyWithVerifierApi, setVerifyWithVerifierApi] = useState(false);
|
|
|
|
const [showCurrency, setShowCurrency] = useState(false);
|
|
const [showPaymentMethod, setShowPaymentMethod] = useState(false);
|
|
const [showProvider, setShowProvider] = useState(false);
|
|
const [showPaymentDate, setShowPaymentDate] = useState(false);
|
|
const [showPaymentTime, setShowPaymentTime] = useState(false);
|
|
|
|
const { colorScheme } = useColorScheme();
|
|
const isDark = colorScheme === "dark";
|
|
const c = useInputColors();
|
|
|
|
useEffect(() => {
|
|
const scanData = getScanData();
|
|
if (!scanData) return;
|
|
if (scanData.amount != null) setAmount(String(scanData.amount));
|
|
if (scanData.currency) setCurrency(scanData.currency);
|
|
if (scanData.paymentDate) {
|
|
try {
|
|
setPaymentDate(
|
|
new Date(scanData.paymentDate).toISOString().split("T")[0],
|
|
);
|
|
} catch (_) {}
|
|
}
|
|
if (scanData.paymentTime) setPaymentTime(scanData.paymentTime);
|
|
if (scanData.paymentMethod) setPaymentMethod(scanData.paymentMethod);
|
|
if (scanData.transactionId) setTransactionId(scanData.transactionId);
|
|
if (scanData.referenceNumber) setReferenceNumber(scanData.referenceNumber);
|
|
if (scanData.merchantName) setMerchantName(scanData.merchantName);
|
|
if (scanData.merchantId) setMerchantId(scanData.merchantId);
|
|
if (scanData.provider) setProvider(scanData.provider);
|
|
if (scanData.senderName) setSenderName(scanData.senderName);
|
|
if (scanData.senderPhone) setSenderPhone(scanData.senderPhone.replace(/^\+251|\++/g, ""));
|
|
}, []);
|
|
|
|
const handleNext = () => {
|
|
if (step === 0) {
|
|
if (!amount || parseFloat(amount) <= 0) {
|
|
toast.error(
|
|
"Validation Error",
|
|
"Amount is required and must be greater than 0",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
setStep((s) => s + 1);
|
|
};
|
|
|
|
const handleSubmit = async () => {
|
|
if (!amount || parseFloat(amount) <= 0) {
|
|
toast.error(
|
|
"Validation Error",
|
|
"Amount is required and must be greater than 0",
|
|
);
|
|
throw new Error("Amount is required");
|
|
}
|
|
if (!transactionId) {
|
|
toast.error("Validation Error", "Transaction ID is required");
|
|
throw new Error("Transaction ID is required");
|
|
}
|
|
|
|
setSubmitting(true);
|
|
try {
|
|
const payload = {
|
|
amount: parseFloat(amount),
|
|
currency,
|
|
paymentDate,
|
|
paymentTime,
|
|
paymentMethod,
|
|
transactionId,
|
|
referenceNumber,
|
|
merchantName,
|
|
merchantId,
|
|
provider,
|
|
senderName,
|
|
senderPhone: senderPhone ? (senderPhone.startsWith("+") ? senderPhone : `+251${senderPhone}`) : undefined,
|
|
verifyWithProvider,
|
|
verifyWithVerifierApi,
|
|
};
|
|
|
|
await api.scan.paymentReceiptManual({ body: payload });
|
|
toast.success("Success", "Receipt added successfully!");
|
|
nav.back();
|
|
} catch (error: any) {
|
|
console.error("[AddReceipt] Error:", error);
|
|
const msg =
|
|
error?.response?.data?.message ||
|
|
error?.data?.message ||
|
|
error?.message ||
|
|
"Failed to add receipt";
|
|
toast.error("Error", msg);
|
|
throw error;
|
|
} finally {
|
|
setSubmitting(false);
|
|
}
|
|
};
|
|
|
|
const handlePickImage = async () => {
|
|
try {
|
|
const { status } =
|
|
await ImagePicker.requestMediaLibraryPermissionsAsync();
|
|
if (status !== "granted") {
|
|
toast.error(
|
|
"Permission Denied",
|
|
"We need access to your gallery to upload receipts.",
|
|
);
|
|
return;
|
|
}
|
|
|
|
const result = await ImagePicker.launchImageLibraryAsync({
|
|
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
|
allowsEditing: true,
|
|
quality: 0.8,
|
|
});
|
|
|
|
if (!result.canceled && result.assets && result.assets.length > 0) {
|
|
const uri = result.assets[0].uri;
|
|
await handleProcessImage(uri);
|
|
}
|
|
} catch (e: any) {
|
|
console.error("[AddReceipt] Pick Image Error:", e);
|
|
toast.error("Picker Failed", "Could not launch gallery picker.");
|
|
}
|
|
};
|
|
|
|
const handleProcessImage = async (uri: string) => {
|
|
setScanning(true);
|
|
toast.info("Processing...", "Uploading receipt to AI extraction engine.");
|
|
try {
|
|
const formData = new FormData();
|
|
const fileExt = uri.split(".").pop() || "jpg";
|
|
const fileName = `receipt-${Date.now()}.${fileExt}`;
|
|
const type = `image/${fileExt === "jpg" ? "jpeg" : fileExt}`;
|
|
|
|
formData.append("file", {
|
|
uri: Platform.OS === "android" ? uri : uri.replace("file://", ""),
|
|
name: fileName,
|
|
type: type,
|
|
} as any);
|
|
|
|
const response = await fetch(`${BASE_URL}scan/payment-receipt`, {
|
|
method: "POST",
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
Accept: "application/json",
|
|
},
|
|
body: formData,
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const err = await response
|
|
.json()
|
|
.catch(() => ({ message: "Scan processing failed." }));
|
|
throw new Error(err.message || "AI extraction failed.");
|
|
}
|
|
|
|
const scanResult = await response.json();
|
|
|
|
if (!scanResult.success) {
|
|
throw new Error(
|
|
scanResult.message || "AI extraction was unsuccessful.",
|
|
);
|
|
}
|
|
|
|
toast.success("Success!", "Data extracted successfully.");
|
|
|
|
const ocr = scanResult.data || {};
|
|
if (ocr.amount != null) setAmount(String(ocr.amount));
|
|
if (ocr.currency) setCurrency(ocr.currency);
|
|
if (ocr.paymentDate) {
|
|
try {
|
|
setPaymentDate(new Date(ocr.paymentDate).toISOString().split("T")[0]);
|
|
} catch (_) {}
|
|
}
|
|
if (ocr.paymentTime) setPaymentTime(ocr.paymentTime);
|
|
if (ocr.paymentMethod) setPaymentMethod(ocr.paymentMethod);
|
|
if (ocr.transactionId) setTransactionId(ocr.transactionId);
|
|
if (ocr.referenceNumber) setReferenceNumber(ocr.referenceNumber);
|
|
if (ocr.merchantName) setMerchantName(ocr.merchantName);
|
|
if (ocr.merchantId) setMerchantId(ocr.merchantId);
|
|
if (ocr.provider) setProvider(ocr.provider);
|
|
if (ocr.senderName) setSenderName(ocr.senderName);
|
|
if (ocr.senderPhone) setSenderPhone(ocr.senderPhone.replace(/^\+251|\++/g, ""));
|
|
|
|
try {
|
|
await handleSubmit();
|
|
} catch {
|
|
const nextCount = scanFailures + 1;
|
|
setScanFailures(nextCount);
|
|
if (nextCount >= 2) {
|
|
toast.info("Scan failed, fill details below", "");
|
|
} else {
|
|
toast.error("Extraction failed, try again", "");
|
|
}
|
|
}
|
|
} catch (err: any) {
|
|
console.error("[AddReceipt] Extraction Error:", err);
|
|
|
|
const nextCount = scanFailures + 1;
|
|
setScanFailures(nextCount);
|
|
|
|
if (nextCount >= 2) {
|
|
toast.info("Scan failed, fill details below", "");
|
|
} else {
|
|
toast.error("Extraction failed, try again", "");
|
|
}
|
|
} finally {
|
|
setScanning(false);
|
|
}
|
|
};
|
|
|
|
const formattedAmount = (parseFloat(amount) || 0).toLocaleString("en-US", {
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2,
|
|
});
|
|
|
|
return (
|
|
<ScreenWrapper className="bg-background">
|
|
<FormFlow
|
|
steps={STEPS}
|
|
currentStep={step}
|
|
onNext={handleNext}
|
|
onBack={() => setStep(step - 1)}
|
|
onComplete={handleSubmit}
|
|
loading={submitting}
|
|
completeLabel="Add Receipt"
|
|
>
|
|
{step === 0 && (
|
|
<>
|
|
<Pressable
|
|
onPress={handlePickImage}
|
|
disabled={scanning}
|
|
className="bg-primary/10 border border-primary/20 rounded-[8px] p-4 flex-row items-center gap-3.5 mb-5"
|
|
>
|
|
{scanning ? (
|
|
<ActivityIndicator color="#ea580c" size="small" />
|
|
) : (
|
|
<Upload color="#ea580c" size={20} strokeWidth={2.5} />
|
|
)}
|
|
<View className="flex-1">
|
|
<Text className="text-primary font-sans-black text-xs">
|
|
Scan from Gallery
|
|
</Text>
|
|
<Text className="text-muted-foreground text-[9px] font-sans-bold mt-0.5">
|
|
Upload image to auto-fill form
|
|
</Text>
|
|
</View>
|
|
</Pressable>
|
|
|
|
<Label>Payment Details</Label>
|
|
<View className="bg-card rounded-[6px] py-4 gap-4">
|
|
<View className="flex-row gap-4">
|
|
<Field
|
|
label="Amount"
|
|
value={amount}
|
|
onChangeText={setAmount}
|
|
placeholder="0.00"
|
|
numeric
|
|
flex={1}
|
|
/>
|
|
<View className="flex-1">
|
|
<Text className="text-[14px] font-sans-bold mb-1.5 ml-1 text-foreground">
|
|
Currency
|
|
</Text>
|
|
<Pressable
|
|
onPress={() => setShowCurrency(true)}
|
|
className="h-11 px-3 border border-border rounded-[6px] flex-row items-center justify-between"
|
|
style={{ backgroundColor: c.bg, borderColor: c.border }}
|
|
>
|
|
<Text
|
|
className="text-xs font-sans-bold"
|
|
style={{ color: c.text }}
|
|
>
|
|
{currency}
|
|
</Text>
|
|
<ChevronDown size={14} color={c.text} strokeWidth={3} />
|
|
</Pressable>
|
|
</View>
|
|
</View>
|
|
|
|
<View className="flex-row gap-4">
|
|
<View className="flex-1">
|
|
<Text className="text-[14px] font-sans-bold mb-1.5 ml-1 text-foreground">
|
|
Payment Date
|
|
</Text>
|
|
<Pressable
|
|
onPress={() => setShowPaymentDate(true)}
|
|
className="h-11 px-3 border border-border rounded-[6px] flex-row items-center justify-between"
|
|
style={{ backgroundColor: c.bg, borderColor: c.border }}
|
|
>
|
|
<Text
|
|
className="text-xs font-sans-medium"
|
|
style={{ color: c.text }}
|
|
>
|
|
{paymentDate}
|
|
</Text>
|
|
<CalendarSearch
|
|
size={14}
|
|
color="#ea580c"
|
|
strokeWidth={2.5}
|
|
/>
|
|
</Pressable>
|
|
</View>
|
|
<View className="flex-1">
|
|
<Text className="text-[14px] font-sans-bold mb-1.5 ml-1 text-foreground">
|
|
Payment Time
|
|
</Text>
|
|
<Pressable
|
|
onPress={() => setShowPaymentTime(true)}
|
|
className="h-11 px-3 border border-border rounded-[6px] flex-row items-center justify-between"
|
|
style={{ backgroundColor: c.bg, borderColor: c.border }}
|
|
>
|
|
<Text
|
|
className="text-xs font-sans-medium"
|
|
style={{ color: c.text }}
|
|
>
|
|
{paymentTime || "Select"}
|
|
</Text>
|
|
<Clock size={14} color="#ea580c" strokeWidth={2.5} />
|
|
</Pressable>
|
|
</View>
|
|
</View>
|
|
|
|
<View className="flex-row gap-4">
|
|
<View className="flex-1">
|
|
<Text className="text-[14px] font-sans-bold mb-1.5 ml-1 text-foreground">
|
|
Payment Method
|
|
</Text>
|
|
<Pressable
|
|
onPress={() => setShowPaymentMethod(true)}
|
|
className="h-11 px-3 border border-border rounded-[6px] flex-row items-center justify-between"
|
|
style={{ backgroundColor: c.bg, borderColor: c.border }}
|
|
>
|
|
<Text
|
|
className="text-xs font-sans-bold"
|
|
style={{ color: c.text }}
|
|
>
|
|
{paymentMethod}
|
|
</Text>
|
|
<ChevronDown size={14} color={c.text} strokeWidth={3} />
|
|
</Pressable>
|
|
</View>
|
|
</View>
|
|
<View className="flex-1">
|
|
<Text className="text-[14px] font-sans-bold mb-1.5 ml-1 text-foreground">
|
|
Provider
|
|
</Text>
|
|
<Pressable
|
|
onPress={() => setShowProvider(true)}
|
|
className="h-11 px-3 border border-border rounded-[6px] flex-row items-center justify-between"
|
|
style={{ backgroundColor: c.bg, borderColor: c.border }}
|
|
>
|
|
<Text
|
|
className="text-xs font-sans-bold"
|
|
style={{ color: c.text }}
|
|
>
|
|
{provider}
|
|
</Text>
|
|
<ChevronDown size={14} color={c.text} strokeWidth={3} />
|
|
</Pressable>
|
|
</View>
|
|
</View>
|
|
</>
|
|
)}
|
|
|
|
{step === 1 && (
|
|
<>
|
|
<Label>Transaction Info</Label>
|
|
<View className="bg-card rounded-[6px] py-4 gap-4">
|
|
<Field
|
|
label="Transaction ID"
|
|
value={transactionId}
|
|
onChangeText={setTransactionId}
|
|
placeholder="e.g. DAE9TDSO6T"
|
|
/>
|
|
<Field
|
|
label="Reference Number"
|
|
value={referenceNumber}
|
|
onChangeText={setReferenceNumber}
|
|
placeholder="e.g. REF-001"
|
|
/>
|
|
</View>
|
|
</>
|
|
)}
|
|
|
|
{step === 2 && (
|
|
<>
|
|
<Label>Merchant Details</Label>
|
|
<View className="bg-card rounded-[6px] py-4 gap-4">
|
|
<Field
|
|
label="Merchant Name"
|
|
value={merchantName}
|
|
onChangeText={setMerchantName}
|
|
placeholder="e.g. Acme Corp"
|
|
/>
|
|
<Field
|
|
label="Merchant ID"
|
|
value={merchantId}
|
|
onChangeText={setMerchantId}
|
|
placeholder="e.g. MER-123"
|
|
/>
|
|
</View>
|
|
</>
|
|
)}
|
|
|
|
{step === 3 && (
|
|
<>
|
|
<Label>Sender Info</Label>
|
|
<View className="bg-card rounded-[6px] py-4 gap-4">
|
|
<View className="flex-row gap-4">
|
|
<Field
|
|
label="Sender Name"
|
|
value={senderName}
|
|
onChangeText={setSenderName}
|
|
placeholder="e.g. John Doe"
|
|
flex={1}
|
|
/>
|
|
</View>
|
|
<View>
|
|
<Text className="text-[14px] font-sans-bold mb-1.5 ml-1 text-foreground">
|
|
Sender Phone
|
|
</Text>
|
|
<View className="flex-row items-center h-11 px-3 border border-border rounded-[6px]" style={{ backgroundColor: c.bg, borderColor: c.border }}>
|
|
<Text className="text-foreground font-sans-bold text-xs">+251</Text>
|
|
<TextInput
|
|
className="flex-1 ml-2 text-foreground text-xs font-sans-medium"
|
|
placeholder="912345678"
|
|
placeholderTextColor={c.placeholder}
|
|
value={senderPhone}
|
|
onChangeText={setSenderPhone}
|
|
keyboardType="phone-pad"
|
|
maxLength={9}
|
|
style={{ textAlignVertical: "center" }}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</>
|
|
)}
|
|
|
|
{step === 4 && (
|
|
<>
|
|
<Label>Verification</Label>
|
|
<View className="bg-card rounded-[6px] py-4 gap-6">
|
|
<View className="flex-row items-center justify-between">
|
|
<View className="flex-1">
|
|
<Text className="text-[14px] font-sans-bold text-foreground">
|
|
Verify with Provider
|
|
</Text>
|
|
<Text className="text-[12px] text-muted-foreground mt-0.5">
|
|
Check payment status with the provider
|
|
</Text>
|
|
</View>
|
|
<Switch
|
|
value={verifyWithProvider}
|
|
onValueChange={setVerifyWithProvider}
|
|
trackColor={{ false: "#334155", true: "#ea580c" }}
|
|
thumbColor={verifyWithProvider ? "#fff" : "#64748b"}
|
|
/>
|
|
</View>
|
|
<View className="flex-row items-center justify-between">
|
|
<View className="flex-1">
|
|
<Text className="text-[14px] font-sans-bold text-foreground">
|
|
Verify with Verifier API
|
|
</Text>
|
|
<Text className="text-[12px] text-muted-foreground mt-0.5">
|
|
Run verification through the verifier service
|
|
</Text>
|
|
</View>
|
|
<Switch
|
|
value={verifyWithVerifierApi}
|
|
onValueChange={setVerifyWithVerifierApi}
|
|
trackColor={{ false: "#334155", true: "#ea580c" }}
|
|
thumbColor={verifyWithVerifierApi ? "#fff" : "#64748b"}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</>
|
|
)}
|
|
|
|
{step === 5 && (
|
|
<>
|
|
<Label>Summary</Label>
|
|
<View className="bg-card rounded-[6px] mt-4 p-4 border border-border gap-3">
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
Amount
|
|
</Text>
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
{currency} {formattedAmount}
|
|
</Text>
|
|
</View>
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
Payment Date
|
|
</Text>
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
{paymentDate}
|
|
</Text>
|
|
</View>
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
Payment Time
|
|
</Text>
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
{paymentTime}
|
|
</Text>
|
|
</View>
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
Payment Method
|
|
</Text>
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
{paymentMethod}
|
|
</Text>
|
|
</View>
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
Provider
|
|
</Text>
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
{provider}
|
|
</Text>
|
|
</View>
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
Transaction ID
|
|
</Text>
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
{transactionId}
|
|
</Text>
|
|
</View>
|
|
{referenceNumber ? (
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
Reference
|
|
</Text>
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
{referenceNumber}
|
|
</Text>
|
|
</View>
|
|
) : null}
|
|
{merchantName ? (
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
Merchant
|
|
</Text>
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
{merchantName}
|
|
</Text>
|
|
</View>
|
|
) : null}
|
|
{merchantId ? (
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
Merchant ID
|
|
</Text>
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
{merchantId}
|
|
</Text>
|
|
</View>
|
|
) : null}
|
|
{senderName ? (
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
Sender
|
|
</Text>
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
{senderName}
|
|
</Text>
|
|
</View>
|
|
) : null}
|
|
{senderPhone ? (
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
Sender Phone
|
|
</Text>
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
+251{senderPhone}
|
|
</Text>
|
|
</View>
|
|
) : null}
|
|
<View className="border-t border-border/40 my-1" />
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-[16px] font-sans-bold text-foreground">
|
|
Total
|
|
</Text>
|
|
<Text className="text-[16px] font-sans-bold text-primary">
|
|
{currency} {formattedAmount}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
</>
|
|
)}
|
|
</FormFlow>
|
|
|
|
<PickerModal
|
|
visible={showCurrency}
|
|
onClose={() => setShowCurrency(false)}
|
|
title="Select Currency"
|
|
>
|
|
{currencies.map((curr) => (
|
|
<SelectOption
|
|
key={curr}
|
|
label={curr}
|
|
value={curr}
|
|
selected={currency === curr}
|
|
onSelect={(v) => {
|
|
setCurrency(v);
|
|
setShowCurrency(false);
|
|
}}
|
|
/>
|
|
))}
|
|
</PickerModal>
|
|
|
|
<PickerModal
|
|
visible={showPaymentMethod}
|
|
onClose={() => setShowPaymentMethod(false)}
|
|
title="Select Payment Method"
|
|
>
|
|
{paymentMethods.map((method) => (
|
|
<SelectOption
|
|
key={method}
|
|
label={method}
|
|
value={method}
|
|
selected={paymentMethod === method}
|
|
onSelect={(v) => {
|
|
setPaymentMethod(v);
|
|
setShowPaymentMethod(false);
|
|
}}
|
|
/>
|
|
))}
|
|
</PickerModal>
|
|
|
|
<PickerModal
|
|
visible={showProvider}
|
|
onClose={() => setShowProvider(false)}
|
|
title="Select Provider"
|
|
>
|
|
{providers.map((prov) => (
|
|
<SelectOption
|
|
key={prov}
|
|
label={prov}
|
|
value={prov}
|
|
selected={provider === prov}
|
|
onSelect={(v) => {
|
|
setProvider(v);
|
|
setShowProvider(false);
|
|
}}
|
|
/>
|
|
))}
|
|
</PickerModal>
|
|
|
|
<PickerModal
|
|
visible={showPaymentDate}
|
|
onClose={() => setShowPaymentDate(false)}
|
|
title="Select Payment Date"
|
|
>
|
|
<CalendarGrid
|
|
selectedDate={paymentDate}
|
|
onSelect={(v) => {
|
|
setPaymentDate(v);
|
|
setShowPaymentDate(false);
|
|
}}
|
|
/>
|
|
</PickerModal>
|
|
|
|
<TimerPickerModal
|
|
visible={showPaymentTime}
|
|
setIsVisible={setShowPaymentTime}
|
|
closeOnOverlayPress
|
|
LinearGradient={LinearGradient}
|
|
modalTitle="Select Time"
|
|
onCancel={() => setShowPaymentTime(false)}
|
|
onConfirm={(pickedDuration) => {
|
|
const hours = String(pickedDuration.hours ?? 0).padStart(2, "0");
|
|
const minutes = String(pickedDuration.minutes ?? 0).padStart(2, "0");
|
|
setPaymentTime(`${hours}:${minutes}`);
|
|
setShowPaymentTime(false);
|
|
}}
|
|
styles={{
|
|
theme: isDark ? "dark" : "light",
|
|
|
|
modalTitle: {
|
|
fontSize: 18,
|
|
fontWeight: "700",
|
|
},
|
|
contentContainer: {
|
|
width: "80%",
|
|
marginHorizontal: 16,
|
|
},
|
|
confirmButton: {
|
|
borderRadius: 8,
|
|
paddingHorizontal: 40,
|
|
},
|
|
cancelButton: {
|
|
borderRadius: 8,
|
|
borderWidth: 1,
|
|
borderColor: "#EDD5D1",
|
|
paddingHorizontal: 40,
|
|
},
|
|
}}
|
|
hideSeconds
|
|
/>
|
|
</ScreenWrapper>
|
|
);
|
|
}
|