231 lines
8.4 KiB
TypeScript
231 lines
8.4 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import { Calendar, ChevronDown, Clock, User } from "lucide-react";
|
|
import { Link, useSearchParams } from "react-router-dom";
|
|
import type { AgendaRowData } from "../components/AgendaRow";
|
|
import { AgendaRow } from "../components/AgendaRow";
|
|
import { EventBanner } from "../components/EventBanner";
|
|
import { HeaderMobile } from "../components/HeaderMobile";
|
|
import { HyperNavigationFAB } from "../components/HyperNavigationFAB";
|
|
import { MobileShell } from "../components/MobileShell";
|
|
import { getVisitorLoginPreview, getVisitorTrackPreview, type VisitorTrackPreview } from "../lib/api";
|
|
import { formatEventScheduleRange } from "../lib/sessionDisplay";
|
|
import { readStoredVisitorContext } from "../types/visitorContext";
|
|
import type { LoginPreviewResponse } from "../types/visitorContext";
|
|
|
|
const agendaRows: AgendaRowData[] = [
|
|
{
|
|
time: "06:00 PM",
|
|
duration: "45 MINS",
|
|
trackTitle: "Asthma",
|
|
sessionType: "Symposium",
|
|
description:
|
|
"Role of GINA Guidelines for Inning and Monitoring Asthma Treatment",
|
|
speakers: [
|
|
{ name: "Dr. Mansi Gupta", role: "Speaker" },
|
|
{ name: "Dr. Soumya Mitra", role: "Speaker" },
|
|
{ name: "Dr. Kumar Utsav Samaria", role: "Speaker" },
|
|
],
|
|
},
|
|
{
|
|
time: "07:00 PM",
|
|
duration: "45 MINS",
|
|
trackTitle: "Asthma",
|
|
sessionType: "Panel Discussion",
|
|
description:
|
|
"Role of GINA Guidelines for Iniang and Monitoring Asthma Treatment",
|
|
speakers: [
|
|
{ name: "Dr. Mansi Gupta", role: "Speaker" },
|
|
{ name: "Dr. Soumya Mitra", role: "Speaker" },
|
|
{ name: "Dr. Kumar Utsav Samaria", role: "Speaker" },
|
|
{ name: "Dr. Agam Vora", role: "Moderator" },
|
|
],
|
|
},
|
|
{
|
|
time: "08:00 PM",
|
|
duration: "45 MINS",
|
|
trackTitle: "Interventional Pulmonology",
|
|
sessionType: "Keynote Address",
|
|
description:
|
|
"Role of GINA Guidelines for Iniang and Monitoring Asthma Treatment",
|
|
speakers: [
|
|
{ name: "Dr. Mansi Gupta", role: "Speaker" },
|
|
{ name: "Dr. Soumya Mitra", role: "Speaker" },
|
|
{ name: "Dr. Kumar Utsav Samaria", role: "Speaker" },
|
|
],
|
|
},
|
|
];
|
|
|
|
export function EventTrackPage() {
|
|
const [searchParams] = useSearchParams();
|
|
const eventId = searchParams.get("event_id")?.trim() || "";
|
|
const trackId = searchParams.get("track_id")?.trim() || "";
|
|
const ctx = readStoredVisitorContext();
|
|
|
|
const [track, setTrack] = useState<VisitorTrackPreview | null>(null);
|
|
const [eventInfo, setEventInfo] = useState<LoginPreviewResponse | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [err, setErr] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
if (!eventId || !trackId) {
|
|
setErr("Missing track link. Open the track from your event QR code or invite.");
|
|
setLoading(false);
|
|
setTrack(null);
|
|
setEventInfo(null);
|
|
return;
|
|
}
|
|
|
|
let cancelled = false;
|
|
setLoading(true);
|
|
setErr(null);
|
|
|
|
(async () => {
|
|
try {
|
|
const tr = await getVisitorTrackPreview(trackId, eventId);
|
|
if (cancelled) return;
|
|
setTrack(tr);
|
|
if (ctx?.eventId === eventId && ctx.event) {
|
|
setEventInfo(ctx.event);
|
|
} else {
|
|
const ev = await getVisitorLoginPreview(eventId);
|
|
if (cancelled) return;
|
|
setEventInfo(ev);
|
|
}
|
|
} catch (e) {
|
|
if (!cancelled) {
|
|
setErr(e instanceof Error ? e.message : "Could not load track.");
|
|
setTrack(null);
|
|
setEventInfo(null);
|
|
}
|
|
} finally {
|
|
if (!cancelled) setLoading(false);
|
|
}
|
|
})();
|
|
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [eventId, trackId, ctx?.eventId]);
|
|
|
|
if (!eventId || !trackId) {
|
|
return (
|
|
<MobileShell className="relative">
|
|
<HeaderMobile eventId={eventId || null} />
|
|
<main className="flex flex-1 flex-col items-center justify-center bg-[#f2f2f7] px-4 py-10">
|
|
<p className="text-center text-sm text-[#24282f]">{err}</p>
|
|
<Link to="/" className="mt-4 text-sm font-semibold text-[#1e72ef] underline">
|
|
Home
|
|
</Link>
|
|
</main>
|
|
</MobileShell>
|
|
);
|
|
}
|
|
|
|
if (loading) {
|
|
return (
|
|
<MobileShell className="relative">
|
|
<HeaderMobile eventId={eventId || null} />
|
|
<main className="flex flex-1 items-center justify-center bg-[#f2f2f7] px-4 py-10">
|
|
<p className="text-sm text-[#24282f]">Loading track…</p>
|
|
</main>
|
|
</MobileShell>
|
|
);
|
|
}
|
|
|
|
if (err || !track) {
|
|
return (
|
|
<MobileShell className="relative">
|
|
<HeaderMobile eventId={eventId || null} />
|
|
<main className="flex flex-1 flex-col items-center justify-center bg-[#f2f2f7] px-4 py-10">
|
|
<p className="text-center text-sm text-red-600">{err ?? "Track not found."}</p>
|
|
</main>
|
|
</MobileShell>
|
|
);
|
|
}
|
|
|
|
const ev = eventInfo;
|
|
const eventName = ev?.name ?? "Event";
|
|
const schedule =
|
|
ev?.startDate && ev?.endDate
|
|
? formatEventScheduleRange(
|
|
typeof ev.startDate === "string" ? ev.startDate : String(ev.startDate),
|
|
typeof ev.endDate === "string" ? ev.endDate : String(ev.endDate),
|
|
)
|
|
: "";
|
|
|
|
const trackTypeLabel = track.trackType ? String(track.trackType).replace(/_/g, " ") : "Track";
|
|
|
|
const headerLogo =
|
|
ev?.logoUrl?.trim() ||
|
|
(ctx?.eventId === eventId && ctx.event?.logoUrl?.trim() ? ctx.event.logoUrl.trim() : undefined);
|
|
|
|
return (
|
|
<MobileShell className="relative">
|
|
<HeaderMobile logoUrl={headerLogo} eventId={eventId || null} />
|
|
<EventBanner />
|
|
|
|
<main className="relative z-0 flex flex-1 flex-col bg-[#f2f2f7] px-4 pb-28 pt-6">
|
|
<div className="mx-auto w-full max-w-[370px]">
|
|
<div className="mb-6 flex flex-col gap-4">
|
|
<div className="inline-flex w-fit rounded bg-[#e5e5ea] px-2 py-0.5">
|
|
<span className="text-[10px] tracking-wide text-[#24282f]">{eventName.toUpperCase()}</span>
|
|
</div>
|
|
<h1 className="text-lg font-semibold tracking-wide text-[#24282f]">{track.name}</h1>
|
|
{track.hall?.trim() ? (
|
|
<p className="text-xs text-[rgba(36,40,47,0.7)]">{track.hall.trim()}</p>
|
|
) : null}
|
|
<p className="text-[10px] uppercase tracking-wide text-[#8e8e93]">{trackTypeLabel}</p>
|
|
<div className="flex flex-col gap-1 text-xs text-[#24282f]">
|
|
<div className="flex items-center gap-2">
|
|
<Calendar className="size-4 shrink-0" strokeWidth={1.75} />
|
|
<span className="font-inter tracking-wide">{schedule || "—"}</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<Clock className="size-4 shrink-0" strokeWidth={1.75} />
|
|
<span className="font-inter tracking-wide">See sessions below</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<User className="size-4 shrink-0" strokeWidth={1.75} />
|
|
<span className="font-inter tracking-wide">Track agenda (sample)</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mb-4 flex w-full max-w-[370px] flex-col gap-2">
|
|
<h2 className="font-inter text-base font-semibold text-[#24282f]">Sessions</h2>
|
|
<button
|
|
type="button"
|
|
className="flex h-[43px] w-full items-center justify-between rounded-lg border border-[rgba(116,112,112,0.3)] bg-white px-4 py-3"
|
|
>
|
|
<div className="flex items-center gap-2">
|
|
<Calendar className="size-4 shrink-0 text-[#24282f]" strokeWidth={1.75} />
|
|
<span className="font-inter text-sm text-[#24282f]">{schedule || "Schedule"}</span>
|
|
</div>
|
|
<ChevronDown className="size-4 shrink-0 text-[#1d66ef]" strokeWidth={2} />
|
|
</button>
|
|
</div>
|
|
|
|
<div className="relative rounded-md bg-white p-4 shadow-[0_1px_4px_rgba(12,12,13,0.05)]">
|
|
<div className="flex flex-col gap-6">
|
|
{agendaRows.map((row) => (
|
|
<AgendaRow key={row.time + row.sessionType} row={row} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<p className="mt-6 text-center text-xs text-[#24282f]/60">
|
|
<Link
|
|
className="text-[#1e72ef] underline"
|
|
to={`/questions?event_id=${encodeURIComponent(eventId)}`}
|
|
>
|
|
Ask & session questions
|
|
</Link>
|
|
</p>
|
|
</div>
|
|
</main>
|
|
|
|
<HyperNavigationFAB eventId={eventId} />
|
|
</MobileShell>
|
|
);
|
|
}
|