173 lines
5.7 KiB
TypeScript
173 lines
5.7 KiB
TypeScript
|
|
"use client";
|
||
|
|
|
||
|
|
import React, { useState, useEffect } from 'react';
|
||
|
|
import {
|
||
|
|
DevicePhoneMobileIcon,
|
||
|
|
ExclamationTriangleIcon,
|
||
|
|
ArrowPathIcon
|
||
|
|
} from '@heroicons/react/24/outline';
|
||
|
|
import { SimDetailsCard, type SimDetails } from './SimDetailsCard';
|
||
|
|
import { DataUsageChart, type SimUsage } from './DataUsageChart';
|
||
|
|
import { SimActions } from './SimActions';
|
||
|
|
import { authenticatedApi } from '@/lib/api';
|
||
|
|
|
||
|
|
interface SimManagementSectionProps {
|
||
|
|
subscriptionId: number;
|
||
|
|
}
|
||
|
|
|
||
|
|
interface SimInfo {
|
||
|
|
details: SimDetails;
|
||
|
|
usage: SimUsage;
|
||
|
|
}
|
||
|
|
|
||
|
|
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 = async () => {
|
||
|
|
try {
|
||
|
|
setError(null);
|
||
|
|
|
||
|
|
const data = await authenticatedApi.get<{
|
||
|
|
details: SimDetails;
|
||
|
|
usage: SimUsage;
|
||
|
|
}>(`/subscriptions/${subscriptionId}/sim`);
|
||
|
|
|
||
|
|
setSimInfo(data);
|
||
|
|
} catch (error: any) {
|
||
|
|
if (error.status === 400) {
|
||
|
|
// Not a SIM subscription - this component shouldn't be shown
|
||
|
|
setError('This subscription is not a SIM service');
|
||
|
|
} else {
|
||
|
|
setError(error instanceof Error ? error.message : 'Failed to load SIM information');
|
||
|
|
}
|
||
|
|
} finally {
|
||
|
|
setLoading(false);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
fetchSimInfo();
|
||
|
|
}, [subscriptionId]);
|
||
|
|
|
||
|
|
const handleRefresh = () => {
|
||
|
|
setLoading(true);
|
||
|
|
fetchSimInfo();
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleActionSuccess = () => {
|
||
|
|
// Refresh SIM info after any successful action
|
||
|
|
fetchSimInfo();
|
||
|
|
};
|
||
|
|
|
||
|
|
if (loading) {
|
||
|
|
return (
|
||
|
|
<div className="space-y-6">
|
||
|
|
<div className="bg-white shadow rounded-lg p-6">
|
||
|
|
<div className="flex items-center mb-4">
|
||
|
|
<DevicePhoneMobileIcon className="h-6 w-6 text-blue-600 mr-3" />
|
||
|
|
<h2 className="text-xl font-semibold text-gray-900">SIM Management</h2>
|
||
|
|
</div>
|
||
|
|
<div className="animate-pulse space-y-4">
|
||
|
|
<div className="h-4 bg-gray-200 rounded w-3/4"></div>
|
||
|
|
<div className="h-4 bg-gray-200 rounded w-1/2"></div>
|
||
|
|
<div className="h-32 bg-gray-200 rounded"></div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (error) {
|
||
|
|
return (
|
||
|
|
<div className="bg-white shadow rounded-lg p-6">
|
||
|
|
<div className="flex items-center mb-4">
|
||
|
|
<DevicePhoneMobileIcon className="h-6 w-6 text-blue-600 mr-3" />
|
||
|
|
<h2 className="text-xl font-semibold text-gray-900">SIM Management</h2>
|
||
|
|
</div>
|
||
|
|
<div className="text-center py-8">
|
||
|
|
<ExclamationTriangleIcon className="h-12 w-12 text-red-400 mx-auto mb-4" />
|
||
|
|
<h3 className="text-lg font-medium text-gray-900 mb-2">Unable to Load SIM Information</h3>
|
||
|
|
<p className="text-gray-600 mb-4">{error}</p>
|
||
|
|
<button
|
||
|
|
onClick={handleRefresh}
|
||
|
|
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||
|
|
>
|
||
|
|
<ArrowPathIcon className="h-4 w-4 mr-2" />
|
||
|
|
Retry
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!simInfo) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="space-y-6">
|
||
|
|
{/* Section Header */}
|
||
|
|
<div className="bg-white shadow rounded-lg p-6">
|
||
|
|
<div className="flex items-center justify-between">
|
||
|
|
<div className="flex items-center">
|
||
|
|
<DevicePhoneMobileIcon className="h-6 w-6 text-blue-600 mr-3" />
|
||
|
|
<div>
|
||
|
|
<h2 className="text-xl font-semibold text-gray-900">SIM Management</h2>
|
||
|
|
<p className="text-gray-600">Manage your SIM service and data usage</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<button
|
||
|
|
onClick={handleRefresh}
|
||
|
|
disabled={loading}
|
||
|
|
className="inline-flex items-center px-3 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50"
|
||
|
|
>
|
||
|
|
<ArrowPathIcon className={`h-4 w-4 mr-2 ${loading ? 'animate-spin' : ''}`} />
|
||
|
|
Refresh
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* SIM Details */}
|
||
|
|
<SimDetailsCard
|
||
|
|
simDetails={simInfo.details}
|
||
|
|
isLoading={false}
|
||
|
|
error={null}
|
||
|
|
/>
|
||
|
|
|
||
|
|
{/* Data Usage */}
|
||
|
|
<DataUsageChart
|
||
|
|
usage={simInfo.usage}
|
||
|
|
remainingQuotaMb={simInfo.details.remainingQuotaMb}
|
||
|
|
isLoading={false}
|
||
|
|
error={null}
|
||
|
|
/>
|
||
|
|
|
||
|
|
{/* SIM Actions */}
|
||
|
|
<SimActions
|
||
|
|
subscriptionId={subscriptionId}
|
||
|
|
simType={simInfo.details.simType}
|
||
|
|
status={simInfo.details.status}
|
||
|
|
onTopUpSuccess={handleActionSuccess}
|
||
|
|
onPlanChangeSuccess={handleActionSuccess}
|
||
|
|
onCancelSuccess={handleActionSuccess}
|
||
|
|
onReissueSuccess={handleActionSuccess}
|
||
|
|
/>
|
||
|
|
|
||
|
|
{/* Additional Information */}
|
||
|
|
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||
|
|
<h3 className="text-sm font-medium text-blue-800 mb-2">Important Information</h3>
|
||
|
|
<ul className="text-sm text-blue-700 space-y-1">
|
||
|
|
<li>• Data usage is updated in real-time and may take a few minutes to reflect recent activity</li>
|
||
|
|
<li>• Top-up data will be available immediately after successful processing</li>
|
||
|
|
<li>• SIM cancellation is permanent and cannot be undone</li>
|
||
|
|
{simInfo.details.simType === 'esim' && (
|
||
|
|
<li>• eSIM profile reissue will provide a new QR code for activation</li>
|
||
|
|
)}
|
||
|
|
</ul>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|