EventBuzz_Website/src/pages/EventLandingPage.tsx

233 lines
8.8 KiB
TypeScript

import { useEffect, useMemo, useState } from "react";
import { Calendar, MapPin } from "lucide-react";
import { Link, useSearchParams } from "react-router-dom";
import {
getVisitorEventAgenda,
getVisitorLoginPreview,
type VisitorSessionPreview,
} from "../lib/api";
import { formatEventScheduleRange } from "../lib/sessionDisplay";
import {
groupSessionsIntoAgendaDays,
LandingAgendaCards,
LandingMySessionsList,
} from "../components/EventLandingAgenda";
import { EventBanner } from "../components/EventBanner";
import { useEventRegistrationSettings } from "../hooks/useEventRegistrationSettings";
import { useSessionBookmarks } from "../hooks/useSessionBookmarks";
import { readStoredVisitorContext } from "../types/visitorContext";
import type { LoginPreviewResponse } from "../types/visitorContext";
import { EventRatingBadge } from "../components/EventRatingBadge";
import { HeaderMobile } from "../components/HeaderMobile";
import { MobileShell } from "../components/MobileShell";
export function EventLandingPage() {
const [searchParams] = useSearchParams();
const urlEventId = searchParams.get("event_id")?.trim() || "";
const ctx = readStoredVisitorContext();
const eventId = urlEventId || ctx?.eventId || "";
const [fetchedEvent, setFetchedEvent] = useState<LoginPreviewResponse | null>(null);
useEffect(() => {
if (!eventId) {
setFetchedEvent(null);
return;
}
let cancelled = false;
getVisitorLoginPreview(eventId)
.then((ev) => {
if (!cancelled) setFetchedEvent(ev);
})
.catch(() => {
if (!cancelled) setFetchedEvent(null);
});
return () => {
cancelled = true;
};
}, [eventId]);
const event = fetchedEvent ?? (ctx?.eventId === eventId ? ctx.event : null);
const reg = useEventRegistrationSettings(event ?? null);
const { bookmarkedIds, toggle: toggleBookmark } = useSessionBookmarks(eventId);
const [agendaSessions, setAgendaSessions] = useState<VisitorSessionPreview[]>([]);
const [agendaLoading, setAgendaLoading] = useState(false);
const [agendaErr, setAgendaErr] = useState<string | null>(null);
useEffect(() => {
if (!eventId) {
setAgendaSessions([]);
setAgendaErr(null);
setAgendaLoading(false);
return;
}
let cancelled = false;
setAgendaLoading(true);
setAgendaErr(null);
setAgendaSessions([]);
getVisitorEventAgenda(eventId)
.then((sessions) => {
if (!cancelled) setAgendaSessions(sessions);
})
.catch((e) => {
if (!cancelled) {
setAgendaErr(e instanceof Error ? e.message : "Could not load agenda.");
setAgendaSessions([]);
}
})
.finally(() => {
if (!cancelled) setAgendaLoading(false);
});
return () => {
cancelled = true;
};
}, [eventId]);
const [dayOpen, setDayOpen] = useState<Record<string, boolean>>({});
useEffect(() => {
setDayOpen({});
}, [eventId]);
const agendaDays = useMemo(() => groupSessionsIntoAgendaDays(agendaSessions), [agendaSessions]);
const bookmarkedSessionsSorted = useMemo(() => {
if (!bookmarkedIds.size) return [];
return agendaSessions
.filter((s) => bookmarkedIds.has(s._id))
.sort((a, b) => new Date(a.startTime).getTime() - new Date(b.startTime).getTime());
}, [agendaSessions, bookmarkedIds]);
useEffect(() => {
const first = agendaDays[0]?.dateKey;
if (!first) return;
setDayOpen((prev) => {
if (Object.keys(prev).length > 0) return prev;
return { [first]: true };
});
}, [agendaDays]);
const schedule =
event?.startDate && event?.endDate
? formatEventScheduleRange(
typeof event.startDate === "string" ? event.startDate : String(event.startDate),
typeof event.endDate === "string" ? event.endDate : String(event.endDate),
)
: "";
const scopedQs = eventId ? new URLSearchParams({ event_id: eventId }).toString() : "";
const ratingsHref = scopedQs ? `/ratings?${scopedQs}` : "/ratings";
const bannerSrc = event?.bannerImage?.trim() || event?.image?.trim() || "";
if (!eventId) {
return (
<MobileShell className="relative bg-transparent">
<HeaderMobile showRewardsAndLogout={false} showBack={false} />
<main className="flex flex-1 flex-col items-center justify-center px-4 py-10">
<p className="text-center text-sm text-[#24282f]">Open this page from your event link or sign in again.</p>
<Link to="/" className="mt-4 text-sm font-semibold text-[#1e72ef] underline">
Home
</Link>
</main>
</MobileShell>
);
}
if (!event) {
return (
<MobileShell className="relative bg-transparent">
<HeaderMobile logoUrl={ctx?.event?.logoUrl} eventId={eventId || null} showBack={false} />
<main className="flex flex-1 items-center justify-center px-4 py-10">
<p className="text-sm text-[#24282f]">Loading event…</p>
</main>
</MobileShell>
);
}
return (
<MobileShell className="relative bg-transparent">
<HeaderMobile logoUrl={event.logoUrl} eventId={eventId || null} showBack={false} />
<EventBanner imageUrl={bannerSrc} alt={event.name} className="rounded-b-lg shadow-sm" />
<main className="relative z-0 flex w-full max-w-none flex-1 flex-col pb-8 pt-6">
<div className="flex w-full max-w-none flex-col gap-6 px-4">
<section className="w-full">
<div className="w-full rounded-lg card-bg p-4 shadow-md">
<h1 className="text-lg font-semibold">{event.name}</h1>
<div className="mt-2 flex flex-wrap items-center gap-2 text-xs">
<EventRatingBadge ratings={event.ratings} />
{reg.enableRatings ? (
<Link to={ratingsHref} className="font-semibold uppercase tracking-wide theme-btn-link">
rate now
</Link>
) : (
<span className="text-[var(--theme-card-text-color)]/50">Ratings off</span>
)}
</div>
<p className="mt-3 text-sm leading-5 text-[var(--theme-card-text-color)]">
{event.description?.trim() || event.subtitle?.trim() || "Welcome to this event."}
</p>
{/* <button type="button" className="mt-2 text-xs font-semibold uppercase tracking-wide text-[#1e72e theme-btn-link">
Read More
</button> */}
<div className="mt-4 flex flex-col gap-2 border-t border-[var(--theme-card-text-color)]/5 pt-4 text-xs text-[var(--theme-card-text-color)]/95">
<div className="flex items-center gap-2">
<Calendar className="size-4 shrink-0 text-[var(--theme-button-color)]" strokeWidth={1.75} />
<span className="">{schedule || "—"}</span>
</div>
{event.location?.trim() ? (
<div className="flex items-center gap-2">
<MapPin className="size-4 shrink-0 text-[var(--theme-button-color)]" strokeWidth={1.75} />
<span className="">{event.location.trim()}</span>
</div>
) : null}
</div>
</div>
</section>
{bookmarkedIds.size > 0 ? (
<section className="w-full card-bg rounded-lg shadow-md p-4">
<h2 className="text-md font-semibold">My Sessions</h2>
{agendaLoading && !agendaSessions.length ? (
<p className="mt-3 px-4 text-sm text-[var(--theme-card-text-color)]/80">Loading…</p>
) : (
<LandingMySessionsList
sessions={bookmarkedSessionsSorted}
eventId={eventId}
bookmarkedIds={bookmarkedIds}
toggleBookmark={toggleBookmark}
/>
)}
</section>
) : null}
<section className="w-full card-bg rounded-lg shadow-md p-4">
<h2 className="text-base font-semibold">Agenda</h2>
{agendaErr ? (
<p className="mt-3 px-4 text-sm text-red-600">{agendaErr}</p>
) : null}
{agendaLoading && !agendaSessions.length ? (
<p className="mt-3 px-4 text-sm text-[var(--theme-card-text-color)]/80">Loading agenda…</p>
) : agendaDays.length === 0 ? (
<p className="mt-3 px-4 text-sm text-[var(--theme-card-text-color)]/80">No sessions scheduled for this event yet.</p>
) : (
<LandingAgendaCards
days={agendaDays}
dayOpen={dayOpen}
setDayOpen={setDayOpen}
eventId={eventId}
bookmarkedIds={bookmarkedIds}
toggleBookmark={toggleBookmark}
/>
)}
</section>
</div>
</main>
</MobileShell>
);
}