feat:drinkkaart
This commit is contained in:
144
apps/web/src/components/drinkkaart/TopUpModal.tsx
Normal file
144
apps/web/src/components/drinkkaart/TopUpModal.tsx
Normal file
@@ -0,0 +1,144 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user