feat: fail fallback pae added

This commit is contained in:
Afra ‌ 2026-06-28 16:20:43 +03:30
parent 60406f9893
commit 14f3fc68e9
7 changed files with 98 additions and 7 deletions

View file

@ -5,9 +5,11 @@ import CafeDetail from './pages/CafeDetail'
import PaymentSuccess from './pages/PaymentSuccess'
import CafeOwnerRegister from './pages/CafeOwnerRegister'
import CafeRegisterSuccess from './pages/CafeRegisterSuccess'
import CafeRegisterFail from './pages/CafeRegisterFail'
import CafeOwnerPanel from './pages/CafeOwnerPanel'
import AddEvent from './pages/AddEvent'
import AddEventSuccess from './pages/AddEventSuccess'
import AddEventFail from './pages/AddEventFail'
import EventReservations from './pages/EventReservations'
import MyReservations from './pages/MyReservations'
import Login from './pages/Login'
@ -36,9 +38,11 @@ export default function App() {
<Route path="/reservations" element={<RequireRole role="customer"><MyReservations /></RequireRole>} />
<Route path="/owner/register" element={<RequireRole role="cafe_owner"><CafeOwnerRegister /></RequireRole>} />
<Route path="/owner/register/success" element={<RequireRole role="cafe_owner"><CafeRegisterSuccess /></RequireRole>} />
<Route path="/owner/register/fail" element={<RequireRole role="cafe_owner"><CafeRegisterFail /></RequireRole>} />
<Route path="/owner/panel" element={<RequireRole role="cafe_owner"><CafeOwnerPanel /></RequireRole>} />
<Route path="/owner/events/new" element={<RequireRole role="cafe_owner"><AddEvent /></RequireRole>} />
<Route path="/owner/events/success" element={<RequireRole role="cafe_owner"><AddEventSuccess /></RequireRole>} />
<Route path="/owner/events/fail" element={<RequireRole role="cafe_owner"><AddEventFail /></RequireRole>} />
<Route path="/owner/events/:id/reservations" element={<RequireRole role="cafe_owner"><EventReservations /></RequireRole>} />
<Route path="/login" element={<Login />} />
<Route path="/cafe/login" element={<Login />} />

View file

@ -0,0 +1,18 @@
export default function FailIcon({ className = 'mx-auto h-28 w-28' }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 119 119" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M37.9171 74.4333L74.1559 38.1342C76.724 35.5615 80.2279 34.9239 82.012 36.7033C83.7943 38.4839 83.1619 41.9888 80.5938 44.5622L44.3551 80.8595C41.7863 83.433 38.2831 84.0709 36.499 82.2904C34.716 80.5109 35.3483 77.006 37.9171 74.4333Z"
fill="#C23B3B"
/>
<path
d="M80.8379 74.4333L44.5996 38.1342C42.0304 35.5615 38.5265 34.9239 36.7431 36.7033C34.9601 38.4839 35.5924 41.9888 38.1616 44.5622L74.4 80.8595C76.9681 83.433 80.472 84.0709 82.2554 82.2904C84.0384 80.5109 83.4061 77.006 80.8379 74.4333Z"
fill="#C23B3B"
/>
<path
d="M59.5 0C43.7197 0 28.5856 6.26821 17.4278 17.4267C6.26891 28.5856 0 43.719 0 59.5C0 75.2803 6.26891 90.4144 17.4278 101.572C28.5856 112.731 43.7197 118.999 59.5 118.999C75.281 118.999 90.4144 112.73 101.572 101.572C112.731 90.4134 119 75.2803 119 59.5C119 43.7197 112.731 28.5856 101.572 17.4267C90.4144 6.26891 75.281 0 59.5 0ZM59.5 9.34303C72.8025 9.34303 85.5598 14.6275 94.9663 24.0337C104.373 33.4402 109.657 46.1968 109.657 59.5C109.657 72.8014 104.373 85.5591 94.9663 94.9653C85.5598 104.371 72.8025 109.656 59.5 109.656C46.1975 109.656 33.4409 104.371 24.0347 94.9653C14.6285 85.5591 9.34409 72.8014 9.34409 59.5C9.34409 46.1968 14.6285 33.4402 24.0347 24.0337C33.4409 14.6275 46.1975 9.34303 59.5 9.34303Z"
fill="#C23B3B"
/>
</svg>
)
}

View file

