195 lines
7.7 KiB
TypeScript
Raw Normal View History

"use client";
import { SubCard } from "@/components/molecules/SubCard/SubCard";
import { DetailHeader } from "@/components/molecules/DetailHeader/DetailHeader";
import { useEffect, useState } from "react";
import { useParams, useSearchParams } from "next/navigation";
import Link from "next/link";
import { ServerIcon, CalendarIcon, DocumentTextIcon } from "@heroicons/react/24/outline";
import { format } from "date-fns";
import { useSubscription } from "@/features/subscriptions/hooks";
import { InvoicesList } from "@/features/billing/components/InvoiceList/InvoiceList";
import { Formatting } from "@customer-portal/domain/toolkit";
import { PageLayout } from "@/components/templates/PageLayout";
const { formatCurrency: sharedFormatCurrency } = Formatting;
import { SimManagementSection } from "@/features/sim-management";
import {
getBillingCycleLabel,
getSubscriptionStatusIcon,
getSubscriptionStatusVariant,
} from "@/features/subscriptions/utils/status-presenters";
export function SubscriptionDetailContainer() {
const params = useParams();
const searchParams = useSearchParams();
const [showInvoices, setShowInvoices] = useState(true);
const [showSimManagement, setShowSimManagement] = useState(false);
const subscriptionId = parseInt(params.id as string);
const { data: subscription, isLoading, error } = useSubscription(subscriptionId);
useEffect(() => {
const updateVisibility = () => {
const hash = typeof window !== "undefined" ? window.location.hash : "";
const service = (searchParams.get("service") || "").toLowerCase();
const isSimContext = hash.includes("sim-management") || service === "sim";
if (isSimContext) {
setShowInvoices(false);
setShowSimManagement(true);
} else {
setShowInvoices(true);
setShowSimManagement(false);
}
};
updateVisibility();
if (typeof window !== "undefined") {
window.addEventListener("hashchange", updateVisibility);
return () => window.removeEventListener("hashchange", updateVisibility);
}
return;
}, [searchParams]);
const formatDate = (dateString: string | undefined) => {
if (!dateString) return "N/A";
try {
return format(new Date(dateString), "MMM d, yyyy");
} catch {
return "Invalid date";
}
};
const formatCurrency = (amount: number) => sharedFormatCurrency(amount || 0);
const pageError =
error || !subscription
? process.env.NODE_ENV === "development"
? error instanceof Error
? error.message
: "Subscription not found"
: "Unable to load subscription details. Please try again."
: null;
const isSimService = Boolean(subscription?.productName?.toLowerCase().includes("sim"));
return (
<PageLayout
icon={<ServerIcon className="h-6 w-6" />}
title={subscription?.productName ?? "Subscription"}
description={
subscription ? `Service ID: ${subscription.serviceId}` : "View your subscription details"
}
breadcrumbs={[
{ label: "Subscriptions", href: "/subscriptions" },
{ label: subscription?.productName ?? "Subscription" },
]}
loading={isLoading}
error={pageError}
>
{subscription ? (
<div className="max-w-7xl mx-auto">
<SubCard className="mb-6">
<DetailHeader
title="Subscription Details"
subtitle="Service subscription information"
leftIcon={getSubscriptionStatusIcon(subscription.status)}
status={{
label: subscription.status,
variant: getSubscriptionStatusVariant(subscription.status),
}}
/>
<div className="pt-4">
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div>
<h4 className="text-sm font-medium text-muted-foreground uppercase tracking-wider">
Billing Amount
</h4>
<div className="mt-2 flex items-baseline gap-2">
<p className="text-2xl font-bold text-foreground">
{formatCurrency(subscription.amount)}
</p>
<span className="text-sm text-muted-foreground">
{getBillingCycleLabel(subscription.cycle)}
</span>
</div>
</div>
<div>
<h4 className="text-sm font-medium text-muted-foreground uppercase tracking-wider">
Next Due Date
</h4>
<div className="flex items-center mt-2">
<CalendarIcon className="h-4 w-4 text-muted-foreground/70 mr-2" />
<p className="text-lg font-medium text-foreground">
{formatDate(subscription.nextDue)}
</p>
</div>
</div>
<div>
<h4 className="text-sm font-medium text-muted-foreground uppercase tracking-wider">
Registration Date
</h4>
<div className="flex items-center mt-2">
<CalendarIcon className="h-4 w-4 text-muted-foreground/70 mr-2" />
<p className="text-lg font-medium text-foreground">
{formatDate(subscription.registrationDate)}
</p>
</div>
</div>
</div>
</div>
</SubCard>
{isSimService && (
<div className="mb-8">
<SubCard>
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4">
<div>
<h3 className="text-xl font-semibold text-foreground">Service Management</h3>
<p className="text-sm text-muted-foreground mt-1">
Switch between billing and SIM management views
</p>
</div>
<div className="flex flex-col sm:flex-row gap-2 bg-muted rounded-xl p-2 border border-border/60">
<Link
href={`/subscriptions/${subscriptionId}#sim-management`}
className={`px-6 py-3 text-sm font-semibold rounded-lg transition-colors min-w-[140px] text-center ${
showSimManagement
? "bg-card text-primary shadow-[var(--cp-shadow-1)]"
: "text-muted-foreground hover:text-foreground hover:bg-card/60"
}`}
>
<ServerIcon className="h-4 w-4 inline mr-2" />
SIM Management
</Link>
<Link
href={`/subscriptions/${subscriptionId}`}
className={`px-6 py-3 text-sm font-semibold rounded-lg transition-colors min-w-[120px] text-center ${
showInvoices
? "bg-card text-primary shadow-[var(--cp-shadow-1)]"
: "text-muted-foreground hover:text-foreground hover:bg-card/60"
}`}
>
<DocumentTextIcon className="h-4 w-4 inline mr-2" />
Invoices
</Link>
</div>
</div>
</SubCard>
</div>
)}
{showSimManagement && (
<div className="mb-10">
<SimManagementSection subscriptionId={subscriptionId} />
</div>
)}
{showInvoices && <InvoicesList subscriptionId={subscriptionId} pageSize={5} />}
</div>
) : null}
</PageLayout>
);
}
export default SubscriptionDetailContainer;