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

434 lines
14 KiB
TypeScript

import React, { useRef, useState } from "react";
import {
Text,
View,
ScrollView,
TextInput,
ActivityIndicator,
} from "react-native";
import { Button } from "~/components/ui/button";
import { router, useLocalSearchParams } from "expo-router";
import BackButton from "~/components/ui/backButton";
import { useAuthWithProfile } from "~/lib/hooks/useAuthWithProfile";
import useTransactions from "~/lib/hooks/useTransactions";
import { ROUTES } from "~/lib/routes";
import ScreenWrapper from "~/components/ui/ScreenWrapper";
import { useTranslation } from "react-i18next";
import ModalToast from "~/components/ui/toast";
import { TicketService } from "~/lib/services/ticketService";
import { awardPoints } from "~/lib/services/pointsService";
import { applyReferral } from "~/lib/services/referralService";
export default function TransDetail() {
const { t } = useTranslation();
const params = useLocalSearchParams<{
transactionId?: string;
amount?: string;
type?: string;
recipientName?: string;
date?: string;
status?: string;
note?: string;
flowType?: string;
ticketTierId?: string;
eventId?: string;
ticketCount?: string;
fromHistory?: string;
}>();
const { user } = useAuthWithProfile();
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 [isProcessing, setIsProcessing] = useState(false);
const [referralCode, setReferralCode] = useState("");
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);
};
// Parse amount from cents to dollars
const amountInCents = parseInt(params.amount || "0");
const amountInDollars = amountInCents / 100;
// Calculate processing fee (1.25% of the amount)
const processingFeeInCents = Math.round(amountInCents * 0.0125);
const processingFeeInDollars = processingFeeInCents / 100;
// Calculate total (amount + processing fee)
const totalInCents = amountInCents + processingFeeInCents;
const totalInDollars = totalInCents / 100;
const isEventTicket = params.type === "event_ticket";
const fromHistory = params.fromHistory === "true";
// Format date
const formatDate = (dateString?: string) => {
if (!dateString) return t("transdetail.dateUnknown");
const date = new Date(dateString);
return date.toLocaleDateString("en-US", {
month: "2-digit",
day: "2-digit",
year: "numeric",
});
};
// Get transaction description based on type
const getTransactionDescription = () => {
switch (params.type) {
case "send":
return t("transdetail.descriptionSend", {
recipientName: params.recipientName,
});
case "receive":
return t("transdetail.descriptionReceive", {
recipientName: params.recipientName,
});
case "add_cash":
return t("transdetail.descriptionAddCash");
case "cash_out":
return t("transdetail.descriptionCashOut");
case "event_ticket":
return `Ticket purchase for ${params.recipientName || "event"}`;
default:
return t("transdetail.descriptionDefault");
}
};
const handlePrimaryAction = async () => {
if (isEventTicket) {
if (!user) {
showToast(
t("transconfirm.toastErrorTitle"),
"You must be logged in to buy tickets.",
"error"
);
return;
}
const ticketTierId = params.ticketTierId;
const eventId = params.eventId;
const ticketCount = params.ticketCount ? parseInt(params.ticketCount) : 1;
if (!ticketTierId || !eventId || !ticketCount || isNaN(ticketCount)) {
showToast(
t("transconfirm.toastErrorTitle"),
"Missing ticket information.",
"error"
);
return;
}
try {
setIsProcessing(true);
const token = await user.getIdToken();
await TicketService.buyTicket(token, {
ticketTierId,
eventId,
ticketCount,
});
try {
await awardPoints("purchase_ticket");
} catch (error) {
console.warn(
"[TransDetail] Failed to award purchase ticket points",
error
);
}
// Apply referral for event purchase if a code was provided
if (referralCode.trim() && eventId) {
try {
const referralResult = await applyReferral({
referralCode: referralCode.trim(),
uid: user.uid,
referralReason: "event",
contextId: eventId,
});
console.log(
"[TransDetail] Event referral apply result",
referralResult
);
if (!referralResult.success) {
console.warn(
"[TransDetail] Failed to apply event referral:",
referralResult.error
);
showToast(
t("transconfirm.toastErrorTitle"),
referralResult.error || "Failed to apply referral code",
"error"
);
}
} catch (referralError) {
console.error(
"[TransDetail] Error while applying event referral code",
referralError
);
showToast(
t("transconfirm.toastErrorTitle"),
"Failed to apply referral code",
"error"
);
}
}
router.replace({
pathname: "/(screens)/taskcomp",
params: {
amount: amountInDollars.toFixed(2),
message: `Ticket for ${
params.recipientName || "event"
} purchased successfully.`,
flowType: "event_ticket",
},
});
} catch (error) {
console.error("[TransDetail] Error buying ticket", error);
let apiMessage: string | undefined;
if (error instanceof Error) {
try {
const parsed = JSON.parse(error.message);
if (parsed && typeof parsed.message === "string") {
apiMessage = parsed.message;
}
} catch {}
}
showToast(
t("transconfirm.toastErrorTitle"),
apiMessage || "Could not complete ticket purchase. Please try again.",
"error"
);
} finally {
setIsProcessing(false);
}
return;
}
if (!fromHistory && referralCode.trim() && user && params.transactionId) {
try {
setIsProcessing(true);
const referralResult = await applyReferral({
referralCode: referralCode.trim(),
uid: user.uid,
referralReason: "transaction",
contextId: params.transactionId,
});
console.log(
"[TransDetail] Transaction referral apply result",
referralResult
);
if (!referralResult.success) {
console.warn(
"[TransDetail] Failed to apply transaction referral:",
referralResult.error
);
showToast(
t("transconfirm.toastErrorTitle"),
referralResult.error || "Failed to apply referral code",
"error"
);
}
} catch (referralError) {
console.error(
"[TransDetail] Error while applying transaction referral code",
referralError
);
showToast(
t("transconfirm.toastErrorTitle"),
"Failed to apply referral code",
"error"
);
} finally {
setIsProcessing(false);
}
}
router.replace(ROUTES.HOME);
router.push(ROUTES.SEND_OR_REQUEST_MONEY);
};
return (
<ScreenWrapper edges={[]}>
<View className="flex-1 w-full justify-between">
<ScrollView
contentContainerStyle={{
justifyContent: "center",
paddingBottom: 24,
}}
className="w-full flex-1"
>
<View>
<BackButton />
</View>
<View
className="flex flex-col py-5 pt-20 items-center"
style={{ gap: 4 }}
>
<Text className="text-xl font-dmsans-bold text-primary">
{t("transdetail.title")}
</Text>
</View>
<View className="bg-secondary-foreground mx-3 rounded py-10 mt-5">
<View className="flex items-center py-5" style={{ gap: 8 }}>
<Text className="text-2xl font-dmsans-bold text-primary">
${amountInDollars.toFixed(2)}
</Text>
<Text
className="text-sm text-gray-500 font-dmsans"
style={{ textAlign: "center" }}
>
{getTransactionDescription()}
</Text>
</View>
<View
className="flex px-2 py-5 border-t border-b border-gray-300 m-3"
style={{ gap: 8 }}
>
<Text className="text-sm font-dmsans-bold mt-5">
{t("transdetail.sectionTitle")}
</Text>
<View className="flex flex-row justify-between w-full">
<Text className="font-dmsans">
{t("transdetail.dateLabel")}
</Text>
<Text className="font-dmsans" style={{ textAlign: "right" }}>
{formatDate(params.date)}
</Text>
</View>
<View className="flex flex-row justify-between w-full">
<Text className="font-dmsans">
{t("transdetail.statusLabel")}
</Text>
<Text className="font-dmsans" style={{ textAlign: "right" }}>
{params.status || t("transdetail.statusUnknown")}
</Text>
</View>
{params.note && (
<View className="flex flex-row justify-between w-full">
<Text className="font-dmsans">
{t("transdetail.noteLabel")}
</Text>
<Text className="font-dmsans" style={{ textAlign: "right" }}>
{params.note}
</Text>
</View>
)}
<View className="flex flex-row justify-between w-full">
<Text className="font-dmsans">
{t("transdetail.processingFeeLabel")}
</Text>
<Text className="font-dmsans" style={{ textAlign: "right" }}>
${processingFeeInDollars.toFixed(2)}
</Text>
</View>
<View className="flex flex-row justify-between w-full">
<Text className="font-dmsans">
{t("transdetail.subtotalLabel")}
</Text>
<Text className="font-dmsans" style={{ textAlign: "right" }}>
${amountInDollars.toFixed(2)}
</Text>
</View>
<View className="flex flex-row justify-between w-full border-t border-gray-300 pt-5">
<Text className="font-dmsans-bold text-base">
{t("transdetail.totalLabel")}
</Text>
<Text
className="font-dmsans text-base"
style={{ textAlign: "right" }}
>
${totalInDollars.toFixed(2)}
</Text>
</View>
</View>
</View>
{/* Referral code input (only shown when not coming from History) */}
{!fromHistory && (
<View className="px-6 mt-6">
<Text className="text-base font-dmsans text-black mb-2">
{t("transdetail.referralCodeLabel", "Referral Code")}
</Text>
<TextInput
value={referralCode}
onChangeText={setReferralCode}
placeholder="Enter referral code"
placeholderTextColor="#9CA3AF"
className="w-full rounded-2xl px-4 py-3 bg-[#FFF5E9] border border-[#D9E2D5] font-dmsans text-base text-black"
/>
</View>
)}
</ScrollView>
<View className="w-full px-5 pb-8" style={{ gap: 12 }}>
<Button
className="bg-primary rounded-full"
onPress={handlePrimaryAction}
disabled={isProcessing}
>
{isProcessing ? (
<View
className="flex-row items-center justify-center"
style={{ gap: 8 }}
>
<ActivityIndicator size="small" color="#FFFFFF" />
<Text className="font-dmsans text-white">Processing...</Text>
</View>
) : (
<Text className="font-dmsans text-white">
{isEventTicket ? "Pay" : t("transdetail.sendAgainButton")}
</Text>
)}
</Button>
</View>
</View>
<ModalToast
visible={toastVisible}
title={toastTitle}
description={toastDescription}
variant={toastVariant}
/>
</ScreenWrapper>
);
}