@ -204,10 +204,8 @@ export default function AddEvent() {
const [entryFee, setEntryFee] = useState(100000)
const [description, setDescription] = useState('')
const [isSubmitting, setIsSubmitting] = useState(false)
const [error, setError] = useState('')
const handleSubmit = async () => {
setError('')
setIsSubmitting(true)
try {
await createCafeSchedule(getCafeUuid() ?? '', {
@ -218,7 +216,7 @@ export default function AddEvent() {
})
navigate('/owner/events/success')
} catch {
setError('ثبت رویداد با خطا مواجه شد. لطفاً دوباره تلاش کنید.')
navigate('/owner/events/fail')
} finally {
setIsSubmitting(false)
}
@ -247,8 +245,6 @@ export default function AddEvent() {
</div>
<DescriptionField value={description} onChange={setDescription} />
{error && <p className="mt-4 text-right text-sm text-red-500">{error}</p>}
</div>
<div className="pointer-events-none fixed inset-x-0 bottom-0 flex justify-end bg-white px-4 py-4">

View file

@ -0,0 +1,34 @@
import { useNavigate } from 'react-router-dom'
import FailIcon from '../components/FailIcon'
export default function AddEventFail() {
const navigate = useNavigate()
return (
<div className="flex min-h-screen flex-col bg-white pb-32">
<div className="h-16 w-full bg-gray-200" />
<div className="flex flex-1 flex-col items-center justify-center px-6 text-center">
<FailIcon />
<h1 className="mt-6 text-xl font-light text-gray-900">ثبت ایونت با خطا مواجه شد. لطفا دوباره تلاش کنید.</h1>
</div>
<div className="fixed inset-x-0 bottom-0 flex flex-col gap-3 bg-white px-4 py-4">
<button
type="button"
onClick={() => navigate('/owner/events/new')}
className="w-full rounded-2xl bg-[#223D5D] py-4 text-lg font-semibold text-white"
>
افزودن ایونت
</button>
<button
type="button"
onClick={() => navigate('/owner/panel')}
className="w-full rounded-2xl border border-[#223D5D] py-4 text-lg font-semibold text-[#223D5D]"
>
مشاهده پنل
</button>
</div>
</div>
)
}

View file

@ -401,7 +401,14 @@ export default function CafeOwnerRegister() {
navigate("/owner/register/success");
}
} catch {
setError("ثبت اطلاعات با خطا مواجه شد. لطفاً دوباره تلاش کنید.");
// only the final cafe-registration step has a dedicated success page to mirror —
// owner registration (step 1) and editing an existing cafe just advance/redirect
// inline on success, so their failures stay inline too
if (step === 2 && !isEditMode) {
navigate("/owner/register/fail");
} else {
setError("ثبت اطلاعات با خطا مواجه شد. لطفاً دوباره تلاش کنید.");
}
} finally {
setIsSubmitting(false);
}

View file

@ -0,0 +1,29 @@
import { useNavigate } from 'react-router-dom'
import FailIcon from '../components/FailIcon'
export default function CafeRegisterFail() {
const navigate = useNavigate()
return (
<div className="flex min-h-screen flex-col bg-white pb-32">
<div className="h-16 w-full bg-gray-200" />
<div className="flex flex-1 flex-col items-center justify-center px-6 text-center">
<FailIcon />
<h1 className="mt-6 text-xl font-light text-gray-900">ثبت کافه با خطا مواجه شد. لطفا دوباره تلاش کنید.</h1>
</div>
<div className="fixed inset-x-0 bottom-0 flex flex-col gap-3 bg-white px-4 py-4">
{/* no "مشاهده پنل" option here, unlike the other fail pages — registration never
completed, so there's no cafe yet for the panel or "add event" flow to operate on */}
<button
type="button"
onClick={() => navigate('/owner/register')}
className="w-full rounded-2xl bg-[#223D5D] py-4 text-lg font-semibold text-white"
>
تلاش دوباره
</button>
</div>
</div>
)
}

View file

@ -1,5 +1,6 @@
import { useEffect, useState } from 'react'
import { useNavigate, useSearchParams } from 'react-router-dom'
import FailIcon from '../components/FailIcon'
import InfoIcon from '../components/InfoIcon'
import SuccessIcon from '../components/SuccessIcon'
import { createReservation, ReservationResult } from '../services/campaignApi'
@ -26,6 +27,8 @@ export default function PaymentSuccess() {
const isSuccess = searchParams.get('state') === SUCCESS_STATE
const [reservation, setReservation] = useState<ReservationResult | null>(null)
const [error, setError] = useState('')
// gateway-reported failure, or a successful payment whose reservation couldn't be created
const isFail = !isSuccess || !!error
useEffect(() => {
if (!isSuccess) return
@ -42,7 +45,7 @@ export default function PaymentSuccess() {
return (
<div className="flex min-h-screen items-center justify-center bg-gray-50 p-4">
<div className="w-full max-w-sm rounded-3xl bg-white p-6 shadow-lg">
<SuccessIcon />
{isFail ? <FailIcon /> : <SuccessIcon />}
{isSuccess ? (
reservation ? (