barsa 7502068ea9 refactor: remove unused billing and payment components, enhance animation capabilities
- Deleted loading and page components for invoices and payment methods to streamline the billing section.
- Updated AnimatedContainer, InlineToast, and other components to utilize framer-motion for improved animations.
- Refactored AppShell and Sidebar components to enhance layout and integrate new animation features.
- Adjusted various sections across the portal to ensure consistent animation behavior and visual appeal.
2026-03-06 14:48:34 +09:00

146 lines
5.1 KiB
TypeScript

"use client";
import { useState } from "react";
import { useParams } from "next/navigation";
import { LoadingCard, Skeleton } from "@/components/atoms/loading-skeleton";
import { ErrorState } from "@/components/atoms/error-state";
import { CheckCircleIcon, DocumentTextIcon } from "@heroicons/react/24/outline";
import { PageLayout } from "@/components/templates/PageLayout";
import { logger } from "@/core/logger";
import { openSsoLink } from "@/features/billing/utils/sso";
import { useInvoice, useCreateInvoiceSsoLink } from "@/features/billing/hooks";
import {
InvoiceItems,
InvoiceTotals,
InvoiceSummaryBar,
} from "@/features/billing/components/InvoiceDetail";
function InvoiceDetailSkeleton() {
return (
<PageLayout icon={<DocumentTextIcon />} title="Invoice">
<div className="space-y-6">
<LoadingCard />
<div className="bg-card text-card-foreground rounded-xl border border-border p-6 space-y-4 shadow-[var(--cp-shadow-1)]">
<div className="flex items-center justify-between">
<div className="space-y-2">
<Skeleton className="h-5 w-40" />
<Skeleton className="h-4 w-28" />
</div>
<div className="flex items-center gap-2">
<Skeleton className="h-9 w-24" />
<Skeleton className="h-9 w-28" />
</div>
</div>
<div className="space-y-2">
{Array.from({ length: 5 }).map((_, i) => (
<div key={i} className="flex items-center justify-between">
<Skeleton className="h-4 w-64" />
<Skeleton className="h-4 w-24" />
</div>
))}
</div>
</div>
</div>
</PageLayout>
);
}
export function InvoiceDetailContainer() {
const params = useParams();
const [loadingDownload, setLoadingDownload] = useState(false);
const [loadingPayment, setLoadingPayment] = useState(false);
const rawInvoiceParam = params["id"];
const invoiceIdParam = Array.isArray(rawInvoiceParam) ? rawInvoiceParam[0] : rawInvoiceParam;
const createSsoLinkMutation = useCreateInvoiceSsoLink();
const { data: invoice, error } = useInvoice(invoiceIdParam ?? "");
const isLoading = !invoice && !error;
const handleCreateSsoLink = (target: "view" | "download" | "pay" = "view") => {
void (async () => {
if (!invoice) return;
const isDownload = target === "download";
if (isDownload) setLoadingDownload(true);
else setLoadingPayment(true);
try {
const ssoLink = await createSsoLinkMutation.mutateAsync({ invoiceId: invoice.id, target });
openSsoLink(ssoLink.url, { newTab: !isDownload });
} catch (err) {
logger.error("Failed to create SSO link", err);
} finally {
if (isDownload) setLoadingDownload(false);
else setLoadingPayment(false);
}
})();
};
if (isLoading) return <InvoiceDetailSkeleton />;
if (error || !invoice) {
return (
<PageLayout
icon={<DocumentTextIcon />}
title="Invoice"
backLink={{ label: "Back to Billing", href: "/account/billing" }}
>
<ErrorState
title="Error loading invoice"
message={error instanceof Error ? error.message : "Invoice not found"}
variant="page"
/>
</PageLayout>
);
}
return (
<PageLayout
icon={<DocumentTextIcon />}
title={`Invoice #${invoice.id}`}
backLink={{ label: "Back to Billing", href: "/account/billing" }}
>
<div>
<div className="bg-card text-card-foreground rounded-xl shadow-[var(--cp-shadow-1)] border border-border overflow-hidden">
<InvoiceSummaryBar
invoice={invoice}
loadingDownload={loadingDownload}
loadingPayment={loadingPayment}
onDownload={() => handleCreateSsoLink("download")}
onPay={() => handleCreateSsoLink("pay")}
/>
{invoice.status === "Paid" && (
<div className="px-8 py-4 bg-success-soft border-b border-success/25">
<div className="flex items-center gap-3">
<div className="flex-shrink-0">
<CheckCircleIcon className="w-6 h-6 text-success" />
</div>
<div>
<h3 className="text-sm font-semibold text-foreground">Payment received</h3>
<p className="text-sm text-muted-foreground">
Paid on {invoice.paidDate || invoice.issuedAt}
</p>
</div>
</div>
</div>
)}
<div className="p-8">
<div className="space-y-8">
<InvoiceItems items={invoice.items ?? []} currency={invoice.currency} />
<div className="border-t border-border pt-8">
<InvoiceTotals
subtotal={invoice.subtotal}
tax={invoice.tax}
total={invoice.total}
/>
</div>
</div>
</div>
</div>
</div>
</PageLayout>
);
}
export default InvoiceDetailContainer;