From 406fba535ddd0ae977ea9ee0cf6465228b0019ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Afra=20=E2=80=8C?= Date: Wed, 24 Jun 2026 22:47:43 +0330 Subject: [PATCH] perf: cfinal touches --- src/App.tsx | 5 +- src/components/ReservationModal.tsx | 50 +++++++++++++++--- src/pages/CafeDetail.tsx | 4 +- src/pages/CafeOwnerRegister.tsx | 2 +- src/pages/Home.tsx | 82 +++++++++++++++-------------- src/pages/Login.tsx | 2 +- src/pages/LoginOtp.tsx | 16 ++++-- src/pages/PaymentSuccess.tsx | 81 ++++++++++++++++++---------- src/services/api.ts | 10 +--- src/services/authApi.ts | 20 +++++++ src/services/campaignApi.ts | 29 +++++++++- src/services/ipgApi.ts | 10 +--- src/services/walletApi.ts | 10 +--- src/utils/authInterceptor.ts | 61 +++++++++++++++++++++ src/utils/authToken.ts | 9 ++++ src/utils/pendingReservation.ts | 21 ++++++++ 16 files changed, 299 insertions(+), 113 deletions(-) create mode 100644 src/utils/authInterceptor.ts create mode 100644 src/utils/pendingReservation.ts diff --git a/src/App.tsx b/src/App.tsx index 8421a84..13e42c8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,5 @@ import { useEffect } from 'react' -import { BrowserRouter, Routes, Route, useLocation } from 'react-router-dom' +import { BrowserRouter, Routes, Route, useLocation, Navigate } from 'react-router-dom' import Home from './pages/Home' import CafeDetail from './pages/CafeDetail' import PaymentSuccess from './pages/PaymentSuccess' @@ -29,7 +29,8 @@ export default function App() { - } /> + } /> + } /> } /> } /> } /> diff --git a/src/components/ReservationModal.tsx b/src/components/ReservationModal.tsx index 6855011..eb2fb46 100644 --- a/src/components/ReservationModal.tsx +++ b/src/components/ReservationModal.tsx @@ -1,9 +1,12 @@ 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 InfoIcon from "./InfoIcon"; @@ -12,6 +15,7 @@ interface ReservationModalProps { onClose: () => void; cafe: Cafe; ticketPrice: number; + cafeMatchScheduleUuid: string; } // TODO: there's no "get the logged-in customer's own wallet uuid" endpoint yet — @@ -80,7 +84,9 @@ export default function ReservationModal({ onClose, cafe, ticketPrice, + cafeMatchScheduleUuid, }: ReservationModalProps) { + const navigate = useNavigate(); const [fullName, setFullName] = useState(""); const [count, setCount] = useState(1); const [walletBalance, setWalletBalance] = useState(0); @@ -111,14 +117,36 @@ export default function ReservationModal({ 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 { - 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); + 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); @@ -230,11 +258,17 @@ export default function ReservationModal({ {error &&

{error}

} diff --git a/src/pages/CafeDetail.tsx b/src/pages/CafeDetail.tsx index c8e63d0..457de7f 100644 --- a/src/pages/CafeDetail.tsx +++ b/src/pages/CafeDetail.tsx @@ -354,7 +354,8 @@ export default function CafeDetail() { @@ -365,6 +366,7 @@ export default function CafeDetail() { onClose={() => setIsReservationOpen(false)} cafe={cafe} ticketPrice={schedule ? Number(schedule.ticket_price) : 0} + cafeMatchScheduleUuid={schedule?.schedule_uuid ?? ""} /> ); diff --git a/src/pages/CafeOwnerRegister.tsx b/src/pages/CafeOwnerRegister.tsx index 462ccc3..9bb250d 100644 --- a/src/pages/CafeOwnerRegister.tsx +++ b/src/pages/CafeOwnerRegister.tsx @@ -473,7 +473,7 @@ export default function CafeOwnerRegister() { onChange={setAddress} /> - - - - - ) -} - -function ListIcon() { - return ( - - - - - - - - - ) -} +// MapIcon/ListIcon — only used by the disabled نقشه/لیست toggle above, commented out together with it +// function MapIcon() { +// return ( +// +// +// +// +// +// ) +// } +// +// function ListIcon() { +// return ( +// +// +// +// +// +// +// +// +// ) +// } function MapPlaceholder() { return ( @@ -213,7 +214,9 @@ function CafeCard({ cafe }: { cafe: Cafe }) { } export default function Home() { - const [view, setView] = useState<'list' | 'map'>('list') + // نقشه/لیست toggle — disabled for now, the map view isn't part of the app yet + // (will be added in a later version); uncomment together with its JSX below + // const [view, setView] = useState<'list' | 'map'>('list') const [cities, setCities] = useState([]) const [selectedCityUuid, setSelectedCityUuid] = useState('') const [selectedMatchUuid, setSelectedMatchUuid] = useState('') @@ -234,9 +237,9 @@ export default function Home() { }, []) useEffect(() => { - // wait until both real defaults are known — city_uuid/event_uuid must always be sent together + // wait until both real defaults are known — city_uuid/event_id must always be sent together if (!effectiveCityUuid || !effectiveMatchUuid) return - fetchCafes({ city_uuid: effectiveCityUuid, event_uuid: effectiveMatchUuid }) + fetchCafes({ city_uuid: effectiveCityUuid, event_id: effectiveMatchUuid }) .then(setCafes) .catch(() => {}) }, [effectiveCityUuid, effectiveMatchUuid]) @@ -258,6 +261,7 @@ export default function Home() {

کافه‌ها

+ {/* نقشه/لیست toggle — commented out together with the `view` state above, see that comment
+ */}
- {view === 'list' ? ( -
- {cafes.map((cafe) => ( - - ))} -
- ) : ( - - )} + {/* always the list view for now — restore the `view === 'list' ? ... : ` ternary when the map toggle above is restored */} +
+ {cafes.map((cafe) => ( + + ))} +