"use client"; import { useState } from "react"; import { CreditCardIcon } from "@heroicons/react/24/outline"; import { PageLayout } from "@/components/templates/PageLayout"; import { ErrorBoundary } from "@/components/molecules"; import { useSession } from "@/features/auth/hooks"; import { useAuthStore } from "@/features/auth/stores/auth.store"; import { isApiError } from "@/core/api"; import { openSsoLink } from "@/features/billing/utils/sso"; import { usePaymentRefresh } from "@/features/billing/hooks/usePaymentRefresh"; import { PaymentMethodCard, usePaymentMethods, useCreatePaymentMethodsSsoLink, } from "@/features/billing"; import type { PaymentMethodList } from "@customer-portal/domain/payments"; import { InlineToast } from "@/components/atoms/inline-toast"; import { Button } from "@/components/atoms/button"; import { Skeleton } from "@/components/atoms/loading-skeleton"; import { InvoicesList } from "@/features/billing/components/InvoiceList/InvoiceList"; import { logger } from "@/core/logger"; function PaymentMethodsSkeleton() { return (
{Array.from({ length: 2 }).map((_, i) => (
))}
); } function PaymentMethodsSection({ paymentMethodsData, onManage, isPending, }: { paymentMethodsData: PaymentMethodList; onManage: () => void; isPending: boolean; }) { const hasMethods = paymentMethodsData.paymentMethods.length > 0; return (

Payment Methods

{hasMethods ? `${paymentMethodsData.paymentMethods.length} payment method${paymentMethodsData.paymentMethods.length === 1 ? "" : "s"} on file` : "No payment methods on file"}

{hasMethods && (
{paymentMethodsData.paymentMethods.map(paymentMethod => ( ))}
)}
); } export function BillingOverview() { const [error, setError] = useState(null); const { isAuthenticated } = useSession(); const paymentMethodsQuery = usePaymentMethods(); const { data: paymentMethodsData, isLoading: isLoadingPaymentMethods, isFetching: isFetchingPaymentMethods, error: paymentMethodsError, } = paymentMethodsQuery; const createPaymentMethodsSsoLink = useCreatePaymentMethodsSsoLink(); const { hasCheckedAuth } = useAuthStore(); const paymentRefresh = usePaymentRefresh({ refetch: async () => { const result = await paymentMethodsQuery.refetch(); return { data: result.data }; }, hasMethods: data => Boolean(data && (data.totalCount > 0 || data.paymentMethods.length > 0)), attachFocusListeners: true, }); const openPaymentMethods = async () => { if (!isAuthenticated) { setError("Please log in to access payment methods."); return; } setError(null); try { const ssoLink = await createPaymentMethodsSsoLink.mutateAsync(); openSsoLink(ssoLink.url, { newTab: true }); } catch (err: unknown) { logger.error("Failed to open payment methods", err); if ( isApiError(err) && "response" in err && typeof err.response === "object" && err.response !== null && "status" in err.response && err.response.status === 401 ) { setError("Authentication failed. Please log in again."); } else { setError("Unable to access payment methods. Please try again later."); } } }; const isPaymentLoading = !hasCheckedAuth || isLoadingPaymentMethods || isFetchingPaymentMethods; const combinedError = (() => { if (error) return new Error(error); if (paymentMethodsError instanceof Error) return paymentMethodsError; if (paymentMethodsError) return new Error(String(paymentMethodsError)); return null; })(); return ( } title="Billing" error={combinedError}>
{isPaymentLoading && } {!isPaymentLoading && paymentMethodsData && ( void openPaymentMethods()} isPending={createPaymentMethodsSsoLink.isPending} /> )}

Invoices

); } export default BillingOverview;