2025-09-17 18:43:43 +09:00
|
|
|
"use client";
|
|
|
|
|
|
2025-11-29 16:42:08 +09:00
|
|
|
import { useEffect, useState } from "react";
|
2025-09-17 18:43:43 +09:00
|
|
|
import Link from "next/link";
|
|
|
|
|
import { useParams } from "next/navigation";
|
2025-09-20 11:35:40 +09:00
|
|
|
import { PageLayout } from "@/components/templates/PageLayout";
|
2025-09-25 15:14:36 +09:00
|
|
|
import { SubCard } from "@/components/molecules/SubCard/SubCard";
|
2025-11-29 16:42:08 +09:00
|
|
|
import { DevicePhoneMobileIcon, CheckCircleIcon } from "@heroicons/react/24/outline";
|
2025-12-29 18:19:27 +09:00
|
|
|
import { simActionsService } from "@/features/subscriptions/api/sim-actions.api";
|
2025-12-26 10:30:09 +09:00
|
|
|
import type { SimAvailablePlan } from "@customer-portal/domain/sim";
|
2025-09-25 15:54:54 +09:00
|
|
|
import { AlertBanner } from "@/components/molecules/AlertBanner/AlertBanner";
|
2025-11-29 16:42:08 +09:00
|
|
|
import { Formatting } from "@customer-portal/domain/toolkit";
|
2025-12-16 13:54:31 +09:00
|
|
|
import { Button } from "@/components/atoms";
|
2025-09-17 18:43:43 +09:00
|
|
|
|
2025-11-29 16:42:08 +09:00
|
|
|
const { formatCurrency } = Formatting;
|
2025-09-17 18:43:43 +09:00
|
|
|
|
|
|
|
|
export function SimChangePlanContainer() {
|
|
|
|
|
const params = useParams();
|
2026-01-15 11:28:25 +09:00
|
|
|
const subscriptionId = params["id"] as string;
|
2025-12-26 10:30:09 +09:00
|
|
|
const [plans, setPlans] = useState<SimAvailablePlan[]>([]);
|
|
|
|
|
const [selectedPlan, setSelectedPlan] = useState<SimAvailablePlan | null>(null);
|
2025-09-17 18:43:43 +09:00
|
|
|
const [message, setMessage] = useState<string | null>(null);
|
|
|
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
|
const [loading, setLoading] = useState(false);
|
2025-11-29 16:42:08 +09:00
|
|
|
const [loadingPlans, setLoadingPlans] = useState(true);
|
2025-09-17 18:43:43 +09:00
|
|
|
|
2025-11-29 16:42:08 +09:00
|
|
|
useEffect(() => {
|
|
|
|
|
const fetchPlans = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const availablePlans = await simActionsService.getAvailablePlans(subscriptionId);
|
|
|
|
|
setPlans(availablePlans);
|
|
|
|
|
} catch (e: unknown) {
|
2025-12-16 13:54:31 +09:00
|
|
|
setError(
|
|
|
|
|
process.env.NODE_ENV === "development"
|
|
|
|
|
? e instanceof Error
|
|
|
|
|
? e.message
|
|
|
|
|
: "Failed to load available plans"
|
|
|
|
|
: "Unable to load available plans right now. Please try again."
|
|
|
|
|
);
|
2025-11-29 16:42:08 +09:00
|
|
|
} finally {
|
|
|
|
|
setLoadingPlans(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
void fetchPlans();
|
|
|
|
|
}, [subscriptionId]);
|
|
|
|
|
|
|
|
|
|
const currentPlan = plans.find(p => p.isCurrentPlan);
|
2025-09-17 18:43:43 +09:00
|
|
|
|
|
|
|
|
const submit = async (e: React.FormEvent) => {
|
|
|
|
|
e.preventDefault();
|
2025-11-29 16:42:08 +09:00
|
|
|
if (!selectedPlan) {
|
2025-09-17 18:43:43 +09:00
|
|
|
setError("Please select a new plan");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
setLoading(true);
|
|
|
|
|
setMessage(null);
|
|
|
|
|
setError(null);
|
|
|
|
|
try {
|
2025-11-29 16:42:08 +09:00
|
|
|
const result = await simActionsService.changePlanFull(subscriptionId, {
|
|
|
|
|
newPlanCode: selectedPlan.freebitPlanCode,
|
|
|
|
|
newPlanSku: selectedPlan.sku,
|
|
|
|
|
newPlanName: selectedPlan.name,
|
2025-09-17 18:43:43 +09:00
|
|
|
});
|
2025-11-29 16:42:08 +09:00
|
|
|
setMessage(`Plan change scheduled for ${result.scheduledAt || "the 1st of next month"}`);
|
|
|
|
|
setSelectedPlan(null);
|
2025-09-17 18:43:43 +09:00
|
|
|
} catch (e: unknown) {
|
2025-12-16 13:54:31 +09:00
|
|
|
setError(
|
|
|
|
|
process.env.NODE_ENV === "development"
|
|
|
|
|
? e instanceof Error
|
|
|
|
|
? e.message
|
|
|
|
|
: "Failed to change plan"
|
|
|
|
|
: "Unable to submit your plan change right now. Please try again."
|
|
|
|
|
);
|
2025-09-17 18:43:43 +09:00
|
|
|
} finally {
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<PageLayout
|
|
|
|
|
icon={<DevicePhoneMobileIcon />}
|
|
|
|
|
title="Change Plan"
|
|
|
|
|
description="Switch to a different data plan"
|
|
|
|
|
>
|
2025-11-29 16:42:08 +09:00
|
|
|
<div className="max-w-4xl mx-auto">
|
2025-09-17 18:43:43 +09:00
|
|
|
<div className="mb-4">
|
|
|
|
|
<Link
|
2025-12-25 13:20:45 +09:00
|
|
|
href={`/account/subscriptions/${subscriptionId}#sim-management`}
|
2025-12-16 13:54:31 +09:00
|
|
|
className="text-primary hover:underline"
|
2025-09-17 18:43:43 +09:00
|
|
|
>
|
|
|
|
|
← Back to SIM Management
|
|
|
|
|
</Link>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<SubCard>
|
2025-11-29 16:42:08 +09:00
|
|
|
<div className="mb-6">
|
2025-12-16 13:54:31 +09:00
|
|
|
<h2 className="text-lg font-semibold text-foreground mb-2">Change Your Plan</h2>
|
|
|
|
|
<p className="text-sm text-muted-foreground">
|
|
|
|
|
Select a new plan below. Plan changes will take effect on the 1st of the following
|
|
|
|
|
month. Changes must be requested before the 25th of the current month.
|
2025-11-29 16:42:08 +09:00
|
|
|
</p>
|
|
|
|
|
</div>
|
2025-09-17 18:43:43 +09:00
|
|
|
|
|
|
|
|
{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>
|
|
|
|
|
)}
|
|
|
|
|
|
2025-11-29 16:42:08 +09:00
|
|
|
{loadingPlans ? (
|
|
|
|
|
<div className="animate-pulse space-y-4">
|
|
|
|
|
{[1, 2, 3, 4].map(i => (
|
2025-12-16 13:54:31 +09:00
|
|
|
<div key={i} className="h-24 bg-muted rounded-lg"></div>
|
2025-11-29 16:42:08 +09:00
|
|
|
))}
|
2025-09-17 18:43:43 +09:00
|
|
|
</div>
|
2025-11-29 16:42:08 +09:00
|
|
|
) : (
|
|
|
|
|
<form onSubmit={e => void submit(e)} className="space-y-6">
|
|
|
|
|
{/* Current Plan */}
|
|
|
|
|
{currentPlan && (
|
2025-12-16 13:54:31 +09:00
|
|
|
<div className="bg-info-soft border border-info/25 rounded-lg p-4 mb-6">
|
2025-11-29 16:42:08 +09:00
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
<div>
|
2025-12-16 13:54:31 +09:00
|
|
|
<div className="text-xs text-info font-medium uppercase">Current Plan</div>
|
|
|
|
|
<div className="text-lg font-semibold text-foreground">
|
|
|
|
|
{currentPlan.name}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="text-sm text-muted-foreground">{currentPlan.simDataSize}</div>
|
2025-11-29 16:42:08 +09:00
|
|
|
</div>
|
|
|
|
|
<div className="text-right">
|
2025-12-16 13:54:31 +09:00
|
|
|
<div className="text-xl font-bold text-foreground">
|
2025-11-29 16:42:08 +09:00
|
|
|
{currentPlan.monthlyPrice ? formatCurrency(currentPlan.monthlyPrice) : "—"}
|
|
|
|
|
</div>
|
2025-12-16 13:54:31 +09:00
|
|
|
<div className="text-xs text-muted-foreground">/month</div>
|
2025-11-29 16:42:08 +09:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2025-09-17 18:43:43 +09:00
|
|
|
|
2025-11-29 16:42:08 +09:00
|
|
|
{/* Available Plans */}
|
|
|
|
|
<div className="space-y-3">
|
2025-12-16 13:54:31 +09:00
|
|
|
<label className="block text-sm font-medium text-muted-foreground">
|
|
|
|
|
Select a New Plan
|
|
|
|
|
</label>
|
2025-11-29 16:42:08 +09:00
|
|
|
<div className="grid gap-3">
|
|
|
|
|
{plans
|
|
|
|
|
.filter(p => !p.isCurrentPlan)
|
|
|
|
|
.map(plan => (
|
|
|
|
|
<label
|
|
|
|
|
key={plan.id}
|
2025-12-16 13:54:31 +09:00
|
|
|
className={`relative flex items-center justify-between p-4 border rounded-lg cursor-pointer transition-colors ${
|
2025-11-29 16:42:08 +09:00
|
|
|
selectedPlan?.id === plan.id
|
2025-12-16 13:54:31 +09:00
|
|
|
? "border-primary bg-primary-soft"
|
|
|
|
|
: "border-border hover:bg-muted bg-card"
|
2025-11-29 16:42:08 +09:00
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
<div className="flex items-center">
|
|
|
|
|
<input
|
|
|
|
|
type="radio"
|
|
|
|
|
name="plan"
|
|
|
|
|
value={plan.id}
|
|
|
|
|
checked={selectedPlan?.id === plan.id}
|
|
|
|
|
onChange={() => setSelectedPlan(plan)}
|
|
|
|
|
className="sr-only"
|
|
|
|
|
/>
|
|
|
|
|
<div
|
|
|
|
|
className={`w-5 h-5 rounded-full border-2 mr-4 flex items-center justify-center ${
|
2025-12-16 13:54:31 +09:00
|
|
|
selectedPlan?.id === plan.id ? "border-primary" : "border-border"
|
2025-11-29 16:42:08 +09:00
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
{selectedPlan?.id === plan.id && (
|
2025-12-16 13:54:31 +09:00
|
|
|
<div className="w-3 h-3 rounded-full bg-primary"></div>
|
2025-11-29 16:42:08 +09:00
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
2025-12-16 13:54:31 +09:00
|
|
|
<div className="font-medium text-foreground">{plan.name}</div>
|
|
|
|
|
<div className="text-sm text-muted-foreground">
|
|
|
|
|
{plan.simDataSize} • {plan.simPlanType}
|
|
|
|
|
</div>
|
2025-11-29 16:42:08 +09:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="text-right">
|
2025-12-16 13:54:31 +09:00
|
|
|
<div className="text-lg font-semibold text-foreground">
|
2025-11-29 16:42:08 +09:00
|
|
|
{plan.monthlyPrice ? formatCurrency(plan.monthlyPrice) : "—"}
|
|
|
|
|
</div>
|
2025-12-16 13:54:31 +09:00
|
|
|
<div className="text-xs text-muted-foreground">/month</div>
|
2025-11-29 16:42:08 +09:00
|
|
|
</div>
|
|
|
|
|
{selectedPlan?.id === plan.id && (
|
2025-12-16 13:54:31 +09:00
|
|
|
<CheckCircleIcon className="absolute top-2 right-2 h-5 w-5 text-primary" />
|
2025-11-29 16:42:08 +09:00
|
|
|
)}
|
|
|
|
|
</label>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-09-17 18:43:43 +09:00
|
|
|
|
2025-11-29 16:42:08 +09:00
|
|
|
{/* Info Box */}
|
2025-12-16 13:54:31 +09:00
|
|
|
<div className="bg-warning-soft border border-warning/25 rounded-lg p-4">
|
|
|
|
|
<h3 className="text-sm font-medium text-foreground mb-1">Important Notes</h3>
|
|
|
|
|
<ul className="text-sm text-muted-foreground space-y-1">
|
2025-11-29 16:42:08 +09:00
|
|
|
<li>• Plan changes take effect on the 1st of the following month</li>
|
|
|
|
|
<li>• Requests must be made before the 25th of the current month</li>
|
|
|
|
|
<li>• Your current data balance will be reset when the new plan activates</li>
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Submit */}
|
|
|
|
|
<div className="flex gap-3">
|
2025-12-16 13:54:31 +09:00
|
|
|
<Button
|
2025-11-29 16:42:08 +09:00
|
|
|
type="submit"
|
|
|
|
|
disabled={loading || !selectedPlan}
|
2025-12-16 13:54:31 +09:00
|
|
|
loading={loading}
|
|
|
|
|
loadingText="Processing…"
|
2025-11-29 16:42:08 +09:00
|
|
|
>
|
2025-12-16 13:54:31 +09:00
|
|
|
Confirm Plan Change
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
as="a"
|
2025-12-25 13:20:45 +09:00
|
|
|
href={`/account/subscriptions/${subscriptionId}#sim-management`}
|
2025-12-16 13:54:31 +09:00
|
|
|
variant="outline"
|
2025-11-29 16:42:08 +09:00
|
|
|
>
|
|
|
|
|
Cancel
|
2025-12-16 13:54:31 +09:00
|
|
|
</Button>
|
2025-11-29 16:42:08 +09:00
|
|
|
</div>
|
|
|
|
|
</form>
|
|
|
|
|
)}
|
2025-09-17 18:43:43 +09:00
|
|
|
</SubCard>
|
|
|
|
|
</div>
|
|
|
|
|
</PageLayout>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default SimChangePlanContainer;
|