- Deleted migration file that removed cached profile fields from the users table, centralizing profile data retrieval from WHMCS. - Updated CsrfMiddleware to include new public authentication endpoints for password reset, setting password, and WHMCS account linking. - Enhanced error handling in password and WHMCS linking workflows to provide clearer feedback on missing mappings and improve user experience. - Adjusted user creation and update methods in UsersFacade to handle cases where WHMCS mappings are not yet available, ensuring smoother account setup.
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>
|
|
);
|
|
}
|