373 lines
8.7 KiB
TypeScript
373 lines
8.7 KiB
TypeScript
import React, { useState, useEffect, useLayoutEffect } from "react";
|
||
import {
|
||
View,
|
||
Text,
|
||
StyleSheet,
|
||
TouchableOpacity,
|
||
ActivityIndicator,
|
||
} from "react-native";
|
||
import { useNavigation, useRoute } from "@react-navigation/native";
|
||
import { useRouter } from "expo-router";
|
||
import api from "@/services/axiosClient";
|
||
import { BASE_URL } from "@/constants/config";
|
||
import { useSnackbar } from "@/contexts/Snackbar";
|
||
import { SafeAreaView } from "react-native-safe-area-context";
|
||
|
||
// Import your success/failure icons
|
||
import SuccessIcon from "@/assets/icons/check_circle.svg";
|
||
import FailureIcon from "@/assets/icons/cancel.svg";
|
||
import PendingIcon from "@/assets/icons/pending.svg";
|
||
|
||
interface TransactionDetailData {
|
||
id: number;
|
||
amount: string;
|
||
status: string;
|
||
transaction_date: string | null;
|
||
upi_handle: string;
|
||
payment_mode: string[];
|
||
paid_by_upi_handle: string | null;
|
||
order_id: string;
|
||
payment_reference_id: string | null;
|
||
transaction_order_id: string;
|
||
}
|
||
|
||
interface TransactionResponse {
|
||
success: boolean;
|
||
data: {
|
||
payments: TransactionDetailData[];
|
||
pagination: {
|
||
total_records: number;
|
||
page_number: number;
|
||
page_size: number;
|
||
};
|
||
};
|
||
}
|
||
|
||
export default function TransactionDetailScreen() {
|
||
const navigation = useNavigation();
|
||
const route = useRoute();
|
||
const router = useRouter();
|
||
const { showSnackbar } = useSnackbar();
|
||
|
||
// Get payment ID from route params
|
||
const { paymentId } = route.params as { paymentId: number };
|
||
|
||
const [transactionData, setTransactionData] =
|
||
useState<TransactionDetailData | null>(null);
|
||
const [isLoading, setIsLoading] = useState(true);
|
||
|
||
useLayoutEffect(() => {
|
||
navigation.setOptions({
|
||
headerStyle: {
|
||
backgroundColor: "#F3F5F8",
|
||
},
|
||
headerTitle: "Transaction Detail",
|
||
headerTitleStyle: {
|
||
fontSize: 18,
|
||
fontWeight: "600",
|
||
color: "#111827",
|
||
},
|
||
headerLeft: () => (
|
||
<TouchableOpacity
|
||
onPress={() => router.back()}
|
||
style={styles.backButton}
|
||
>
|
||
<Text style={styles.backText}>‹</Text>
|
||
</TouchableOpacity>
|
||
),
|
||
});
|
||
}, [navigation, router]);
|
||
|
||
useEffect(() => {
|
||
fetchTransactionDetail();
|
||
}, [paymentId]);
|
||
|
||
const fetchTransactionDetail = async () => {
|
||
try {
|
||
setIsLoading(true);
|
||
const response = await api.get(
|
||
`${BASE_URL}/api/v1/payment-history?id=${paymentId}`
|
||
);
|
||
const result: TransactionResponse = response.data;
|
||
|
||
if (result.success && result.data.payments.length > 0) {
|
||
setTransactionData(result.data.payments[0]);
|
||
} else {
|
||
showSnackbar("Transaction details not found", "error");
|
||
router.back();
|
||
}
|
||
} catch (err) {
|
||
console.error("Error fetching transaction details:", err);
|
||
const errorMessage =
|
||
err instanceof Error ? err.message : "Something went wrong";
|
||
showSnackbar(errorMessage, "error");
|
||
router.back();
|
||
} finally {
|
||
setIsLoading(false);
|
||
}
|
||
};
|
||
|
||
const formatCurrency = (amount: string) => {
|
||
return `₹${parseFloat(amount).toLocaleString()}`;
|
||
};
|
||
|
||
const formatDateTime = (dateString: string | null) => {
|
||
if (!dateString) return "--";
|
||
const date = new Date(dateString);
|
||
const dateStr = date.toLocaleDateString("en-IN", {
|
||
day: "2-digit",
|
||
month: "long",
|
||
year: "numeric",
|
||
});
|
||
const timeStr = date.toLocaleTimeString("en-US", {
|
||
hour: "numeric",
|
||
minute: "2-digit",
|
||
hour12: true,
|
||
});
|
||
const dayStr = date.toLocaleDateString("en-US", { weekday: "long" });
|
||
return `${dateStr}, ${timeStr}, ${dayStr}`;
|
||
};
|
||
|
||
const getStatusIcon = (status: string) => {
|
||
if (status.toLowerCase() === "confirmed") {
|
||
return <SuccessIcon width={80} height={80} />;
|
||
} else if (status.toLowerCase() === "pending") {
|
||
return <PendingIcon width={80} height={80} />;
|
||
} else {
|
||
return <FailureIcon width={80} height={80} />;
|
||
}
|
||
};
|
||
|
||
const getStatusText = (status: string) => {
|
||
switch (status.toLowerCase()) {
|
||
case "success":
|
||
case "completed":
|
||
case "confirmed":
|
||
return "Payment successful";
|
||
case "failure":
|
||
case "failed":
|
||
return "Payment failed";
|
||
case "pending":
|
||
return "Payment pending";
|
||
default:
|
||
return `Payment ${status}`;
|
||
}
|
||
};
|
||
|
||
if (isLoading) {
|
||
return (
|
||
<SafeAreaView style={styles.container}>
|
||
<View style={styles.loadingContainer}>
|
||
<ActivityIndicator size="large" color="#00BE88" />
|
||
</View>
|
||
</SafeAreaView>
|
||
);
|
||
}
|
||
|
||
if (!transactionData) {
|
||
return (
|
||
<SafeAreaView style={styles.container}>
|
||
<View style={styles.errorContainer}>
|
||
<Text style={styles.errorText}>Transaction not found</Text>
|
||
</View>
|
||
</SafeAreaView>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<SafeAreaView style={styles.container}>
|
||
<View style={styles.content}>
|
||
{/* Status Icon */}
|
||
<View style={styles.iconContainer}>
|
||
{getStatusIcon(transactionData.status)}
|
||
</View>
|
||
|
||
{/* Amount */}
|
||
<Text style={styles.amount}>
|
||
{formatCurrency(transactionData.amount)}
|
||
</Text>
|
||
|
||
{/* Status Text */}
|
||
<Text style={styles.statusText}>
|
||
{getStatusText(transactionData.status)}
|
||
</Text>
|
||
|
||
{/* Date Time */}
|
||
<Text style={styles.dateTime}>
|
||
{formatDateTime(transactionData.transaction_date)}
|
||
</Text>
|
||
|
||
{/* Transaction Details Card */}
|
||
<View style={styles.detailsCard}>
|
||
<Text style={styles.detailsTitle}>Transaction Details</Text>
|
||
|
||
<DetailRow
|
||
label="Payment mode"
|
||
value={transactionData.payment_mode.join(", ") || "--"}
|
||
/>
|
||
|
||
<DetailRow
|
||
label="Paid to"
|
||
value={transactionData.upi_handle || "--"}
|
||
/>
|
||
|
||
<DetailRow
|
||
label="Paid by"
|
||
value={transactionData.paid_by_upi_handle || "--"}
|
||
/>
|
||
|
||
<DetailRow
|
||
label="Order ID"
|
||
value={transactionData.order_id || "--"}
|
||
/>
|
||
|
||
<DetailRow
|
||
label="Transaction ID"
|
||
value={transactionData.transaction_order_id || "--"}
|
||
/>
|
||
|
||
<DetailRow
|
||
label="RRN"
|
||
value={transactionData.payment_reference_id || "--"}
|
||
isLast={true}
|
||
/>
|
||
</View>
|
||
</View>
|
||
</SafeAreaView>
|
||
);
|
||
}
|
||
|
||
const DetailRow = ({
|
||
label,
|
||
value,
|
||
isLast = false,
|
||
}: {
|
||
label: string;
|
||
value: string;
|
||
isLast?: boolean;
|
||
}) => (
|
||
<View style={[styles.detailRow, isLast && styles.detailRowLast]}>
|
||
<Text style={styles.detailLabel}>{label}</Text>
|
||
<Text style={styles.detailValue}>{value}</Text>
|
||
</View>
|
||
);
|
||
|
||
const styles = StyleSheet.create({
|
||
container: {
|
||
flex: 1,
|
||
backgroundColor: "#F3F5F8",
|
||
},
|
||
loadingContainer: {
|
||
flex: 1,
|
||
justifyContent: "center",
|
||
alignItems: "center",
|
||
},
|
||
errorContainer: {
|
||
flex: 1,
|
||
justifyContent: "center",
|
||
alignItems: "center",
|
||
},
|
||
errorText: {
|
||
fontSize: 16,
|
||
color: "#6B7280",
|
||
},
|
||
content: {
|
||
flex: 1,
|
||
padding: 16,
|
||
alignItems: "center",
|
||
},
|
||
backButton: {
|
||
paddingLeft: 16,
|
||
paddingRight: 8,
|
||
paddingVertical: 8,
|
||
},
|
||
backText: {
|
||
fontSize: 24,
|
||
color: "#111827",
|
||
fontWeight: "300",
|
||
},
|
||
iconContainer: {
|
||
marginTop: 32,
|
||
marginBottom: 16,
|
||
},
|
||
successIcon: {
|
||
width: 60,
|
||
height: 60,
|
||
borderRadius: 30,
|
||
backgroundColor: "#00BE88",
|
||
justifyContent: "center",
|
||
alignItems: "center",
|
||
},
|
||
failureIcon: {
|
||
width: 60,
|
||
height: 60,
|
||
borderRadius: 30,
|
||
backgroundColor: "#EF4444",
|
||
justifyContent: "center",
|
||
alignItems: "center",
|
||
},
|
||
checkmark: {
|
||
color: "white",
|
||
fontSize: 24,
|
||
fontWeight: "bold",
|
||
},
|
||
xmark: {
|
||
color: "white",
|
||
fontSize: 20,
|
||
fontWeight: "bold",
|
||
},
|
||
amount: {
|
||
fontSize: 20,
|
||
fontWeight: "600",
|
||
color: "#252A34",
|
||
marginBottom: 16,
|
||
},
|
||
statusText: {
|
||
fontSize: 14,
|
||
fontWeight: "400",
|
||
color: "#252A34",
|
||
marginBottom: 4,
|
||
},
|
||
dateTime: {
|
||
fontSize: 14,
|
||
color: "#252A34",
|
||
textAlign: "center",
|
||
marginBottom: 24,
|
||
},
|
||
detailsCard: {
|
||
backgroundColor: "#FCFCFC",
|
||
borderRadius: 8,
|
||
padding: 16,
|
||
width: "100%",
|
||
},
|
||
detailsTitle: {
|
||
fontSize: 14,
|
||
fontWeight: "600",
|
||
color: "#252A34",
|
||
marginBottom: 8,
|
||
},
|
||
detailRow: {
|
||
flexDirection: "row",
|
||
justifyContent: "space-between",
|
||
alignItems: "flex-start",
|
||
paddingVertical: 8,
|
||
borderBottomWidth: 1,
|
||
borderBottomColor: "#E5E9F0",
|
||
},
|
||
detailRowLast: {
|
||
borderBottomWidth: 0,
|
||
},
|
||
detailLabel: {
|
||
fontSize: 14,
|
||
color: "#252A34",
|
||
flex: 1,
|
||
},
|
||
detailValue: {
|
||
fontSize: 14,
|
||
fontWeight: "600",
|
||
color: "#252A34",
|
||
flex: 1,
|
||
textAlign: "right",
|
||
},
|
||
});
|