"use client"; import { useEffect, useMemo, useState } from "react"; import { useNavigate, useParams } from "@/lib/router"; import { api } from "@/lib/api"; import type * as Types from "@/lib/types"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import GalleryLightbox, { type GalleryLightboxItem } from "@/components/GalleryLightbox"; import ProgressiveImage from "@/components/ProgressiveImage"; import { useToast } from "@/hooks/use-toast"; import Markdown from "@/components/Markdown"; import CouponDialogFa from "@/components/CouponDialogFa"; import { formatJalali, formatNumberPersian, formatToman, getEventCardImageUrl, getEventHeroImageUrl, getEventSeoImageUrl, getGalleryImageBlurUrl, getGalleryImageFullUrl, getGalleryImagePreviewUrl, resolveErrorMessage, toPersianDigits, } from "@/lib/utils"; import { useAuth } from "@/contexts/AuthContext"; import { buildAuthPath } from "@/lib/auth-return"; const typeLabel: Record = { online: "آنلاین", on_site: "حضوری", hybrid: "آنلاین و حضوری", }; type EventDetailProps = { initialEvent?: Types.EventDetailSchema | null; }; function buildPaymentSnapshot( event: Types.EventDetailSchema, eventThumb: string | null, payload: { baseAmount: number; discountAmount: number; amount: number; }, ) { return JSON.stringify({ event_id: event.id, slug: event.slug, title: event.title, thumb: eventThumb, base_amount: payload.baseAmount, discount_amount: payload.discountAmount, amount: payload.amount, started_at: new Date().toISOString(), success_markdown: event.registration_success_markdown, }); } export default function EventDetail({ initialEvent = null }: EventDetailProps) { const { slug } = useParams<{ slug: string }>(); const { isAuthenticated } = useAuth(); const navigate = useNavigate(); const { toast } = useToast(); const [event, setEvent] = useState(initialEvent); const [eventThumb, setEventThumb] = useState( initialEvent ? getEventCardImageUrl(initialEvent) : null, ); const [lightboxIndex, setLightboxIndex] = useState(null); const [loading, setLoading] = useState(!initialEvent); const [open, setOpen] = useState(false); const [submitting, setSubmitting] = useState(false); const [alreadyRegistered, setAlreadyRegistered] = useState(false); const [nowTs, setNowTs] = useState(() => Date.now()); const basePrice = Number(event?.price ?? 0); const isFree = useMemo(() => basePrice <= 0, [basePrice]); useEffect(() => { let cancelled = false; async function checkRegistration() { if (!isAuthenticated || !event?.id) { return; } try { const res = await api.getRegistrationStatus(event.id); if (!cancelled) { setAlreadyRegistered(res.is_registered); } } catch { // Ignore registration status failures on the detail page. } } checkRegistration(); return () => { cancelled = true; }; }, [event?.id, isAuthenticated]); const goSuccess = (registrationId?: string) => { if (!event) return; const query = registrationId ? `?registration_id=${registrationId}` : ""; setAlreadyRegistered(true); toast({ title: "ثبت‌نام با موفقیت انجام شد!", variant: "success" }); navigate(`/events/${event.slug}/success${query}`); }; const handleMainCTA = async () => { if (!event) return; if (!isAuthenticated) { toast({ title: "ابتدا وارد شوید", description: "برای ثبت‌نام در رویداد باید وارد حساب کاربری خود شوید.", variant: "destructive", }); navigate(buildAuthPath(`/events/${event.slug}`)); return; } if (!isFree) { setOpen(true); return; } try { setSubmitting(true); const res = await api.registerForEvent(event.id); goSuccess(res.ticket_id); } catch (error: unknown) { const msg = resolveErrorMessage(error, ""); if (msg.includes("already registered")) { setAlreadyRegistered(true); toast({ title: "شما قبلاً ثبت‌نام کرده‌اید", variant: "destructive" }); return; } toast({ title: "خطا در ثبت‌نام", description: msg || "لطفاً دوباره تلاش کنید.", variant: "destructive", }); } finally { setSubmitting(false); } }; const handleContinueFromModal = async (coupon?: string, finalAmount?: number) => { if (!event) return; if (!isAuthenticated) { toast({ title: "ابتدا وارد شوید", description: "برای ثبت‌نام در رویداد باید وارد حساب کاربری خود شوید.", variant: "destructive", }); navigate(buildAuthPath(`/events/${event.slug}`)); return; } try { setSubmitting(true); const reg = await api.registerForEvent(event.id, coupon); if (finalAmount === 0) { window.sessionStorage.setItem( "payment:last", buildPaymentSnapshot(event, eventThumb, { baseAmount: Number(event.price ?? 0), discountAmount: Number(event.price ?? 0), amount: 0, }), ); await api.ChangeRegistrationStatus(reg.id, "confirmed"); goSuccess(reg.ticket_id); return; } const result = await api.createPayment({ event_id: event.id, description: `پرداخت رویداد: ${event.title}`, discount_code: (coupon ?? "").trim() || null, }); if (!result?.start_pay_url || Number(result.amount) === 0) { window.sessionStorage.setItem( "payment:last", buildPaymentSnapshot(event, eventThumb, { baseAmount: result.base_amount, discountAmount: result.discount_amount ?? result.base_amount, amount: 0, }), ); goSuccess(reg.ticket_id); return; } window.sessionStorage.setItem( "payment:last", buildPaymentSnapshot(event, eventThumb, { baseAmount: result.base_amount, discountAmount: result.discount_amount, amount: result.amount, }), ); window.location.href = result.start_pay_url; } catch (error: unknown) { const msg = resolveErrorMessage(error, ""); if (msg.includes("already registered")) { setAlreadyRegistered(true); toast({ title: "شما قبلاً ثبت‌نام کرده‌اید", variant: "destructive" }); return; } toast({ title: "خطا در پردازش پرداخت", description: msg || "لطفاً دوباره تلاش کنید.", variant: "destructive", }); } finally { setSubmitting(false); setOpen(false); } }; useEffect(() => { let cancelled = false; async function loadEvent() { try { if (!slug) return; if (initialEvent && initialEvent.slug === slug) { setEvent(initialEvent); setEventThumb(getEventCardImageUrl(initialEvent)); return; } const data = await api.getEventBySlug(slug); if (cancelled) return; setEvent(data); setEventThumb(getEventCardImageUrl(data)); } catch (error: unknown) { if (cancelled) return; toast({ title: "خطا در بارگذاری رویداد", description: resolveErrorMessage(error, "لطفاً دوباره تلاش کنید."), variant: "destructive", }); } finally { if (!cancelled) { setLoading(false); } } } loadEvent(); return () => { cancelled = true; }; }, [initialEvent, slug, toast]); useEffect(() => { const timer = window.setInterval(() => setNowTs(Date.now()), 1000); return () => window.clearInterval(timer); }, []); const rsTs = useMemo( () => (event?.registration_start_date ? new Date(event.registration_start_date).getTime() : null), [event?.registration_start_date], ); const deadlineTs = useMemo( () => (event?.registration_end_date ? new Date(event.registration_end_date).getTime() : null), [event?.registration_end_date], ); const remainingMs = useMemo( () => (deadlineTs != null ? Math.max(0, deadlineTs - nowTs) : null), [deadlineTs, nowTs], ); const formatCountdownTwoDigit = (value: number) => toPersianDigits(value.toString().padStart(2, "0")); const formatCountdownNumber = (value: number) => formatNumberPersian(value); const formatRemainingWords = (ms: number) => { const total = Math.max(0, Math.floor(ms / 1000)); const days = Math.floor(total / 86400); const hours = Math.floor((total % 86400) / 3600); const minutes = Math.floor((total % 3600) / 60); const seconds = total % 60; if (days === 0) { return `${formatCountdownTwoDigit(hours)} ساعت و ${formatCountdownTwoDigit(minutes)} دقیقه و ${formatCountdownTwoDigit(seconds)} ثانیه`; } return `${formatCountdownNumber(days)} روز و ${formatCountdownTwoDigit(hours)} ساعت و ${formatCountdownTwoDigit(minutes)} دقیقه و ${formatCountdownTwoDigit(seconds)} ثانیه`; }; const meta = useMemo(() => { if (!event) return null; const registrationOpen = (rsTs == null || nowTs >= rsTs) && (deadlineTs == null || nowTs <= deadlineTs); const unlimited = event.capacity == null; const remaining = unlimited ? Number.POSITIVE_INFINITY : Math.max(0, (event.capacity || 0) - (event.registration_count || 0)); const full = !unlimited && remaining <= 0; return { registrationOpen, remaining, full }; }, [deadlineTs, event, nowTs, rsTs]); const galleryItems = useMemo( () => (event?.gallery_images ?? []).map((image) => ({ id: image.id, alt: image.title || event?.title || "تصویر رویداد", title: image.title, previewSrc: getGalleryImagePreviewUrl(image), blurSrc: getGalleryImageBlurUrl(image), fullSrc: getGalleryImageFullUrl(image), })), [event], ); if (loading) { return
در حال بارگذاری رویداد...
; } if (!event) { return
رویداد مورد نظر یافت نشد.
; } const beforeStart = rsTs != null && nowTs < rsTs; const ended = deadlineTs !== null && remainingMs === 0; const showCountdown = !beforeStart && deadlineTs !== null && (remainingMs ?? 0) > 0; return (
{beforeStart && (
ثبت‌نام از {formatJalali(event.registration_start_date!)} آغاز می‌شود.
)} {showCountdown && remainingMs != null && (
زمان باقی‌مانده تا پایان ثبت‌نام: {formatRemainingWords(remainingMs)}
)} {ended && (
مهلت ثبت‌نام به پایان رسیده است.
)}
{event.title} {formatJalali(event.start_time)} {event.end_time ? ` تا ${formatJalali(event.end_time)}` : null}
{typeLabel[event.event_type] || event.event_type}
{galleryItems.length ? (

گالری تصاویر

{galleryItems.map((image, index) => ( ))}
) : null}
اطلاعات ثبت‌نام جزئیات دسترسی به رویداد {event.address &&
آدرس: {event.address}
}
ظرفیت کل: {event.capacity == null ? "نامحدود" : formatNumberPersian(event.capacity)}
{meta && event.capacity != null && (
ظرفیت باقی‌مانده:{" "} {meta.remaining === Number.POSITIVE_INFINITY ? "نامحدود" : formatNumberPersian(meta.remaining)}
)}
هزینه حضور: {event.price ? formatToman(event.price) : "رایگان"}
{!isFree && ( api.checkDiscountCode(event.id, code)} onContinue={handleContinueFromModal} /> )}
{ if (!isOpen) { setLightboxIndex(null); } }} />
); }