tema 675f7d5cfd Remove cached profile fields migration and update CSRF middleware for new public auth endpoints
- 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.
2025-11-21 17:12:34 +09:00

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">
&#8203;
</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>
);
}