172 lines
6.8 KiB
TypeScript

"use client";
import React, { useState } from "react";
import { XMarkIcon, PlusIcon, ExclamationTriangleIcon } from "@heroicons/react/24/outline";
import { apiClient } from "@/lib/api";
interface TopUpModalProps {
subscriptionId: number;
onClose: () => void;
onSuccess: () => void;
onError: (message: string) => void;
}
export function TopUpModal({ subscriptionId, onClose, onSuccess, onError }: TopUpModalProps) {
const [gbAmount, setGbAmount] = useState<string>("1");
const [loading, setLoading] = useState(false);
const getCurrentAmountMb = () => {
const gb = parseInt(gbAmount, 10);
return isNaN(gb) ? 0 : gb * 1000;
};
const isValidAmount = () => {
const gb = Number(gbAmount);
return Number.isInteger(gb) && gb >= 1 && gb <= 50; // 1-50 GB, whole numbers only (Freebit API limit)
};
const calculateCost = () => {
const gb = parseInt(gbAmount, 10);
return isNaN(gb) ? 0 : gb * 500; // 1GB = 500 JPY
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!isValidAmount()) {
onError("Please enter a whole number between 1 GB and 100 GB");
return;
}
setLoading(true);
try {
const requestBody = {
quotaMb: getCurrentAmountMb(),
};
await apiClient.POST("/api/subscriptions/{id}/sim/top-up", {
params: { path: { id: subscriptionId } },
body: requestBody,
});
onSuccess();
} catch (error: unknown) {
onError(error instanceof Error ? error.message : "Failed to top up SIM");
} finally {
setLoading(false);
}
};
const handleBackdropClick = (e: React.MouseEvent) => {
if (e.target === e.currentTarget) {
onClose();
}
};
return (
<div className="fixed inset-0 z-50 overflow-y-auto" onClick={handleBackdropClick}>
<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"></div>
<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">
{/* Header */}
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center">
<div className="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
<PlusIcon className="h-6 w-6 text-blue-600" />
</div>
<div className="ml-4">
<h3 className="text-lg leading-6 font-medium text-gray-900">Top Up Data</h3>
<p className="text-sm text-gray-500">Add data quota to your SIM service</p>
</div>
</div>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-500 focus:outline-none"
>
<XMarkIcon className="h-6 w-6" />
</button>
</div>
<form onSubmit={e => void handleSubmit(e)}>
{/* Amount Input */}
<div className="mb-6">
<label className="block text-sm font-medium text-gray-700 mb-2">Amount (GB)</label>
<div className="relative">
<input
type="number"
value={gbAmount}
onChange={e => setGbAmount(e.target.value)}
placeholder="Enter amount in GB"
min="1"
max="50"
step="1"
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 pr-12"
/>
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<span className="text-gray-500 text-sm">GB</span>
</div>
</div>
<p className="text-xs text-gray-500 mt-1">
Enter the amount of data you want to add (1 - 50 GB, whole numbers)
</p>
</div>
{/* Cost Display */}
<div className="mb-6 p-4 bg-blue-50 rounded-lg border border-blue-200">
<div className="flex justify-between items-center">
<div>
<div className="text-sm font-medium text-blue-900">
{gbAmount && !isNaN(parseInt(gbAmount, 10)) ? `${gbAmount} GB` : "0 GB"}
</div>
<div className="text-xs text-blue-700">= {getCurrentAmountMb()} MB</div>
</div>
<div className="text-right">
<div className="text-lg font-bold text-blue-900">
¥{calculateCost().toLocaleString()}
</div>
<div className="text-xs text-blue-700">(1GB = ¥500)</div>
</div>
</div>
</div>
{/* Validation Warning */}
{!isValidAmount() && gbAmount && (
<div className="mb-4 bg-red-50 border border-red-200 rounded-lg p-3">
<div className="flex items-center">
<ExclamationTriangleIcon className="h-4 w-4 text-red-500 mr-2" />
<p className="text-sm text-red-800">
Amount must be a whole number between 1 GB and 50 GB
</p>
</div>
</div>
)}
{/* Action Buttons */}
<div className="flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-3 space-y-3 space-y-reverse sm:space-y-0">
<button
type="button"
onClick={onClose}
disabled={loading}
className="w-full sm:w-auto px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50"
>
Back
</button>
<button
type="submit"
disabled={loading || !isValidAmount()}
className="w-full sm:w-auto px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50"
>
{loading ? "Processing..." : `Top Up Now - ¥${calculateCost().toLocaleString()}`}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
);
}