145 lines
4.0 KiB
TypeScript
145 lines
4.0 KiB
TypeScript
import { useMutation } from "@tanstack/react-query";
|
||
import { useState } from "react";
|
||
import { Button } from "@/components/ui/button";
|
||
import { Input } from "@/components/ui/input";
|
||
import {
|
||
formatCents,
|
||
TOPUP_MAX_CENTS,
|
||
TOPUP_MIN_CENTS,
|
||
TOPUP_PRESETS_CENTS,
|
||
} from "@/lib/drinkkaart";
|
||
import { orpc } from "@/utils/orpc";
|
||
|
||
interface TopUpModalProps {
|
||
onClose: () => void;
|
||
}
|
||
|
||
export function TopUpModal({ onClose }: TopUpModalProps) {
|
||
const [selectedCents, setSelectedCents] = useState<number>(1000);
|
||
const [customEuros, setCustomEuros] = useState("");
|
||
const [useCustom, setUseCustom] = useState(false);
|
||
|
||
const checkoutMutation = useMutation({
|
||
...orpc.drinkkaart.getTopUpCheckoutUrl.mutationOptions(),
|
||
onSuccess: (data: { checkoutUrl: string }) => {
|
||
window.location.href = data.checkoutUrl;
|
||
},
|
||
onError: (err: Error) => {
|
||
console.error("Checkout error:", err);
|
||
},
|
||
});
|
||
|
||
const amountCents = useCustom
|
||
? Math.round((Number.parseFloat(customEuros || "0") || 0) * 100)
|
||
: selectedCents;
|
||
|
||
const isValidAmount =
|
||
Number.isInteger(amountCents) &&
|
||
amountCents >= TOPUP_MIN_CENTS &&
|
||
amountCents <= TOPUP_MAX_CENTS;
|
||
|
||
const handleSubmit = () => {
|
||
if (!isValidAmount) return;
|
||
checkoutMutation.mutate({ amountCents });
|
||
};
|
||
|
||
return (
|
||
<div className="fixed inset-0 z-50 flex items-end justify-center sm:items-center">
|
||
<button
|
||
type="button"
|
||
className="absolute inset-0 bg-black/60"
|
||
aria-label="Sluiten"
|
||
onClick={onClose}
|
||
/>
|
||
<div className="relative z-10 w-full max-w-sm rounded-t-2xl bg-[#1a3d40] p-6 sm:rounded-2xl">
|
||
<div className="mb-5 flex items-center justify-between">
|
||
<h2 className="font-['Intro',sans-serif] text-white text-xl">
|
||
Drinkkaart opladen
|
||
</h2>
|
||
<button
|
||
type="button"
|
||
onClick={onClose}
|
||
className="text-white/50 hover:text-white"
|
||
aria-label="Sluiten"
|
||
>
|
||
✕
|
||
</button>
|
||
</div>
|
||
|
||
{/* Preset amounts */}
|
||
<div className="mb-4 grid grid-cols-4 gap-2">
|
||
{TOPUP_PRESETS_CENTS.map((cents) => (
|
||
<button
|
||
key={cents}
|
||
type="button"
|
||
onClick={() => {
|
||
setSelectedCents(cents);
|
||
setUseCustom(false);
|
||
}}
|
||
className={`rounded-lg border py-3 font-medium text-sm transition-colors ${
|
||
!useCustom && selectedCents === cents
|
||
? "border-white bg-white text-[#214e51]"
|
||
: "border-white/20 text-white hover:border-white/50"
|
||
}`}
|
||
>
|
||
{formatCents(cents)}
|
||
</button>
|
||
))}
|
||
</div>
|
||
|
||
{/* Custom amount */}
|
||
<div className="mb-5">
|
||
<label
|
||
htmlFor="custom-amount"
|
||
className="mb-2 block text-sm text-white/60"
|
||
>
|
||
Eigen bedrag (€ 1 – € 500)
|
||
</label>
|
||
<div className="relative">
|
||
<span className="absolute top-1/2 left-3 -translate-y-1/2 text-white/50">
|
||
€
|
||
</span>
|
||
<Input
|
||
id="custom-amount"
|
||
type="number"
|
||
min="1"
|
||
max="500"
|
||
step="0.50"
|
||
placeholder="Bijv. 15"
|
||
value={customEuros}
|
||
onChange={(e) => {
|
||
setCustomEuros(e.target.value);
|
||
setUseCustom(true);
|
||
}}
|
||
className="border-white/20 bg-white/10 pl-8 text-white placeholder:text-white/30"
|
||
/>
|
||
</div>
|
||
{useCustom && !isValidAmount && customEuros !== "" && (
|
||
<p className="mt-1 text-red-400 text-sm">
|
||
Bedrag moet tussen {formatCents(TOPUP_MIN_CENTS)} en{" "}
|
||
{formatCents(TOPUP_MAX_CENTS)} liggen
|
||
</p>
|
||
)}
|
||
</div>
|
||
|
||
<div className="mb-3 rounded-lg bg-white/5 px-4 py-3">
|
||
<span className="text-sm text-white/60">Te betalen: </span>
|
||
<span className="font-semibold text-white">
|
||
{isValidAmount ? formatCents(amountCents) : "—"}
|
||
</span>
|
||
</div>
|
||
|
||
<Button
|
||
onClick={handleSubmit}
|
||
disabled={!isValidAmount || checkoutMutation.isPending}
|
||
className="w-full bg-white font-semibold text-[#214e51] hover:bg-white/90 disabled:opacity-50"
|
||
>
|
||
{checkoutMutation.isPending
|
||
? "Doorsturen naar betaling..."
|
||
: "Naar betaling"}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|