2025-09-17 18:43:43 +09:00
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import { useEffect, useState } from "react";
|
|
|
|
|
import { useParams, useSearchParams } from "next/navigation";
|
|
|
|
|
import Link from "next/link";
|
2025-12-23 15:19:20 +09:00
|
|
|
import {
|
|
|
|
|
ServerIcon,
|
|
|
|
|
CalendarIcon,
|
|
|
|
|
DocumentTextIcon,
|
|
|
|
|
GlobeAltIcon,
|
|
|
|
|
XCircleIcon,
|
|
|
|
|
} from "@heroicons/react/24/outline";
|
2025-09-17 18:43:43 +09:00
|
|
|
import { useSubscription } from "@/features/subscriptions/hooks";
|
|
|
|
|
import { InvoicesList } from "@/features/billing/components/InvoiceList/InvoiceList";
|
2025-10-09 10:49:03 +09:00
|
|
|
import { Formatting } from "@customer-portal/domain/toolkit";
|
2025-12-16 13:54:31 +09:00
|
|
|
import { PageLayout } from "@/components/templates/PageLayout";
|
2025-12-16 16:08:17 +09:00
|
|
|
import { StatusPill } from "@/components/atoms/status-pill";
|
2025-12-29 17:17:36 +09:00
|
|
|
import { formatIsoDate } from "@/lib/utils";
|
2025-10-09 10:49:03 +09:00
|
|
|
|
2025-10-22 10:58:16 +09:00
|
|
|
const { formatCurrency: sharedFormatCurrency } = Formatting;
|
2025-12-26 17:27:22 +09:00
|
|
|
import { SimManagementSection } from "@/features/sim";
|
2025-11-18 14:06:27 +09:00
|
|
|
import {
|
|
|
|
|
getBillingCycleLabel,
|
|
|
|
|
getSubscriptionStatusVariant,
|
|
|
|
|
} from "@/features/subscriptions/utils/status-presenters";
|
2025-12-16 16:08:17 +09:00
|
|
|
import { cn } from "@/lib/utils";
|
2025-09-17 18:43:43 +09:00
|
|
|
|
|
|
|
|
export function SubscriptionDetailContainer() {
|
|
|
|
|
const params = useParams();
|
|
|
|
|
const searchParams = useSearchParams();
|
2025-12-16 16:08:17 +09:00
|
|
|
const [activeTab, setActiveTab] = useState<"overview" | "sim">("overview");
|
2025-09-17 18:43:43 +09:00
|
|
|
|
|
|
|
|
const subscriptionId = parseInt(params.id as string);
|
|
|
|
|
const { data: subscription, isLoading, error } = useSubscription(subscriptionId);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2025-12-16 16:08:17 +09:00
|
|
|
const updateTab = () => {
|
2025-09-17 18:43:43 +09:00
|
|
|
const hash = typeof window !== "undefined" ? window.location.hash : "";
|
|
|
|
|
const service = (searchParams.get("service") || "").toLowerCase();
|
|
|
|
|
const isSimContext = hash.includes("sim-management") || service === "sim";
|
2025-12-16 16:08:17 +09:00
|
|
|
setActiveTab(isSimContext ? "sim" : "overview");
|
2025-09-17 18:43:43 +09:00
|
|
|
};
|
2025-12-16 16:08:17 +09:00
|
|
|
updateTab();
|
2025-09-17 18:43:43 +09:00
|
|
|
if (typeof window !== "undefined") {
|
2025-12-16 16:08:17 +09:00
|
|
|
window.addEventListener("hashchange", updateTab);
|
|
|
|
|
return () => window.removeEventListener("hashchange", updateTab);
|
2025-09-17 18:43:43 +09:00
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}, [searchParams]);
|
|
|
|
|
|
2025-12-29 17:17:36 +09:00
|
|
|
const formatDate = (dateString: string | undefined) => formatIsoDate(dateString);
|
2025-09-17 18:43:43 +09:00
|
|
|
|
2025-10-22 10:58:16 +09:00
|
|
|
const formatCurrency = (amount: number) => sharedFormatCurrency(amount || 0);
|
2025-09-17 18:43:43 +09:00
|
|
|
|
2025-12-16 13:54:31 +09:00
|
|
|
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;
|
2025-09-17 18:43:43 +09:00
|
|
|
|
2025-12-23 15:19:20 +09:00
|
|
|
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";
|
2025-09-17 18:43:43 +09:00
|
|
|
|
|
|
|
|
return (
|
2025-12-16 13:54:31 +09:00
|
|
|
<PageLayout
|
|
|
|
|
icon={<ServerIcon className="h-6 w-6" />}
|
|
|
|
|
title={subscription?.productName ?? "Subscription"}
|
|
|
|
|
description={
|
|
|
|
|
subscription ? `Service ID: ${subscription.serviceId}` : "View your subscription details"
|
|
|
|
|
}
|
|
|
|
|
breadcrumbs={[
|
2025-12-25 13:20:45 +09:00
|
|
|
{ label: "Subscriptions", href: "/account/subscriptions" },
|
2025-12-16 13:54:31 +09:00
|
|
|
{ label: subscription?.productName ?? "Subscription" },
|
|
|
|
|
]}
|
|
|
|
|
loading={isLoading}
|
|
|
|
|
error={pageError}
|
|
|
|
|
>
|
|
|
|
|
{subscription ? (
|
2025-12-16 16:08:17 +09:00
|
|
|
<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" />
|
2025-12-16 13:54:31 +09:00
|
|
|
</div>
|
2025-12-16 16:08:17 +09:00
|
|
|
<div>
|
|
|
|
|
<h2 className="text-lg font-semibold text-foreground">Subscription Details</h2>
|
|
|
|
|
<p className="text-sm text-muted-foreground">
|
|
|
|
|
Service subscription information
|
2025-12-16 13:54:31 +09:00
|
|
|
</p>
|
|
|
|
|
</div>
|
2025-09-17 18:43:43 +09:00
|
|
|
</div>
|
2025-12-16 16:08:17 +09:00
|
|
|
<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>
|
2025-10-29 18:19:50 +09:00
|
|
|
</div>
|
2025-09-17 18:43:43 +09:00
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-12-16 16:08:17 +09:00
|
|
|
</div>
|
2025-09-17 18:43:43 +09:00
|
|
|
|
2025-12-16 16:08:17 +09:00
|
|
|
{/* Tab Navigation for SIM Services */}
|
2025-12-16 13:54:31 +09:00
|
|
|
{isSimService && (
|
2025-12-16 16:08:17 +09:00
|
|
|
<div className="flex items-center gap-1 p-1 bg-muted rounded-lg w-fit">
|
|
|
|
|
<Link
|
2025-12-25 13:20:45 +09:00
|
|
|
href={`/account/subscriptions/${subscriptionId}`}
|
2025-12-16 16:08:17 +09:00
|
|
|
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
|
2025-12-25 13:20:45 +09:00
|
|
|
href={`/account/subscriptions/${subscriptionId}#sim-management`}
|
2025-12-16 16:08:17 +09:00
|
|
|
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>
|
2025-12-16 13:54:31 +09:00
|
|
|
</div>
|
|
|
|
|
)}
|
2025-09-17 18:43:43 +09:00
|
|
|
|
2025-12-16 16:08:17 +09:00
|
|
|
{/* SIM Management Section */}
|
|
|
|
|
{activeTab === "sim" && isSimService && (
|
|
|
|
|
<SimManagementSection subscriptionId={subscriptionId} />
|
2025-12-16 13:54:31 +09:00
|
|
|
)}
|
2025-09-17 18:43:43 +09:00
|
|
|
|
2025-12-16 16:08:17 +09:00
|
|
|
{/* 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>
|
|
|
|
|
)}
|
2025-12-23 15:19:20 +09:00
|
|
|
|
|
|
|
|
{/* 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
|
2025-12-25 13:20:45 +09:00
|
|
|
href={`/account/subscriptions/${subscriptionId}/internet/cancel`}
|
2025-12-23 15:19:20 +09:00
|
|
|
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>
|
|
|
|
|
)}
|
2025-11-22 18:11:43 +09:00
|
|
|
</div>
|
2025-12-16 13:54:31 +09:00
|
|
|
) : null}
|
|
|
|
|
</PageLayout>
|
2025-09-17 18:43:43 +09:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default SubscriptionDetailContainer;
|