import { uiLocale } from '@/utils/uiLocale'; import { useState } from 'react'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router'; import { motion } from 'framer-motion'; import { balanceApi } from '../api/balance'; import { useToast } from '../components/Toast'; import { useDestructiveConfirm } from '../platform/hooks/useNativeDialog'; import { Card } from '@/components/data-display/Card'; import { Button } from '@/components/primitives/Button'; import { BackIcon } from '@/components/icons'; import { staggerContainer, staggerItem } from '@/components/motion/transitions'; function formatCardDate(dateStr: string): string { try { const date = new Date(dateStr); if (isNaN(date.getTime())) return dateStr; return date.toLocaleDateString(uiLocale()); } catch { return dateStr; } } export default function SavedCards() { const { t } = useTranslation(); const navigate = useNavigate(); const queryClient = useQueryClient(); const { showToast } = useToast(); const confirmDelete = useDestructiveConfirm(); const { data: savedCardsData, isLoading, isError, } = useQuery({ queryKey: ['saved-cards'], queryFn: balanceApi.getSavedCards, }); const savedCards = savedCardsData?.cards; const [deletingCardId, setDeletingCardId] = useState(null); const handleDeleteCard = async (cardId: number) => { if (deletingCardId !== null) return; const confirmed = await confirmDelete( t('balance.savedCards.confirmUnlink'), t('balance.savedCards.unlink'), ); if (!confirmed) return; setDeletingCardId(cardId); try { await balanceApi.deleteSavedCard(cardId); await queryClient.invalidateQueries({ queryKey: ['saved-cards'] }); showToast({ type: 'success', title: t('balance.savedCards.unlinkSuccess'), message: '', duration: 3000, }); } catch (error) { console.error('Failed to unlink card:', error); showToast({ type: 'error', title: t('balance.savedCards.unlinkError'), message: '', duration: 3000, }); } finally { setDeletingCardId(null); } }; return ( // key: remount the container when loading resolves — stagger orchestration // runs once on mount, so cards arriving from the API later would otherwise // stay stuck at their initial variant (opacity 0) after a hard refresh {/* Header */}

{t('balance.savedCards.pageTitle')}

{/* Loading state */} {isLoading && (
{[1, 2].map((i) => (
))}
)} {/* Error state */} {isError && (
{t('balance.savedCards.loadError')}
)} {/* Cards List */} {!isLoading && !isError && savedCards && savedCards.length > 0 ? (
{savedCards.map((card) => (
💳
{card.title || `${card.card_type || t('balance.savedCards.card')} ${card.card_last4 ? `*${card.card_last4}` : ''}`}
{t('balance.savedCards.linkedAt', { date: formatCardDate(card.created_at), })}
))}
) : !isLoading && !isError && savedCards ? ( /* Empty state - only show when data loaded and empty */
💳
{t('balance.savedCards.empty')}
) : null} ); }