246 lines
10 KiB
TypeScript
Raw Normal View History

"use client";
import { useEffect, useState } from "react";
import { useParams, useSearchParams } from "next/navigation";
import Link from "next/link";
import {
ServerIcon,
CalendarIcon,
DocumentTextIcon,
GlobeAltIcon,
XCircleIcon,
} from "@heroicons/react/24/outline";
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";
import { StatusPill } from "@/components/atoms/status-pill";
import { formatIsoDate } from "@/lib/utils";
const { formatCurrency: sharedFormatCurrency } = Formatting;
import { SimManagementSection } from "@/features/sim";
import {
getBillingCycleLabel,
getSubscriptionStatusVariant,
} from "@/features/subscriptions/utils/status-presenters";
import { cn } from "@/lib/utils";
export function SubscriptionDetailContainer() {
const params = useParams();
const searchParams = useSearchParams();
const [activeTab, setActiveTab] = useState<"overview" | "sim">("overview");
const subscriptionId = parseInt(params.id as string);
const { data: subscription, isLoading, error } = useSubscription(subscriptionId);
useEffect(() => {
const updateTab = () => {
const hash = typeof window !== "undefined" ? window.location.hash : "";
const service = (searchParams.get("service") || "").toLowerCase();
const isSimContext = hash.includes("sim-management") || service === "sim";
setActiveTab(isSimContext ? "sim" : "overview");
};
updateTab();
if (typeof window !== "undefined") {
window.addEventListener("hashchange", updateTab);
return () => window.removeEventListener("hashchange", updateTab);
}
return;
}, [searchParams]);
const formatDate = (dateString: string | undefined) => formatIsoDate(dateString);
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 productNameLower = subscription?.productName?.toLowerCase() ?? "";
const isSimService = productNameLower.includes("sim");
// Match: "Internet", "SonixNet via NTT Optical Fiber", or any NTT-based fiber service
const isInternetService =
productNameLower.includes("internet") ||
productNameLower.includes("sonixnet") ||
(productNameLower.includes("ntt") && productNameLower.includes("fiber"));
const canCancel = subscription?.status === "Active";
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: "/account/subscriptions" },
{ label: subscription?.productName ?? "Subscription" },
]}
loading={isLoading}
error={pageError}
>
{subscription ? (
<div className="space-y-6">
{/* Main Subscription Card */}
<div className="bg-card border border-border rounded-2xl shadow-sm overflow-hidden">
{/* Header with status */}
<div className="px-6 py-5 border-b border-border">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="h-10 w-10 rounded-xl bg-primary/10 flex items-center justify-center">
<ServerIcon className="h-5 w-5 text-primary" />
</div>
<div>
<h2 className="text-lg font-semibold text-foreground">Subscription Details</h2>
<p className="text-sm text-muted-foreground">
Service subscription information
</p>
</div>
</div>
<StatusPill
label={subscription.status}
variant={getSubscriptionStatusVariant(subscription.status)}
/>
</div>
</div>
{/* Stats Row */}
<div className="px-6 py-5 grid grid-cols-1 md:grid-cols-3 gap-6 bg-muted/20">
<div>
<h4 className="text-xs font-semibold 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-xs font-semibold text-muted-foreground uppercase tracking-wider">
Next Due Date
</h4>
<div className="flex items-center mt-2 gap-2">
<CalendarIcon className="h-4 w-4 text-muted-foreground" />
<p className="text-lg font-medium text-foreground">
{formatDate(subscription.nextDue)}
</p>
</div>
</div>
<div>
<h4 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">
Registration Date
</h4>
<div className="flex items-center mt-2 gap-2">
<CalendarIcon className="h-4 w-4 text-muted-foreground" />
<p className="text-lg font-medium text-foreground">
{formatDate(subscription.registrationDate)}
</p>
</div>
</div>
</div>
</div>
{/* Tab Navigation for SIM Services */}
{isSimService && (
<div className="flex items-center gap-1 p-1 bg-muted rounded-lg w-fit">
<Link
href={`/account/subscriptions/${subscriptionId}`}
className={cn(
"px-4 py-2 text-sm font-medium rounded-md transition-all flex items-center gap-2",
activeTab === "overview"
? "bg-card text-foreground shadow-sm"
: "text-muted-foreground hover:text-foreground"
)}
>
<DocumentTextIcon className="h-4 w-4" />
Overview & Billing
</Link>
<Link
href={`/account/subscriptions/${subscriptionId}#sim-management`}
className={cn(
"px-4 py-2 text-sm font-medium rounded-md transition-all flex items-center gap-2",
activeTab === "sim"
? "bg-card text-foreground shadow-sm"
: "text-muted-foreground hover:text-foreground"
)}
>
<ServerIcon className="h-4 w-4" />
SIM Management
</Link>
</div>
)}
{/* SIM Management Section */}
{activeTab === "sim" && isSimService && (
<SimManagementSection subscriptionId={subscriptionId} />
)}
{/* Billing History Section */}
{activeTab === "overview" && (
<div className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-lg font-semibold text-foreground">Billing History</h3>
</div>
<InvoicesList subscriptionId={subscriptionId} pageSize={5} showFilters={false} />
</div>
)}
{/* Internet Service Actions */}
{isInternetService && activeTab === "overview" && (
<div className="bg-card border border-border rounded-2xl shadow-sm overflow-hidden">
<div className="px-6 py-5 border-b border-border">
<div className="flex items-center gap-3">
<div className="h-10 w-10 rounded-xl bg-primary/10 flex items-center justify-center">
<GlobeAltIcon className="h-5 w-5 text-primary" />
</div>
<div>
<h2 className="text-lg font-semibold text-foreground">Service Actions</h2>
<p className="text-sm text-muted-foreground">
Manage your Internet subscription
</p>
</div>
</div>
</div>
<div className="px-6 py-5">
{canCancel ? (
<div className="flex items-center justify-between">
<div>
<h4 className="text-sm font-medium text-foreground">Cancel Service</h4>
<p className="text-xs text-muted-foreground mt-0.5">
Request cancellation of your Internet subscription
</p>
</div>
<Link
href={`/account/subscriptions/${subscriptionId}/internet/cancel`}
className="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-danger-foreground bg-danger hover:bg-danger/90 rounded-lg transition-colors"
>
<XCircleIcon className="h-4 w-4" />
Request Cancellation
</Link>
</div>
) : (
<p className="text-sm text-muted-foreground">
Service actions are not available for {subscription.status.toLowerCase()}{" "}
subscriptions.
</p>
)}
</div>
</div>
)}
</div>
) : null}
</PageLayout>
);
}
export default SubscriptionDetailContainer;