- Consolidated SIM details and actions into a more organized layout, enhancing readability and usability. - Removed unnecessary usage data handling, focusing on essential SIM details. - Updated loading and error states for better user feedback during data fetching. - Introduced new components for SIM actions and feature toggles, streamlining the management interface.
235 lines
8.9 KiB
TypeScript
235 lines
8.9 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState, useEffect, useCallback } from "react";
|
|
import {
|
|
DevicePhoneMobileIcon,
|
|
ExclamationTriangleIcon,
|
|
ArrowPathIcon,
|
|
} from "@heroicons/react/24/outline";
|
|
import { SimDetailsCard, type SimDetails } from "./SimDetailsCard";
|
|
import { SimActions } from "./SimActions";
|
|
import { apiClient } from "@/lib/api";
|
|
import { SimFeatureToggles } from "./SimFeatureToggles";
|
|
|
|
interface SimManagementSectionProps {
|
|
subscriptionId: number;
|
|
}
|
|
|
|
interface SimInfo {
|
|
details: SimDetails;
|
|
}
|
|
|
|
export function SimManagementSection({ subscriptionId }: SimManagementSectionProps) {
|
|
const [simInfo, setSimInfo] = useState<SimInfo | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const fetchSimInfo = useCallback(async () => {
|
|
try {
|
|
setError(null);
|
|
|
|
const response = await apiClient.GET("/api/subscriptions/{id}/sim", {
|
|
params: { path: { id: subscriptionId } },
|
|
});
|
|
|
|
const payload = response.data as { details: SimDetails; usage: any } | undefined;
|
|
|
|
if (!payload) {
|
|
throw new Error("Failed to load SIM information");
|
|
}
|
|
|
|
// Only use the details part, ignore usage data
|
|
setSimInfo({ details: payload.details });
|
|
} catch (err: unknown) {
|
|
const hasStatus = (v: unknown): v is { status: number } =>
|
|
typeof v === "object" &&
|
|
v !== null &&
|
|
"status" in v &&
|
|
typeof (v as { status: unknown }).status === "number";
|
|
if (hasStatus(err) && err.status === 400) {
|
|
// Not a SIM subscription - this component shouldn't be shown
|
|
setError("This subscription is not a SIM service");
|
|
} else {
|
|
setError(err instanceof Error ? err.message : "Failed to load SIM information");
|
|
}
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [subscriptionId]);
|
|
|
|
useEffect(() => {
|
|
void fetchSimInfo();
|
|
}, [fetchSimInfo]);
|
|
|
|
const handleRefresh = () => {
|
|
setLoading(true);
|
|
void fetchSimInfo();
|
|
};
|
|
|
|
const handleActionSuccess = () => {
|
|
// Refresh SIM info after any successful action
|
|
void fetchSimInfo();
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="space-y-8">
|
|
<div className="bg-white shadow-lg rounded-xl border border-gray-100 p-8">
|
|
<div className="flex items-center mb-6">
|
|
<div className="bg-blue-50 rounded-xl p-2 mr-4">
|
|
<DevicePhoneMobileIcon className="h-6 w-6 text-blue-600" />
|
|
</div>
|
|
<div>
|
|
<h2 className="text-2xl font-bold text-gray-900">SIM Management</h2>
|
|
<p className="text-gray-600 mt-1">Loading your SIM service details...</p>
|
|
</div>
|
|
</div>
|
|
<div className="animate-pulse space-y-6">
|
|
<div className="h-6 bg-gradient-to-r from-gray-200 to-gray-300 rounded-lg w-3/4"></div>
|
|
<div className="h-5 bg-gradient-to-r from-gray-200 to-gray-300 rounded-lg w-1/2"></div>
|
|
<div className="h-48 bg-gradient-to-r from-gray-200 to-gray-300 rounded-xl"></div>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div className="h-32 bg-gradient-to-r from-gray-200 to-gray-300 rounded-xl"></div>
|
|
<div className="h-32 bg-gradient-to-r from-gray-200 to-gray-300 rounded-xl"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<div className="bg-white shadow-lg rounded-xl border border-red-100 p-8">
|
|
<div className="flex items-center mb-6">
|
|
<div className="bg-blue-50 rounded-xl p-2 mr-4">
|
|
<DevicePhoneMobileIcon className="h-6 w-6 text-blue-600" />
|
|
</div>
|
|
<div>
|
|
<h2 className="text-2xl font-bold text-gray-900">SIM Management</h2>
|
|
<p className="text-gray-600 mt-1">Unable to load SIM information</p>
|
|
</div>
|
|
</div>
|
|
<div className="text-center py-12">
|
|
<div className="bg-red-50 rounded-full p-4 w-20 h-20 mx-auto mb-6">
|
|
<ExclamationTriangleIcon className="h-12 w-12 text-red-500 mx-auto" />
|
|
</div>
|
|
<h3 className="text-xl font-semibold text-gray-900 mb-3">
|
|
Unable to Load SIM Information
|
|
</h3>
|
|
<p className="text-gray-600 mb-8 max-w-md mx-auto">{error}</p>
|
|
<button
|
|
onClick={handleRefresh}
|
|
className="inline-flex items-center px-6 py-3 border border-transparent text-sm font-semibold rounded-xl text-white bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 hover:shadow-lg hover:scale-105 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all duration-200"
|
|
>
|
|
<ArrowPathIcon className="h-5 w-5 mr-2" />
|
|
Retry
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!simInfo) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<div id="sim-management" className="space-y-6">
|
|
{/* Header Section */}
|
|
<div className="bg-white shadow-sm rounded-lg border border-gray-200 p-6">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-gray-900">
|
|
{simInfo.details.simType === "esim" ? "eSIM" : "Physical SIM"} Service
|
|
</h1>
|
|
<p className="text-gray-600 mt-1">Subscription ID {subscriptionId}</p>
|
|
</div>
|
|
<span className="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-green-100 text-green-800">
|
|
{simInfo.details.status.charAt(0).toUpperCase() + simInfo.details.status.slice(1)}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Two Column Layout */}
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
{/* Left Column - Main Actions */}
|
|
<div className="lg:col-span-2 space-y-6">
|
|
{/* Subscription Details Card */}
|
|
<div className="bg-white shadow-sm rounded-lg border border-gray-200 p-6">
|
|
<div className="mb-4">
|
|
<h2 className="text-lg font-semibold text-gray-900">Subscription Details</h2>
|
|
</div>
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<div className="text-center">
|
|
<p className="text-sm text-gray-500 uppercase tracking-wide">Monthly Cost</p>
|
|
<p className="text-lg font-semibold text-gray-900">¥3,100</p>
|
|
</div>
|
|
<div className="text-center">
|
|
<p className="text-sm text-gray-500 uppercase tracking-wide">Next Billing</p>
|
|
<p className="text-lg font-semibold text-gray-900">Jul 1, 2024</p>
|
|
</div>
|
|
<div className="text-center">
|
|
<p className="text-sm text-gray-500 uppercase tracking-wide">Registration</p>
|
|
<p className="text-lg font-semibold text-gray-900">Aug 2, 2023</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* SIM Management Actions Card */}
|
|
<div className="bg-white shadow-sm rounded-lg border border-gray-200 p-6">
|
|
<div className="mb-4">
|
|
<h2 className="text-lg font-semibold text-gray-900">SIM Management Actions</h2>
|
|
</div>
|
|
<SimActions
|
|
subscriptionId={subscriptionId}
|
|
simType={simInfo.details.simType}
|
|
status={simInfo.details.status}
|
|
currentPlanCode={simInfo.details.planCode}
|
|
onTopUpSuccess={handleActionSuccess}
|
|
onPlanChangeSuccess={handleActionSuccess}
|
|
onCancelSuccess={handleActionSuccess}
|
|
onReissueSuccess={handleActionSuccess}
|
|
embedded={true}
|
|
/>
|
|
</div>
|
|
|
|
{/* Voice Status Card */}
|
|
<div className="bg-white shadow-sm rounded-lg border border-gray-200 p-6">
|
|
<div className="mb-4">
|
|
<h2 className="text-lg font-semibold text-gray-900">Voice Status</h2>
|
|
</div>
|
|
<SimFeatureToggles
|
|
subscriptionId={subscriptionId}
|
|
voiceMailEnabled={simInfo.details.voiceMailEnabled}
|
|
callWaitingEnabled={simInfo.details.callWaitingEnabled}
|
|
internationalRoamingEnabled={simInfo.details.internationalRoamingEnabled}
|
|
networkType={simInfo.details.networkType}
|
|
onChanged={handleActionSuccess}
|
|
embedded
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Right Column - SIM Details & Usage */}
|
|
<div className="space-y-6">
|
|
{/* SIM Details Card */}
|
|
<div className="bg-white shadow-sm rounded-lg border border-gray-200 p-6">
|
|
<div className="mb-4">
|
|
<h2 className="text-lg font-semibold text-gray-900">SIM Details</h2>
|
|
</div>
|
|
<SimDetailsCard
|
|
simDetails={simInfo.details}
|
|
isLoading={false}
|
|
error={null}
|
|
embedded={true}
|
|
showFeaturesSummary={false}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
);
|
|
}
|