- Added FreebitMapperService to facilitate account normalization and improve code organization. - Updated FreebitAuthService to streamline error handling and response parsing, replacing custom exceptions with standard error messages. - Enhanced FreebitClientService to ensure proper URL construction and improved logging for API errors. - Refactored FreebitOperationsService to include new request types and validation, ensuring better handling of SIM operations. - Updated FreebitOrchestratorService to utilize the new mapper for account normalization across various methods. - Improved SIM management features in the portal, including better handling of SIM details and usage information. - Refactored components to enhance user experience and maintainability, including updates to the ChangePlanModal and SimActions components.
126 lines
4.8 KiB
TypeScript
126 lines
4.8 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState } from "react";
|
|
import { apiClient } from "@/lib/api";
|
|
import { XMarkIcon } from "@heroicons/react/24/outline";
|
|
import { mapToSimplifiedFormat } from "../utils/plan";
|
|
|
|
interface ChangePlanModalProps {
|
|
subscriptionId: number;
|
|
currentPlanCode?: string;
|
|
onClose: () => void;
|
|
onSuccess: () => void;
|
|
onError: (message: string) => void;
|
|
}
|
|
|
|
export function ChangePlanModal({
|
|
subscriptionId,
|
|
currentPlanCode,
|
|
onClose,
|
|
onSuccess,
|
|
onError,
|
|
}: ChangePlanModalProps) {
|
|
const PLAN_CODES = ["5GB", "10GB", "25GB", "50GB"] as const;
|
|
type PlanCode = (typeof PLAN_CODES)[number];
|
|
|
|
const normalizedCurrentPlan = mapToSimplifiedFormat(currentPlanCode);
|
|
|
|
const allowedPlans = (PLAN_CODES as readonly PlanCode[]).filter(
|
|
code => code !== (normalizedCurrentPlan as PlanCode)
|
|
);
|
|
|
|
const [newPlanCode, setNewPlanCode] = useState<"" | PlanCode>("");
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
const submit = async () => {
|
|
if (!newPlanCode) {
|
|
onError("Please select a new plan");
|
|
return;
|
|
}
|
|
setLoading(true);
|
|
try {
|
|
await apiClient.POST("/api/subscriptions/{id}/sim/change-plan", {
|
|
params: { path: { id: subscriptionId } },
|
|
body: {
|
|
newPlanCode,
|
|
},
|
|
});
|
|
onSuccess();
|
|
} catch (e: unknown) {
|
|
onError(e instanceof Error ? e.message : "Failed to change plan");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="fixed inset-0 z-50 overflow-y-auto">
|
|
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
|
<div
|
|
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
|
|
aria-hidden="true"
|
|
></div>
|
|
|
|
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
|
|
​
|
|
</span>
|
|
<div className="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full">
|
|
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
|
<div className="sm:flex sm:items-start">
|
|
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left w-full">
|
|
<div className="flex items-center justify-between">
|
|
<h3 className="text-lg leading-6 font-medium text-gray-900">Change SIM Plan</h3>
|
|
<button onClick={onClose} className="text-gray-400 hover:text-gray-600">
|
|
<XMarkIcon className="h-5 w-5" />
|
|
</button>
|
|
</div>
|
|
<div className="mt-4 space-y-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700">
|
|
Select New Plan
|
|
</label>
|
|
<select
|
|
value={newPlanCode}
|
|
onChange={e => setNewPlanCode(e.target.value as PlanCode)}
|
|
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 text-sm"
|
|
>
|
|
<option value="">Choose a plan</option>
|
|
{allowedPlans.map(code => (
|
|
<option key={code} value={code}>
|
|
{code}
|
|
</option>
|
|
))}
|
|
</select>
|
|
<p className="mt-1 text-xs text-gray-500">
|
|
Only plans different from your current plan are listed. The change will be
|
|
scheduled for the 1st of the next month.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
|
|
<button
|
|
type="button"
|
|
onClick={() => void submit()}
|
|
disabled={loading}
|
|
className="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm disabled:opacity-50"
|
|
>
|
|
{loading ? "Processing..." : "Change Plan"}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={onClose}
|
|
disabled={loading}
|
|
className="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
|
|
>
|
|
Back
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|