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

303 lines
9.0 KiB
TypeScript

import React from "react";
import {
View,
Text,
ScrollView,
TouchableOpacity,
FlatList,
} from "react-native";
import { Button } from "~/components/ui/button";
import { LucideCreditCard, LucidePlus, LucideTrash } from "lucide-react-native";
import { ROUTES } from "~/lib/routes";
import { router } from "expo-router";
import { useAuthWithProfile } from "~/lib/hooks/useAuthWithProfile";
import { useUserWallet } from "~/lib/hooks/useUserWallet";
import { CreditCard } from "~/lib/services/walletService";
import TopBar from "~/components/ui/topBar";
import {
ApplePayIcon,
CreditDebitCardIcon,
GooglePayIcon,
} from "~/components/ui/icons";
import ScreenWrapper from "~/components/ui/ScreenWrapper";
import { Input } from "~/components/ui/input";
import ModalToast from "~/components/ui/toast";
import { useTranslation } from "react-i18next";
import PermissionAlertModal from "~/components/ui/permissionAlertModal";
// Individual Card Component
const CardItem = ({
card,
onRemove,
}: {
card: CreditCard;
onRemove: (card: CreditCard) => void;
}) => {
const getCardColor = (cardType: string) => {
switch (cardType?.toLowerCase()) {
case "visa":
return "bg-blue-50";
case "mastercard":
return "bg-green-50";
case "american express":
return "bg-green-50";
case "discover":
return "bg-orange-50";
default:
return "bg-gray-50";
}
};
const handleRemovePress = (card: CreditCard) => {
setSelectedCard(card);
setRemoveModalVisible(true);
};
const handleRemove = () => {
onRemove(card);
};
return (
<View
className={`flex flex-row justify-between w-full items-center py-4 ${getCardColor(
card.cardType || ""
)} rounded-md px-3`}
>
<View className="flex flex-row space-x-3 items-center flex-1">
<View className="bg-[#FFB668]/15 p-5 h-15 items-center justify-center flex rounded">
<CreditDebitCardIcon width={30} height={30} />
</View>
<View className="w-4" />
<View className="flex space-y-1 flex-1">
<Text className="font-dmsans text-primary">
{card.cardType || "Card"}
</Text>
<Text className="font-dmsans-medium text-secondary text-sm">
{card.cardNumber}
</Text>
<Text className="font-dmsans-medium text-gray-500 text-xs">
Expires {card.expiryDate}
</Text>
</View>
</View>
<View className="flex flex-row space-x-2 items-center">
<TouchableOpacity onPress={handleRemove} className="p-2 rounded-full">
<LucideTrash color="#FFB84D" className="text-red-600" size={20} />
</TouchableOpacity>
</View>
</View>
);
};
export default function ListCard() {
const { user } = useAuthWithProfile();
const { wallet, loading, error, removeCreditCard } = useUserWallet(user);
const [searchQuery, setSearchQuery] = React.useState("");
const { t } = useTranslation();
const [toastVisible, setToastVisible] = React.useState(false);
const [toastTitle, setToastTitle] = React.useState("");
const [toastDescription, setToastDescription] = React.useState<
string | undefined
>(undefined);
const [toastVariant, setToastVariant] = React.useState<
"success" | "error" | "warning" | "info"
>("info");
const toastTimeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(
null
);
const [removeModalVisible, setRemoveModalVisible] = React.useState(false);
const [selectedCard, setSelectedCard] = React.useState<CreditCard | null>(
null
);
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);
};
React.useEffect(() => {
return () => {
if (toastTimeoutRef.current) {
clearTimeout(toastTimeoutRef.current);
}
};
}, []);
const handleRemoveCard = async (cardId: string) => {
try {
await removeCreditCard(cardId);
showToast(
t("cards.toastRemoveSuccessTitle"),
t("cards.toastRemoveSuccess"),
"success"
);
} catch (error) {
showToast(
t("cards.toastRemoveErrorTitle"),
t("cards.toastRemoveError"),
"error"
);
}
};
const renderCards = () => {
if (loading) {
return (
<View className="flex items-center justify-center">
<Text className="text-gray-500 font-dmsans">
{t("cardmang.loading")}
</Text>
</View>
);
}
if (error) {
return (
<View className="flex items-center justify-center py-10">
<Text className="text-red-500 font-dmsans">
{t("cardmang.errorTitle")}
</Text>
<Text className="text-gray-400 font-dmsans-medium text-sm mt-1">
{error}
</Text>
</View>
);
}
if (!wallet?.cards || wallet.cards.length === 0) {
return (
<View className="flex items-center justify-center py-10">
<LucideCreditCard color="#D1D5DB" size={48} />
<Text className="text-gray-500 font-dmsans mt-4">
{t("cardmang.emptyTitle")}
</Text>
<Text className="text-gray-400 font-dmsans-medium text-sm mt-1 text-center px-4">
{t("cardmang.emptySubtitle")}
</Text>
</View>
);
}
return (
<FlatList
data={wallet.cards}
keyExtractor={(item, index) => item.id + String(index)}
scrollEnabled={false}
ItemSeparatorComponent={() => <View className="h-2" />}
renderItem={({ item }) => (
<CardItem card={item} onRemove={handleRemovePress} />
)}
/>
);
};
return (
<ScreenWrapper edges={["bottom"]}>
<View className="flex items-center h-full w-full">
<ScrollView
contentContainerStyle={{ paddingBottom: 96 }}
showsVerticalScrollIndicator={false}
>
<TopBar />
<View className="flex flex-col px-5 space-y-1 py-5 items-left">
<Text className="text-xl font-dmsans text-primary">
{t("cardmang.title")}
</Text>
<View className="h-2" />
<Text className="text-base font-dmsans text-gray-400">
{t("cardmang.subtitle")}
</Text>
</View>
<View className="px-5">
<Input
value={searchQuery}
onChangeText={setSearchQuery}
placeholderText={t("cardmang.searchPlaceholder")}
containerClassName="w-full"
borderClassName="border-[#D9DBE9] bg-white"
placeholderColor="#7E7E7E"
textClassName="text-[#000] text-sm"
/>
</View>
<View className="flex flex-col items-left px-5 py-5 w-full">
{/* Add Card Button */}
<View className="flex flex-row items-center w-full">
<Button
className="flex flex-row items-center space-x-2 bg-primary rounded-md p-3 w-full"
onPress={() => router.push(ROUTES.ADD_CARD)}
>
<LucidePlus color="#FFB84D" className="w-[14px] h-[14px]" />
<View className="w-2" />
<Text className="text-white text-sm font-dmsans-regular">
{t("cardmang.addCardButton")}
</Text>
</Button>
</View>
<View className="h-2" />
{/* Cards List */}
<View className="flex flex-col w-full mt-5">
<Text className="text-lg font-dmsans-medium text-primary mb-4">
{t("cardmang.paymentOptionsTitle")}
</Text>
{renderCards()}
</View>
</View>
</ScrollView>
</View>
<ModalToast
visible={toastVisible}
title={toastTitle}
description={toastDescription}
variant={toastVariant}
/>
<PermissionAlertModal
visible={removeModalVisible}
title={t("cardmang.removeTitle") || "Remove Card"}
message={
selectedCard
? t(
"cardmang.removeMessage",
"Are you sure you want to remove this card?"
)
: "Are you sure you want to remove this card?"
}
primaryText={t("cardmang.removeConfirm", "Remove")}
secondaryText={t("cardmang.removeCancel", "Cancel")}
primaryVariant="danger"
onPrimary={async () => {
if (selectedCard) {
await handleRemoveCard(selectedCard.id);
}
setRemoveModalVisible(false);
setSelectedCard(null);
}}
onSecondary={() => {
setRemoveModalVisible(false);
setSelectedCard(null);
}}
/>
</ScreenWrapper>
);
}