- Updated various components to use consistent color tokens, improving visual coherence. - Refactored layout components to utilize the new PublicShell for better structure. - Enhanced error and status messaging styles for improved user feedback. - Standardized button usage across forms and modals for a unified interaction experience. - Introduced new UI design tokens and guidelines in documentation to support future development.
168 lines
5.9 KiB
TypeScript
168 lines
5.9 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import Link from "next/link";
|
|
import { useParams } from "next/navigation";
|
|
import { PageLayout } from "@/components/templates/PageLayout";
|
|
import { SubCard } from "@/components/molecules/SubCard/SubCard";
|
|
import { simActionsService } from "@/features/subscriptions/services/sim-actions.service";
|
|
import { AlertBanner } from "@/components/molecules/AlertBanner/AlertBanner";
|
|
import { DevicePhoneMobileIcon } from "@heroicons/react/24/outline";
|
|
import { Button } from "@/components/atoms";
|
|
|
|
export function SimTopUpContainer() {
|
|
const params = useParams();
|
|
const subscriptionId = params.id as string;
|
|
const [gbAmount, setGbAmount] = useState<string>("1");
|
|
const [loading, setLoading] = useState(false);
|
|
const [message, setMessage] = useState<string | null>(null);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
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; // 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()) {
|
|
setError("Please enter a whole number between 1 GB and 50 GB");
|
|
return;
|
|
}
|
|
setLoading(true);
|
|
setMessage(null);
|
|
setError(null);
|
|
|
|
try {
|
|
await simActionsService.topUp(subscriptionId, { quotaMb: getCurrentAmountMb() });
|
|
setMessage(`Successfully topped up ${gbAmount} GB for ¥${calculateCost().toLocaleString()}`);
|
|
} catch (e: unknown) {
|
|
setError(
|
|
process.env.NODE_ENV === "development"
|
|
? e instanceof Error
|
|
? e.message
|
|
: "Failed to submit top-up"
|
|
: "Unable to submit your top-up right now. Please try again."
|
|
);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<PageLayout
|
|
icon={<DevicePhoneMobileIcon />}
|
|
title="Top Up Data"
|
|
description="Add data to your SIM"
|
|
>
|
|
<div className="max-w-3xl mx-auto">
|
|
<div className="mb-4">
|
|
<Link
|
|
href={`/subscriptions/${subscriptionId}#sim-management`}
|
|
className="text-primary hover:underline"
|
|
>
|
|
← Back to SIM Management
|
|
</Link>
|
|
</div>
|
|
|
|
<SubCard>
|
|
<p className="text-sm text-muted-foreground mb-6">
|
|
Add additional data quota to your SIM service. Enter the amount of data you want to add.
|
|
</p>
|
|
|
|
{message && (
|
|
<div className="mb-4">
|
|
<AlertBanner variant="success" title="Top-up Submitted">
|
|
{message}
|
|
</AlertBanner>
|
|
</div>
|
|
)}
|
|
{error && (
|
|
<div className="mb-4">
|
|
<AlertBanner variant="error" title="Top-up Failed">
|
|
{error}
|
|
</AlertBanner>
|
|
</div>
|
|
)}
|
|
|
|
<form onSubmit={e => void handleSubmit(e)} className="space-y-6">
|
|
<div>
|
|
<label className="block text-sm font-medium text-muted-foreground 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-input rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring pr-12 bg-background text-foreground placeholder:text-muted-foreground"
|
|
/>
|
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
|
<span className="text-muted-foreground text-sm">GB</span>
|
|
</div>
|
|
</div>
|
|
<p className="text-xs text-muted-foreground mt-1">
|
|
Enter the amount of data you want to add (1 - 50 GB, whole numbers)
|
|
</p>
|
|
</div>
|
|
|
|
<div className="p-4 bg-info-soft rounded-lg border border-info/25">
|
|
<div className="flex justify-between items-center">
|
|
<div>
|
|
<div className="text-sm font-medium text-foreground">
|
|
{gbAmount && !isNaN(parseInt(gbAmount, 10)) ? `${gbAmount} GB` : "0 GB"}
|
|
</div>
|
|
<div className="text-xs text-muted-foreground">= {getCurrentAmountMb()} MB</div>
|
|
</div>
|
|
<div className="text-right">
|
|
<div className="text-lg font-bold text-foreground">
|
|
¥{calculateCost().toLocaleString()}
|
|
</div>
|
|
<div className="text-xs text-muted-foreground">(1GB = ¥500)</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{!isValidAmount() && gbAmount && (
|
|
<div className="bg-destructive-soft border border-destructive/25 rounded-lg p-3">
|
|
<p className="text-sm text-destructive">
|
|
Please enter a valid whole number between 1 and 50 GB.
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex gap-3">
|
|
<Button type="submit" disabled={loading} loading={loading} loadingText="Processing…">
|
|
Submit Top Up
|
|
</Button>
|
|
<Button
|
|
as="a"
|
|
href={`/subscriptions/${subscriptionId}#sim-management`}
|
|
variant="outline"
|
|
>
|
|
Back
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
</SubCard>
|
|
</div>
|
|
</PageLayout>
|
|
);
|
|
}
|
|
|
|
export default SimTopUpContainer;
|