165 lines
5.4 KiB
TypeScript
165 lines
5.4 KiB
TypeScript
|
|
"use client";
|
||
|
|
|
||
|
|
import { useMemo, useState } from "react";
|
||
|
|
import Link from "next/link";
|
||
|
|
import { useParams } from "next/navigation";
|
||
|
|
import { PageLayout } from "@/components/layout/PageLayout";
|
||
|
|
import { SubCard } from "@/components/ui/sub-card";
|
||
|
|
import { DevicePhoneMobileIcon } from "@heroicons/react/24/outline";
|
||
|
|
import { simActionsService } from "@/features/subscriptions/services/sim-actions.service";
|
||
|
|
import { AlertBanner } from "@/components/common/AlertBanner";
|
||
|
|
|
||
|
|
const PLAN_CODES = ["PASI_5G", "PASI_10G", "PASI_25G", "PASI_50G"] as const;
|
||
|
|
type PlanCode = (typeof PLAN_CODES)[number];
|
||
|
|
const PLAN_LABELS: Record<PlanCode, string> = {
|
||
|
|
PASI_5G: "5GB",
|
||
|
|
PASI_10G: "10GB",
|
||
|
|
PASI_25G: "25GB",
|
||
|
|
PASI_50G: "50GB",
|
||
|
|
};
|
||
|
|
|
||
|
|
export function SimChangePlanContainer() {
|
||
|
|
const params = useParams();
|
||
|
|
const subscriptionId = parseInt(params.id as string);
|
||
|
|
const [currentPlanCode] = useState<string>("");
|
||
|
|
const [newPlanCode, setNewPlanCode] = useState<"" | PlanCode>("");
|
||
|
|
const [assignGlobalIp, setAssignGlobalIp] = useState(false);
|
||
|
|
const [scheduledAt, setScheduledAt] = useState("");
|
||
|
|
const [message, setMessage] = useState<string | null>(null);
|
||
|
|
const [error, setError] = useState<string | null>(null);
|
||
|
|
const [loading, setLoading] = useState(false);
|
||
|
|
|
||
|
|
const options = useMemo(
|
||
|
|
() => (PLAN_CODES as readonly PlanCode[]).filter(c => c !== (currentPlanCode as PlanCode)),
|
||
|
|
[currentPlanCode]
|
||
|
|
);
|
||
|
|
|
||
|
|
const submit = async (e: React.FormEvent) => {
|
||
|
|
e.preventDefault();
|
||
|
|
if (!newPlanCode) {
|
||
|
|
setError("Please select a new plan");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
setLoading(true);
|
||
|
|
setMessage(null);
|
||
|
|
setError(null);
|
||
|
|
try {
|
||
|
|
await simActionsService.changePlan(subscriptionId, {
|
||
|
|
newPlanCode,
|
||
|
|
assignGlobalIp,
|
||
|
|
scheduledAt: scheduledAt ? scheduledAt.replace(/-/g, "") : undefined,
|
||
|
|
});
|
||
|
|
setMessage("Plan change submitted successfully");
|
||
|
|
} catch (e: unknown) {
|
||
|
|
setError(e instanceof Error ? e.message : "Failed to change plan");
|
||
|
|
} finally {
|
||
|
|
setLoading(false);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<PageLayout
|
||
|
|
icon={<DevicePhoneMobileIcon />}
|
||
|
|
title="Change Plan"
|
||
|
|
description="Switch to a different data plan"
|
||
|
|
>
|
||
|
|
<div className="max-w-3xl mx-auto">
|
||
|
|
<div className="mb-4">
|
||
|
|
<Link
|
||
|
|
href={`/subscriptions/${subscriptionId}#sim-management`}
|
||
|
|
className="text-blue-600 hover:text-blue-700"
|
||
|
|
>
|
||
|
|
← Back to SIM Management
|
||
|
|
</Link>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<SubCard>
|
||
|
|
<p className="text-sm text-gray-600 mb-6">
|
||
|
|
Change Plan: Switch to a different data plan. Important: Plan changes must be requested
|
||
|
|
before the 25th of the month. Changes will take effect on the 1st of the following
|
||
|
|
month.
|
||
|
|
</p>
|
||
|
|
|
||
|
|
{message && (
|
||
|
|
<div className="mb-4">
|
||
|
|
<AlertBanner variant="success" title="Plan Change Submitted">
|
||
|
|
{message}
|
||
|
|
</AlertBanner>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
{error && (
|
||
|
|
<div className="mb-4">
|
||
|
|
<AlertBanner variant="error" title="Plan Change Failed">
|
||
|
|
{error}
|
||
|
|
</AlertBanner>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
<form onSubmit={e => void submit(e)} className="space-y-6">
|
||
|
|
<div>
|
||
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">New Plan</label>
|
||
|
|
<select
|
||
|
|
value={newPlanCode}
|
||
|
|
onChange={e => setNewPlanCode(e.target.value as PlanCode)}
|
||
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-md"
|
||
|
|
>
|
||
|
|
<option value="">Choose a plan</option>
|
||
|
|
{options.map(code => (
|
||
|
|
<option key={code} value={code}>
|
||
|
|
{PLAN_LABELS[code]}
|
||
|
|
</option>
|
||
|
|
))}
|
||
|
|
</select>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="flex items-center">
|
||
|
|
<input
|
||
|
|
id="globalip"
|
||
|
|
type="checkbox"
|
||
|
|
checked={assignGlobalIp}
|
||
|
|
onChange={e => setAssignGlobalIp(e.target.checked)}
|
||
|
|
className="h-4 w-4 text-blue-600 border-gray-300 rounded"
|
||
|
|
/>
|
||
|
|
<label htmlFor="globalip" className="ml-2 text-sm text-gray-700">
|
||
|
|
Assign global IP
|
||
|
|
</label>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||
|
|
Schedule (optional)
|
||
|
|
</label>
|
||
|
|
<input
|
||
|
|
type="date"
|
||
|
|
value={scheduledAt}
|
||
|
|
onChange={e => setScheduledAt(e.target.value)}
|
||
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-md"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="flex gap-3">
|
||
|
|
<button
|
||
|
|
type="submit"
|
||
|
|
disabled={loading}
|
||
|
|
className="px-4 py-2 rounded-md bg-blue-600 text-white text-sm disabled:opacity-50"
|
||
|
|
>
|
||
|
|
{loading ? "Processing…" : "Submit Plan Change"}
|
||
|
|
</button>
|
||
|
|
<Link
|
||
|
|
href={`/subscriptions/${subscriptionId}#sim-management`}
|
||
|
|
className="px-4 py-2 rounded-md border border-gray-300 text-sm text-gray-700 bg-white hover:bg-gray-50"
|
||
|
|
>
|
||
|
|
Back
|
||
|
|
</Link>
|
||
|
|
</div>
|
||
|
|
</form>
|
||
|
|
</SubCard>
|
||
|
|
</div>
|
||
|
|
</PageLayout>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
export default SimChangePlanContainer;
|
||
|
|
|
||
|
|
|