import { useEffect, useState } from "react"; import { createPortal } from "react-dom"; import { useNavigate } from "react-router-dom"; import { Cafe } from "../services/campaignApi"; import { chargePageUrl, createCharge } from "../services/ipgApi"; import { fetchWalletBalances } from "../services/walletApi"; import { formatPersianNumber } from "../utils/formatNumber"; import { toE164 } from "../utils/phone"; import { setPendingReservation } from "../utils/pendingReservation"; import { getUserPhone } from "../utils/userPhone"; import Icon from "./Icon"; interface ReservationModalProps { isOpen: boolean; onClose: () => void; cafe: Cafe; ticketPrice: number; cafeMatchScheduleUuid: string; } // TODO: there's no "get the logged-in customer's own wallet uuid" endpoint yet — // these are fixed test wallet ids given directly for now, replace once that exists const WALLET_UUIDS = ["e7c9d4d1-4d1f-43b2-96f7-4d4a168f480d", "af7d967f-30c0-409b-9066-2549f2da5e5e"]; function PaymentRow({ label, amount, emphasize = false, bottomBorder = false, infoIconParam = false, }: { label: string; amount: number; emphasize?: boolean; bottomBorder?: boolean; infoIconParam?: boolean; }) { return (
{label} {infoIconParam && ( )}
{formatPersianNumber(amount)} تومان
); } export default function ReservationModal({ isOpen, onClose, cafe, ticketPrice, cafeMatchScheduleUuid, }: ReservationModalProps) { const navigate = useNavigate(); const [fullName, setFullName] = useState(""); const [count, setCount] = useState(1); const [walletBalance, setWalletBalance] = useState(0); const [isSubmitting, setIsSubmitting] = useState(false); const [error, setError] = useState(""); useEffect(() => { if (!isOpen) return; const previousOverflow = document.body.style.overflow; document.body.style.overflow = "hidden"; return () => { document.body.style.overflow = previousOverflow; }; }, [isOpen]); useEffect(() => { if (!isOpen) return; fetchWalletBalances(WALLET_UUIDS) .then((results) => setWalletBalance(results[0]?.balance ?? 0)) .catch(() => {}); }, [isOpen]); if (!isOpen) return null; const reservationAmount = ticketPrice * count; const payableAmount = Math.max(0, reservationAmount - walletBalance); const handlePay = async () => { setError(""); if (!fullName.trim()) { setError("لطفاً نام و نام خانوادگی را وارد کنید."); return; } setIsSubmitting(true); const payload = { cafe_match_schedule_uuid: cafeMatchScheduleUuid, people_count: count, full_name: fullName, phone_number: toE164(getUserPhone() ?? ""), }; try { if (payableAmount > 0) { // reservation creation happens on /payment/success, only once the gateway confirms state=20 setPendingReservation(payload); const appBaseUrl = (import.meta.env.VITE_APP_BASE_URL || window.location.origin).replace(/\/$/, ""); const { uuid } = await createCharge({ amount: payableAmount, callback_url: `${appBaseUrl}/payment/success`, }); window.location.href = chargePageUrl(uuid); } else { // wallet already covers the full amount — no gateway involved, land straight on the same success page setPendingReservation(payload); navigate("/payment/success?state=20"); } } catch { setError("اتصال به درگاه پرداخت با خطا مواجه شد. لطفاً دوباره تلاش کنید."); setIsSubmitting(false); } }; return createPortal( <>

{cafe.name}

{cafe.address}

setFullName(e.target.value)} placeholder="نام و نام خانوادگی" className="w-full rounded-2xl border border-gray-200 px-4 py-3 text-right placeholder:font-light placeholder:text-[#999999]" />
{formatPersianNumber(count)} نفر

پرداخت

توجه داشته باشید که مسئولیت رزرو و حضور به‌موقع در کافه بر عهده کاربر است.

{error &&

{error}

}
, document.body, ); }