Theme setup for entire website.
This commit is contained in:
parent
418f85b402
commit
391096ea87
|
|
@ -27,12 +27,13 @@ import { CodeEntryPage } from "./pages/CodeEntryPage";
|
|||
import { PrivacyPolicyPage } from "./pages/PrivacyPolicyPage";
|
||||
import { TermsOfServicePage } from "./pages/TermsOfServicePage";
|
||||
import { useEventTheme } from "./hooks/useEventTheme";
|
||||
import { ThemeGate } from "./components/ThemeGate";
|
||||
|
||||
export default function App() {
|
||||
// Re-apply theme from stored visitor context on refresh / navigation.
|
||||
useEventTheme();
|
||||
const { ready: themeReady } = useEventTheme();
|
||||
|
||||
return (
|
||||
<ThemeGate ready={themeReady}>
|
||||
<div className="flex min-h-dvh flex-1 flex-col">
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
|
|
@ -66,5 +67,6 @@ export default function App() {
|
|||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</ThemeGate>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,17 +13,17 @@ export type AgendaRowData = {
|
|||
|
||||
function SpeakerLineView({ name, role }: SpeakerLine) {
|
||||
const pill =
|
||||
role === "Speaker" ? "bg-[#f0fbd7]" : "bg-[#fbefd7]";
|
||||
role === "Speaker" ? "bg-emerald-50" : "bg-yellow-100";
|
||||
return (
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex size-6 shrink-0 items-center justify-center rounded-full bg-[#ededed]">
|
||||
<User className="size-3 text-[#24282f]/70" strokeWidth={2} />
|
||||
<div className="flex size-6 shrink-0 items-center justify-center rounded-full bg-zinc-100">
|
||||
<User className="size-3 text-gray-600" strokeWidth={2} />
|
||||
</div>
|
||||
<span className="text-xs font-medium tracking-wide text-[#24282f]">{name}</span>
|
||||
<span className="text-xs font-medium tracking-wide text-[var(--theme-card-text-color)]">{name}</span>
|
||||
</div>
|
||||
<span
|
||||
className={`rounded px-2 py-0.5 text-[10px] font-normal tracking-wide text-[#24282f] ${pill}`}
|
||||
className={`rounded px-2 py-0.5 text-[10px] font-normal tracking-wide ${pill}`}
|
||||
>
|
||||
{role}
|
||||
</span>
|
||||
|
|
@ -39,37 +39,36 @@ export function AgendaRow({ row }: AgendaRowProps) {
|
|||
return (
|
||||
<div className="relative flex gap-2 pb-3">
|
||||
<div className="relative z-[1] flex shrink-0 flex-col items-center">
|
||||
<div className="flex size-10 items-center justify-center rounded-full bg-[#c2deff]">
|
||||
<User className="size-4 text-[#24282f]/80" strokeWidth={2} />
|
||||
<div className="flex size-10 items-center justify-center rounded-full bg-[var(--theme-button-color)]/15">
|
||||
<User className="size-4 text-[var(--theme-card-text-color)]" strokeWidth={2} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex min-w-0 flex-1 flex-col gap-2">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="flex min-w-0 flex-col gap-1">
|
||||
<div className="flex items-center gap-1 text-[10px] tracking-wide text-[#24282f]">
|
||||
<div className="flex min-w-0 flex-col gap-1 text-[var(--theme-card-text-color)]">
|
||||
<div className="flex items-center gap-1 text-[10px] tracking-wide">
|
||||
<span>{row.time}</span>
|
||||
<span className="h-3 w-px bg-[rgba(36,40,47,0.5)]" />
|
||||
<span className="h-3 w-px bg-zinc-300" />
|
||||
<span>{row.duration}</span>
|
||||
</div>
|
||||
<h3 className="text-sm font-semibold leading-[22px] tracking-wide text-[#24282f]">
|
||||
<h3 className="text-sm font-semibold leading-[22px] tracking-wide">
|
||||
{row.trackTitle}
|
||||
</h3>
|
||||
<div
|
||||
className="inline-flex w-fit items-center rounded px-2 py-0.5 text-[10px] tracking-wide text-[#24282f]"
|
||||
style={{ backgroundColor: "rgba(94, 147, 239, 0.14)" }}
|
||||
className="inline-flex w-fit items-center rounded px-2 py-0.5 text-[10px] tracking-wide bg-zinc-200 text-[var(--theme-card-text-color)]"
|
||||
>
|
||||
{row.sessionType}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="shrink-0 text-[#1d66ef]"
|
||||
className="shrink-0 text-[var(--theme-button-color)]"
|
||||
aria-label="Bookmark session"
|
||||
>
|
||||
<Bookmark className="size-5" strokeWidth={1.75} />
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-xs leading-normal tracking-wide text-[rgba(36,40,47,0.87)]">
|
||||
<p className="text-xs leading-normal tracking-wide text-[var(--theme-card-text-color)]/80">
|
||||
{row.description}
|
||||
</p>
|
||||
<div className="flex flex-col gap-2">
|
||||
|
|
|
|||
|
|
@ -35,39 +35,39 @@ export type AgendaRowTrackV2Data = {
|
|||
export function AgendaRowTrackV2({ row }: { row: AgendaRowTrackV2Data }) {
|
||||
const [detailsOpen, setDetailsOpen] = useState(false);
|
||||
const joinBtnClass = row.joinLive
|
||||
? "inline-flex h-8 w-[84px] items-center justify-between rounded-full bg-[var(--theme-button-color)] pl-[18px] pr-2 text-xs font-semibold uppercase text-white shadow-[0_0_0_2px_rgba(239,141,29,0.5),0_4px_14px_rgba(239,141,29,0.45)] ring-2 ring-[var(--theme-button-color)]/40"
|
||||
: "inline-flex h-8 w-[84px] items-center justify-between rounded-full bg-[var(--theme-button-color)] transition-colors duration-300 ease-in-out hover:bg-[var(--theme-button-color)]/90 pl-[18px] pr-2 text-xs font-semibold uppercase text-white shadow-[0_4px_4px_rgba(0,0,0,0.15)]";
|
||||
? "inline-flex h-8 w-[84px] items-center justify-between rounded-full theme-btn-bg pl-[18px] pr-2 text-xs font-semibold uppercase text-white shadow-sm ring-2 ring-[var(--theme-button-color)]/40"
|
||||
: "inline-flex h-8 w-[84px] items-center justify-between rounded-full theme-btn-bg transition-colors duration-300 ease-in-out hover:bg-[var(--theme-button-color)]/90 pl-[18px] pr-2 text-xs font-semibold uppercase text-white shadow-sm";
|
||||
|
||||
return (
|
||||
<div className="relative w-full pb-4">
|
||||
<div className="flex min-w-0 w-full flex-col gap-2">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="flex items-start justify-between gap-2 text-[var(--theme-card-text-color)]">
|
||||
<div className="flex min-w-0 flex-col gap-1">
|
||||
<div className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1">
|
||||
<span
|
||||
className="inline-flex shrink-0 rounded px-2 py-0.5 text-[10px] text-[#24282f]"
|
||||
style={{ backgroundColor: "rgba(94, 147, 239, 0.14)" }}
|
||||
className="inline-flex shrink-0 rounded px-2 py-0.5 text-[10px] bg-zinc-200 text-gray-600"
|
||||
|
||||
>
|
||||
{row.sessionType}
|
||||
</span>
|
||||
<div className="flex min-w-0 flex-wrap items-center gap-1 text-[10px] tracking-wide text-[#24282f]">
|
||||
<div className="flex min-w-0 flex-wrap items-center gap-1 text-[10px] tracking-wide">
|
||||
{row.sessionDateLabel ? (
|
||||
<>
|
||||
<span className="shrink-0">{row.sessionDateLabel}</span>
|
||||
<span className="h-3 w-px shrink-0 bg-[rgba(36,40,47,0.5)]" />
|
||||
<span className="h-3 w-px shrink-0 bg-[var(--theme-card-text-color)]/50" />
|
||||
</>
|
||||
) : null}
|
||||
<span className="shrink-0">{row.time}</span>
|
||||
<span className="h-3 w-px shrink-0 bg-[rgba(36,40,47,0.5)]" />
|
||||
<span className="h-3 w-px shrink-0 bg-[var(--theme-card-text-color)]/50" />
|
||||
<span className="shrink-0">{row.duration}</span>
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="text-sm font-semibold leading-[22px] text-[#24282f]">
|
||||
<h3 className="text-sm font-semibold leading-[22px]">
|
||||
{row.trackTitle}
|
||||
</h3>
|
||||
{row.trackLocation?.trim() ? (
|
||||
<div className="flex items-start gap-1.5 text-xs text-[rgba(36,40,47,0.75)]">
|
||||
<MapPin className="mt-0.5 size-3.5 shrink-0 text-[#1e72ef]" strokeWidth={1.75} />
|
||||
<div className="flex items-start gap-1.5 text-xs text-[var(--theme-card-text-color)]/80">
|
||||
<MapPin className="mt-0.5 size-3.5 shrink-0 text-[var(--theme-button-color)]" strokeWidth={1.75} />
|
||||
<span className="leading-snug">{row.trackLocation.trim()}</span>
|
||||
</div>
|
||||
) : null}
|
||||
|
|
@ -93,7 +93,7 @@ export function AgendaRowTrackV2({ row }: { row: AgendaRowTrackV2Data }) {
|
|||
e.stopPropagation();
|
||||
row.bookmark!.onToggle();
|
||||
}}
|
||||
className="rounded-md p-0.5 text-[#1d66ef] hover:bg-[#1d66ef]/10"
|
||||
className="rounded-md p-0.5 text-[var(--theme-button-color)]"
|
||||
aria-label={row.bookmark.isBookmarked ? "Remove bookmark" : "Bookmark session"}
|
||||
aria-pressed={row.bookmark.isBookmarked}
|
||||
>
|
||||
|
|
@ -111,7 +111,7 @@ export function AgendaRowTrackV2({ row }: { row: AgendaRowTrackV2Data }) {
|
|||
e.stopPropagation();
|
||||
setDetailsOpen((o) => !o);
|
||||
}}
|
||||
className="mt-[18px] rounded px-1 py-1 text-[#8e8e93] hover:bg-black/5 hover:text-[#24282f]"
|
||||
className="mt-[18px] rounded px-1 py-1 text-[var(--theme-card-text-color)]/80 hover:bg-black/5 hover:text-[var(--theme-card-text-color)]]"
|
||||
aria-expanded={detailsOpen}
|
||||
aria-label={detailsOpen ? "Hide session details" : "Show session details"}
|
||||
>
|
||||
|
|
@ -123,15 +123,15 @@ export function AgendaRowTrackV2({ row }: { row: AgendaRowTrackV2Data }) {
|
|||
</div>
|
||||
</div>
|
||||
{detailsOpen ? (
|
||||
<div className="flex min-w-0 flex-col gap-2">
|
||||
<div className="flex min-w-0 flex-col gap-2 text-[var(--theme-card-text-color)]/80">
|
||||
{row.description?.trim() ? (
|
||||
<p className="text-xs leading-normal text-[rgba(36,40,47,0.87)]">{row.description}</p>
|
||||
<p className="text-xs leading-normal">{row.description}</p>
|
||||
) : null}
|
||||
<div className="flex min-w-0 items-center gap-1.5">
|
||||
<span className="shrink-0 rounded bg-[#f0fbd7] px-2 py-0.5 text-[10px] text-[#24282f]">
|
||||
<span className="shrink-0 rounded bg-green-100 text-green-800 px-2 py-0.5 text-[10px]">
|
||||
Speaker
|
||||
</span>
|
||||
<p className="min-w-0 flex-1 text-xs font-medium leading-snug text-[#24282f] line-clamp-1">
|
||||
<p className="min-w-0 flex-1 text-xs font-medium leading-snug line-clamp-1">
|
||||
{row.speakerNames.join(", ")}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -79,20 +79,20 @@ export function AskQuestionModal({
|
|||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby={`ask-question-title${idSuffix}`}
|
||||
className="relative z-10 w-full max-w-[min(100%,402px)] rounded-t-2xl bg-whit card-bg px-5 pb-8 pt-5 shadow-[0_-8px_32px_rgba(0,0,0,0.12)] sm:mx-4 sm:rounded-2xl sm:shadow-xl"
|
||||
className="relative z-10 w-full max-w-[min(100%,402px)] rounded-t-2xl card-bg px-5 pb-8 pt-5 shadow-md sm:mx-4 sm:rounded-2xl sm:shadow-xl"
|
||||
>
|
||||
<div className="mb-4 flex items-start justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<h3 id={`ask-question-title${idSuffix}`} className="text-lg font-semibold text-[#24282f]">
|
||||
<h3 id={`ask-question-title${idSuffix}`} className="text-lg font-semibold text-[var(--theme-card-text-color)]">
|
||||
Ask a Question
|
||||
</h3>
|
||||
<p className="mt-1 text-sm leading-snug text-[rgba(36,40,47,0.65)]">
|
||||
<p className="mt-2 text-sm leading-snug text-[var(--theme-card-text-color)]/80">
|
||||
Submit your question to the session moderator.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="shrink-0 rounded-full p-1.5 text-[#24282f] transition-colors duration-300 ease-in-out hover:bg-[#e4e4e4]"
|
||||
className="shrink-0 rounded-full p-1.5 text-[var(--theme-card-text-color)] transition-colors duration-300 ease-in-out"
|
||||
aria-label="Close"
|
||||
onClick={close}
|
||||
>
|
||||
|
|
@ -110,13 +110,13 @@ export function AskQuestionModal({
|
|||
placeholder="Type your question here..."
|
||||
rows={5}
|
||||
disabled={submitting}
|
||||
className="min-h-[120px] w-full resize-y rounded-xl border border-[#c7c7cc] bg-[#f2f2f7]/40 px-3 py-3 text-sm text-[#24282f] placeholder:text-[rgba(36,40,47,0.45)] transition-all duration-300 ease-in-out focus:border-[#ef8d1d focus:border-[var(--theme-button-color)] focus:outline-none focus:ring-2 focus:ring-[#ef8d1d]/30 focus:ring-[var(--theme-button-color)]/30 disabled:opacity-60"
|
||||
className="min-h-[120px] w-full resize-y rounded-xl border border-[var(--theme-card-text-color)]/10 bg-[var(--theme-card-color)]/40 px-3 py-3 text-sm text-[var(--theme-card-text-color)] placeholder:text-[var(--theme-card-text-color)]/60 transition-all duration-300 ease-in-out focus:border-[var(--theme-button-color)] focus:outline-none focus:ring-2 focus:ring-[var(--theme-button-color)]/30 disabled:opacity-60"
|
||||
/>
|
||||
{submitError ? <p className="text-sm text-red-600">{submitError}</p> : null}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={submitting}
|
||||
className="w-full rounded-xl bg-[#ef8d1 bg-[var(--theme-button-color)] py-3 text-sm font-semibold text-white shadow-[0_4px_4px_rgba(0,0,0,0.12)] transition-all duration-300 ease-in-out hover:bg-[#e0800c] transition hover:bg-[var(--theme-button-color)]/90 active:scale-[0.99] disabled:opacity-60"
|
||||
className="w-full rounded-lg theme-btn-bg py-3 text-sm font-semibold text-white shadow-md transition-all duration-300 ease-in-out transition hover:bg-[var(--theme-button-color)]/80 disabled:opacity-60"
|
||||
>
|
||||
{submitting ? "Submitting…" : "Submit Question"}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ export function AudienceQuestionCard({
|
|||
const shell =
|
||||
variant === "embedded"
|
||||
? "flex flex-col gap-4 p-4"
|
||||
: "flex flex-col gap-4 rounded-md border border-[#f2f2f7] card-bg p-4 shadow-[0_1px_4px_rgba(12,12,13,0.05)]";
|
||||
: "flex flex-col gap-4 rounded-lg card-bg p-4 shadow-md";
|
||||
|
||||
const doVote = useCallback(
|
||||
async (direction: "up" | "down") => {
|
||||
|
|
@ -86,7 +86,7 @@ export function AudienceQuestionCard({
|
|||
type="button"
|
||||
disabled={!canVote || voting}
|
||||
onClick={() => void doVote("up")}
|
||||
className={`flex items-center gap-2 rounded-full border-2 px-2 py-2 transition ${
|
||||
className={`flex items-center gap-2 rounded-full border-2 px-2 py-1 transition ${
|
||||
userVote === "up"
|
||||
? "border-[#34c759] bg-[#34c759]/10 text-[#34c759]"
|
||||
: "border-[#34c759] bg-white text-[#34c759] hover:bg-[#34c759]/5"
|
||||
|
|
@ -94,7 +94,7 @@ export function AudienceQuestionCard({
|
|||
aria-label={canVote ? "Upvote" : undefined}
|
||||
>
|
||||
<ThumbsUp className="size-4 shrink-0" strokeWidth={2} />
|
||||
<span className="text-[10px] font-medium tracking-wide">{item.up}</span>
|
||||
<span className="text-sm font-medium tracking-wide">{item.up}</span>
|
||||
</button>
|
||||
);
|
||||
|
||||
|
|
@ -103,14 +103,14 @@ export function AudienceQuestionCard({
|
|||
type="button"
|
||||
disabled={!canVote || voting}
|
||||
onClick={() => void doVote("down")}
|
||||
className={`flex items-center gap-2 rounded-full border-2 px-2 py-2 transition ${
|
||||
className={`flex items-center gap-2 rounded-full border-2 px-2 py-1 transition ${
|
||||
userVote === "down"
|
||||
? "border-[#ff3b30] bg-[#ff3b30]/10 text-[#ff3b30]"
|
||||
: "border-[#ff3b30] bg-white text-[#ff3b30] hover:bg-[#ff3b30]/5"
|
||||
} disabled:cursor-not-allowed disabled:opacity-45`}
|
||||
aria-label={canVote ? "Downvote" : undefined}
|
||||
>
|
||||
<span className="text-[10px] font-medium tracking-wide">{item.down}</span>
|
||||
<span className="text-xs font-medium tracking-wide">{item.down}</span>
|
||||
<ThumbsDown className="size-4 shrink-0" strokeWidth={2} />
|
||||
</button>
|
||||
);
|
||||
|
|
@ -118,12 +118,12 @@ export function AudienceQuestionCard({
|
|||
return (
|
||||
<div className={shell}>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex size-8 items-center justify-center rounded-full bg-[#f2f2f7]">
|
||||
<User className="size-4 text-[#24282f]/70" strokeWidth={2} />
|
||||
<div className="flex size-10 items-center justify-center rounded-full bg-[var(--theme-button-color)]/10">
|
||||
<User className="size-5 text-[var(theme-card-text-color)]/70" strokeWidth={2} />
|
||||
</div>
|
||||
<span className="text-base font-semibold tracking-wide text-[#24282f]">{item.author}</span>
|
||||
<span className="text-sm font-semibold tracking-wide text-[var(theme-card-text-color)]">{item.author}</span>
|
||||
</div>
|
||||
<p className="text-base leading-[22px] tracking-wide text-[#24282f]">{item.text}</p>
|
||||
<p className="text-sm leading-[22px] tracking-wide text-[var(theme-card-text-color)]">{item.text}</p>
|
||||
<div className="flex flex-col items-end gap-1">
|
||||
<div className="flex justify-end gap-2">
|
||||
{canVote ? (
|
||||
|
|
|
|||
|
|
@ -23,10 +23,12 @@ export function DynamicFeedbackForm({
|
|||
eventId,
|
||||
schema,
|
||||
variant = "light",
|
||||
className = '',
|
||||
}: {
|
||||
eventId: string;
|
||||
schema: VisitorPublicFeedbackForm;
|
||||
variant?: "light" | "gradient";
|
||||
className?: string | '';
|
||||
}) {
|
||||
const [values, setValues] = useState<Record<string, unknown>>({});
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
|
@ -86,11 +88,11 @@ export function DynamicFeedbackForm({
|
|||
<div
|
||||
className={
|
||||
variant === "gradient"
|
||||
? "rounded-xl bg-white p-6 text-center shadow-[0_8px_12px_6px_rgba(0,0,0,0.15),0_4px_4px_rgba(0,0,0,0.3)]"
|
||||
: "rounded-md bg-white p-4 text-center shadow-[0_1px_4px_rgba(12,12,13,0.05)]"
|
||||
? "rounded-md card-bg p-4 text-center shadow-md"
|
||||
: "rounded-md card-bg p-4 text-center shadow-md"
|
||||
}
|
||||
>
|
||||
<p className="font-inter text-sm font-semibold text-[#24282f]">Thank you</p>
|
||||
<p className="font-inter text-sm font-semibold">Thank you</p>
|
||||
<p className="mt-2 rounded-md border border-[#badbcc] bg-[#d1e7dd] px-4 py-3 text-sm font-bold text-[#198754]">
|
||||
{schema.thankYouMessage?.trim() || "Your response has been recorded."}
|
||||
</p>
|
||||
|
|
@ -100,13 +102,13 @@ export function DynamicFeedbackForm({
|
|||
|
||||
const fieldShell =
|
||||
variant === "gradient"
|
||||
? "rounded-xl bg-white p-6 shadow-[0_8px_12px_6px_rgba(0,0,0,0.15),0_4px_4px_rgba(0,0,0,0.3)]"
|
||||
: "rounded-md bg-whit card-bg p-4 shadow-[0_1px_4px_rgba(12,12,13,0.05)]";
|
||||
? `rounded-md p-4 pb-2 ${className}`
|
||||
: `rounded-md p-4 pb-2 ${className}`;
|
||||
|
||||
return (
|
||||
<form onSubmit={onSubmit} className={fieldShell}>
|
||||
{schema.description?.trim() ? (
|
||||
<p className="mb-4 text-sm leading-snug text-[rgba(36,40,47,0.67)]">{stripHtml(schema.description.trim())}</p>
|
||||
<p className="mb-4 text-sm leading-snug text-[var(--theme-card-text-color)]/80">{stripHtml(schema.description.trim())}</p>
|
||||
) : null}
|
||||
<div className="flex flex-col gap-4">
|
||||
{fields.map((f) => (
|
||||
|
|
@ -120,7 +122,7 @@ export function DynamicFeedbackForm({
|
|||
<button
|
||||
type="submit"
|
||||
disabled={submitting}
|
||||
className="mt-4 w-full rounded-full bg-[var(--theme-button-color)] py-3 text-xs font-semibold uppercase tracking-wide text-white shadow-[0_4px_4px_rgba(0,0,0,0.15)] transition-colors duration-300 ease-in-out hover:bg-[var(--theme-button-color)]/90 disabled:opacity-60"
|
||||
className="mt-4 w-full rounded-lg bg-[var(--theme-button-color)] py-3 text-sm font-semibold uppercase tracking-wide text-white shadow-[0_4px_4px_rgba(0,0,0,0.15)] transition-colors duration-300 ease-in-out hover:bg-[var(--theme-button-color)]/90 disabled:opacity-60"
|
||||
>
|
||||
{submitting ? "Submitting…" : "Submit"}
|
||||
</button>
|
||||
|
|
@ -159,7 +161,7 @@ function FieldInput({
|
|||
if (t === "file") {
|
||||
return (
|
||||
<p className="text-sm text-[rgba(36,40,47,0.67)]">
|
||||
<span className="font-semibold text-[#24282f]">{label}</span>
|
||||
<span className="font-semibold text-[var(--theme-card-text-color)]">{label}</span>
|
||||
<span className="mt-1 block">File upload isn't supported in the mobile app yet.</span>
|
||||
</p>
|
||||
);
|
||||
|
|
@ -170,11 +172,11 @@ function FieldInput({
|
|||
const selected = Number.isFinite(n) ? n : 0;
|
||||
return (
|
||||
<fieldset className="flex flex-col gap-2">
|
||||
<legend className="text-sm font-semibold text-[#24282f]">
|
||||
<legend className="text-sm font-semibold text-[var(--theme-card-text-color)]">
|
||||
{label}
|
||||
{f.required ? <span className="text-red-600"> *</span> : null}
|
||||
</legend>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex gap-2 mt-1">
|
||||
{Array.from({ length: 5 }, (_, i) => {
|
||||
const star = i + 1;
|
||||
return (
|
||||
|
|
@ -223,13 +225,13 @@ function FieldInput({
|
|||
if (t === "radio") {
|
||||
return (
|
||||
<fieldset className="flex flex-col gap-2">
|
||||
<legend className="text-sm font-semibold text-[#24282f]">
|
||||
<legend className="text-sm font-semibold text-[var(--theme-card-text-color)]">
|
||||
{label}
|
||||
{f.required ? <span className="text-red-600"> *</span> : null}
|
||||
</legend>
|
||||
<div className="flex flex-col gap-2">
|
||||
{(opts.length ? opts : ["Yes", "No"]).map((o) => (
|
||||
<label key={o} className="flex items-center gap-2 text-sm text-[#24282f]">
|
||||
<label key={o} className="flex items-center gap-2 text-sm text-[var(--theme-card-text-color)]">
|
||||
<input type="radio" name={label} checked={value === o} onChange={() => onChange(label, o)} />
|
||||
{o}
|
||||
</label>
|
||||
|
|
@ -247,13 +249,13 @@ function FieldInput({
|
|||
};
|
||||
return (
|
||||
<fieldset className="flex flex-col gap-2">
|
||||
<legend className="text-sm font-semibold text-[#24282f]">
|
||||
<legend className="text-sm font-semibold text-[var(--theme-card-text-color)]">
|
||||
{label}
|
||||
{f.required ? <span className="text-red-600"> *</span> : null}
|
||||
</legend>
|
||||
<div className="flex flex-col gap-2">
|
||||
{opts.map((o) => (
|
||||
<label key={o} className="flex items-center gap-2 text-sm text-[#24282f]">
|
||||
<label key={o} className="flex items-center gap-2 text-sm text-[var(--theme-card-text-color)]">
|
||||
<input type="checkbox" checked={arr.includes(o)} onChange={() => toggle(o)} />
|
||||
{o}
|
||||
</label>
|
||||
|
|
@ -265,7 +267,7 @@ function FieldInput({
|
|||
|
||||
if (t === "checkbox") {
|
||||
return (
|
||||
<label className="flex items-center gap-2 text-sm text-[#24282f]">
|
||||
<label className="flex items-center gap-2 text-sm text-[var(--theme-card-text-color)]">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={value === true || value === "yes" || value === "Yes"}
|
||||
|
|
@ -282,7 +284,7 @@ function FieldInput({
|
|||
if (t === "email" || t === "phone") {
|
||||
return (
|
||||
<label className="flex flex-col gap-2">
|
||||
<span className="text-sm font-semibold text-[#24282f]">
|
||||
<span className="text-sm font-semibold text-[var(--theme-card-text-color)]">
|
||||
{label}
|
||||
{f.required ? <span className="text-red-600"> *</span> : null}
|
||||
</span>
|
||||
|
|
@ -300,7 +302,7 @@ function FieldInput({
|
|||
if (t === "textarea") {
|
||||
return (
|
||||
<label className="flex flex-col gap-2">
|
||||
<span className="text-sm font-semibold text-[#24282f]">
|
||||
<span className="text-sm font-semibold text-[var(--theme-card-text-color)]">
|
||||
{label}
|
||||
{f.required ? <span className="text-red-600"> *</span> : null}
|
||||
</span>
|
||||
|
|
@ -324,7 +326,7 @@ function FieldInput({
|
|||
if (t === "text" && longText) {
|
||||
return (
|
||||
<label className="flex flex-col gap-2">
|
||||
<span className="text-sm font-semibold text-[#24282f]">
|
||||
<span className="text-sm font-semibold text-[var(--theme-card-text-color)]">
|
||||
{label}
|
||||
{f.required ? <span className="text-red-600"> *</span> : null}
|
||||
</span>
|
||||
|
|
@ -341,7 +343,7 @@ function FieldInput({
|
|||
|
||||
return (
|
||||
<label className="flex flex-col gap-2">
|
||||
<span className="text-sm font-semibold text-[#24282f]">
|
||||
<span className="text-sm font-semibold text-[var(--theme-card-text-color)]">
|
||||
{label}
|
||||
{f.required ? <span className="text-red-600"> *</span> : null}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -4,13 +4,14 @@ export type EventBannerProps = {
|
|||
/** Event banner URL from login-preview (`bannerImage` or `image`) */
|
||||
imageUrl?: string | null;
|
||||
alt?: string;
|
||||
className?: string | '';
|
||||
};
|
||||
|
||||
export function EventBanner({ imageUrl, alt = "" }: EventBannerProps) {
|
||||
export function EventBanner({ imageUrl, alt = "", className = "" }: EventBannerProps) {
|
||||
const src = imageUrl?.trim();
|
||||
if (src) {
|
||||
return (
|
||||
<div className="w-full shrink-0 overflow-hidden bg-[#e5e5ea]">
|
||||
<div className={`w-full shrink-0 overflow-hidden bg-[#e5e5ea] ${className}`}>
|
||||
<img
|
||||
src={src}
|
||||
alt={alt}
|
||||
|
|
@ -22,8 +23,8 @@ export function EventBanner({ imageUrl, alt = "" }: EventBannerProps) {
|
|||
|
||||
return (
|
||||
<div
|
||||
className="eb-gradient-banner flex h-[130px] shrink-0 items-center justify-center border-y border-[#f2f2f7] px-8 py-10 sm:px-16"
|
||||
data-name="Event Logo"
|
||||
className={`eb-gradient-banner flex h-[130px] shrink-0 items-center justify-center border-y border-[#f2f2f7] px-8 py-10 sm:px-16"
|
||||
data-name="Event Logo ${className}`}
|
||||
>
|
||||
<EventBuzzLogo
|
||||
variant="white"
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ export function LandingAgendaCards({
|
|||
return (
|
||||
<div
|
||||
key={day.dateKey}
|
||||
className="w-full rounded-lg border-x-0 border-[rgba(116,112,112,0.2)] bg-whit card-bg shadow-sm sm:border-x"
|
||||
className="w-full rounded-lg border border-[var(--theme-card-text-color)]/5"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -181,34 +181,34 @@ export function LandingAgendaCards({
|
|||
aria-expanded={isOpen}
|
||||
>
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<Calendar className="size-4 shrink-0 text-[#1e72ef]" strokeWidth={1.75} />
|
||||
<span className="font-inter text-sm font-semibold text-[#24282f]">{day.dateLabel}</span>
|
||||
<Calendar className="size-4 shrink-0 text-[var(--theme-button-color)]" strokeWidth={1.75} />
|
||||
<span className="text-sm font-semibold">{day.dateLabel}</span>
|
||||
</div>
|
||||
<ChevronDown
|
||||
className={`size-4 shrink-0 text-[#1e72ef] transition-transform ${isOpen ? "rotate-180" : ""}`}
|
||||
className={`size-4 shrink-0 text-[var(--theme-card-text-color)]/70 transition-transform ${isOpen ? "rotate-180" : ""}`}
|
||||
strokeWidth={2}
|
||||
aria-hidden
|
||||
/>
|
||||
</button>
|
||||
{isOpen ? (
|
||||
<div className="w-full border-t border-[#f2f2f7] pb-4 pt-1">
|
||||
<div className="w-full border-t border-[var(--theme-card-text-color)]/5 pb-4 pt-1">
|
||||
{day.trackGroups.map((tg, tgIdx) => (
|
||||
<div key={tg.trackId} className="w-full">
|
||||
{tgIdx > 0 ? (
|
||||
<div
|
||||
className="my-5 w-full border-t-[3px] border-[#1e72ef] shadow-[0_1px_0_rgba(12,12,13,0.06)]"
|
||||
className="my-5 w-full border-t-[3px] border-[var(--theme-button-color)] shadow-[0_1px_0_rgba(12,12,13,0.06)]"
|
||||
role="separator"
|
||||
aria-hidden
|
||||
/>
|
||||
) : null}
|
||||
<div className="px-4 pt-2">
|
||||
<p className="font-inter text-xs font-semibold uppercase tracking-wide text-[#8e8e93]">
|
||||
<p className="font-inter text-xs font-semibold uppercase tracking-wide text-[var(--theme-card-text-color)]/60">
|
||||
{tg.trackName}
|
||||
</p>
|
||||
{tg.trackHall ? (
|
||||
<div className="mt-2 flex items-start gap-2 text-sm text-[#24282f]">
|
||||
<div className="mt-2 flex items-start gap-2 text-sm text-[var(--theme-card-text-color)]">
|
||||
<MapPin
|
||||
className="mt-0.5 size-4 shrink-0 text-[#1e72ef]"
|
||||
className="mt-0.5 size-4 shrink-0 text-[var(--theme-button-color)]"
|
||||
strokeWidth={1.75}
|
||||
/>
|
||||
<span className="font-inter leading-snug">{tg.trackHall}</span>
|
||||
|
|
@ -216,7 +216,7 @@ export function LandingAgendaCards({
|
|||
) : null}
|
||||
</div>
|
||||
<div className="relative mt-3 w-full max-w-none px-0">
|
||||
<div className="relative w-full max-w-none rounded-none bg-[#fafafa p-4 shadow-[0_1px_4px_rgba(12,12,13,0.05)] sm:rounded-md">
|
||||
<div className="relative w-full max-w-none rounded-none px-4">
|
||||
<div className="flex w-full flex-col gap-4">
|
||||
{tg.sessions.map((s) => (
|
||||
<AgendaRowTrackV2
|
||||
|
|
@ -257,7 +257,7 @@ export function LandingMySessionsList({
|
|||
return (
|
||||
<div className="mt-3 w-full max-w-none">
|
||||
<div className="relative w-full">
|
||||
<div className="relative w-full max-w-none rounded-none bg-[#fafafa] p-4 shadow-[0_1px_4px_rgba(12,12,13,0.05)] sm:rounded-md">
|
||||
<div className="relative w-full max-w-none rounded-none py-2 border-b border-[var(--theme-card-text-color)]/5 last:border-b-0">
|
||||
<div className="flex w-full flex-col gap-4">
|
||||
{sessions.map((s) => (
|
||||
<AgendaRowTrackV2
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export function EventRatingBadge({ ratings }: { ratings: EventRatingsSummary | n
|
|||
);
|
||||
})}
|
||||
</div>
|
||||
<span className="font-inter text-[#8e8e93]">
|
||||
<span className="text-[var(--theme-card-text-color)]/60">
|
||||
{average.toFixed(1)} ({count})
|
||||
</span>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ export function HeaderMobile({
|
|||
|
||||
return (
|
||||
<header
|
||||
className="sticky top-0 z-20 flex h-[60px] shrink-0 items-center border-b border-white/40 bg-white px-4 shadow-[0_1px_4px_rgba(12,12,13,0.05),0_1px_4px_rgba(12,12,13,0.1)]"
|
||||
className="sticky top-0 z-20 flex h-[60px] shrink-0 items-center border-b border-white/40 bg-[var(--theme-header-color)] backdrop-blur-sm px-4 shadow-xs"
|
||||
data-name="Header_Mobile"
|
||||
>
|
||||
<div className="flex w-full items-center justify-between gap-2">
|
||||
|
|
@ -140,13 +140,13 @@ export function HeaderMobile({
|
|||
<button
|
||||
type="button"
|
||||
onClick={() => (onBack ? onBack() : navigate(-1))}
|
||||
className="flex size-10 shrink-0 items-center justify-center rounded-full bg-[#f2f2f7] text-[#777777] transition-colors duration-300 ease-in-out hover:bg-[#e4e4e4]"
|
||||
className="flex size-10 shrink-0 items-center justify-center rounded-full bg-[var(--theme-button-color)]/10 text-[var(--theme-button-color)]/70"
|
||||
aria-label="Back"
|
||||
>
|
||||
<ChevronLeft className="size-6" strokeWidth={2} />
|
||||
</button>
|
||||
) : null}
|
||||
<div className="flex h-10 min-w-0 max-w-[min(200px,42vw)] items-center justify-center rounded border border-[#f2f2f7] bg-white px-2 py-1.5">
|
||||
<div className="flex h-10 min-w-0 max-w-[min(200px,42vw)] items-center justify-center px-2 py-1.5">
|
||||
{logoSrc ? (
|
||||
<img
|
||||
src={logoSrc}
|
||||
|
|
@ -172,7 +172,7 @@ export function HeaderMobile({
|
|||
className="flex items-center gap-1.5 rounded-full border border-[var(--theme-button-color)] transition-all duration-300 ease-in-out hover:bg-[var(--theme-button-color)]/5 px-3 py-2 sm:gap-2 sm:px-4"
|
||||
>
|
||||
<Trophy className="size-5 shrink-0 text-[var(--theme-button-color)] sm:size-6" strokeWidth={1.75} />
|
||||
<span className="min-w-[1.25rem] text-sm font-medium tabular-nums tracking-wide text-[#24282f]">
|
||||
<span className="min-w-[1.25rem] text-sm font-medium tabular-nums tracking-wide text-[var(--theme-button-color)]">
|
||||
{pointsLabel}
|
||||
</span>
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -61,19 +61,19 @@ export function HyperNavigationFAB({
|
|||
const ratingsTo = eventId ? `/ratings?${ratingsQs.toString()}` : "/ratings";
|
||||
|
||||
const items: MenuItem[] = [
|
||||
{ label: "Event Home", to: eventHome, icon: <Home className="size-4 shrink-0 text-[#1e72ef]" strokeWidth={1.75} /> },
|
||||
{ label: "Rewards", to: rewards, icon: <Gift className="size-4 shrink-0 text-[#1e72ef]" strokeWidth={1.75} /> },
|
||||
{ label: "Leaderboard", to: leaderboard, icon: <Trophy className="size-4 shrink-0 text-[#1e72ef]" strokeWidth={1.75} /> },
|
||||
{ label: "Event Home", to: eventHome, icon: <Home className="size-4 shrink-0 text-[var(--theme-button-color)]" strokeWidth={1.75} /> },
|
||||
{ label: "Rewards", to: rewards, icon: <Gift className="size-4 shrink-0 text-[var(--theme-button-color)]" strokeWidth={1.75} /> },
|
||||
{ label: "Leaderboard", to: leaderboard, icon: <Trophy className="size-4 shrink-0 text-[var(--theme-button-color)]" strokeWidth={1.75} /> },
|
||||
...(showFeedbackNav
|
||||
? [{ label: "Feedback", to: feedbackTo, icon: <MessageCircle className="size-4 shrink-0 text-[#1e72ef]" strokeWidth={1.75} /> }]
|
||||
? [{ label: "Feedback", to: feedbackTo, icon: <MessageCircle className="size-4 shrink-0 text-[var(--theme-button-color)]" strokeWidth={1.75} /> }]
|
||||
: []),
|
||||
...(showRatingsNav
|
||||
? [{ label: "Ratings", to: ratingsTo, icon: <Star className="size-4 shrink-0 text-[#1e72ef]" strokeWidth={1.75} /> }]
|
||||
? [{ label: "Ratings", to: ratingsTo, icon: <Star className="size-4 shrink-0 text-[var(--theme-button-color)]" strokeWidth={1.75} /> }]
|
||||
: []),
|
||||
{
|
||||
label: "All Questions",
|
||||
to: allQuestions,
|
||||
icon: <MessageCircleQuestion className="size-4 shrink-0 text-[#1e72ef]" strokeWidth={1.75} />,
|
||||
icon: <MessageCircleQuestion className="size-4 shrink-0 text-[var(--theme-button-color)]" strokeWidth={1.75} />,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -126,7 +126,7 @@ export function HyperNavigationFAB({
|
|||
aria-expanded={open}
|
||||
aria-haspopup="menu"
|
||||
aria-label={open ? "Close quick navigation" : "Open quick navigation"}
|
||||
className="relative flex size-12 shrink-0 items-center justify-center rounded-full border-2 border-white text-white shadow-[0_14px_18px_rgba(0,0,0,0.2),0_2px_3px_rgba(0,0,0,0.17)] eb-gradient-fab"
|
||||
className="relative flex size-12 shrink-0 items-center justify-center rounded-full border-2 border-white/60 text-white shadow-[0_14px_18px_rgba(0,0,0,0.2),0_2px_3px_rgba(0,0,0,0.17)] eb-gradient-fa theme-btn-bg"
|
||||
>
|
||||
<span
|
||||
className="pointer-events-none absolute inset-[-2px] rounded-full shadow-[inset_0_6px_4px_rgba(0,0,0,0.25)]"
|
||||
|
|
|
|||
|
|
@ -47,16 +47,16 @@ export function PhotoWallCropModal({
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-end justify-center bg-black/55 p-4 sm:items-center">
|
||||
<div className="flex w-full max-w-[420px] flex-col overflow-hidden rounded-2xl bg-white shadow-xl">
|
||||
<div className="border-b border-[#f2f2f7] px-4 py-3">
|
||||
<p className="font-inter text-base font-semibold text-[#24282f]">Crop your photo</p>
|
||||
<p className="mt-0.5 font-inter text-xs text-[rgba(36,40,47,0.65)]">
|
||||
<div className="fixed inset-0 z-50 flex items-end justify-center bg-black/70 p-4 sm:items-center">
|
||||
<div className="flex w-full max-w-[420px] flex-col overflow-hidden rounded-2xl card-bg shadow-xl">
|
||||
<div className="border-b border-[var(--theme-card-border-color)]/5 px-4 py-3">
|
||||
<p className="font-inter text-base font-semibold text-[var(--theme-card-text-color)]">Crop your photo</p>
|
||||
<p className="mt-0.5 font-inter text-xs text-[var(--theme-card-text-color)]/80">
|
||||
Adjust to match the wall photo ratio before uploading.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="relative h-[280px] w-full bg-[#1a1d24] sm:h-[320px]">
|
||||
<div className="relative h-[280px] w-full bg-gray-800 sm:h-[320px]">
|
||||
<Cropper
|
||||
image={imageSrc}
|
||||
crop={crop}
|
||||
|
|
@ -70,7 +70,7 @@ export function PhotoWallCropModal({
|
|||
|
||||
<div className="flex flex-col gap-3 px-4 py-4">
|
||||
<label className="flex flex-col gap-1.5">
|
||||
<span className="font-inter text-xs font-semibold text-[#24282f]">Zoom</span>
|
||||
<span className="font-inter text-xs font-semibold text-[var(--theme-card-text-color)]">Zoom</span>
|
||||
<input
|
||||
type="range"
|
||||
min={1}
|
||||
|
|
@ -78,7 +78,7 @@ export function PhotoWallCropModal({
|
|||
step={0.05}
|
||||
value={zoom}
|
||||
onChange={(e) => setZoom(Number(e.target.value))}
|
||||
className="w-full accent-[#ef8d1d]"
|
||||
className="w-full accent-[var(--theme-button-color)]"
|
||||
/>
|
||||
</label>
|
||||
|
||||
|
|
@ -93,7 +93,7 @@ export function PhotoWallCropModal({
|
|||
type="button"
|
||||
onClick={onCancel}
|
||||
disabled={working}
|
||||
className="flex-1 rounded-full border border-[rgba(116,112,112,0.3)] bg-white py-3 text-xs font-semibold uppercase tracking-wide text-[#24282f] disabled:opacity-60"
|
||||
className="flex-1 rounded-full border border-zinc-300 bg-zinc-200 py-3 text-xs font-semibold uppercase tracking-wide text-gray-600 disabled:opacity-60"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
|
|
@ -101,7 +101,7 @@ export function PhotoWallCropModal({
|
|||
type="button"
|
||||
onClick={() => void confirm()}
|
||||
disabled={working || !croppedAreaPixels}
|
||||
className="flex-1 rounded-full border border-[rgba(214,200,200,0.6)] bg-[#ef8d1d] py-3 text-xs font-semibold uppercase tracking-wide text-white shadow-[0_4px_4px_rgba(0,0,0,0.15)] disabled:opacity-60"
|
||||
className="flex-1 rounded-full theme-btn-bg py-3 text-xs font-semibold uppercase tracking-wide text-white shadow-sm disabled:opacity-60"
|
||||
>
|
||||
{working ? "Cropping…" : "Use photo"}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ export function PhotoWallEventHeader({
|
|||
if (d.showDateRange && dateLine) {
|
||||
metaItems.push(
|
||||
<span key="date" className="inline-flex items-center gap-1.5">
|
||||
<Calendar className="size-3.5 shrink-0" strokeWidth={1.75} />
|
||||
<Calendar className="size-3.5 shrink-0 text-[var(--theme-button-color)]" strokeWidth={1.75} />
|
||||
{dateLine}
|
||||
</span>,
|
||||
);
|
||||
|
|
@ -85,7 +85,7 @@ export function PhotoWallEventHeader({
|
|||
if (showLoc) {
|
||||
metaItems.push(
|
||||
<span key="loc" className="inline-flex items-center gap-1.5">
|
||||
<MapPin className="size-3.5 shrink-0" strokeWidth={1.75} />
|
||||
<MapPin className="size-3.5 shrink-0 text-[var(--theme-button-color)]" strokeWidth={1.75} />
|
||||
{venue}
|
||||
</span>,
|
||||
);
|
||||
|
|
@ -105,7 +105,7 @@ export function PhotoWallEventHeader({
|
|||
return (
|
||||
<header className="flex w-full flex-col items-center gap-3 text-center">
|
||||
{showLogo ? (
|
||||
<div className="flex h-[103px] w-full items-center justify-center rounded-xl border border-[#f2f2f7] px-4">
|
||||
<div className="flex h-[103px] w-full items-center justify-center rounded-xl px-4">
|
||||
<img
|
||||
src={event.logoUrl!}
|
||||
alt={event.name}
|
||||
|
|
@ -121,22 +121,22 @@ export function PhotoWallEventHeader({
|
|||
{showTitle || showSubtitle || metaItems.length > 0 ? (
|
||||
<div className="flex w-full flex-col items-center gap-2">
|
||||
{eyebrowText ? (
|
||||
<p className="font-inter text-base font-semibold text-[#24282f]">{eyebrowText}</p>
|
||||
<p className="text-base font-semibold text-[var(--theme-card-text-color)]/80">{eyebrowText}</p>
|
||||
) : null}
|
||||
{showTitle ? (
|
||||
<h1 className="font-inter text-[28px] font-bold leading-tight text-[#24282f] sm:text-[32px]">
|
||||
<h1 className="text-[24px] font-bold leading-tight text-[var(--theme-card-text-color)] sm:text-[26px]">
|
||||
{event.name}
|
||||
</h1>
|
||||
) : null}
|
||||
{showSubtitle ? (
|
||||
<p className="font-inter text-sm font-medium text-[#24282f]/80">{event.subtitle}</p>
|
||||
<p className="text-sm font-medium text-[var(--theme-card-text-color)]/80">{event.subtitle}</p>
|
||||
) : null}
|
||||
{metaItems.length > 0 ? (
|
||||
<div className="flex flex-wrap items-center justify-center gap-x-2 gap-y-1 text-xs text-[#24282f]">
|
||||
<div className="flex flex-wrap items-center justify-center gap-x-2 gap-y-1 text-xs text-[var(--theme-card-text-color)]/80">
|
||||
{metaItems.map((item, index) => (
|
||||
<span key={index} className="inline-flex items-center gap-1.5 font-inter">
|
||||
{index > 0 ? (
|
||||
<span aria-hidden="true" className="text-[rgba(36,40,47,0.4)]">
|
||||
<span aria-hidden="true" className="text-[var(--theme-card-text-color)]/40">
|
||||
·
|
||||
</span>
|
||||
) : null}
|
||||
|
|
@ -147,7 +147,7 @@ export function PhotoWallEventHeader({
|
|||
) : null}
|
||||
</div>
|
||||
) : eyebrowText ? (
|
||||
<p className="font-inter text-base font-semibold text-[#24282f]">{eyebrowText}</p>
|
||||
<p className="font-inter text-base font-semibold text-[var(--theme-card-text-color)]">{eyebrowText}</p>
|
||||
) : null}
|
||||
</header>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -93,16 +93,16 @@ export function SessionQuestionsCard({
|
|||
|
||||
return (
|
||||
<section
|
||||
className="relative z-10 flex w-full max-w-none flex-col gap-4 rounded-lg border border-[rgba(116,112,112,0.2)] bg-whit card-bg py-4 shadow-sm"
|
||||
className="relative z-10 flex w-full max-w-none flex-col gap-4 rounded-lg card-bg py-4 shadow-md"
|
||||
data-name="Session Questions"
|
||||
>
|
||||
<div className="flex h-8 items-center justify-between gap-2 px-4">
|
||||
<div className="flex min-w-0 flex-1 items-center gap-2">
|
||||
<h2 className="truncate text-lg font-semibold tracking-wide text-[#24282f]">
|
||||
<h2 className="truncate text-md font-semibold tracking-wide">
|
||||
{title}
|
||||
</h2>
|
||||
{total > 0 ? (
|
||||
<span className="shrink-0 rounded bg-[#e5e5ea] px-2 py-0.5 text-[10px] tracking-wide text-[#24282f]">
|
||||
<span className="shrink-0 rounded bg-zinc-200 px-2 py-0.5 text-[10px] tracking-wide text-zinc-700">
|
||||
{safeIndex + 1}/{total}
|
||||
</span>
|
||||
) : null}
|
||||
|
|
@ -111,26 +111,26 @@ export function SessionQuestionsCard({
|
|||
<div className="flex shrink-0 gap-1">
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-full bg-[#f2f2f7] p-1"
|
||||
className="rounded-full bg-zinc-300 p-1"
|
||||
aria-label="Previous question"
|
||||
onClick={goPrev}
|
||||
>
|
||||
<ChevronLeft className="size-4 text-[#24282f]" />
|
||||
<ChevronLeft className="size-4 text-zinc-700" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-full bg-[#f2f2f7] p-1"
|
||||
className="rounded-full bg-zinc-300 p-1"
|
||||
aria-label="Next question"
|
||||
onClick={goNext}
|
||||
>
|
||||
<ChevronRight className="size-4 text-[#24282f]" />
|
||||
<ChevronRight className="size-4 text-zinc-700" />
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="border-t border-[#f2f2f7]">
|
||||
<div className="border-t border-[var(--theme-card-text-color)]/5">
|
||||
{live && loading ? (
|
||||
<p className="px-4 py-6 text-center text-sm text-[rgba(36,40,47,0.55)]">Loading questions…</p>
|
||||
<p className="px-4 py-6 text-center text-sm text-[var(--theme-card-text-color)]/80">Loading questions…</p>
|
||||
) : live && error ? (
|
||||
<p className="px-4 py-6 text-center text-sm text-red-600">{error}</p>
|
||||
) : current ? (
|
||||
|
|
@ -143,19 +143,19 @@ export function SessionQuestionsCard({
|
|||
/>
|
||||
</div>
|
||||
) : (
|
||||
<p className="px-4 py-6 text-center text-sm text-[rgba(36,40,47,0.55)]">No questions yet.</p>
|
||||
<p className="px-4 py-6 text-center text-sm text-[var(--theme-card-text-color)]/80">No questions yet.</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-3 px-4">
|
||||
{viewAllTo ? (
|
||||
<Link
|
||||
to={viewAllTo}
|
||||
className="font-inter text-xs font-semibold uppercase tracking-wider text-[#1e72e theme-btn-link"
|
||||
className="text-xs font-semibold uppercase tracking-wider theme-btn-link"
|
||||
>
|
||||
View All
|
||||
</Link>
|
||||
) : (
|
||||
<span className="font-inter text-xs font-semibold uppercase tracking-wider text-[rgba(36,40,47,0.35)]">
|
||||
<span className="text-xs font-semibold uppercase tracking-wider text-[var(--theme-card-text-color)]/40">
|
||||
View All
|
||||
</span>
|
||||
)}
|
||||
|
|
@ -163,7 +163,7 @@ export function SessionQuestionsCard({
|
|||
type="button"
|
||||
onClick={() => live && setAskModalOpen(true)}
|
||||
disabled={!live}
|
||||
className="shrink-0 rounded-lg bg-[#ef8d1 bg-[var(--theme-button-color)] px-3 py-2 text-xs font-semibold text-white shadow-[0_2px_4px_rgba(0,0,0,0.1)] transition-all duration-300 ease-in-out hover:bg-[#e0800c] transition hover:bg-[var(--theme-button-color)]/90 disabled:opacity-50"
|
||||
className="shrink-0 rounded-lg bg-[var(--theme-button-color)] px-3 py-2 text-xs font-semibold text-white shadow-md transition-all duration-300 ease-in-out transition disabled:opacity-50"
|
||||
>
|
||||
Ask your Question
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ function typeBadgeClass(type: string): string {
|
|||
const t = type.toLowerCase();
|
||||
if (t === "poll") return "bg-[#eaddff]";
|
||||
if (t === "survey") return "bg-[#e5e5ea]";
|
||||
return "bg-[#ebffee]";
|
||||
return "bg-green-100";
|
||||
}
|
||||
|
||||
function typeLabel(type: string): string {
|
||||
|
|
@ -44,7 +44,7 @@ export function SessionQuizPollsSection({ eventId, sessionId }: SessionQuizPolls
|
|||
}).toString()}`;
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-none rounded-lg border border-[rgba(116,112,112,0.2)] bg-whit card-bg shadow-sm">
|
||||
<div className="w-full max-w-none rounded-lg card-bg shadow-md">
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full items-center justify-between gap-2 px-4 py-3 text-left"
|
||||
|
|
@ -52,46 +52,46 @@ export function SessionQuizPollsSection({ eventId, sessionId }: SessionQuizPolls
|
|||
aria-expanded={open}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-semibold text-[#24282f]">Quiz & Polls</span>
|
||||
<span className="rounded bg-[#ebffee] px-2 py-0.5 text-[10px] uppercase text-[#24282f]">
|
||||
<span className="text-md font-semibold text-[var(--theme-card-text-color)]">Quiz & Polls</span>
|
||||
<span className="rounded bg-green-100 px-2 py-0.5 text-[10px] uppercase text-gray-800">
|
||||
{loading ? "…" : `${items.length} Active`}
|
||||
</span>
|
||||
</div>
|
||||
{open ? (
|
||||
<ChevronUp className="size-4 shrink-0 text-[#24282f]" strokeWidth={2} />
|
||||
<ChevronUp className="size-4 shrink-0 text-[var(--theme-card-text-color)]/70" strokeWidth={2} />
|
||||
) : (
|
||||
<ChevronDown className="size-4 shrink-0 text-[#24282f]" strokeWidth={2} />
|
||||
<ChevronDown className="size-4 shrink-0 text-[var(--theme-card-text-color)]/70" strokeWidth={2} />
|
||||
)}
|
||||
</button>
|
||||
|
||||
{open ? (
|
||||
<div className="border-t border-[#f2f2f7] px-4 pb-4 pt-1">
|
||||
<div className="border-t border-[var(--theme-card-text-color)]/5 px-4 pb-4 pt-1">
|
||||
{!live ? (
|
||||
<p className="py-3 text-center text-sm text-[rgba(36,40,47,0.55)]">Join a session to see quizzes.</p>
|
||||
<p className="py-3 text-center text-sm text-[var(--theme-card-text-color)]/70">Join a session to see quizzes.</p>
|
||||
) : error ? (
|
||||
<p className="py-3 text-center text-sm text-red-600">{error}</p>
|
||||
) : loading ? (
|
||||
<p className="py-3 text-center text-sm text-[rgba(36,40,47,0.55)]">Loading quizzes…</p>
|
||||
<p className="py-3 text-center text-sm text-[var(--theme-card-text-color)]/70">Loading quizzes…</p>
|
||||
) : items.length === 0 ? (
|
||||
<p className="py-3 text-center text-sm text-[rgba(36,40,47,0.55)]">No live quizzes or polls right now.</p>
|
||||
<p className="py-3 text-center text-sm text-[var(--theme-card-text-color)]/70">No live quizzes or polls right now.</p>
|
||||
) : (
|
||||
<ul className="flex flex-col gap-0">
|
||||
{items.map((item) => (
|
||||
<li
|
||||
key={item._id}
|
||||
className="flex items-center justify-between gap-2 border-b border-[#f2f2f7] py-3 last:border-0"
|
||||
className="flex items-center justify-between gap-2 border-b border-[var(--theme-card-text-color)]/5 py-3 last:border-0 text-[var(--theme-card-text-color)]"
|
||||
>
|
||||
<div className="min-w-0 flex-1">
|
||||
<span
|
||||
className={`inline-block rounded px-2 py-0.5 text-[10px] font-medium text-[#24282f] ${typeBadgeClass(item.type)}`}
|
||||
className={`inline-block rounded px-2 py-0.5 text-[10px] font-medium text-gray-800 ${typeBadgeClass(item.type)}`}
|
||||
>
|
||||
{typeLabel(item.type).toUpperCase()}
|
||||
</span>
|
||||
<p className="mt-1 truncate text-sm font-medium text-[#24282f]">{item.title || "Untitled"}</p>
|
||||
<p className="mt-1 truncate text-sm font-medium">{item.title || "Untitled"}</p>
|
||||
</div>
|
||||
<Link
|
||||
to={takeHref(item._id)}
|
||||
className="shrink-0 rounded bg-[#0b4dd theme-btn-bg px-3 py-1.5 text-center text-xs font-semibold text-white transition hover:bg-[#0a43b8]"
|
||||
className="shrink-0 rounded theme-btn-bg px-3 py-1.5 text-center text-xs font-semibold text-white"
|
||||
>
|
||||
{actionLabel(item.type)}
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
import { useEffect, useRef, useState, type ReactNode } from "react";
|
||||
import { ThemeLoadingScreen } from "./ThemeLoadingScreen";
|
||||
|
||||
/** Keep loader up at least this long so fast networks don't flash it. */
|
||||
const MIN_VISIBLE_MS = 900;
|
||||
/** Opacity transition duration — must match CSS. */
|
||||
const FADE_MS = 480;
|
||||
|
||||
type ThemeGateProps = {
|
||||
/** Theme fetch finished (success or failure). */
|
||||
ready: boolean;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows a full-page loader until theme is ready, holds briefly, then fades out
|
||||
* over the already-themed app content.
|
||||
*/
|
||||
export function ThemeGate({ ready, children }: ThemeGateProps) {
|
||||
const [overlayVisible, setOverlayVisible] = useState(!ready);
|
||||
const [fading, setFading] = useState(false);
|
||||
const shownAtRef = useRef(Date.now());
|
||||
const fadeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const holdTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ready) {
|
||||
if (holdTimerRef.current) clearTimeout(holdTimerRef.current);
|
||||
if (fadeTimerRef.current) clearTimeout(fadeTimerRef.current);
|
||||
shownAtRef.current = Date.now();
|
||||
setFading(false);
|
||||
setOverlayVisible(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const elapsed = Date.now() - shownAtRef.current;
|
||||
const holdMs = Math.max(0, MIN_VISIBLE_MS - elapsed);
|
||||
|
||||
holdTimerRef.current = setTimeout(() => {
|
||||
setFading(true);
|
||||
fadeTimerRef.current = setTimeout(() => {
|
||||
setOverlayVisible(false);
|
||||
setFading(false);
|
||||
}, FADE_MS);
|
||||
}, holdMs);
|
||||
|
||||
return () => {
|
||||
if (holdTimerRef.current) clearTimeout(holdTimerRef.current);
|
||||
if (fadeTimerRef.current) clearTimeout(fadeTimerRef.current);
|
||||
};
|
||||
}, [ready]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{ready ? children : null}
|
||||
{overlayVisible ? <ThemeLoadingScreen fading={fading} /> : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
type ThemeLoadingScreenProps = {
|
||||
/** When true, opacity animates to 0 before unmount. */
|
||||
fading?: boolean;
|
||||
};
|
||||
|
||||
/** Full-viewport gate while public visitor theme CSS vars are applied. */
|
||||
export function ThemeLoadingScreen({ fading = false }: ThemeLoadingScreenProps) {
|
||||
return (
|
||||
<div
|
||||
className={`eb-theme-loader fixed inset-0 z-[10000] flex min-h-dvh w-full flex-col items-center justify-center bg-[#151937 bg-linear-to-t from-stone-900/95 to-gray-950/95 backdrop-blur-xl ${
|
||||
fading ? "eb-theme-loader--out" : ""
|
||||
}`}
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
aria-busy={!fading}
|
||||
aria-label="Loading theme"
|
||||
>
|
||||
<div
|
||||
className="size-10 animate-spin rounded-full border-[3px] border-white/25 border-t-white"
|
||||
aria-hidden
|
||||
/>
|
||||
<span className="sr-only">Loading…</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,22 +1,64 @@
|
|||
import { useEffect } from "react";
|
||||
import { applyEventTheme, clearEventTheme } from "../theme/eventTheme";
|
||||
import type { EventThemeConfig } from "../types/visitorContext";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { getPublicVisitorTheme } from "../lib/api";
|
||||
import { applyEventTheme } from "../theme/eventTheme";
|
||||
import { readStoredVisitorContext } from "../types/visitorContext";
|
||||
|
||||
function resolveEventIdFromSearch(searchParams: URLSearchParams): string {
|
||||
const fromEventId = searchParams.get("event_id")?.trim() || "";
|
||||
if (fromEventId) return fromEventId;
|
||||
const fromEventIdCamel = searchParams.get("eventId")?.trim() || "";
|
||||
if (fromEventIdCamel) return fromEventIdCamel;
|
||||
const type = searchParams.get("type");
|
||||
const id = searchParams.get("id")?.trim() || "";
|
||||
if (type === "event" && id) return id;
|
||||
return readStoredVisitorContext()?.eventId || "";
|
||||
}
|
||||
|
||||
export type UseEventThemeResult = {
|
||||
/** Event id used for the public theme request (empty when none). */
|
||||
eventId: string;
|
||||
/**
|
||||
* Applies event theme CSS variables to `<html>`.
|
||||
* When `theme` is omitted, uses theme from stored visitor context (if any).
|
||||
* True when either there is no event to theme, or the theme request has
|
||||
* finished (success or failure). Gate UI on this to avoid a default→theme flash.
|
||||
*/
|
||||
export function useEventTheme(theme?: EventThemeConfig | null) {
|
||||
useEffect(() => {
|
||||
const resolved = theme ?? readStoredVisitorContext()?.event?.theme ?? null;
|
||||
if (resolved) {
|
||||
applyEventTheme(resolved);
|
||||
}
|
||||
return () => {
|
||||
// Keep vars while navigating between themed event pages;
|
||||
// only clear when an explicit null theme is passed.
|
||||
if (theme === null) clearEventTheme();
|
||||
ready: boolean;
|
||||
};
|
||||
}, [theme]);
|
||||
|
||||
/**
|
||||
* Fetches GET /api/public/visitor-theme/:eventId (from URL / stored context)
|
||||
* and applies CSS variables to `<html>`.
|
||||
*/
|
||||
export function useEventTheme(): UseEventThemeResult {
|
||||
const [searchParams] = useSearchParams();
|
||||
const eventId = resolveEventIdFromSearch(searchParams);
|
||||
const [ready, setReady] = useState(!eventId);
|
||||
|
||||
useEffect(() => {
|
||||
if (!eventId) {
|
||||
setReady(true);
|
||||
return;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
setReady(false);
|
||||
|
||||
getPublicVisitorTheme(eventId)
|
||||
.then((data) => {
|
||||
if (cancelled) return;
|
||||
applyEventTheme(data.theme);
|
||||
setReady(true);
|
||||
})
|
||||
.catch(() => {
|
||||
if (cancelled) return;
|
||||
// Still unblock UI; keep defaults / any previously applied theme.
|
||||
setReady(true);
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [eventId]);
|
||||
|
||||
return { eventId, ready };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -169,3 +169,14 @@ body {
|
|||
transform: scale(0.85);
|
||||
}
|
||||
}
|
||||
|
||||
/* Theme bootstrap loader — held briefly then faded over themed content */
|
||||
.eb-theme-loader {
|
||||
opacity: 1;
|
||||
transition: opacity 0.48s ease;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.eb-theme-loader--out {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
|
|
|||
154
src/lib/api.ts
154
src/lib/api.ts
|
|
@ -1,101 +1,11 @@
|
|||
import type { AttendeeParticipant } from "./attendeeAuth";
|
||||
import { getAttendeeToken } from "./attendeeAuth";
|
||||
import { applyEventTheme } from "../theme/eventTheme";
|
||||
import type { LoginPreviewResponse, ResolveCodeResponse, VisitorJoinType } from "../types/visitorContext";
|
||||
|
||||
/** Temporary test payload for getVisitorLoginPreview — remove when wiring live API again. */
|
||||
const LOGIN_PREVIEW_SAMPLE: LoginPreviewResponse = {
|
||||
_id: "69ce3c91be56b4a3b09b2333",
|
||||
organization: "69b58b13aff81b3887b0026c",
|
||||
name: "COPD Conference",
|
||||
description: "COPD Conference in various locations joined using Zoom",
|
||||
type: "conference",
|
||||
status: "upcoming",
|
||||
startDate: "2026-06-25T12:00:00.000Z",
|
||||
endDate: "2026-09-04T12:00:00.000Z",
|
||||
location:
|
||||
"Stratawiz Technologies Pvt Ltd, First Floor, D-Building, Anant Shilp Society, DSK Raanwaara Rd, Patil Nagar, Bavdhan, Pune, Maharashtra 411021",
|
||||
locationType: "virtual",
|
||||
tags: [],
|
||||
image:
|
||||
"https://did94hhhcgq1d.cloudfront.net/eventbuzz/69b58b13aff81b3887b0026c/69ce3c91be56b4a3b09b2333/event/logo/event-logo-1775123603963.png",
|
||||
bannerImage:
|
||||
"https://did94hhhcgq1d.cloudfront.net/eventbuzz/69b58b13aff81b3887b0026c/69ce3c91be56b4a3b09b2333/event/banner/event-banner-1782452674436.png",
|
||||
metadata: {
|
||||
totalRevenue: 0,
|
||||
rating: 3.9,
|
||||
ratingCount: 8,
|
||||
registrationCustomFields: [],
|
||||
},
|
||||
category: undefined,
|
||||
virtualLink: "https://zoom.us",
|
||||
subtitle: undefined,
|
||||
settings: {
|
||||
registrationOpen: true,
|
||||
requiresApproval: false,
|
||||
registrationMode: "Open",
|
||||
verifyEmail: false,
|
||||
registrationDeadline: "2026-09-12T12:00:00.000Z",
|
||||
enableFeedback: true,
|
||||
enableRatings: true,
|
||||
authenticationType: "none",
|
||||
registrationBaseFields: "name_email",
|
||||
},
|
||||
logoUrl:
|
||||
"https://did94hhhcgq1d.cloudfront.net/eventbuzz/69b58b13aff81b3887b0026c/69ce3c91be56b4a3b09b2333/event/logo/event-logo-1775123603963.png",
|
||||
ratings: {
|
||||
average: 3.9,
|
||||
count: 8,
|
||||
},
|
||||
// theme: {
|
||||
// "theme": "simple",
|
||||
// "color": "#151937",
|
||||
// "font": "Roboto",
|
||||
// "primaryColor": "#303F9F",
|
||||
// "secondaryColor": "#4CAF50",
|
||||
// "headerColor": "",
|
||||
// "buttonColor": "#2c357a",
|
||||
// "textColor": "#ffffff",
|
||||
// "textHighlightColor": "",
|
||||
// "cardColor": "rgba(255,255,255,0.65)",
|
||||
// "cardTextColor": "#ffffff"
|
||||
// },
|
||||
// theme: {
|
||||
// theme: "gradient",
|
||||
// color: "#bd35e6",
|
||||
// // "gradient": {
|
||||
// // "color1": "#1e72ef",
|
||||
// // "color2": "#ef8d1d"
|
||||
// // },
|
||||
// font: "Open Sans",
|
||||
// primaryColor: "#1e72ef",
|
||||
// secondaryColor: "#bd35e6",
|
||||
// headerColor: "",
|
||||
// buttonColor: "#1e72ef",
|
||||
// textColor: "#ffffff",
|
||||
// textHighlightColor: "",
|
||||
// cardColor: "rgba(255, 255, 255, 0.9)",
|
||||
// cardTextColor: "#333333",
|
||||
// },
|
||||
theme: {
|
||||
theme: "background",
|
||||
color: "#1e72ef",
|
||||
background: {
|
||||
url: "https://did94hhhcgq1d.cloudfront.net/rapport/backgrounds/eventbuzz-bg2.jpg",
|
||||
size: "cover",
|
||||
repeat: "no",
|
||||
},
|
||||
font: "Poppins",
|
||||
primaryColor: "#1e72ef",
|
||||
secondaryColor: "#bd35e6",
|
||||
headerColor: "",
|
||||
buttonColor: "#1e72ef",
|
||||
textColor: "#ffffff",
|
||||
textHighlightColor: "",
|
||||
cardColor: "rgba(255, 255, 255, 0.9)",
|
||||
cardTextColor: "#333333",
|
||||
},
|
||||
};
|
||||
import type {
|
||||
EventThemeConfig,
|
||||
LoginPreviewResponse,
|
||||
ResolveCodeResponse,
|
||||
VisitorJoinType,
|
||||
} from "../types/visitorContext";
|
||||
|
||||
function getBaseUrl(): string {
|
||||
const raw = import.meta.env.VITE_API_BASE_URL;
|
||||
|
|
@ -150,25 +60,41 @@ export async function postVisitorResolveCode(code: string): Promise<ResolveCodeR
|
|||
}
|
||||
|
||||
export async function getVisitorLoginPreview(eventId: string): Promise<LoginPreviewResponse> {
|
||||
// TODO: restore live API call below once theme testing is done
|
||||
void eventId;
|
||||
const data = LOGIN_PREVIEW_SAMPLE;
|
||||
if (data.theme) applyEventTheme(data.theme);
|
||||
return data;
|
||||
const base = getBaseUrl();
|
||||
const url = `${base}/api/visitor/events/${encodeURIComponent(eventId)}/login-preview`;
|
||||
const res = await fetch(url);
|
||||
const body = await parseJson(res);
|
||||
if (!res.ok) {
|
||||
throw new Error(messageFromBody(body));
|
||||
}
|
||||
if (!body || typeof body !== "object" || !("data" in body)) {
|
||||
throw new Error("Invalid response");
|
||||
}
|
||||
return (body as { data: LoginPreviewResponse }).data;
|
||||
}
|
||||
|
||||
// const base = getBaseUrl();
|
||||
// const url = `${base}/api/visitor/events/${encodeURIComponent(eventId)}/login-preview`;
|
||||
// const res = await fetch(url);
|
||||
// const body = await parseJson(res);
|
||||
// if (!res.ok) {
|
||||
// throw new Error(messageFromBody(body));
|
||||
// }
|
||||
// if (!body || typeof body !== "object" || !("data" in body)) {
|
||||
// throw new Error("Invalid response");
|
||||
// }
|
||||
// const data = (body as { data: LoginPreviewResponse }).data;
|
||||
// if (data?.theme) applyEventTheme(data.theme);
|
||||
// return data;
|
||||
/** Public visitor theme for an event — GET /api/public/visitor-theme/:eventId */
|
||||
export type PublicVisitorThemeResponse = {
|
||||
eventId: string;
|
||||
theme: EventThemeConfig;
|
||||
};
|
||||
|
||||
export async function getPublicVisitorTheme(eventId: string): Promise<PublicVisitorThemeResponse> {
|
||||
const base = getBaseUrl();
|
||||
const url = `${base}/api/public/visitor-theme/${encodeURIComponent(eventId)}`;
|
||||
const res = await fetch(url);
|
||||
const body = await parseJson(res);
|
||||
if (!res.ok) {
|
||||
throw new Error(messageFromBody(body));
|
||||
}
|
||||
if (!body || typeof body !== "object" || !("data" in body)) {
|
||||
throw new Error("Invalid response");
|
||||
}
|
||||
const data = (body as { data: PublicVisitorThemeResponse }).data;
|
||||
if (!data?.eventId || !data.theme) {
|
||||
throw new Error("Invalid response");
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
export type VisitorEventTrackListItem = {
|
||||
|
|
|
|||
|
|
@ -45,8 +45,8 @@ import {
|
|||
function WhiteCard({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<div className="flex w-full max-w-[354px] flex-col items-center">
|
||||
<div className="w-full rounded-xl shadow-[0_8px_12px_6px_rgba(0,0,0,0.15),0_4px_4px_rgba(0,0,0,0.3)]">
|
||||
<div className="rounded-xl bg-white px-6 py-6">{children}</div>
|
||||
<div className="w-full rounded-xl shadow-md">
|
||||
<div className="rounded-xl bg-whit card-bg px-6 py-6">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -410,24 +410,24 @@ export function AiAvatarPage() {
|
|||
</div>
|
||||
) : null}
|
||||
<div className="flex flex-col items-center py-4 text-center">
|
||||
<div className="mb-5 flex h-16 w-16 items-center justify-center rounded-full bg-[#fdecea]">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-[#D32F2F]" aria-hidden />
|
||||
<div className="mb-5 flex h-16 w-16 items-center justify-center rounded-full bg-[var(--theme-button-color)]/10">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-[var(--theme-button-color)]" aria-hidden />
|
||||
</div>
|
||||
<h1 className="mb-2 text-[22px] font-semibold text-[#1a1a1a]">Creating your avatar</h1>
|
||||
<h1 className="mb-2 text-[22px] font-semibold text-[var(--theme-card-text-color)]">Creating your avatar</h1>
|
||||
<p
|
||||
key={pipelineMsgIndex}
|
||||
className="mb-5 min-h-[2.5rem] text-sm text-[#666] transition-opacity duration-300"
|
||||
className="mb-5 min-h-[2.5rem] text-sm text-[var(--theme-card-text-color)]/80 transition-opacity duration-300"
|
||||
aria-live="polite"
|
||||
>
|
||||
{GENERATING_PIPELINE_MESSAGES[pipelineMsgIndex]}
|
||||
</p>
|
||||
<div className="mb-2 h-1.5 w-full overflow-hidden rounded-full bg-[#eee]">
|
||||
<div className="mb-2 h-1.5 w-full overflow-hidden rounded-full bg-[var(--theme-card-text-color)]/5">
|
||||
<div
|
||||
className="h-full rounded-full bg-[#D32F2F] transition-[width] duration-700 ease-out"
|
||||
className="h-full rounded-full bg-[var(--theme-button-color)] transition-[width] duration-700 ease-out"
|
||||
style={{ width: `${progressPct}%` }}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-[#999]">This can take up to a minute — hang tight</p>
|
||||
<p className="text-xs text-[var(--theme-card-text-color)]/60">This can take up to a minute — hang tight</p>
|
||||
</div>
|
||||
</WhiteCard>
|
||||
</AuthShell>
|
||||
|
|
@ -447,12 +447,12 @@ export function AiAvatarPage() {
|
|||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<h1 className="mb-2 text-center text-[22px] font-semibold text-[#1a1a1a]">Your avatar</h1>
|
||||
<p className="mb-4 text-center text-sm text-[#666]">
|
||||
Here’s your AI avatar. You can download or share it.
|
||||
<h1 className="mb-2 text-center text-[22px] font-semibold">Your avatar</h1>
|
||||
<p className="mb-4 text-center text-sm text-[var(--theme-card-text-color)]/80">
|
||||
Here's your AI avatar. You can download or share it.
|
||||
</p>
|
||||
{(done.fileUrl || done.previewUrl) ? (
|
||||
<div className="mb-4 overflow-hidden rounded-lg border border-[#e5e5e5] bg-[#f8f8f8]">
|
||||
<div className="mb-4 overflow-hidden rounded-lg">
|
||||
<img
|
||||
ref={resultImgRef}
|
||||
crossOrigin="anonymous"
|
||||
|
|
@ -477,7 +477,7 @@ export function AiAvatarPage() {
|
|||
<button
|
||||
type="button"
|
||||
onClick={resetForNew}
|
||||
className="inline-flex h-11 w-full items-center justify-center rounded-lg bg-[#D32F2F] px-4 text-sm font-semibold text-white hover:bg-[#b71c1c]"
|
||||
className="inline-flex h-11 w-full items-center justify-center rounded-lg theme-btn-bg px-4 text-sm font-semibold text-white"
|
||||
>
|
||||
Create another
|
||||
</button>
|
||||
|
|
@ -485,7 +485,7 @@ export function AiAvatarPage() {
|
|||
<button
|
||||
type="button"
|
||||
onClick={() => void onDownload()}
|
||||
className="inline-flex h-11 w-full items-center justify-center gap-2 rounded-lg border border-[#ddd] bg-white px-4 text-sm font-semibold text-[#333] hover:bg-[#fafafa]"
|
||||
className="inline-flex h-11 w-full items-center justify-center gap-2 rounded-lg border border-zinc-300 bg-zinc-100 px-4 text-sm font-semibold text-gray-600"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
Download photo
|
||||
|
|
@ -494,14 +494,14 @@ export function AiAvatarPage() {
|
|||
</div>
|
||||
{showShare ? (
|
||||
<div className="mt-4">
|
||||
<p className="mb-2 text-center text-xs font-semibold uppercase tracking-wide text-[#888]">
|
||||
<p className="mb-2 text-center text-xs font-semibold uppercase tracking-wide text-[var(--theme-card-text-color)]/60">
|
||||
Share
|
||||
</p>
|
||||
{useNativeShare ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void onNativeShare()}
|
||||
className="inline-flex h-11 w-full items-center justify-center gap-2 rounded-lg border border-[#ddd] bg-white px-4 text-sm font-semibold text-[#333] hover:bg-[#fafafa]"
|
||||
className="inline-flex h-11 w-full items-center justify-center gap-2 rounded-lg border border-zinc-300 bg-zinc-100 px-4 text-sm font-semibold text-gray-600"
|
||||
>
|
||||
<Share2 className="h-4 w-4" />
|
||||
Share
|
||||
|
|
@ -513,7 +513,7 @@ export function AiAvatarPage() {
|
|||
key={item.key}
|
||||
type="button"
|
||||
onClick={() => void onShare(item.key)}
|
||||
className="inline-flex h-10 items-center justify-center gap-2 rounded-lg border border-[#ddd] bg-white px-3 text-xs font-semibold text-[#333] hover:bg-[#fafafa]"
|
||||
className="inline-flex h-10 items-center justify-center gap-2 rounded-lg border border-zinc-300 bg-zinc-100 px-3 text-xs font-semibold text-gray-600"
|
||||
>
|
||||
<ShareIcon platform={item.key} />
|
||||
{item.label}
|
||||
|
|
@ -544,22 +544,22 @@ export function AiAvatarPage() {
|
|||
|
||||
{meta?.accessMode === "login" && !loggedIn ? (
|
||||
<div className="space-y-3">
|
||||
<h1 className="mb-1 text-center text-[22px] font-semibold text-[#1a1a1a]">AI Avatar</h1>
|
||||
<p className="text-center text-sm text-[#666]">Sign in to create your AI avatar.</p>
|
||||
<h1 className="mb-1 text-center text-[22px] font-semibold text-[var(--theme-card-text-color)]">AI Avatar</h1>
|
||||
<p className="text-center text-sm text-[var(--theme-card-text-color)]/80">Sign in to create your AI avatar.</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void goLogin()}
|
||||
className="inline-flex h-11 w-full items-center justify-center rounded-lg bg-[#D32F2F] px-4 text-sm font-semibold text-white hover:bg-[#b71c1c]"
|
||||
className="inline-flex h-11 w-full items-center justify-center rounded-lg theme-btn-bg px-4 text-sm font-semibold text-white"
|
||||
>
|
||||
Sign in to continue
|
||||
</button>
|
||||
</div>
|
||||
) : !selectedAvatar ? (
|
||||
<div className="space-y-4">
|
||||
<h1 className="mb-1 text-center text-[22px] font-semibold text-[#1a1a1a]">Choose an avatar</h1>
|
||||
<p className="text-center text-sm text-[#666]">Pick an avatar, then upload your selfie.</p>
|
||||
<h1 className="mb-1 text-center text-[22px] font-semibold text-[var(--theme-card-text-color)]">Choose an avatar</h1>
|
||||
<p className="text-center text-sm text-[var(--theme-card-text-color)]/80">Pick an avatar, then upload your selfie.</p>
|
||||
{!meta?.avatars?.length ? (
|
||||
<p className="text-center text-sm text-[#666]">No avatars available yet.</p>
|
||||
<p className="text-center text-sm text-[var(--theme-card-text-color)]/80">No avatars available yet.</p>
|
||||
) : (
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{meta.avatars.map((avatar) => (
|
||||
|
|
@ -567,9 +567,9 @@ export function AiAvatarPage() {
|
|||
key={avatar._id}
|
||||
type="button"
|
||||
onClick={() => setSelectedAvatar(avatar)}
|
||||
className="overflow-hidden rounded-lg border border-[#e5e5e5] bg-[#f8f8f8] text-left hover:border-[#D32F2F]"
|
||||
className="overflow-hidden rounded-lg border border-[var(--theme-card-text-color)]/15 text-left hover:border-[var(--theme-card-text-color)]/50"
|
||||
>
|
||||
<div className="aspect-square w-full bg-white">
|
||||
<div className="aspect-square w-full">
|
||||
{avatar.imageUrl ? (
|
||||
<img
|
||||
src={avatar.imageUrl}
|
||||
|
|
@ -577,12 +577,12 @@ export function AiAvatarPage() {
|
|||
className="h-full w-full object-contain"
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-full items-center justify-center text-xs text-[#999]">
|
||||
<div className="flex h-full items-center justify-center text-xs text-[var(--theme-card-text-color)]/60">
|
||||
No preview
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<p className="truncate px-2 py-2 text-center text-xs font-semibold text-[#333]">
|
||||
<p className="truncate px-2 py-2 text-center text-xs font-semibold">
|
||||
{avatar.name}
|
||||
</p>
|
||||
</button>
|
||||
|
|
@ -592,7 +592,7 @@ export function AiAvatarPage() {
|
|||
{err ? <p className="text-center text-sm text-red-600">{err}</p> : null}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-4 text-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
|
|
@ -600,13 +600,13 @@ export function AiAvatarPage() {
|
|||
clearSelectedPhoto();
|
||||
setErr(null);
|
||||
}}
|
||||
className="text-sm font-semibold text-[#D32F2F]"
|
||||
className="text-sm font-semibold theme-btn-link"
|
||||
>
|
||||
← Change avatar
|
||||
</button>
|
||||
<h1 className="mb-1 text-center text-[22px] font-semibold text-[#1a1a1a]">Upload selfie</h1>
|
||||
<p className="text-center text-sm text-[#666]">
|
||||
Avatar: <span className="font-semibold text-[#333]">{selectedAvatar.name}</span>
|
||||
<h1 className="mb-1 text-center text-[22px] font-semibold text-[var(--theme-card-text-color)]">Upload selfie</h1>
|
||||
<p className="text-center text-sm text-[var(--theme-card-text-color)]/80">
|
||||
Avatar: <span className="font-semibold">{selectedAvatar.name}</span>
|
||||
</p>
|
||||
|
||||
<input
|
||||
|
|
@ -618,7 +618,7 @@ export function AiAvatarPage() {
|
|||
onChange={(e) => void handleFileChange(e.target.files?.[0] || null)}
|
||||
/>
|
||||
{previewUrl ? (
|
||||
<div className="overflow-hidden rounded-lg border border-[#e5e5e5] bg-[#f8f8f8]">
|
||||
<div className="overflow-hidden rounded-lg">
|
||||
<img src={previewUrl} alt="Your selfie" className="mx-auto max-h-[240px] w-full object-contain" />
|
||||
</div>
|
||||
) : null}
|
||||
|
|
@ -626,7 +626,7 @@ export function AiAvatarPage() {
|
|||
type="button"
|
||||
disabled={picking}
|
||||
onClick={() => fileRef.current?.click()}
|
||||
className="inline-flex h-11 w-full items-center justify-center rounded-lg border border-[#ddd] bg-white px-4 text-sm font-semibold text-[#333] hover:bg-[#fafafa] disabled:opacity-55"
|
||||
className="inline-flex h-11 w-full items-center justify-center rounded-lg border border-zinc-300 bg-zinc-100 px-4 text-sm font-semibold text-gray-600 disabled:opacity-55"
|
||||
>
|
||||
{picking ? "Processing…" : file ? "Change selfie" : "Choose selfie"}
|
||||
</button>
|
||||
|
|
@ -634,7 +634,7 @@ export function AiAvatarPage() {
|
|||
type="button"
|
||||
disabled={!file || submitting}
|
||||
onClick={() => void onSubmit()}
|
||||
className="inline-flex h-11 w-full items-center justify-center rounded-lg bg-[#D32F2F] px-4 text-sm font-semibold text-white hover:bg-[#b71c1c] disabled:opacity-55"
|
||||
className="inline-flex h-11 w-full items-center justify-center rounded-lg theme-btn-bg px-4 text-sm font-semibold text-white hover:bg-[#b71c1c] disabled:opacity-55"
|
||||
>
|
||||
{submitting ? "Creating avatar…" : "Create avatar"}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -121,9 +121,9 @@ export function EventLandingPage() {
|
|||
|
||||
if (!eventId) {
|
||||
return (
|
||||
<MobileShell className="relative">
|
||||
<MobileShell className="relative bg-transparent">
|
||||
<HeaderMobile showRewardsAndLogout={false} showBack={false} />
|
||||
<main className="flex flex-1 flex-col items-center justify-center bg-[#f2f2f7] px-4 py-10">
|
||||
<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
|
||||
|
|
@ -135,9 +135,9 @@ export function EventLandingPage() {
|
|||
|
||||
if (!event) {
|
||||
return (
|
||||
<MobileShell className="relative">
|
||||
<MobileShell className="relative bg-transparent">
|
||||
<HeaderMobile logoUrl={ctx?.event?.logoUrl} eventId={eventId || null} showBack={false} />
|
||||
<main className="flex flex-1 items-center justify-center bg-[#f2f2f7] px-4 py-10">
|
||||
<main className="flex flex-1 items-center justify-center px-4 py-10">
|
||||
<p className="text-sm text-[#24282f]">Loading event…</p>
|
||||
</main>
|
||||
</MobileShell>
|
||||
|
|
@ -145,40 +145,40 @@ export function EventLandingPage() {
|
|||
}
|
||||
|
||||
return (
|
||||
<MobileShell className="relative">
|
||||
<MobileShell className="relative bg-transparent">
|
||||
<HeaderMobile logoUrl={event.logoUrl} eventId={eventId || null} showBack={false} />
|
||||
<EventBanner imageUrl={bannerSrc} alt={event.name} />
|
||||
<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 bg-[#f2f2f pb-8 pt-6">
|
||||
<div className="flex w-full max-w-none flex-col gap-6">
|
||||
<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 border border-[rgba(116,112,112,0.2)] bg-whit card-bg p-4 shadow-sm">
|
||||
<h1 className="text-lg font-semibold text-[#24282f]">{event.name}</h1>
|
||||
<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-inter font-semibold uppercase tracking-wide text-[#1e72e theme-btn-link">
|
||||
<Link to={ratingsHref} className="font-semibold uppercase tracking-wide theme-btn-link">
|
||||
rate now
|
||||
</Link>
|
||||
) : (
|
||||
<span className="font-inter text-[#8e8e93]">Ratings off</span>
|
||||
<span className="text-[var(--theme-card-text-color)]/50">Ratings off</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="mt-3 text-base leading-5 text-[rgba(36,40,47,0.87)]">
|
||||
<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 font-inter text-xs font-semibold uppercase tracking-wide text-[#1e72e theme-btn-link">
|
||||
{/* <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-[#f2f2f7] pt-4 text-xs text-[#24282f]">
|
||||
</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-[#8e8e93]" strokeWidth={1.75} />
|
||||
<span className="font-inter">{schedule || "—"}</span>
|
||||
<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-[#8e8e93]" strokeWidth={1.75} />
|
||||
<span className="font-inter">{event.location.trim()}</span>
|
||||
<MapPin className="size-4 shrink-0 text-[var(--theme-button-color)]" strokeWidth={1.75} />
|
||||
<span className="">{event.location.trim()}</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
|
@ -186,12 +186,12 @@ export function EventLandingPage() {
|
|||
</section>
|
||||
|
||||
{bookmarkedIds.size > 0 ? (
|
||||
<section className="w-full">
|
||||
<div className="w-full px-4">
|
||||
<h2 className="font-inter text-base font-semibold text-[var(--theme-text-color)]">My Sessions</h2>
|
||||
</div>
|
||||
<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-[rgba(36,40,47,0.55)]">Loading…</p>
|
||||
<p className="mt-3 px-4 text-sm text-[var(--theme-card-text-color)]/80">Loading…</p>
|
||||
) : (
|
||||
<LandingMySessionsList
|
||||
sessions={bookmarkedSessionsSorted}
|
||||
|
|
@ -203,17 +203,17 @@ export function EventLandingPage() {
|
|||
</section>
|
||||
) : null}
|
||||
|
||||
<section className="w-full">
|
||||
<div className="w-full px-4">
|
||||
<h2 className="font-inter text-base font-semibold text-[var(--theme-text-color)]">Agenda</h2>
|
||||
</div>
|
||||
<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-[rgba(36,40,47,0.55)]">Loading agenda…</p>
|
||||
<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-[rgba(36,40,47,0.55)]">No sessions scheduled for this event yet.</p>
|
||||
<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}
|
||||
|
|
|
|||
|
|
@ -109,11 +109,11 @@ export function EventTrackPage() {
|
|||
|
||||
if (!eventId || !trackId) {
|
||||
return (
|
||||
<MobileShell className="relative">
|
||||
<MobileShell className="relative bg-transparent">
|
||||
<HeaderMobile eventId={eventId || null} />
|
||||
<main className="flex flex-1 flex-col items-center justify-center bg-[#f2f2f7] px-4 py-10">
|
||||
<main className="flex flex-1 flex-col items-center justify-center 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">
|
||||
<Link to="/" className="mt-4 text-sm font-semibold theme-btn-link underline">
|
||||
Home
|
||||
</Link>
|
||||
</main>
|
||||
|
|
@ -123,10 +123,10 @@ export function EventTrackPage() {
|
|||
|
||||
if (loading) {
|
||||
return (
|
||||
<MobileShell className="relative">
|
||||
<MobileShell className="relative bg-transparent">
|
||||
<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 className="flex flex-1 items-center justify-center px-4 py-10">
|
||||
<p className="text-sm text-[var(--theme-card-text-color)]">Loading track…</p>
|
||||
</main>
|
||||
</MobileShell>
|
||||
);
|
||||
|
|
@ -134,9 +134,9 @@ export function EventTrackPage() {
|
|||
|
||||
if (err || !track) {
|
||||
return (
|
||||
<MobileShell className="relative">
|
||||
<MobileShell className="relative bg-transparent">
|
||||
<HeaderMobile eventId={eventId || null} />
|
||||
<main className="flex flex-1 flex-col items-center justify-center bg-[#f2f2f7] px-4 py-10">
|
||||
<main className="flex flex-1 flex-col items-center justify-center px-4 py-10">
|
||||
<p className="text-center text-sm text-red-600">{err ?? "Track not found."}</p>
|
||||
</main>
|
||||
</MobileShell>
|
||||
|
|
@ -160,42 +160,44 @@ export function EventTrackPage() {
|
|||
(ctx?.eventId === eventId && ctx.event?.logoUrl?.trim() ? ctx.event.logoUrl.trim() : undefined);
|
||||
|
||||
return (
|
||||
<MobileShell className="relative">
|
||||
<MobileShell className="relative bg-transparent">
|
||||
<HeaderMobile logoUrl={headerLogo} eventId={eventId || null} />
|
||||
<EventBanner />
|
||||
<EventBanner className="rounded-b-lg border-none" />
|
||||
|
||||
<main className="relative z-0 flex flex-1 flex-col bg-[#f2f2f7] px-4 pb-28 pt-6">
|
||||
<main className="relative z-0 flex flex-1 flex-col 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 className="mb-6 flex flex-col gap-4 card-bg p-4 rounded-lg">
|
||||
<div className="inline-flex w-fit rounded bg-zinc-200 px-2 py-0.5">
|
||||
<span className="text-[10px] tracking-wide">{eventName.toUpperCase()}</span>
|
||||
</div>
|
||||
<h1 className="text-lg font-semibold tracking-wide text-[#24282f]">{track.name}</h1>
|
||||
<h1 className="text-lg font-semibold tracking-wide">{track.name}</h1>
|
||||
{track.hall?.trim() ? (
|
||||
<p className="text-xs text-[rgba(36,40,47,0.7)]">{track.hall.trim()}</p>
|
||||
<p className="text-xs text-[var(--theme-card-text-color)]/80">{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]">
|
||||
<p className="text-xs uppercase font-semibold tracking-wide text-[var(--theme-card-text-color)]/70">{trackTypeLabel}</p>
|
||||
<div className="flex flex-col gap-2 text-xs text-[var(--theme-card-text-color)]/80">
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar className="size-4 shrink-0" strokeWidth={1.75} />
|
||||
<Calendar className="size-4 shrink-0 text-[var(--theme-button-color)]" 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} />
|
||||
<Clock className="size-4 shrink-0 text-[var(--theme-button-color)]" 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} />
|
||||
<User className="size-4 shrink-0 text-[var(--theme-button-color)]" 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>
|
||||
<div className="card-bg p-4 rounded-lg">
|
||||
<h2 className="font-inter text-base font-semibold mb-2">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"
|
||||
className="flex h-[43px] w-full items-center justify-between rounded-lg border border-[var(--theme-button-color)]/10 bg-[var(--theme-button-color)]/9 px-4 py-3"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar className="size-4 shrink-0 text-[#24282f]" strokeWidth={1.75} />
|
||||
|
|
@ -204,8 +206,9 @@ export function EventTrackPage() {
|
|||
<ChevronDown className="size-4 shrink-0 text-[#1d66ef]" strokeWidth={2} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative rounded-md bg-white p-4 shadow-[0_1px_4px_rgba(12,12,13,0.05)]">
|
||||
<div className="relative rounded-lg card-bg p-4 shadow-md">
|
||||
<div className="flex flex-col gap-6">
|
||||
{agendaRows.map((row) => (
|
||||
<AgendaRow key={row.time + row.sessionType} row={row} />
|
||||
|
|
@ -213,9 +216,9 @@ export function EventTrackPage() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<p className="mt-6 text-center text-xs text-[#24282f]/60">
|
||||
<p className="mt-6 text-center text-sm">
|
||||
<Link
|
||||
className="text-[#1e72ef] underline"
|
||||
className="text-[var(--theme-text-color)] underline"
|
||||
to={`/questions?event_id=${encodeURIComponent(eventId)}`}
|
||||
>
|
||||
Ask & session questions
|
||||
|
|
|
|||
|
|
@ -43,50 +43,50 @@ export function EventTrackV2Page() {
|
|||
const [tab, setTab] = useState(0);
|
||||
|
||||
return (
|
||||
<MobileShell className="relative">
|
||||
<MobileShell className="relative bg-transparent">
|
||||
<HeaderMobile
|
||||
logoUrl={ctx?.eventId === eventId ? ctx.event?.logoUrl : undefined}
|
||||
eventId={eventId || null}
|
||||
/>
|
||||
<EventBanner />
|
||||
<EventBanner className="rounded-b-lg border-none" />
|
||||
|
||||
<main className="relative z-0 flex flex-1 flex-col bg-[#f2f2f7] px-4 pb-28 pt-6">
|
||||
<main className="relative z-0 flex flex-1 flex-col px-4 pb-28 pt-6">
|
||||
<div className="mx-auto w-full max-w-[370px]">
|
||||
<div className="mb-4 flex flex-col gap-4">
|
||||
<span className="inline-flex w-fit rounded bg-[#e5e5ea] px-2 py-0.5 text-[10px] text-[#24282f]">
|
||||
<div className="mb-4 flex flex-col gap-4 card-bg p-4 rounded-lg">
|
||||
<span className="inline-flex w-fit rounded bg-zinc-200 px-2 py-0.5 text-[10px] text-gray-600">
|
||||
NAPCON JAIPUR 2026
|
||||
</span>
|
||||
<h1 className="text-lg font-semibold text-[#24282f]">This will be Track Name</h1>
|
||||
<div className="flex items-center gap-2 text-xs text-[#24282f]">
|
||||
<MapPin className="size-4 shrink-0" strokeWidth={1.75} />
|
||||
<h1 className="text-lg font-semibold">This will be Track Name</h1>
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<MapPin className="size-4 shrink-0 text-[var(--theme-button-color)]" strokeWidth={1.75} />
|
||||
<span className="font-inter">Health and Wellness Expo</span>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1 text-xs text-[#24282f]">
|
||||
<div className="flex flex-col gap-2 text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar className="size-4" strokeWidth={1.75} />
|
||||
<Calendar className="size-4 text-[var(--theme-button-color)]" strokeWidth={1.75} />
|
||||
<span className="font-inter">November 22, 2025 - November 23, 2025</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Clock className="size-4" strokeWidth={1.75} />
|
||||
<Clock className="size-4 text-[var(--theme-button-color)]" strokeWidth={1.75} />
|
||||
<span className="font-inter">6:00 PM - 10:00 PM</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<User className="size-4" strokeWidth={1.75} />
|
||||
<User className="size-4 text-[var(--theme-button-color)]" strokeWidth={1.75} />
|
||||
<span className="font-inter">400 Participants (Capacity)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-4 flex w-full border-b-2 border-[#c7c7cc]">
|
||||
<div className="mb-4 flex w-full card-bg p-4 py-2 rounded-lg">
|
||||
{dates.map((d, i) => (
|
||||
<button
|
||||
key={d}
|
||||
type="button"
|
||||
onClick={() => setTab(i)}
|
||||
className={`flex-1 pb-2 pt-1 text-center text-base ${
|
||||
className={`flex-1 pb-2 pt-1 text-center text-xs ${
|
||||
tab === i
|
||||
? "border-b-2 border-[#1e72ef] font-semibold text-[#24282f]"
|
||||
: "font-normal text-[#24282f]"
|
||||
? "border-b-2 border-[var(--theme-button-color)] font-semibold"
|
||||
: "font-normal"
|
||||
}`}
|
||||
style={{ marginBottom: -2 }}
|
||||
>
|
||||
|
|
@ -95,7 +95,7 @@ export function EventTrackV2Page() {
|
|||
))}
|
||||
</div>
|
||||
|
||||
<div className="relative rounded-md bg-white p-4 shadow-[0_1px_4px_rgba(12,12,13,0.05)]">
|
||||
<div className="relative rounded-md card-bg p-4 shadow-md">
|
||||
<div className="flex flex-col gap-4">
|
||||
{rowsV2.map((row) => (
|
||||
<AgendaRowTrackV2 key={row.time + row.sessionType} row={row} />
|
||||
|
|
@ -103,8 +103,8 @@ export function EventTrackV2Page() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<p className="mt-4 text-center text-xs">
|
||||
<Link className="text-[#1e72ef] underline" to="/event">
|
||||
<p className="mt-4 text-center text-sm">
|
||||
<Link className="text-[var(--theme-text-color)] underline" to="/event">
|
||||
Back to event
|
||||
</Link>
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -98,14 +98,16 @@ export function FeedbackPage() {
|
|||
return (
|
||||
<MobileShell className="relative">
|
||||
<HeaderMobile logoUrl={ctx?.event?.logoUrl} eventId={null} />
|
||||
<main className="flex flex-1 flex-col bg-[#f2f2f7] px-4 pb-10 pt-6">
|
||||
<main className="flex flex-1 flex-col px-4 pb-10 pt-6">
|
||||
<div className="mx-auto w-full max-w-[370px]">
|
||||
<h1 className="font-inter text-xl font-semibold text-[#24282f]">Feedback</h1>
|
||||
<p className="mt-3 text-sm text-[rgba(36,40,47,0.67)]">Open feedback from your event or session link.</p>
|
||||
<Link to="/" className="mt-4 inline-block text-sm font-semibold text-[#1e72ef] underline">
|
||||
<div className="card-bg rounded-lg shadow-md p-4">
|
||||
<h1 className="font-inter text-xl font-semibold text-[var(--theme-card-text-color)]">Feedback</h1>
|
||||
<p className="mt-3 text-sm text-[var(--theme-card-text-color)]/80">Open feedback from your event or session link.</p>
|
||||
<Link to="/" className="mt-4 inline-block text-sm font-semibold theme-btn-link">
|
||||
Home
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</MobileShell>
|
||||
);
|
||||
|
|
@ -115,14 +117,16 @@ export function FeedbackPage() {
|
|||
return (
|
||||
<MobileShell className="relative">
|
||||
<HeaderMobile logoUrl={eventSnapshot?.logoUrl} eventId={eventId || null} />
|
||||
<main className="flex flex-1 flex-col bg-[#f2f2f7] px-4 pb-10 pt-6">
|
||||
<main className="flex flex-1 flex-col px-4 pb-10 pt-6">
|
||||
<div className="mx-auto w-full max-w-[370px]">
|
||||
<h1 className="font-inter text-xl font-semibold text-[#24282f]">Feedback</h1>
|
||||
<p className="mt-3 text-sm text-[rgba(36,40,47,0.67)]">Feedback is disabled for this event.</p>
|
||||
<Link to="/event" className="mt-4 inline-block text-sm font-semibold text-[#1e72ef] underline">
|
||||
<div className="card-bg rounded-lg shadow-md p-4">
|
||||
<h1 className="text-xl font-semibold text-[var(--theme-card-text-color)]">Feedback</h1>
|
||||
<p className="mt-3 text-sm text-[var(--theme-card-text-color)]/80">Feedback is disabled for this event.</p>
|
||||
<Link to="/event" className="mt-4 inline-block text-sm font-semibold theme-btn-link">
|
||||
Back to event
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</MobileShell>
|
||||
);
|
||||
|
|
@ -131,27 +135,29 @@ export function FeedbackPage() {
|
|||
return (
|
||||
<MobileShell className="relative">
|
||||
<HeaderMobile logoUrl={eventSnapshot?.logoUrl} eventId={eventId || null} />
|
||||
<main className="flex flex-1 flex-col bg-[#f2f2f7 bg-transparent px-4 pb-10 pt-6">
|
||||
<main className="flex flex-1 flex-col px-4 pb-10 pt-6">
|
||||
<div className="mx-auto w-full max-w-[370px]">
|
||||
<h1 className="font-inter text-xl font-semibold text-[var(--theme-text-color)]">{schema?.name || "Feedback"}</h1>
|
||||
<div className="card-bg rounded-lg shadow-md p-4">
|
||||
<h1 className="text-xl font-semibold text-[var(--theme-card-text-color)]">{schema?.name || "Feedback"}</h1>
|
||||
{schemaLoading ? (
|
||||
<p className="mt-4 text-sm text-[rgba(36,40,47,0.67)]">Loading form…</p>
|
||||
<p className="mt-4 text-sm text-[var(--theme-card-text-color)]/80">Loading form…</p>
|
||||
) : schemaErr ? (
|
||||
<div className="mt-4">
|
||||
<p className="text-sm text-red-600">{schemaErr}</p>
|
||||
<p className="mt-2 text-sm text-[rgba(36,40,47,0.67)]">
|
||||
<p className="mt-2 text-sm text-[var(--theme-card-text-color)]/80">
|
||||
Ask the organizer to publish an active feedback form for this event.
|
||||
</p>
|
||||
<Link to="/event" className="mt-4 inline-block text-sm font-semibold text-[#1e72ef] underline">
|
||||
<Link to="/event" className="mt-4 inline-block text-sm font-semibold theme-btn-link">
|
||||
Back to event
|
||||
</Link>
|
||||
</div>
|
||||
) : schema ? (
|
||||
<div className="mt-6">
|
||||
<DynamicFeedbackForm eventId={eventId} schema={schema} variant="light" />
|
||||
<div className="mt-2">
|
||||
<DynamicFeedbackForm eventId={eventId} schema={schema} variant="light" className="px-0" />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</MobileShell>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -9,25 +9,22 @@ export function Home() {
|
|||
return (
|
||||
<AuthShell>
|
||||
<div className="flex w-full max-w-[354px] flex-col items-center">
|
||||
<div className="w-full overflow-hidden rounded-2xl bg-white shadow-[0_8px_12px_6px_rgba(0,0,0,0.15),0_4px_4px_rgba(0,0,0,0.3)]">
|
||||
<div className="h-1.5 w-full bg-gradient-to-r from-[#34A7F2] via-[#1D66EF] to-[#ef8d1d]" aria-hidden />
|
||||
<div className="w-full overflow-hidden rounded-2xl bg-whit card-bg shadow-md">
|
||||
{/* <div className="h-1.5 w-full bg-gradient-to-r from-[#34A7F2] via-[#1D66EF] to-[#ef8d1d]" aria-hidden /> */}
|
||||
|
||||
<form onSubmit={handleJoin} className="flex flex-col gap-6 px-6 pb-8 pt-7">
|
||||
<div className="flex flex-col items-center gap-3 text-center">
|
||||
<div
|
||||
className="flex size-12 items-center justify-center rounded-full"
|
||||
style={{
|
||||
background: "linear-gradient(135deg, rgba(52,167,242,0.12) 0%, rgba(29,102,239,0.16) 100%)",
|
||||
}}
|
||||
className="flex size-12 items-center justify-center rounded-full bg-[var(--theme-button-color)]/10"
|
||||
aria-hidden
|
||||
>
|
||||
<Ticket className="size-6 text-[#1D66EF]" strokeWidth={1.75} />
|
||||
<Ticket className="size-6 text-[var(--theme-button-color)]" strokeWidth={1.75} />
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<h1 className="font-inter text-[22px] font-bold leading-tight text-[#24282f]">
|
||||
<h1 className="font-inter text-[22px] font-bold leading-tight text-[var(--theme-card-text-color)]">
|
||||
Join with your code
|
||||
</h1>
|
||||
<p className="font-inter text-sm leading-relaxed text-[rgba(36,40,47,0.72)]">
|
||||
<p className="text-sm leading-relaxed text-[var(--theme-card-text-color)]/80">
|
||||
Enter the code provided by the event organiser — you’ll usually find it in your
|
||||
invite, on the event screen, or on a poster at the venue.
|
||||
</p>
|
||||
|
|
@ -35,7 +32,7 @@ export function Home() {
|
|||
</div>
|
||||
|
||||
<label className="flex flex-col gap-2">
|
||||
<span className="font-inter text-sm font-semibold text-[#24282f]">Code</span>
|
||||
<span className="font-inter text-sm font-semibold text-[var(--theme-card-text-color)]">Code</span>
|
||||
<input
|
||||
id="join-event-code"
|
||||
type="text"
|
||||
|
|
@ -46,7 +43,7 @@ export function Home() {
|
|||
autoComplete="off"
|
||||
autoCapitalize="characters"
|
||||
spellCheck={false}
|
||||
className="h-[52px] w-full rounded-xl border border-[rgba(116,112,112,0.3)] bg-[rgba(157,157,172,0.07)] px-4 text-center font-inter text-base font-semibold uppercase tracking-[0.18em] text-[#24282f] outline-none transition-shadow focus:border-[#1D66EF]/55 focus:bg-white focus:shadow-[0_0_0_3px_rgba(29,102,239,0.12)] placeholder:font-normal placeholder:tracking-normal placeholder:text-[rgba(36,40,47,0.45)]"
|
||||
className="h-[52px] w-full rounded-xl border border-[var(--theme-card-text-color)]/10 bg-[var(--theme-card-color)]/40 px-4 text-center text-base font-semibold uppercase tracking-[0.18em] text-[var(--theme-card-text-color)] outline-none transition-all duration-300 ease-in-out focus:border-[var(--theme-button-color)] focus:outline-none focus:ring-2 focus:ring-[var(--theme-button-color)]/30 disabled:opacity-60 placeholder:font-normal placeholder:tracking-normal placeholder:text-[var(--theme-card-text-color)]/60"
|
||||
/>
|
||||
</label>
|
||||
|
||||
|
|
@ -59,7 +56,7 @@ export function Home() {
|
|||
<button
|
||||
type="submit"
|
||||
disabled={submitting || !code.trim()}
|
||||
className="w-full rounded-full border border-[rgba(214,200,200,0.6)] bg-[#ef8d1d] py-3.5 text-xs font-semibold uppercase tracking-wide text-white shadow-[0_4px_4px_rgba(0,0,0,0.15)] transition-opacity hover:opacity-95 disabled:opacity-60"
|
||||
className="w-full rounded-lg theme-btn-bg py-3.5 text-sm font-semibold uppercase tracking-wide text-white shadow-sm transition-opacity hover:opacity-95 disabled:opacity-60"
|
||||
>
|
||||
{submitting ? "Joining…" : "Join"}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -77,13 +77,36 @@ export function LeaderboardPage() {
|
|||
return (
|
||||
<MobileShell className="relative">
|
||||
<HeaderMobile logoUrl={ctx?.event?.logoUrl} eventId={eventId || null} />
|
||||
<div className="eb-gradient-auth flex flex-1 flex-col items-center px-6 pb-10 pt-6">
|
||||
<Award className="size-[68px] text-amber-300" strokeWidth={1.25} />
|
||||
<h1 className="mt-1 font-inter text-xl font-semibold text-white">Leaderboard</h1>
|
||||
<div className="eb-gradient-auth flex flex-1 flex-col items-center px-6 pb-10 pt-2">
|
||||
<div className="mt-6 w-full max-w-[354px] rounded-xl card-bg shadow-md">
|
||||
|
||||
<div className="mt-6 w-full max-w-[354px] rounded-xl bg-whit card-bg shadow-[0_8px_12px_6px_rgba(0,0,0,0.15),0_4px_4px_rgba(0,0,0,0.3)]">
|
||||
<div className="p-4">
|
||||
<div className="eb-rank-highlight flex flex-col gap-2 rounded-3xl border-4 border-white px-4 py-4 text-white shadow-[0_9px_16px_rgba(0,0,0,0.14)]">
|
||||
<div className="flex gap-2 justify-between w-full p-4">
|
||||
<div className="flex gap-2 justify-between w-full">
|
||||
<div className="flex items-center gap-2 shrink-0 w-full">
|
||||
<div className="bg-[var(--theme-button-color)]/12 rounded-full p-3">
|
||||
<Award className="size-10 text-[var(--theme-button-color)]" strokeWidth={1.25} />
|
||||
</div>
|
||||
{/* <div className="bg-[var(--theme-button-color)]/12 rounded-lg p-2 w-[80px] h-[70px] flex items-center justify-center text-center font-bold">
|
||||
<Award className="size-8 text-[var(--theme-button-color)]" strokeWidth={1.25} />
|
||||
<span className="text-3xl">{!signedIn ? "—" : loading ? "…" : yourRank != null ? yourRank : "—"}</span>
|
||||
</div> */}
|
||||
<div className="flex flex-col gap-1">
|
||||
<h1 className="mt-1 text-lg font-semibold">Your Rank</h1>
|
||||
<span className="rounded-full bg-green-100 text-green-700 px-3 py-1 text-sm inline-block w-fit">
|
||||
{!signedIn ? "Sign in" : loading ? "…" : yourTotal != null ? `${yourTotal} points` : "0 points"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="eb-rank-highligh flex shrink-0 grow-1 max-w-[150px] font-bold items-center justify-end">
|
||||
<div className="bg-[var(--theme-button-color)] rounded-full w-[65px] h-[65px] text-3xl text-[var(--theme-button-color) text-white flex items-center justify-center">
|
||||
{!signedIn ? "—" : loading ? "…" : yourRank != null ? yourRank : "—"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 pt-2 border-t border-[var(--theme-card-text-color)]/5">
|
||||
{/* <div className="eb-rank-highlight flex flex-col gap-2 rounded-3xl border-4 border-white px-4 py-4 text-white shadow-[0_9px_16px_rgba(0,0,0,0.14)]">
|
||||
<p className="text-center font-inter text-base font-semibold uppercase tracking-wide">Your rank</p>
|
||||
<div className="flex items-center justify-center gap-4">
|
||||
<span className="font-inter text-5xl font-bold drop-shadow-[0_4px_4px_rgba(0,0,0,0.25)]">
|
||||
|
|
@ -99,14 +122,14 @@ export function LeaderboardPage() {
|
|||
: "0 points"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<h2 className="mt-6 font-inter text-base font-semibold text-[#24282f]">Top participants</h2>
|
||||
</div> */}
|
||||
<h2 className="mt-6 font-inter text-base font-semibold">Leaderboard</h2>
|
||||
{err ? (
|
||||
<p className="mt-3 text-xs text-red-600">{err}</p>
|
||||
) : loading && rows.length === 0 ? (
|
||||
<p className="mt-3 text-xs text-[rgba(36,40,47,0.67)]">Loading…</p>
|
||||
<p className="mt-3 text-xs text-[var(--theme-text-color)]/80">Loading…</p>
|
||||
) : top5.length === 0 ? (
|
||||
<p className="mt-3 text-xs text-[rgba(36,40,47,0.67)]">
|
||||
<p className="mt-3 text-xs text-[var(--theme-text-color)]/80">
|
||||
No scores yet. Complete activities and earn points when organizers enable rewards.
|
||||
</p>
|
||||
) : (
|
||||
|
|
@ -116,12 +139,12 @@ export function LeaderboardPage() {
|
|||
return (
|
||||
<li
|
||||
key={row.participantId}
|
||||
className="flex items-center gap-3 border-b border-[#ededed] pb-3 last:border-0"
|
||||
className="flex items-center gap-3 border-b border-[var(--theme-card-text-color)]/5 pb-3 last:border-0"
|
||||
>
|
||||
<span className="shrink-0 rounded bg-[#ebffee] px-3 py-2 text-xs font-medium text-[#009951] shadow-sm">
|
||||
<span className="shrink-0 rounded bg-green-100 text-green-700 px-3 py-2 text-xs font-medium shadow-sm">
|
||||
#{row.rank} · {row.total} pts
|
||||
</span>
|
||||
<span className="min-w-0 flex-1 text-sm font-semibold text-[#24282f]">{row.displayName}</span>
|
||||
<span className="min-w-0 flex-1 text-sm">{row.displayName}</span>
|
||||
{medal ? <Medal tier={medal} /> : null}
|
||||
</li>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -240,9 +240,9 @@ export function LoginPage() {
|
|||
<AuthShell>
|
||||
<div className="flex w-full max-w-[354px] flex-col items-center">
|
||||
<div className="w-full rounded-xl shadow-[0_8px_12px_6px_rgba(0,0,0,0.15),0_4px_4px_rgba(0,0,0,0.3)]">
|
||||
<div className="rounded-xl bg-whit card-bg">
|
||||
<div className="rounded-xl card-bg">
|
||||
<div className="px-6 pb-2 pt-6">
|
||||
<div className="flex h-[103px] items-center justify-center rounded-xl border border-[#f2f2f border-[var(--theme-card-color)]/25 px-4">
|
||||
<div className="flex h-[103px] items-center justify-center rounded-xl border border-[var(--theme-card-color)]/25 px-4">
|
||||
{preview?.logoUrl ? (
|
||||
<img
|
||||
src={preview.logoUrl}
|
||||
|
|
@ -256,13 +256,13 @@ export function LoginPage() {
|
|||
</div>
|
||||
<form onSubmit={handleSubmit} className="flex flex-col gap-8 px-6 pb-8 pt-2">
|
||||
<div className="flex flex-col items-center gap-4 text-center">
|
||||
<p className="font-inte text-base font-semibold text-[#24282f]">Log in to</p>
|
||||
<p className="text-base font-semibold text-[var(--theme-card-text-color)]">Log in to</p>
|
||||
<div className="min-w-0 space-y-1">
|
||||
<p className="font-inte text-[28px] font-bold leading-tight text-[#24282f] sm:text-[32px]">
|
||||
<p className="text-[28px] font-bold leading-tight text-[var(--theme-card-text-color)] sm:text-[32px]">
|
||||
{headline}
|
||||
</p>
|
||||
{preview?.subtitle ? (
|
||||
<p className="font-inte text-sm font-medium text-[#24282f]/80">{preview.subtitle}</p>
|
||||
<p className="text-sm font-medium text-[var(--theme-card-text-color)]/80">{preview.subtitle}</p>
|
||||
) : null}
|
||||
</div>
|
||||
{previewError ? (
|
||||
|
|
@ -276,11 +276,11 @@ export function LoginPage() {
|
|||
</p>
|
||||
) : null}
|
||||
{showMeta ? (
|
||||
<div className="flex flex-wrap items-center justify-center gap-2 text-xs text-[#24282f]">
|
||||
<div className="flex flex-wrap items-center justify-center gap-2 text-xs text-[var(--theme-card-text-color)]">
|
||||
{schedule ? (
|
||||
<>
|
||||
<Calendar className="size-4 shrink-0" strokeWidth={1.75} />
|
||||
<span className="font-inte">{schedule}</span>
|
||||
<span className="">{schedule}</span>
|
||||
</>
|
||||
) : null}
|
||||
{schedule && preview?.location?.trim() ? (
|
||||
|
|
@ -289,7 +289,7 @@ export function LoginPage() {
|
|||
{preview?.location?.trim() ? (
|
||||
<>
|
||||
<MapPin className="size-4 shrink-0" strokeWidth={1.75} />
|
||||
<span className="font-inte">{preview.location.trim()}</span>
|
||||
<span className="">{preview.location.trim()}</span>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
|
|
@ -298,25 +298,25 @@ export function LoginPage() {
|
|||
|
||||
{showInviteField ? (
|
||||
<label className="flex flex-col gap-2">
|
||||
<span className="font-inte text-sm font-semibold text-[#24282f]">Invite code</span>
|
||||
<span className="text-sm font-semibold text-[var(--theme-card-text-color)]">Invite code</span>
|
||||
<input
|
||||
value={inviteInput}
|
||||
onChange={(e) => setInviteInput(e.target.value)}
|
||||
placeholder="Enter your invite code"
|
||||
className="h-[44px] w-full rounded-lg border border-[rgba(116,112,112,0.3)] bg-[rgba(157,157,172,0.07)] px-4 font-inter text-sm text-[#24282f] transition-colors duration-300 ease-in-out focus:border-[var(--theme-button-color)] focus:outline-none focus:ring-2 focus:ring-[var(--theme-button-color)]/30"
|
||||
className="h-[44px] w-full rounded-lg border border-[rgba(116,112,112,0.3)] bg-[rgba(157,157,172,0.07)] px-4 text-sm text-[var(--theme-card-text-color)] transition-colors duration-300 ease-in-out focus:border-[var(--theme-button-color)] focus:outline-none focus:ring-2 focus:ring-[var(--theme-button-color)]/30"
|
||||
/>
|
||||
</label>
|
||||
) : null}
|
||||
|
||||
{showEmail ? (
|
||||
<label className="flex flex-col gap-2">
|
||||
<span className="font-inte text-base font-semibold text-[#24282f]">Email address</span>
|
||||
<span className="text-base font-semibold text-[var(--theme-card-text-color)]">Email address</span>
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="Enter your email address"
|
||||
className="h-[51px] w-full rounded-lg border border-[rgba(116,112,112,0.3)] bg-[rgba(157,157,172,0.07)] px-4 font-inte text-sm tracking-wide text-[#24282f] placeholder:text-[rgba(36,40,47,0.6)] transition-colors duration-300 ease-in-out focus:border-[var(--theme-button-color)] focus:outline-none focus:ring-2 focus:ring-[var(--theme-button-color)]/30"
|
||||
className="h-[51px] w-full rounded-lg border border-[var(--theme-card-text-color)]/10 bg-[var(--theme-card-color)]/40 px-4 text-sm tracking-wide text-[var(--theme-card-text-color)] placeholder:text-[var(--theme-card-text-color)]/60 transition-all duration-300 ease-in-out focus:border-[var(--theme-button-color)] focus:outline-none focus:ring-2 focus:ring-[var(--theme-button-color)]/30 disabled:opacity-60"
|
||||
/>
|
||||
</label>
|
||||
) : null}
|
||||
|
|
@ -335,7 +335,7 @@ export function LoginPage() {
|
|||
<button
|
||||
type="submit"
|
||||
disabled={loading || loadingPreview}
|
||||
className="w-full rounded-full border border-[rgba(214,200,200,0.6)] bg-[var(--theme-button-color)] py-3 text-xs font-semibold uppercase tracking-wide text-white shadow-[0_4px_4px_rgba(0,0,0,0.15)] transition-colors duration-300 ease-in-out hover:bg-[var(--theme-button-color)]/90 disabled:opacity-60"
|
||||
className="w-full rounded-lg theme-btn-bg py-3 text-sm font-semibold uppercase tracking-wide text-white shadow-sm transition-colors duration-300 ease-in-out disabled:opacity-60"
|
||||
>
|
||||
{loading ? "Please wait…" : reg.primaryCtaLabel}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ function WhiteCard({ children, wide }: { children: ReactNode; wide?: boolean })
|
|||
return (
|
||||
<div className={`flex w-full ${wide ? "max-w-[480px]" : "max-w-[354px]"} flex-col items-center`}>
|
||||
<div className="w-full rounded-xl shadow-[0_8px_12px_6px_rgba(0,0,0,0.15),0_4px_4px_rgba(0,0,0,0.3)]">
|
||||
<div className="rounded-xl bg-white px-6 py-6">{children}</div>
|
||||
<div className="rounded-xl card-bg text-[var(--theme-card-text-color)] shadow-md px-6 py-6">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -387,18 +387,18 @@ export function PhotoSearchPage() {
|
|||
</div>
|
||||
) : null}
|
||||
|
||||
<h1 className="mb-1 text-center text-[22px] font-semibold text-[#1a1a1a]">Find Photos</h1>
|
||||
<p className="mb-5 text-center text-sm text-[#666]">
|
||||
<h1 className="mb-1 text-center text-[22px] font-semibold text-[var(--theme-card-text-color)]">Find Photos</h1>
|
||||
<p className="mb-5 text-center text-sm text-[var(--theme-card-text-color)]/80">
|
||||
Upload a selfie to find photos of you from {meta?.eventName || "the event"}.
|
||||
</p>
|
||||
|
||||
{meta?.accessMode === "login" && !loggedIn ? (
|
||||
<div className="space-y-3">
|
||||
<p className="text-center text-sm text-[#666]">Sign in to search for your photos.</p>
|
||||
<p className="text-center text-sm text-[var(--theme-card-text-color)]/80">Sign in to search for your photos.</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void goLogin()}
|
||||
className="inline-flex h-11 w-full items-center justify-center rounded-lg bg-[#D32F2F] px-4 text-sm font-semibold text-white hover:bg-[#b71c1c]"
|
||||
className="inline-flex h-11 w-full items-center justify-center rounded-lg theme-btn-bg px-4 text-sm font-semibold text-white"
|
||||
>
|
||||
Sign in to continue
|
||||
</button>
|
||||
|
|
@ -415,14 +415,14 @@ export function PhotoSearchPage() {
|
|||
onChange={(e) => onPick(e.target.files?.[0] || null)}
|
||||
/>
|
||||
{previewUrl ? (
|
||||
<div className="mb-3 overflow-hidden rounded-lg border border-[#e5e5e5] bg-[#f8f8f8]">
|
||||
<div className="mb-3 overflow-hidden rounded-lg">
|
||||
<img src={previewUrl} alt="Your selfie" className="mx-auto max-h-[240px] w-full object-contain" />
|
||||
</div>
|
||||
) : null}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => fileRef.current?.click()}
|
||||
className="inline-flex h-11 w-full items-center justify-center gap-2 rounded-lg border border-[#ddd] bg-white px-4 text-sm font-semibold text-[#333] hover:bg-[#fafafa]"
|
||||
className="inline-flex h-11 w-full items-center justify-center gap-2 rounded-lg border border-zinc-300 bg-zinc-100 px-4 text-sm font-semibold text-[var(--theme-card-text-color)]"
|
||||
>
|
||||
{file ? "Change photo" : "Choose photo"}
|
||||
</button>
|
||||
|
|
@ -432,7 +432,7 @@ export function PhotoSearchPage() {
|
|||
type="button"
|
||||
disabled={!file || searching}
|
||||
onClick={() => void onSearch()}
|
||||
className="inline-flex h-11 w-full items-center justify-center gap-2 rounded-lg bg-[#D32F2F] px-4 text-sm font-semibold text-white hover:bg-[#b71c1c] disabled:opacity-55"
|
||||
className="inline-flex h-11 w-full items-center justify-center gap-2 rounded-lg bg-[#D32F2F theme-btn-bg px-4 text-sm font-semibold text-white hover:bg-[var(--theme-button-color)]/85 disabled:opacity-55"
|
||||
>
|
||||
<Search className="h-4 w-4" />
|
||||
{searching ? "Searching…" : "Find photos"}
|
||||
|
|
|
|||
|
|
@ -59,9 +59,9 @@ export function PhotoWallPresentPage() {
|
|||
|
||||
return (
|
||||
<div className="eb-gradient-auth min-h-dvh text-white">
|
||||
<div className="px-4 pb-3 pt-4 text-center">
|
||||
<p className="text-xs uppercase tracking-[0.2em] text-white/70">Photo Wall</p>
|
||||
<h1 className="font-inter text-xl font-semibold sm:text-2xl">{data.eventName}</h1>
|
||||
<div className="px-4 pb-3 pt-4 text-center card-bg rounded-lg shadow-sm max-w-[400px] mx-auto mb-3">
|
||||
<p className="text-xs uppercase tracking-[0.2em] text-[var(--theme-card-text-color)]/70">Photo Wall</p>
|
||||
<h1 className="text-xl font-semibold sm:text-2xl text-[var(--theme-card-text-color)]">{data.eventName}</h1>
|
||||
</div>
|
||||
|
||||
{data.photos.length === 0 ? (
|
||||
|
|
@ -98,7 +98,7 @@ export function PhotoWallPresentPage() {
|
|||
/>
|
||||
{showNames && p.displayName ? (
|
||||
<p
|
||||
className="mt-1.5 w-full truncate text-center font-inter text-xs font-medium text-white/90 sm:text-sm"
|
||||
className="mt- w-full truncate text-center text-xs font-medium text-[var(--theme-card-text-color)]/90 bg-[var(--theme-card-color)] rounded-md p-1 sm:text-sm"
|
||||
title={p.displayName}
|
||||
>
|
||||
{p.displayName}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ function WhiteCard({ children }: { children: ReactNode }) {
|
|||
return (
|
||||
<div className="flex w-full max-w-[354px] flex-col items-center">
|
||||
<div className="w-full rounded-xl shadow-[0_8px_12px_6px_rgba(0,0,0,0.15),0_4px_4px_rgba(0,0,0,0.3)]">
|
||||
<div className="rounded-xl bg-white px-6 py-6">{children}</div>
|
||||
<div className="rounded-xl card-bg text-[var(--theme-card-text-color)] shadow-md px-6 py-6">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -371,7 +371,7 @@ export function PhotoWallUploadPage() {
|
|||
return (
|
||||
<AuthShell showLogo={false}>
|
||||
<WhiteCard>
|
||||
<p className="text-center text-sm text-[#666]">Loading…</p>
|
||||
<p className="text-center text-sm text-[var(--theme-card-text-color)]/70">Loading…</p>
|
||||
</WhiteCard>
|
||||
</AuthShell>
|
||||
);
|
||||
|
|
@ -398,10 +398,10 @@ export function PhotoWallUploadPage() {
|
|||
<PhotoWallEventHeader event={meta.event} display={meta.eventHeader || null} />
|
||||
</div>
|
||||
) : null}
|
||||
<h1 className="mb-2 text-center text-[22px] font-semibold text-[#1a1a1a]">
|
||||
<h1 className="mb-2 text-center text-[22px] font-semibold text-[var(--theme-card-text-color)]">
|
||||
{done.framed ? "Your framed photo" : "Photo uploaded"}
|
||||
</h1>
|
||||
<p className="mb-4 text-center text-sm text-[#666]">
|
||||
<p className="mb-4 text-center text-sm text-[var(--theme-card-text-color)]/80">
|
||||
{done.awaitingApproval
|
||||
? "Thanks! Your photo is waiting for approval."
|
||||
: done.framed
|
||||
|
|
@ -435,7 +435,7 @@ export function PhotoWallUploadPage() {
|
|||
<button
|
||||
type="button"
|
||||
onClick={resetForNewUpload}
|
||||
className="inline-flex h-11 w-full items-center justify-center gap-2 rounded-lg bg-[#D32F2F] px-4 text-sm font-semibold text-white hover:bg-[#b71c1c]"
|
||||
className="inline-flex h-11 w-full items-center justify-center gap-2 rounded-lg bg-[#D32F2F theme-btn-bg px-4 text-sm font-semibold text-white hover:bg-[#b71c1c]"
|
||||
>
|
||||
<ImagePlus className="h-4 w-4" />
|
||||
Edit / Upload new photo
|
||||
|
|
@ -453,7 +453,7 @@ export function PhotoWallUploadPage() {
|
|||
</div>
|
||||
{showShare ? (
|
||||
<div className="mt-4">
|
||||
<p className="mb-2 text-center text-xs font-semibold uppercase tracking-wide text-[#888]">
|
||||
<p className="mb-2 text-center text-xs font-semibold uppercase tracking-wide text-[var(--theme-card-text-color)]/80">
|
||||
Share
|
||||
</p>
|
||||
{useNativeShare ? (
|
||||
|
|
@ -496,18 +496,18 @@ export function PhotoWallUploadPage() {
|
|||
<PhotoWallEventHeader event={meta.event} display={meta.eventHeader || null} />
|
||||
</div>
|
||||
) : null}
|
||||
<h1 className="mb-1 text-center text-[22px] font-semibold text-[#1a1a1a]">Upload your photo</h1>
|
||||
<p className="mb-5 text-center text-sm text-[#666]">
|
||||
<h1 className="mb-1 text-center text-[22px] font-semibold text-[var(--theme-card-text-color)]">Upload your photo</h1>
|
||||
<p className="mb-5 text-center text-sm text-[var(--theme-card-text-color)]/80">
|
||||
{meta?.form?.description || "Add a selfie to the event photo wall."}
|
||||
</p>
|
||||
|
||||
{meta?.accessMode === "login" && !loggedIn ? (
|
||||
<div className="space-y-3">
|
||||
<p className="text-center text-sm text-[#666]">Sign in to upload your photo for this event.</p>
|
||||
<p className="text-center text-sm text-[var(--theme-card-text-color)]/80">Sign in to upload your photo for this event.</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void goLogin()}
|
||||
className="inline-flex h-11 w-full items-center justify-center rounded-lg bg-[#D32F2F] px-4 text-sm font-semibold text-white hover:bg-[#b71c1c]"
|
||||
className="inline-flex h-11 w-full items-center justify-center rounded-lg bg-[var(--theme-button-color)] px-4 text-sm font-semibold text-white hover:bg-[#b71c1c]"
|
||||
>
|
||||
Sign in to continue
|
||||
</button>
|
||||
|
|
@ -539,20 +539,20 @@ export function PhotoWallUploadPage() {
|
|||
)}
|
||||
</button>
|
||||
{file ? (
|
||||
<p className="mt-2 truncate text-center text-xs text-[#888]">{file.name}</p>
|
||||
<p className="mt-2 truncate text-center text-xs text-[var(--theme-card-text-color)]/80">{file.name}</p>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{meta?.askUploaderName ? (
|
||||
<label className="block text-sm">
|
||||
<span className="mb-1 block font-medium text-[#333]">
|
||||
<span className="mb-1 block font-medium text-[var(--theme-card-text-color)]">
|
||||
Your name <span className="text-red-500">*</span>
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
value={uploaderName}
|
||||
onChange={(e) => setUploaderName(e.target.value)}
|
||||
className="h-10 w-full rounded-lg border border-[#ddd] px-3 text-sm outline-none focus:border-[#D32F2F]"
|
||||
className="h-10 w-full rounded-lg border border-[rgba(116,112,112,0.3)] px-3 text-sm transition-colors duration-300 ease-in-out focus:border-[var(--theme-button-color)] focus:outline-none focus:ring-2 focus:ring-[var(--theme-button-color)]/30"
|
||||
placeholder="Name shown on the wall"
|
||||
required
|
||||
/>
|
||||
|
|
@ -566,7 +566,7 @@ export function PhotoWallUploadPage() {
|
|||
if (f.type === "textarea") {
|
||||
return (
|
||||
<label key={key} className="block text-sm">
|
||||
<span className="mb-1 block font-medium text-[#333]">
|
||||
<span className="mb-1 block font-medium text-[var(--theme-card-text-color)]">
|
||||
{f.label}
|
||||
{required ? <span className="text-red-500"> *</span> : null}
|
||||
</span>
|
||||
|
|
@ -575,14 +575,14 @@ export function PhotoWallUploadPage() {
|
|||
onChange={(e) => setValues((v) => ({ ...v, [key]: e.target.value }))}
|
||||
required={required}
|
||||
rows={3}
|
||||
className="w-full rounded-lg border border-[#ddd] px-3 py-2 text-sm outline-none focus:border-[#D32F2F]"
|
||||
className="w-full rounded-lg border border-[rgba(116,112,112,0.3)] px-3 py-2 text-sm transition-colors duration-300 ease-in-out focus:border-[var(--theme-button-color)] focus:outline-none focus:ring-2 focus:ring-[var(--theme-button-color)]/30"
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<label key={key} className="block text-sm">
|
||||
<span className="mb-1 block font-medium text-[#333]">
|
||||
<span className="mb-1 block font-medium text-[var(--theme-card-text-color)]">
|
||||
{f.label}
|
||||
{required ? <span className="text-red-500"> *</span> : null}
|
||||
</span>
|
||||
|
|
@ -591,7 +591,7 @@ export function PhotoWallUploadPage() {
|
|||
value={String(values[key] ?? "")}
|
||||
onChange={(e) => setValues((v) => ({ ...v, [key]: e.target.value }))}
|
||||
required={required}
|
||||
className="h-10 w-full rounded-lg border border-[#ddd] px-3 text-sm outline-none focus:border-[#D32F2F]"
|
||||
className="h-10 w-full rounded-lg border border-[rgba(116,112,112,0.3)] px-3 text-sm transition-colors duration-300 ease-in-out focus:border-[var(--theme-button-color)] focus:outline-none focus:ring-2 focus:ring-[var(--theme-button-color)]/30"
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
|
|
@ -602,7 +602,7 @@ export function PhotoWallUploadPage() {
|
|||
<button
|
||||
type="submit"
|
||||
disabled={!file || submitting || picking}
|
||||
className="inline-flex h-11 w-full items-center justify-center rounded-lg bg-[#D32F2F] px-4 text-sm font-semibold text-white hover:bg-[#b71c1c] disabled:opacity-50"
|
||||
className="inline-flex h-11 w-full items-center justify-center rounded-lg bg-[#D32F2F theme-btn-bg px-4 text-sm font-semibold text-white hover:bg-[#b71c1c] disabled:opacity-50"
|
||||
>
|
||||
{submitting ? "Uploading…" : "Upload"}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -103,15 +103,18 @@ export function Questions() {
|
|||
|
||||
if (!eventId) {
|
||||
return (
|
||||
<MobileShell className="relative">
|
||||
<MobileShell className="relative bg-transparent">
|
||||
<HeaderMobile eventId={eventId || null} />
|
||||
<main className="flex flex-1 flex-col items-center justify-center bg-[#f2f2f7] px-4 py-10">
|
||||
<main className="flex flex-1 flex-col items-center justify-center px-4 py-10">
|
||||
<div className="card-bg p-4 rounded-lg">
|
||||
<p className="text-center text-sm text-[#24282f]">
|
||||
Add <span className="font-mono">event_id</span> to the URL to open questions.
|
||||
</p>
|
||||
<Link to="/" className="mt-4 text-sm font-semibold text-[#1e72ef] underline">
|
||||
<Link to="/" className="mt-4 text-sm font-semibold theme-btn-link">
|
||||
Home
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</MobileShell>
|
||||
);
|
||||
|
|
@ -119,21 +122,24 @@ export function Questions() {
|
|||
|
||||
if (!sessionId) {
|
||||
return (
|
||||
<MobileShell className="relative">
|
||||
<MobileShell className="relative bg-transparent">
|
||||
<HeaderMobile logoUrl={headerLogoUrl} eventId={eventId || null} />
|
||||
<main className="relative z-0 flex flex-1 flex-col bg-[#f2f2f7] px-4 pb-28 pt-6">
|
||||
<main className="relative z-0 flex flex-1 flex-col px-4 pb-28 pt-6">
|
||||
<div className="mx-auto w-full max-w-[370px]">
|
||||
<h1 className="text-lg font-semibold text-[#24282f]">{ev?.name ?? "Event"}</h1>
|
||||
<p className="mt-3 text-sm leading-relaxed text-[rgba(36,40,47,0.75)]">
|
||||
<div className="card-bg p-4 rounded-lg">
|
||||
|
||||
<h1 className="text-lg font-semibold">{ev?.name ?? "Event"}</h1>
|
||||
<p className="mt-3 text-sm leading-relaxed text-[var(--theme-card-text-color)]/80">
|
||||
Open a session from the agenda to view all audience questions and ask your own.
|
||||
</p>
|
||||
<Link
|
||||
to={trackHref}
|
||||
className="mt-6 inline-flex items-center gap-1 text-sm font-semibold text-[#1e72ef] underline"
|
||||
className="mt-6 inline-flex items-center gap-1 text-sm font-semibold theme-btn-link"
|
||||
>
|
||||
Browse sessions
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<HyperNavigationFAB eventId={eventId} sessionId={sessionId || undefined} />
|
||||
</MobileShell>
|
||||
|
|
@ -141,17 +147,17 @@ export function Questions() {
|
|||
}
|
||||
|
||||
return (
|
||||
<MobileShell className="relative">
|
||||
<MobileShell className="relative bg-transparent">
|
||||
<HeaderMobile logoUrl={headerLogoUrl} eventId={eventId || null} />
|
||||
|
||||
<main className="relative z-0 flex flex-1 flex-col bg-[#f2f2f card-b pb-28 pt-6">
|
||||
<main className="relative z-0 flex flex-1 flex-col pb-28 pt-6">
|
||||
<div className="mx-auto w-full max-w-[370px] px-3">
|
||||
<div className="flex flex-col gap-2 card-bg p-4 rounded-md">
|
||||
<div className="flex flex-col gap-2 card-bg p-4 rounded-lg">
|
||||
|
||||
<div className="mb-1 inline-flex w-fit rounded bg-[#e5e5ea] px-2 py-0.5">
|
||||
<span className="text-[10px] tracking-wide text-[#24282f]">Audience questions</span>
|
||||
<div className="mb-1 inline-flex w-fit rounded bg-zinc-200 px-2 py-0.5">
|
||||
<span className="text-[10px] tracking-wide text-gray-600">Audience questions</span>
|
||||
</div>
|
||||
<h1 className="text-lg font-semibold text-[#2428">
|
||||
<h1 className="text-lg font-semibold">
|
||||
{sessionTitle ?? "Session questions"}
|
||||
</h1>
|
||||
{ev?.name ? (
|
||||
|
|
@ -161,9 +167,9 @@ export function Questions() {
|
|||
<button
|
||||
type="button"
|
||||
onClick={() => setAskOpen(true)}
|
||||
className="mt-4 flex h-11 w-full items-center justify-between rounded-full bg-[var(--theme-button-color)] px-[18px] text-left shadow-sm transition-colors duration-300 ease-in-out hover:bg-[var(--theme-button-color)]/90"
|
||||
className="mt-4 flex h-11 w-full items-center justify-between rounded-lg theme-btn-bg px-[18px] text-left shadow-sm transition-colors duration-300 ease-in-out hover:bg-[var(--theme-button-color)]/90"
|
||||
>
|
||||
<span className="text-xs font-semibold uppercase tracking-wide text-white">
|
||||
<span className="text-sm font-semibold uppercase tracking-wide text-white">
|
||||
Ask a question
|
||||
</span>
|
||||
<ChevronRight className="size-4 text-white" strokeWidth={2.5} />
|
||||
|
|
|
|||
|
|
@ -636,11 +636,13 @@ export function QuizPollTakePage() {
|
|||
return (
|
||||
<MobileShell className="relative">
|
||||
<HeaderMobile 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]">Invalid quiz link.</p>
|
||||
<Link to="/" className="mt-4 text-sm font-semibold text-[#1e72ef] underline">
|
||||
<main className="flex flex-1 flex-col items-center justify-center px-4 py-10">
|
||||
<div className="mx-auto w-full max-w-[370px] rounded-lg card-bg p-4 text-center shadow-sm">
|
||||
<p className="text-center text-sm">Invalid quiz link.</p>
|
||||
<Link to="/" className="mt-4 text-sm font-semibold text-[var(--theme-button-color)] underline">
|
||||
Home
|
||||
</Link>
|
||||
</div>
|
||||
</main>
|
||||
</MobileShell>
|
||||
);
|
||||
|
|
@ -650,8 +652,10 @@ export function QuizPollTakePage() {
|
|||
return (
|
||||
<MobileShell className="relative">
|
||||
<HeaderMobile logoUrl={headerLogo} eventId={eventId} />
|
||||
<main className="flex flex-1 items-center justify-center bg-[#f2f2f7] px-4 py-10">
|
||||
<p className="text-sm text-[#24282f]">Loading…</p>
|
||||
<main className="flex flex-1 items-center justify-center px-4 py-10">
|
||||
<div className="mx-auto w-full max-w-[370px] rounded-lg card-bg p-4 text-center shadow-sm">
|
||||
<p className="text-sm">Loading…</p>
|
||||
</div>
|
||||
</main>
|
||||
</MobileShell>
|
||||
);
|
||||
|
|
@ -661,11 +665,13 @@ export function QuizPollTakePage() {
|
|||
return (
|
||||
<MobileShell className="relative">
|
||||
<HeaderMobile logoUrl={headerLogo} eventId={eventId} />
|
||||
<main className="flex flex-1 flex-col items-center justify-center bg-[#f2f2f7] px-4 py-10">
|
||||
<main className="flex flex-1 flex-col items-center justify-center px-4 py-10">
|
||||
<div className="mx-auto w-full max-w-[370px] rounded-lg card-bg p-4 text-center shadow-sm">
|
||||
<p className="text-center text-sm text-red-600">{err ?? "Quiz not found."}</p>
|
||||
<Link to={sessionBackHref} className="mt-4 text-sm font-semibold text-[#1e72ef] underline">
|
||||
<Link to={sessionBackHref} className="mt-4 text-sm font-semibold text-[var(--theme-button-color)] underline">
|
||||
Back to session
|
||||
</Link>
|
||||
</div>
|
||||
</main>
|
||||
</MobileShell>
|
||||
);
|
||||
|
|
@ -676,8 +682,10 @@ export function QuizPollTakePage() {
|
|||
return (
|
||||
<MobileShell className="relative">
|
||||
<HeaderMobile logoUrl={headerLogo} eventId={eventId} />
|
||||
<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]">Returning to session…</p>
|
||||
<main className="flex flex-1 flex-col items-center justify-center px-4 py-10">
|
||||
<div className="mx-auto w-full max-w-[370px] rounded-lg card-bg p-4 text-center shadow-sm">
|
||||
<p className="text-center text-sm text-[var(--theme-card-text-color)]/80">Returning to session…</p>
|
||||
</div>
|
||||
</main>
|
||||
</MobileShell>
|
||||
);
|
||||
|
|
@ -687,15 +695,17 @@ export function QuizPollTakePage() {
|
|||
return (
|
||||
<MobileShell className="relative">
|
||||
<HeaderMobile logoUrl={headerLogo} eventId={eventId} />
|
||||
<main className="flex flex-1 flex-col items-center justify-center bg-[#f2f2f7] px-4 py-10">
|
||||
<p className="font-inter text-lg font-semibold text-[#24282f]">Thank you</p>
|
||||
<p className="mt-2 text-center text-sm text-[#24282f]">Your response was submitted.</p>
|
||||
<main className="flex flex-1 flex-col items-center justify-center px-4 py-10">
|
||||
<div className="mx-auto w-full max-w-[370px] rounded-lg card-bg p-4 text-center shadow-sm">
|
||||
<p className="font-inter text-lg font-semibold">Thank you</p>
|
||||
<p className="mt-2 text-center text-sm text-[var(--theme-card-text-color)]/80">Your response was submitted.</p>
|
||||
<Link
|
||||
to={sessionBackHref}
|
||||
className="mt-6 text-sm font-semibold text-[#1e72ef] underline"
|
||||
className="mt-6 text-sm font-semibold text[var(--theme-button-color)] underline"
|
||||
>
|
||||
Back to session
|
||||
</Link>
|
||||
</div>
|
||||
</main>
|
||||
</MobileShell>
|
||||
);
|
||||
|
|
@ -712,20 +722,20 @@ export function QuizPollTakePage() {
|
|||
<div className="flex shrink-0 flex-col">
|
||||
<HeaderMobile logoUrl={headerLogo} eventId={eventId} />
|
||||
</div>
|
||||
<main className="min-h-0 flex-1 overflow-y-auto overscroll-contain bg-[#f2f2f px-4 pb-8 pt-6">
|
||||
<main className="min-h-0 flex-1 overflow-y-auto overscroll-contain px-4 pb-8 pt-6">
|
||||
<div className="mx-auto w-full max-w-[370px]">
|
||||
<div className="rounded-lg border border-[rgba(116,112,112,0.2)] bg-whit card-bg p-5 text-center shadow-sm">
|
||||
<p className="font-inter text-lg font-semibold text-[#24282f]">Thank you</p>
|
||||
<p className="mt-1 text-sm text-[#24282f]">Your response was submitted.</p>
|
||||
<div className="rounded-lg card-bg p-5 text-center shadow-md">
|
||||
<p className="font-inter text-lg font-semibold text-[var(--theme-card-text-color)]]">Thank you</p>
|
||||
<p className="mt-1 text-sm text-[var(--theme-card-text-color)]/80">Your response was submitted.</p>
|
||||
{showRes && isQuiz && typeof scoreSubmitted === "number" ? (
|
||||
<p className="mt-3 text-sm font-semibold text-[#1e72ef]">Your score: {scoreSubmitted}</p>
|
||||
<p className="mt-3 text-sm font-semibold text-[var(--theme-button-color)]">Your score: {scoreSubmitted}</p>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{showRes && isQuiz ? (
|
||||
<div className="mt-6">
|
||||
<p className="text-[10px] font-semibold uppercase tracking-wide text-[#1e72ef]">Results</p>
|
||||
<p className="mt-0.5 text-xs text-[rgba(36,40,47,0.55)]">Questions with correct answers</p>
|
||||
<p className="text-[10px] font-semibold uppercase tracking-wide text-[var(--theme-button-color)]">Results</p>
|
||||
<p className="mt-0.5 text-xs text-[var(--theme-card-text-color)]/80">Questions with correct answers</p>
|
||||
<ul className="mt-4 flex flex-col gap-4">
|
||||
{quiz.questions.map((q, qi) => {
|
||||
const raw = answers[q._id];
|
||||
|
|
@ -734,13 +744,13 @@ export function QuizPollTakePage() {
|
|||
return (
|
||||
<li
|
||||
key={q._id}
|
||||
className="rounded-lg border border-[rgba(116,112,112,0.2)] bg-white p-4 shadow-sm"
|
||||
className="rounded-lg border border-[var(--theme-card-text-color)]/15 card-bg p-4 shadow-sm"
|
||||
>
|
||||
<p className="text-sm font-semibold text-[#24282f]">
|
||||
<span className="text-[#8e8e93]">{qi + 1}. </span>
|
||||
<p className="text-sm font-semibold text-[var(--theme-card-text-color)]">
|
||||
<span className="text-[var(--theme-card-text-color)]">{qi + 1}. </span>
|
||||
{q.question}
|
||||
</p>
|
||||
<p className="mt-2 text-[10px] font-medium uppercase tracking-wide text-[#8e8e93]">
|
||||
<p className="mt-2 text-xs font-medium uppercase tracking-wide text-[var(--theme-card-text-color)]/80">
|
||||
Your answer
|
||||
</p>
|
||||
<p
|
||||
|
|
@ -749,7 +759,7 @@ export function QuizPollTakePage() {
|
|||
? "text-green-700"
|
||||
: graded === false
|
||||
? "text-red-600"
|
||||
: "text-[#24282f]"
|
||||
: "text-[var(--theme-card-text-color)]"
|
||||
}`}
|
||||
>
|
||||
{formatUserAnswerLine(q, raw)}
|
||||
|
|
@ -757,17 +767,17 @@ export function QuizPollTakePage() {
|
|||
</p>
|
||||
{corr ? (
|
||||
<>
|
||||
<p className="mt-2 text-[10px] font-medium uppercase tracking-wide text-[#8e8e93]">
|
||||
<p className="mt-2 text-[10px] font-medium uppercase tracking-wide text-[var(--theme-card-text-color)]/80">
|
||||
Correct answer
|
||||
</p>
|
||||
<p className="text-sm font-semibold text-green-800">{corr}</p>
|
||||
</>
|
||||
) : q.type === "text" ? (
|
||||
<p className="mt-2 text-xs text-[rgba(36,40,47,0.55)]">
|
||||
<p className="mt-2 text-xs text-[var(--theme-card-text-color)]/60">
|
||||
Open-ended — no single correct answer.
|
||||
</p>
|
||||
) : q.type === "rating" ? (
|
||||
<p className="mt-2 text-xs text-[rgba(36,40,47,0.55)]">
|
||||
<p className="mt-2 text-xs text-[var(--theme-card-text-color)]/60">
|
||||
Rating scale — no correct answer key.
|
||||
</p>
|
||||
) : null}
|
||||
|
|
@ -780,16 +790,16 @@ export function QuizPollTakePage() {
|
|||
|
||||
{showRes && isPollOrSurvey ? (
|
||||
<div className="mt-6">
|
||||
<p className="text-[10px] font-semibold uppercase tracking-wide text-[#1e72ef]">Results</p>
|
||||
<p className="mt-0.5 text-xs text-[rgba(36,40,47,0.55)]">Share of responses per option</p>
|
||||
<p className="text-[10px] font-semibold uppercase tracking-wide text-[var(--theme-button-color)]">Results</p>
|
||||
<p className="mt-0.5 text-xs text-[var(--theme-card-text-color)]">Share of responses per option</p>
|
||||
{pollResultsLoading ? (
|
||||
<p className="mt-6 text-center text-sm text-[#8e8e93]">Loading results…</p>
|
||||
<p className="mt-6 text-center text-sm text-[var(--theme-card-text-color)]/80">Loading results…</p>
|
||||
) : null}
|
||||
{pollResultsErr ? (
|
||||
<p className="mt-4 text-center text-sm text-red-600">{pollResultsErr}</p>
|
||||
) : null}
|
||||
{pollResults && !pollResultsLoading ? (
|
||||
<p className="mt-3 text-xs text-[rgba(36,40,47,0.65)]">
|
||||
<p className="mt-3 text-xs text-[var(--theme-card-text-color)]/60">
|
||||
Based on {pollResults.totalSubmissions} submission
|
||||
{pollResults.totalSubmissions === 1 ? "" : "s"}
|
||||
</p>
|
||||
|
|
@ -799,11 +809,11 @@ export function QuizPollTakePage() {
|
|||
{pollResults.questions.map((pq) => (
|
||||
<li
|
||||
key={`${pq.questionIndex}-${pq.questionId}`}
|
||||
className="rounded-lg border border-[rgba(116,112,112,0.2)] bg-white p-4 shadow-sm"
|
||||
className="rounded-lg border border-[var(--theme-card-text-color)]/15 card-bg p-4 shadow-sm"
|
||||
>
|
||||
<p className="text-sm font-semibold text-[#24282f]">{pq.question}</p>
|
||||
<p className="text-sm font-semibold text-[var(--theme-card-text-color)]">{pq.question}</p>
|
||||
{pq.kind === "text" ? (
|
||||
<p className="mt-2 text-sm text-[rgba(36,40,47,0.75)]">
|
||||
<p className="mt-2 text-sm text-[var(--theme-card-text-color)]/80">
|
||||
{(pq.answeredCount ?? 0) === 0
|
||||
? "No text answers yet."
|
||||
: `${pq.answeredCount} of ${pq.totalResponses} answered (${pq.percentAnswered ?? 0}%)`}
|
||||
|
|
@ -812,15 +822,15 @@ export function QuizPollTakePage() {
|
|||
<div className="mt-3 flex flex-col gap-3">
|
||||
{(pq.options || []).map((opt) => (
|
||||
<div key={opt.optionId}>
|
||||
<div className="mb-1 flex justify-between gap-2 text-xs text-[#24282f]">
|
||||
<div className="mb-1 flex justify-between gap-2 text-xs text-[var(--theme-card-text-color)]">
|
||||
<span className="min-w-0 flex-1 leading-snug">{opt.text || "—"}</span>
|
||||
<span className="shrink-0 tabular-nums font-semibold text-[#1e72ef]">
|
||||
<span className="shrink-0 tabular-nums font-semibold text-[var(--theme-button-color)]">
|
||||
{opt.percent}%
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-2 overflow-hidden rounded-full bg-[#e5e5ea]">
|
||||
<div className="h-2 overflow-hidden rounded-full bg-[var(--theme-button-color)]/10">
|
||||
<div
|
||||
className="h-full rounded-full bg-[#1e72ef]"
|
||||
className="h-full rounded-full bg-[var(--theme-button-color)]"
|
||||
style={{ width: `${Math.min(100, Math.max(0, opt.percent))}%` }}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -837,7 +847,7 @@ export function QuizPollTakePage() {
|
|||
|
||||
<Link
|
||||
to={sessionBackHref}
|
||||
className="mt-8 block text-center text-sm font-semibold text-[#1e72ef] underline"
|
||||
className="mt-8 block text-center text-sm font-semibold text-[var(--theme-text-color)] underline"
|
||||
>
|
||||
Back to session
|
||||
</Link>
|
||||
|
|
@ -853,8 +863,10 @@ export function QuizPollTakePage() {
|
|||
<div className="flex shrink-0 flex-col">
|
||||
<HeaderMobile logoUrl={headerLogo} eventId={eventId} />
|
||||
</div>
|
||||
<main className="flex flex-1 items-center justify-center bg-[#f2f2f7] px-4 py-10">
|
||||
<p className="text-sm text-[#24282f]">Loading…</p>
|
||||
<main className="flex flex-1 items-center justify-center px-4 py-10">
|
||||
<div className="mx-auto w-full max-w-[370px] rounded-lg card-bg p-4 text-center shadow-sm">
|
||||
<p className="text-sm">Loading…</p>
|
||||
</div>
|
||||
</main>
|
||||
</MobileShell>
|
||||
);
|
||||
|
|
@ -868,14 +880,16 @@ export function QuizPollTakePage() {
|
|||
<div className="flex shrink-0 flex-col">
|
||||
<HeaderMobile logoUrl={headerLogo} eventId={eventId} />
|
||||
</div>
|
||||
<main className="flex flex-1 flex-col items-center justify-center bg-[#f2f2f7] px-4 py-10">
|
||||
<div className="mx-auto w-full max-w-[370px] rounded-lg border border-[rgba(116,112,112,0.2)] bg-white p-5 text-center shadow-sm">
|
||||
<p className="font-inter text-lg font-semibold text-[#24282f]">Answer submitted</p>
|
||||
<p className="mt-2 text-sm text-[rgba(36,40,47,0.75)]">
|
||||
<main className="flex flex-1 flex-col items-center justify-center px-4 py-10">
|
||||
<div className="mx-auto w-full max-w-[370px] rounded-lg border text-center shadow-sm">
|
||||
<div className="card-bg rounded-lg p-4">
|
||||
<p className="font-inter text-lg font-semibold">Answer submitted</p>
|
||||
<p className="mt-2 text-sm text-[var(--theme-card-text-color)]/80">
|
||||
You have already submitted this question. Please wait for the host to show the next one.
|
||||
</p>
|
||||
</div>
|
||||
<Link to={sessionBackHref} className="mt-6 text-sm font-semibold text-[#1e72ef] underline">
|
||||
</div>
|
||||
<Link to={sessionBackHref} className="mt-6 text-sm font-semibold text-[var(--theme-button-color)] underline">
|
||||
Back to session
|
||||
</Link>
|
||||
</main>
|
||||
|
|
@ -889,14 +903,14 @@ export function QuizPollTakePage() {
|
|||
<div className="flex shrink-0 flex-col">
|
||||
<HeaderMobile logoUrl={headerLogo} eventId={eventId} />
|
||||
</div>
|
||||
<main className="flex flex-1 flex-col items-center justify-center bg-[#f2f2f7] px-4 py-10">
|
||||
<div className="mx-auto w-full max-w-[370px] rounded-lg border border-[rgba(116,112,112,0.2)] bg-white p-5 text-center shadow-sm">
|
||||
<p className="font-inter text-lg font-semibold text-[#24282f]">{quiz.title}</p>
|
||||
<p className="mt-2 text-sm text-[rgba(36,40,47,0.75)]">
|
||||
<main className="flex flex-1 flex-col items-center justify-center px-4 py-10">
|
||||
<div className="mx-auto w-full max-w-[370px] rounded-lg card-bg p-5 text-center shadow-sm">
|
||||
<p className="font-inter text-lg font-semibold">{quiz.title}</p>
|
||||
<p className="mt-2 text-sm text-[var(--theme-card-text-color)]/80">
|
||||
The host will start the next question soon.
|
||||
</p>
|
||||
</div>
|
||||
<Link to={sessionBackHref} className="mt-6 text-sm font-semibold text-[#1e72ef] underline">
|
||||
<Link to={sessionBackHref} className="mt-6 text-sm font-semibold text-[var(--theme-button-color)] underline">
|
||||
Back to session
|
||||
</Link>
|
||||
</main>
|
||||
|
|
@ -906,7 +920,7 @@ export function QuizPollTakePage() {
|
|||
|
||||
const footerBtnClass =
|
||||
"rounded-lg px-3 py-1.5 text-center text-xs font-semibold shadow-sm disabled:opacity-50 disabled:hover:bg-inherit";
|
||||
const footerSecondaryClass = `${footerBtnClass} flex-1 border border-[#e5e5ea] bg-white/40 text-[#24282f] hover:bg-white/40`;
|
||||
const footerSecondaryClass = `${footerBtnClass} flex-1 border border-zinc-300 bg-zinc-100 text-gray-700`;
|
||||
|
||||
return (
|
||||
<MobileShell className="relative h-dvh min-h-0 flex-col">
|
||||
|
|
@ -915,18 +929,18 @@ export function QuizPollTakePage() {
|
|||
</div>
|
||||
<main
|
||||
ref={mainScrollRef}
|
||||
className="relative z-0 min-h-0 flex-1 overflow-y-auto overscroll-contain bg-[#f2f2f7 px-4 pt-6"
|
||||
className="relative z-0 min-h-0 flex-1 overflow-y-auto overscroll-contain px-4 pt-6"
|
||||
>
|
||||
<div className="mx-auto w-full max-w-[370px] pb-4">
|
||||
<div className="flex flex-col gap-1 card-bg p-4 rounded-lg border border-[rgba(116,112,112,0.2)] shadow-sm">
|
||||
<div className="flex flex-col gap-1 card-bg p-4 rounded-lg shadow-sm">
|
||||
<p className="text-[10px] font-semibold uppercase tracking-wide text-[var(--theme-button-color)]">{typeHeading(quiz.type)}</p>
|
||||
<h1 className="mt-1 font-inte text-xl font-semibold text-[#24282f]">{quiz.title}</h1>
|
||||
<h1 className="mt-1 font-inte text-xl font-semibold">{quiz.title}</h1>
|
||||
{isQuizMasterMode && liveQuestion ? (
|
||||
<p className="mt-2 text-xs font-medium tabular-nums text-[rgba(36,40,47,0.55)]">
|
||||
<p className="mt-2 text-xs font-medium tabular-nums text-[var(--theme-card-text-color)]/80">
|
||||
Question {liveQuestionIndex + 1} of {totalQuestions}
|
||||
</p>
|
||||
) : multiQuestion ? (
|
||||
<p className="mt-2 text-xs font-medium tabular-nums text-[rgba(36,40,47,0.55)]">
|
||||
<p className="mt-2 text-xs font-medium tabular-nums text-[var(--theme-card-text-color)]/80">
|
||||
Question {stepSafe + 1} of {totalQuestions}
|
||||
</p>
|
||||
) : null}
|
||||
|
|
@ -936,15 +950,15 @@ export function QuizPollTakePage() {
|
|||
{timerActive && secondsLeft != null ? (
|
||||
<div
|
||||
className={`mt-4 flex items-center justify-between rounded-lg border px-3 py-2.5 ${
|
||||
secondsLeft <= 30 ? "border-amber-400 bg-amber-50" : "border-[#e5e5ea] bg-white"
|
||||
secondsLeft <= 30 ? "border-amber-400 bg-amber-50" : "border-zinc-300 bg-zinc-100"
|
||||
}`}
|
||||
>
|
||||
<span className="text-xs font-medium text-[#24282f]">
|
||||
<span className="text-xs font-medium text-[var(--theme-card-text-color)]">
|
||||
{perQuestionTimer && isQuizMasterMode ? "Time left for this question" : "Time left"}
|
||||
</span>
|
||||
<span
|
||||
className={`font-mono text-lg font-bold tabular-nums ${
|
||||
secondsLeft <= 30 ? "text-amber-800" : "text-[#24282f]"
|
||||
secondsLeft <= 30 ? "text-amber-800" : "text-[var(--theme-card-text-color)]"
|
||||
}`}
|
||||
>
|
||||
{formatCountdown(secondsLeft)}
|
||||
|
|
@ -961,9 +975,9 @@ export function QuizPollTakePage() {
|
|||
).map((q, offset) => {
|
||||
const qi = isQuizMasterMode ? liveQuestionIndex : multiQuestion ? stepSafe : offset;
|
||||
return (
|
||||
<li key={q._id} className="rounded-lg border border-[rgba(116,112,112,0.2)] bg-whit card-bg p-4 shadow-sm">
|
||||
<p className="text-sm font-semibold text-[#24282f]">
|
||||
<span className="text-[#8e8e93]">{qi + 1}. </span>
|
||||
<li key={q._id} className="rounded-lg card-bg p-4 shadow-sm">
|
||||
<p className="text-sm font-semibold text-[var(--theme-card-text-color)]">
|
||||
<span className="text-[var(--theme-card-text-color)]">{qi + 1}. </span>
|
||||
{q.question}
|
||||
</p>
|
||||
<QuestionMedia url={q.imageUrl} alt="" />
|
||||
|
|
@ -997,7 +1011,7 @@ export function QuizPollTakePage() {
|
|||
value={typeof answers[q._id] === "string" ? answers[q._id] : ""}
|
||||
onChange={(e) => setText(q, e.target.value)}
|
||||
rows={3}
|
||||
className="mt-3 w-full rounded-md border border-[#e5e5ea] px-3 py-2 text-sm"
|
||||
className="mt-3 w-full rounded-md border border-[#e5e5ea] px-3 py-2 text-sm transition-all duration-300 ease-in-out focus:border-[var(--theme-button-color)] focus:outline-none focus:ring-2 focus:ring-[var(--theme-button-color)]/30 disabled:opacity-60"
|
||||
placeholder="Your answer"
|
||||
/>
|
||||
) : q.type === "rating" ? (
|
||||
|
|
@ -1009,7 +1023,7 @@ export function QuizPollTakePage() {
|
|||
onClick={() => setSingle(q, String(n))}
|
||||
className={`size-10 rounded-full border text-sm font-semibold ${
|
||||
answers[q._id] === String(n)
|
||||
? "border-[#1e72ef] bg-[#1e72ef] text-white"
|
||||
? "border-[var(--theme-button-color)] bg-[var(--theme-button-color] text-white"
|
||||
: "border-[#e5e5ea] bg-white text-[#24282f]"
|
||||
}`}
|
||||
>
|
||||
|
|
@ -1047,20 +1061,20 @@ export function QuizPollTakePage() {
|
|||
</ul>
|
||||
|
||||
{!signedIn ? (
|
||||
<p className="mt-8 text-center text-xs text-[rgba(36,40,47,0.65)]">
|
||||
<p className="mt-8 text-center text-xs text-[var(--theme-card-text-color)]/80">
|
||||
{multiQuestion ? (
|
||||
<>
|
||||
Use <span className="font-semibold text-[#24282f]">Next</span> or{" "}
|
||||
<span className="font-semibold text-[#24282f]">Submit</span> below after{" "}
|
||||
<Link className="font-semibold text-[#1e72ef] underline" to={visitorEventUrl(eventId)}>
|
||||
Use <span className="font-semibold">Next</span> or{" "}
|
||||
<span className="font-semibold">Submit</span> below after{" "}
|
||||
<Link className="font-semibold theme-btn-link" to={visitorEventUrl(eventId)}>
|
||||
signing in
|
||||
</Link>
|
||||
.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Tap <span className="font-semibold text-[#24282f]">Submit</span> below after{" "}
|
||||
<Link className="font-semibold text-[#1e72ef] underline" to={visitorEventUrl(eventId)}>
|
||||
Tap <span className="font-semibold">Submit</span> below after{" "}
|
||||
<Link className="font-semibold theme-btn-link" to={visitorEventUrl(eventId)}>
|
||||
signing in
|
||||
</Link>
|
||||
.
|
||||
|
|
@ -1080,7 +1094,7 @@ export function QuizPollTakePage() {
|
|||
</main>
|
||||
|
||||
<footer
|
||||
className="z-40 shrink-0 border-t border-[var(--theme-card-color)]/25 bg-whit card-bg shadow-[0_-4px_12px_rgba(0,0,0,0.06)]"
|
||||
className="z-40 shrink-0 border-t border-[var(--theme-card-color)]/25 card-bg shadow-md"
|
||||
style={{ paddingBottom: "max(12px, env(safe-area-inset-bottom))", paddingTop: 12, paddingLeft: 16, paddingRight: 16 }}
|
||||
>
|
||||
<div className="mx-auto flex w-full max-w-[370px] gap-2">
|
||||
|
|
|
|||
|
|
@ -98,14 +98,16 @@ export function RatingsPage() {
|
|||
return (
|
||||
<MobileShell className="relative">
|
||||
<HeaderMobile logoUrl={ctx?.event?.logoUrl} eventId={null} />
|
||||
<main className="flex flex-1 flex-col bg-[#f2f2f7] px-4 pb-10 pt-6">
|
||||
<main className="flex flex-1 flex-col px-4 pb-10 pt-6">
|
||||
<div className="mx-auto w-full max-w-[370px]">
|
||||
<h1 className="font-inter text-xl font-semibold text-[#24282f]">Ratings</h1>
|
||||
<p className="mt-3 text-sm text-[rgba(36,40,47,0.67)]">Open ratings from your event or session link.</p>
|
||||
<Link to="/" className="mt-4 inline-block text-sm font-semibold text-[#1e72ef] underline">
|
||||
<div className="card-bg rounded-lg shadow-md p-4">
|
||||
<h1 className="text-xl font-semibold text-[var(--theme-card-text-color)]">Ratings</h1>
|
||||
<p className="mt-3 text-sm text-[var(--theme-card-text-color)]/80">Open ratings from your event or session link.</p>
|
||||
<Link to="/" className="mt-4 inline-block text-sm font-semibold theme-btn-link">
|
||||
Home
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</MobileShell>
|
||||
);
|
||||
|
|
@ -115,14 +117,16 @@ export function RatingsPage() {
|
|||
return (
|
||||
<MobileShell className="relative">
|
||||
<HeaderMobile logoUrl={eventSnapshot?.logoUrl} eventId={eventId || null} />
|
||||
<main className="flex flex-1 flex-col bg-[#f2f2f7] px-4 pb-10 pt-6">
|
||||
<main className="flex flex-1 flex-col px-4 pb-10 pt-6">
|
||||
<div className="mx-auto w-full max-w-[370px]">
|
||||
<h1 className="font-inter text-xl font-semibold text-[#24282f]">Ratings</h1>
|
||||
<p className="mt-3 text-sm text-[rgba(36,40,47,0.67)]">Ratings are disabled for this event.</p>
|
||||
<Link to="/event" className="mt-4 inline-block text-sm font-semibold text-[#1e72ef] underline">
|
||||
<div className="card-bg rounded-lg shadow-md p-4">
|
||||
<h1 className="text-xl font-semibold text-[var(--theme-card-text-color)]">Ratings</h1>
|
||||
<p className="mt-3 text-sm text-[var(--theme-card-text-color)]/80">Ratings are disabled for this event.</p>
|
||||
<Link to="/event" className="mt-4 inline-block text-sm font-semibold theme-btn-link">
|
||||
Back to event
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</MobileShell>
|
||||
);
|
||||
|
|
@ -131,27 +135,29 @@ export function RatingsPage() {
|
|||
return (
|
||||
<MobileShell className="relative">
|
||||
<HeaderMobile logoUrl={eventSnapshot?.logoUrl} eventId={eventId || null} />
|
||||
<main className="flex flex-1 flex-col bg-[#f2f2f7 bg-transparent px-4 pb-10 pt-6">
|
||||
<main className="flex flex-1 flex-col px-4 pb-10 pt-6">
|
||||
<div className="mx-auto w-full max-w-[370px]">
|
||||
<h1 className="font-inter text-xl font-semibold text-[var(--theme-text-color)]">{schema?.name || "Ratings"}</h1>
|
||||
<div className="card-bg rounded-lg shadow-md p-4">
|
||||
<h1 className="text-xl font-semibold text-[var(--theme-card-text-color)]">{schema?.name || "Ratings"}</h1>
|
||||
{schemaLoading ? (
|
||||
<p className="mt-4 text-sm text-[rgba(36,40,47,0.67)]">Loading form…</p>
|
||||
<p className="mt-4 text-sm text-[var(--theme-card-text-color)]/80">Loading form…</p>
|
||||
) : schemaErr ? (
|
||||
<div className="mt-4">
|
||||
<p className="text-sm text-red-600">{schemaErr}</p>
|
||||
<p className="mt-2 text-sm text-[rgba(36,40,47,0.67)]">
|
||||
<p className="mt-2 text-sm text-[var(--theme-card-text-color)]/80">
|
||||
Ask the organizer to publish an active ratings form for this event.
|
||||
</p>
|
||||
<Link to="/event" className="mt-4 inline-block text-sm font-semibold text-[#1e72ef] underline">
|
||||
<Link to="/event" className="mt-4 inline-block text-sm font-semibold theme-btn-link">
|
||||
Back to event
|
||||
</Link>
|
||||
</div>
|
||||
) : schema ? (
|
||||
<div className="mt-6">
|
||||
<DynamicFeedbackForm eventId={eventId} schema={schema} variant="light" />
|
||||
<div className="mt-2">
|
||||
<DynamicFeedbackForm eventId={eventId} schema={schema} variant="light" className="px-0" />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</MobileShell>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -70,8 +70,8 @@ export function RewardsPage() {
|
|||
return (
|
||||
<MobileShell className="relative">
|
||||
<HeaderMobile logoUrl={ctx?.event?.logoUrl} eventId={eventId || null} />
|
||||
<div className="eb-gradient-auth flex flex-1 flex-col items-center px-6 pb-10 pt-6">
|
||||
<div className="flex flex-col w-full max-w-[354px] items-center card-bg p-4 rounded-md">
|
||||
<div className="eb-gradient-auth flex flex-1 flex-col items-center px-6 pb-10 pt-2">
|
||||
{/* <div className="flex flex-col w-full max-w-[354px] items-center card-bg p-4 rounded-md">
|
||||
<Trophy className="size-[68px] text-amber-300" strokeWidth={1.25} />
|
||||
<h1 className="mt-1 font-inter text-xl font-semibold text-white">Your Rewards</h1>
|
||||
<Link
|
||||
|
|
@ -80,23 +80,46 @@ export function RewardsPage() {
|
|||
>
|
||||
Leaderboard
|
||||
</Link>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div className="mt-6 w-full max-w-[354px] rounded-xl bg-white shadow-[0_8px_12px_6px_rgba(0,0,0,0.15),0_4px_4px_rgba(0,0,0,0.3)]">
|
||||
<div className="p-4">
|
||||
<div className="eb-rank-highlight flex flex-col items-center justify-center gap-2 rounded-3xl border-4 border-white py-4 text-white shadow-[0_9px_16px_rgba(0,0,0,0.14)]">
|
||||
<p className="font-inter text-base font-semibold uppercase tracking-wide">Total Points</p>
|
||||
<p className="font-inter text-5xl font-bold tabular-nums drop-shadow-[0_4px_4px_rgba(0,0,0,0.25)]">
|
||||
{displayTotal}
|
||||
</p>
|
||||
<div className="mt-6 w-full max-w-[354px] rounded-xl bg-whit card-bg shadow-md">
|
||||
<div className="flex gap-2 justify-between w-full p-4">
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
<div className="bg-[var(--theme-button-color)]/12 rounded-full p-3">
|
||||
<Trophy className="size-10 text-[var(--theme-button-color)]" strokeWidth={1.25} />
|
||||
</div>
|
||||
<h2 className="mt-6 font-inter text-base font-semibold text-[#24282f]">Points history</h2>
|
||||
<h1 className="mt-1 text-lg font-semibold text-[var(--theme-card-text-color)]">Your Points</h1>
|
||||
</div>
|
||||
<div className="eb-rank-highligh flex shrink-0 grow-1 max-w-[150px] font-bold items-center justify-end">
|
||||
<div className="bg-[var(--theme-button-color)] rounded-full w-[65px] h-[65px] text-3xl text-[var(--theme-button-color) text-white flex items-center justify-center">
|
||||
{displayTotal}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-center px-4">
|
||||
|
||||
{/* <Link
|
||||
to={eventId ? `/leaderboard?event_id=${encodeURIComponent(eventId)}` : "/leaderboard"}
|
||||
className="w-full rounded-ful rounded-lg border border-[rgba(214,200,200,0.6)] bg-[var(--theme-button-color)] transition-colors duration-300 ease-in-out hover:bg-[var(--theme-button-color)]/90 py-3 text-center text-xs font-semibold uppercase tracking-wide text-white shadow-[0_4px_4px_rgba(0,0,0,0.15)]"
|
||||
>
|
||||
Leaderboard
|
||||
</Link> */}
|
||||
<Link
|
||||
to={eventId ? `/leaderboard?event_id=${encodeURIComponent(eventId)}` : "/leaderboard"}
|
||||
className="w-full rounded-ful rounded-lg border border-[var(--theme-button-color)]/25 bg-[var(--theme-button-color)]/20 text-[var(--theme-button-color)] py-3 text-center text-sm font-semibold uppercase tracking-wide shadow-sm"
|
||||
>
|
||||
Leaderboard
|
||||
</Link>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
|
||||
<h2 className="mt-2 font-inter text-base font-semibold text-[var(--theme-card-text-color)]/80">Points history</h2>
|
||||
<ul className="mt-3 flex flex-col gap-3">
|
||||
{historyLoading && (
|
||||
<li className="text-xs text-[rgba(36,40,47,0.67)]">Loading…</li>
|
||||
<li className="text-xs text-[var(--theme-card-text-color)]/70">Loading…</li>
|
||||
)}
|
||||
{!historyLoading && historyItems.length === 0 && (
|
||||
<li className="text-xs text-[rgba(36,40,47,0.67)]">No transactions yet.</li>
|
||||
<li className="text-xs text-[var(--theme-card-text-color)]/70">No transactions yet.</li>
|
||||
)}
|
||||
{!historyLoading &&
|
||||
historyItems.map((row) => {
|
||||
|
|
@ -110,13 +133,13 @@ export function RewardsPage() {
|
|||
return (
|
||||
<li
|
||||
key={row._id}
|
||||
className="flex gap-4 border-b border-[#ededed] pb-3 last:border-0"
|
||||
className="flex items-center gap-4 border-b border-[var(--theme-card-text-color)]/5 pb-3 last:border-0"
|
||||
>
|
||||
<span className={`shrink-0 rounded px-3 py-2 text-xs font-medium shadow-sm ${cls}`}>
|
||||
{signed}
|
||||
{row.points} pts
|
||||
</span>
|
||||
<span className="text-xs leading-snug text-[rgba(36,40,47,0.67)]">
|
||||
<span className="text-sm leading-snug text-[var(--theme-card-text-color)]/70">
|
||||
{historyLabel(row.source, row.description)}
|
||||
</span>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -363,13 +363,13 @@ export function SingleEventPage() {
|
|||
|
||||
if (!eventId || !sessionId) {
|
||||
return (
|
||||
<MobileShell className="relative">
|
||||
<MobileShell className="relative bg-transparent">
|
||||
<HeaderMobile
|
||||
eventName={headerEventNameForEvent(eventId, null, ctx)}
|
||||
eventId={eventId || null}
|
||||
showBack={false}
|
||||
/>
|
||||
<main className="flex flex-1 flex-col items-center justify-center bg-[#f2f2f7] px-4 py-10">
|
||||
<main className="flex flex-1 flex-col items-center justify-center 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
|
||||
|
|
@ -381,14 +381,14 @@ export function SingleEventPage() {
|
|||
|
||||
if (loading) {
|
||||
return (
|
||||
<MobileShell className="relative">
|
||||
<MobileShell className="relative bg-transparent">
|
||||
<HeaderMobile
|
||||
logoUrl={headerLogoForEvent(eventId, null, ctx)}
|
||||
eventName={headerEventNameForEvent(eventId, null, ctx)}
|
||||
eventId={eventId || null}
|
||||
showBack={false}
|
||||
/>
|
||||
<main className="flex flex-1 items-center justify-center bg-[#f2f2f7] px-4 py-10">
|
||||
<main className="flex flex-1 items-center justify-center px-4 py-10">
|
||||
<p className="text-sm text-[#24282f]">Loading session…</p>
|
||||
</main>
|
||||
</MobileShell>
|
||||
|
|
@ -397,14 +397,14 @@ export function SingleEventPage() {
|
|||
|
||||
if (err || !session) {
|
||||
return (
|
||||
<MobileShell className="relative">
|
||||
<MobileShell className="relative bg-transparent">
|
||||
<HeaderMobile
|
||||
logoUrl={headerLogoForEvent(eventId, eventInfo, ctx)}
|
||||
eventName={headerEventNameForEvent(eventId, eventInfo, ctx)}
|
||||
eventId={eventId || null}
|
||||
showBack={false}
|
||||
/>
|
||||
<main className="flex flex-1 flex-col items-center justify-center bg-[#f2f2f7] px-4 py-10">
|
||||
<main className="flex flex-1 flex-col items-center justify-center px-4 py-10">
|
||||
<p className="text-center text-sm text-red-600">{err ?? "Session not found."}</p>
|
||||
</main>
|
||||
</MobileShell>
|
||||
|
|
@ -469,7 +469,7 @@ export function SingleEventPage() {
|
|||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="eb-gradient-banne card-bg relative flex flex-col items-cente justify-center rounded-b-lg gap-1.5 px-6 pb-4 pt-3 text-cente text-whit text-[var(--theme-card-text-color)]">
|
||||
<div className="card-bg relative flex flex-col justify-center rounded-b-lg shadow-md gap-1.5 px-4 pb-4 pt-3 text-[var(--theme-card-text-color)]">
|
||||
<div className="absolute right-3 top-2.5 z-10 flex max-w-[calc(100%-4.5rem)] flex-col items-end gap-1.5">
|
||||
{live ? (
|
||||
<span className="inline-flex shrink-0 items-center gap-1 rounded-full bg-[#14ae5c] px-2 py-0.5 text-[10px] font-semibold text-white">
|
||||
|
|
@ -480,37 +480,40 @@ export function SingleEventPage() {
|
|||
<button
|
||||
type="button"
|
||||
onClick={() => toggleBookmark(sessionId)}
|
||||
className="shrink-0 rounded-md p-1 text-[var(--theme-button-color)] hover:bg-white/15"
|
||||
className="shrink-0 rounded-md p-1 text-[var(--theme-button-color)]"
|
||||
aria-label={sessionBookmarked ? "Remove bookmark" : "Bookmark session"}
|
||||
aria-pressed={sessionBookmarked}
|
||||
>
|
||||
<Bookmark className={`size-6 ${sessionBookmarked ? "fill-current" : ""}`} strokeWidth={1.75} />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex max-w-full flex-wrap items-center justify-cente gap-x-2 gap-y-1 pe-2 text-[10px]">
|
||||
<div className="flex max-w-full flex-wrap items-center gap-x-2 gap-y-1 pe-2 text-xs">
|
||||
<span className="rounded bg-[var(--theme-button-color)]/20 px-2 py-0.5">{typeLabel}</span>
|
||||
{timeLine ? <span>{timeLine}</span> : null}
|
||||
{trackTitle !== typeLabel ? <span className="opacity-90">{trackTitle}</span> : null}
|
||||
</div>
|
||||
<h1 className="font-inter text-2xl font-bold tracking-tight">{session.title}</h1>
|
||||
<div className="flex w-full max-w-full flex-wrap items-center justify-end gap-x-3 gap-y-1 pr-1 text-sm text-[var(--theme-card-text-color)]/70">
|
||||
<h1 className="text-xl font-semibold tracking-tight">{session.title}</h1>
|
||||
<div className="flex w-full max-w-full flex-wrap items-center justify-between gap-x-3 gap-y-1 pr-1 text-sm text-[var(--theme-card-text-color)]/70">
|
||||
<div className="flex items-center gap-1.5 text-sm text-[var(--theme-card-text-color)]/80">
|
||||
{duration ? (
|
||||
<span className="flex items-center gap-1.5">
|
||||
<Clock className="size-4 shrink-0" strokeWidth={1.75} />
|
||||
<Clock className="size-4 shrink-0 text-[var(--theme-button-color)]" strokeWidth={1.75} />
|
||||
{startT} ({duration})
|
||||
</span>
|
||||
) : startT ? (
|
||||
<span className="flex items-center gap-1.5">
|
||||
<Clock className="size-4 shrink-0" strokeWidth={1.75} />
|
||||
<Clock className="size-4 shrink-0 text-[var(--theme-button-color)]" strokeWidth={1.75} />
|
||||
{startT}
|
||||
</span>
|
||||
) : null}
|
||||
{sessionDate ? (
|
||||
<span className="flex items-center gap-1.5">
|
||||
<Calendar className="size-4 shrink-0" strokeWidth={1.75} />
|
||||
<Calendar className="size-4 shrink-0 text-[var(--theme-button-color)]" strokeWidth={1.75} />
|
||||
{sessionDate}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex items-center justify-end gap-1.5">
|
||||
{hasExpandableSessionMeta ? (
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -519,7 +522,7 @@ export function SingleEventPage() {
|
|||
aria-label={
|
||||
sessionMetaOpen ? "Hide session notes and location" : "Show session notes and location"
|
||||
}
|
||||
className="inline-flex shrink-0 items-center rounded-md p-0.5 text-[var(--theme-card-text-color)] hover:bg-[var(--theme-button-color)]/15"
|
||||
className="inline-flex shrink-0 items-center rounded-md p-0.5 bg-[var(--theme-button-color)]/5 text-[var(--theme-card-text-color)] hover:bg-[var(--theme-button-color)]/15"
|
||||
>
|
||||
<ChevronDown
|
||||
className={`size-5 transition-transform ${sessionMetaOpen ? "rotate-180" : ""}`}
|
||||
|
|
@ -530,17 +533,19 @@ export function SingleEventPage() {
|
|||
) : null}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{sessionMetaOpen && hasExpandableSessionMeta ? (
|
||||
<div className="mt-2 w-full max-w-full border-t border-[var(--theme-card-color)]/35 px-1 pt-3 text-left text-sm text-[var(--theme-card-text-color)]/95">
|
||||
<div className="mt-2 w-full max-w-full border-t border-[var(--theme-card-text-color)]/5 px-1 pt-3 text-left text-sm text-[var(--theme-card-text-color)]/95">
|
||||
{sessionNotes ? (
|
||||
<div className="mb-3">
|
||||
<p className="text-[10px] font-semibold uppercase tracking-wide text-[var(--theme-card-text-color)]/80">Notes</p>
|
||||
<p className="text-xs font-semibold uppercase tracking-wide text-[var(--theme-card-text-color)]/80">Notes</p>
|
||||
<p className="mt-1.5 whitespace-pre-wrap leading-snug">{sessionNotes}</p>
|
||||
</div>
|
||||
) : null}
|
||||
{sessionLocation || trackHall ? (
|
||||
<div>
|
||||
<p className="text-[10px] font-semibold uppercase tracking-wide text-[var(--theme-card-text-color)]/80">Location</p>
|
||||
<p className="text-xs font-semibold uppercase tracking-wide text-[var(--theme-card-text-color)]/80">Location</p>
|
||||
<ul className="mt-1.5 flex flex-col gap-1.5">
|
||||
{sessionLocation ? (
|
||||
<li className="flex items-start gap-2 leading-snug">
|
||||
|
|
@ -550,7 +555,7 @@ export function SingleEventPage() {
|
|||
) : null}
|
||||
{trackHall ? (
|
||||
<li className="flex items-start gap-2 leading-snug">
|
||||
<MapPin className="mt-0.5 size-4 shrink-0 text-white/90" strokeWidth={1.75} />
|
||||
<MapPin className="mt-0.5 size-4 shrink-0 text-[var(--theme-button-color)]" strokeWidth={1.75} />
|
||||
<span>{trackHall}</span>
|
||||
</li>
|
||||
) : null}
|
||||
|
|
@ -558,13 +563,13 @@ export function SingleEventPage() {
|
|||
</div>
|
||||
) : null}
|
||||
{hasCapacity ? (
|
||||
<p className="mt-3 text-xs text-white/85">Capacity: {session.capacity}</p>
|
||||
<p className="mt-3 text-xs font-semibold uppercase text-[var(--theme-card-text-color)]/80">Capacity: {session.capacity}</p>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<main className="test-class relative z-0 w-full max-w-none flex-1 bg-transparent px-4 pb-28 pt-4">
|
||||
<main className="test-class relative z-0 w-full max-w-none flex-1 px-4 pb-28 pt-4">
|
||||
<div className="flex w-full max-w-none flex-col gap-4">
|
||||
{session && session.enableQnA !== false ? (
|
||||
<SessionQuestionsCard
|
||||
|
|
@ -578,33 +583,33 @@ export function SingleEventPage() {
|
|||
<SessionQuizPollsSection eventId={eventId} sessionId={sessionId} />
|
||||
|
||||
{showSessionFeedback || showSessionRatings ? (
|
||||
<div className="flex w-full max-w-none flex-col gap-2">
|
||||
<p className="px-1 text-[10px] font-semibold uppercase tracking-wide text-[#8e8e93]">
|
||||
<div className="flex w-full max-w-none flex-col card-bg rounded-lg shadow-md gap-2 p-3">
|
||||
<p className="px-1 text-md font-semibold uppercas tracking-wide">
|
||||
Feedback & ratings
|
||||
</p>
|
||||
<div className="flex flex-col gap-2">
|
||||
{showSessionFeedback ? (
|
||||
<Link
|
||||
to={sessionFeedbackHref}
|
||||
className="flex items-center justify-between gap-3 rounded-lg border border-[rgba(116,112,112,0.2)] bg-whit card-bg px-3 py-3 text-sm font-medium text-[#24282f] shadow-[0_1px_4px_rgba(12,12,13,0.05)] active:bg-[#f2f2f7]"
|
||||
className="flex items-center justify-between gap-3 rounded-lg border border-[var(--theme-button-color)]/9 bg-[var(--theme-button-color)]/10 px-3 py-4 text-sm font-medium text-[var(--theme-card-text-color)] active:bg-[var(--theme-button-color)]/15"
|
||||
>
|
||||
<span className="flex min-w-0 items-center gap-2.5">
|
||||
<MessageCircle className="size-[18px] shrink-0 text-[#1e72ef]" strokeWidth={1.75} />
|
||||
<MessageCircle className="size-[18px] shrink-0 text-[var(--theme-button-color)]" strokeWidth={1.75} />
|
||||
<span className="truncate">Session feedback</span>
|
||||
</span>
|
||||
<ChevronRight className="size-4 shrink-0 text-[#c7c7cc]" strokeWidth={2} aria-hidden />
|
||||
<ChevronRight className="size-4 shrink-0 text-[var(--theme-card-text-color)]/70" strokeWidth={2} aria-hidden />
|
||||
</Link>
|
||||
) : null}
|
||||
{showSessionRatings ? (
|
||||
<Link
|
||||
to={sessionRatingsHref}
|
||||
className="flex items-center justify-between gap-3 rounded-lg border border-[rgba(116,112,112,0.2)] bg-whit card-bg px-3 py-3 text-sm font-medium text-[#24282f] shadow-[0_1px_4px_rgba(12,12,13,0.05)] active:bg-[#f2f2f7]"
|
||||
className="flex items-center justify-between gap-3 rounded-lg border border-[var(--theme-button-color)]/9 bg-[var(--theme-button-color)]/10 px-3 py-4 text-sm font-medium text-[var(--theme-card-text-color)] active:bg-[var(--theme-button-color)]/15"
|
||||
>
|
||||
<span className="flex min-w-0 items-center gap-2.5">
|
||||
<Star className="size-[18px] shrink-0 text-[#1e72ef]" strokeWidth={1.75} />
|
||||
<Star className="size-[18px] shrink-0 text-[var(--theme-button-color)]" strokeWidth={1.75} />
|
||||
<span className="truncate">Rate this session</span>
|
||||
</span>
|
||||
<ChevronRight className="size-4 shrink-0 text-[#c7c7cc]" strokeWidth={2} aria-hidden />
|
||||
<ChevronRight className="size-4 shrink-0 text-[var(--theme-card-text-color)]/70" strokeWidth={2} aria-hidden />
|
||||
</Link>
|
||||
) : null}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -44,8 +44,8 @@ import {
|
|||
function WhiteCard({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<div className="flex w-full max-w-[354px] flex-col items-center">
|
||||
<div className="w-full rounded-xl shadow-[0_8px_12px_6px_rgba(0,0,0,0.15),0_4px_4px_rgba(0,0,0,0.3)]">
|
||||
<div className="rounded-xl bg-white px-6 py-6">{children}</div>
|
||||
<div className="w-full rounded-xl shadow-md">
|
||||
<div className="rounded-xl card-bg px-6 py-6">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -387,12 +387,12 @@ export function SocialSharePage() {
|
|||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<h1 className="mb-2 text-center text-[22px] font-semibold text-[#1a1a1a]">Your photo</h1>
|
||||
<p className="mb-4 text-center text-sm text-[#666]">
|
||||
Here’s your framed photo. You can download or share it.
|
||||
<h1 className="mb-2 text-center text-[22px] font-semibold">Your photo</h1>
|
||||
<p className="mb-4 text-center text-sm text-[var(--theme-card-text-color)]/80">
|
||||
Here's your framed photo. You can download or share it.
|
||||
</p>
|
||||
{(done.fileUrl || done.previewUrl) ? (
|
||||
<div className="mb-4 overflow-hidden rounded-lg border border-[#e5e5e5] bg-[#f8f8f8]">
|
||||
<div className="mb-4 overflow-hidden rounded-lg">
|
||||
<img
|
||||
ref={resultImgRef}
|
||||
crossOrigin="anonymous"
|
||||
|
|
@ -417,7 +417,7 @@ export function SocialSharePage() {
|
|||
<button
|
||||
type="button"
|
||||
onClick={resetForNew}
|
||||
className="inline-flex h-11 w-full items-center justify-center rounded-lg bg-[#D32F2F] px-4 text-sm font-semibold text-white hover:bg-[#b71c1c]"
|
||||
className="inline-flex h-11 w-full items-center justify-center rounded-lg theme-btn-bg px-4 text-sm font-semibold text-white"
|
||||
>
|
||||
Create another
|
||||
</button>
|
||||
|
|
@ -425,7 +425,7 @@ export function SocialSharePage() {
|
|||
<button
|
||||
type="button"
|
||||
onClick={() => void onDownload()}
|
||||
className="inline-flex h-11 w-full items-center justify-center gap-2 rounded-lg border border-[#ddd] bg-white px-4 text-sm font-semibold text-[#333] hover:bg-[#fafafa]"
|
||||
className="inline-flex h-11 w-full items-center justify-center gap-2 rounded-lg border border-zinc-300 bg-zinc-100 px-4 text-sm font-semibold text-gray-600"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
Download photo
|
||||
|
|
@ -434,14 +434,14 @@ export function SocialSharePage() {
|
|||
</div>
|
||||
{showShare ? (
|
||||
<div className="mt-4">
|
||||
<p className="mb-2 text-center text-xs font-semibold uppercase tracking-wide text-[#888]">
|
||||
<p className="mb-2 text-center text-xs font-semibold uppercase tracking-wide text-[var(--theme-card-text-color)]/60">
|
||||
Share
|
||||
</p>
|
||||
{useNativeShare ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void onNativeShare()}
|
||||
className="inline-flex h-11 w-full items-center justify-center gap-2 rounded-lg border border-[#ddd] bg-white px-4 text-sm font-semibold text-[#333] hover:bg-[#fafafa]"
|
||||
className="inline-flex h-11 w-full items-center justify-center gap-2 rounded-lg border border-zinc-300 bg-zinc-100 px-4 text-sm font-semibold text-gray-600"
|
||||
>
|
||||
<Share2 className="h-4 w-4" />
|
||||
Share
|
||||
|
|
@ -453,7 +453,7 @@ export function SocialSharePage() {
|
|||
key={item.key}
|
||||
type="button"
|
||||
onClick={() => void onShare(item.key)}
|
||||
className="inline-flex h-10 items-center justify-center gap-2 rounded-lg border border-[#ddd] bg-white px-3 text-xs font-semibold text-[#333] hover:bg-[#fafafa]"
|
||||
className="inline-flex h-10 items-center justify-center gap-2 rounded-lg border border-zinc-300 bg-zinc-100 px-3 text-xs font-semibold text-gray-600"
|
||||
>
|
||||
<ShareIcon platform={item.key} />
|
||||
{item.label}
|
||||
|
|
@ -484,22 +484,22 @@ export function SocialSharePage() {
|
|||
|
||||
{meta?.accessMode === "login" && !loggedIn ? (
|
||||
<div className="space-y-3">
|
||||
<h1 className="mb-1 text-center text-[22px] font-semibold text-[#1a1a1a]">Social Share</h1>
|
||||
<p className="text-center text-sm text-[#666]">Sign in to create and share your photo.</p>
|
||||
<h1 className="mb-1 text-center text-[22px] font-semibold">Social Share</h1>
|
||||
<p className="text-center text-sm text-[var(--theme-card-text-color)]/80">Sign in to create and share your photo.</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void goLogin()}
|
||||
className="inline-flex h-11 w-full items-center justify-center rounded-lg bg-[#D32F2F] px-4 text-sm font-semibold text-white hover:bg-[#b71c1c]"
|
||||
className="inline-flex h-11 w-full items-center justify-center rounded-lg theme-btn-bg px-4 text-sm font-semibold text-white hover:bg-[#b71c1c]"
|
||||
>
|
||||
Sign in to continue
|
||||
</button>
|
||||
</div>
|
||||
) : !selectedFrame ? (
|
||||
<div className="space-y-4">
|
||||
<h1 className="mb-1 text-center text-[22px] font-semibold text-[#1a1a1a]">Choose a frame</h1>
|
||||
<p className="text-center text-sm text-[#666]">Pick a frame, then upload your photo.</p>
|
||||
<h1 className="mb-1 text-center text-[22px] font-semibold">Choose a frame</h1>
|
||||
<p className="text-center text-sm text-[var(--theme-card-text-color)]/80">Pick a frame, then upload your photo.</p>
|
||||
{!meta?.frames?.length ? (
|
||||
<p className="text-center text-sm text-[#666]">No frames available yet.</p>
|
||||
<p className="text-center text-sm text-[var(--theme-card-text-color)]/80">No frames available yet.</p>
|
||||
) : (
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{meta.frames.map((frame) => (
|
||||
|
|
@ -507,9 +507,9 @@ export function SocialSharePage() {
|
|||
key={frame._id}
|
||||
type="button"
|
||||
onClick={() => setSelectedFrame(frame)}
|
||||
className="overflow-hidden rounded-lg border border-[#e5e5e5] bg-[#f8f8f8] text-left hover:border-[#D32F2F]"
|
||||
className="overflow-hidden rounded-lg border border-[var(--theme-card-text-color)]/15 text-left hover:border-[var(--theme-card-text-color)]/25"
|
||||
>
|
||||
<div className="aspect-square w-full bg-white">
|
||||
<div className="aspect-square w-full">
|
||||
{frame.frameImageUrl ? (
|
||||
<img
|
||||
src={frame.frameImageUrl}
|
||||
|
|
@ -517,12 +517,12 @@ export function SocialSharePage() {
|
|||
className="h-full w-full object-contain"
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-full items-center justify-center text-xs text-[#999]">
|
||||
<div className="flex h-full items-center justify-center text-xs text-[var(--theme-card-text-color)]/60">
|
||||
No preview
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<p className="truncate px-2 py-2 text-center text-xs font-semibold text-[#333]">
|
||||
<p className="truncate px-2 py-2 text-center text-xs font-semibold text-[var(--theme-card-text-color)]">
|
||||
{frame.name}
|
||||
</p>
|
||||
</button>
|
||||
|
|
@ -532,7 +532,7 @@ export function SocialSharePage() {
|
|||
{err ? <p className="text-center text-sm text-red-600">{err}</p> : null}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-4 text-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
|
|
@ -540,13 +540,13 @@ export function SocialSharePage() {
|
|||
clearSelectedPhoto();
|
||||
setErr(null);
|
||||
}}
|
||||
className="text-sm font-semibold text-[#D32F2F]"
|
||||
className="text-sm font-semibold theme-btn-link"
|
||||
>
|
||||
← Change frame
|
||||
</button>
|
||||
<h1 className="mb-1 text-center text-[22px] font-semibold text-[#1a1a1a]">Upload photo</h1>
|
||||
<p className="text-center text-sm text-[#666]">
|
||||
Frame: <span className="font-semibold text-[#333]">{selectedFrame.name}</span>
|
||||
<h1 className="mb-1 text-center text-[22px] font-semibold text-[var(--theme-card-text-color)]">Upload photo</h1>
|
||||
<p className="text-center text-sm text-[var(--theme-card-text-color)]/80">
|
||||
Frame: <span className="font-semibold">{selectedFrame.name}</span>
|
||||
</p>
|
||||
|
||||
<input
|
||||
|
|
@ -558,7 +558,7 @@ export function SocialSharePage() {
|
|||
onChange={(e) => void handleFileChange(e.target.files?.[0] || null)}
|
||||
/>
|
||||
{previewUrl ? (
|
||||
<div className="overflow-hidden rounded-lg border border-[#e5e5e5] bg-[#f8f8f8]">
|
||||
<div className="overflow-hidden rounded-lg">
|
||||
<img src={previewUrl} alt="Your photo" className="mx-auto max-h-[240px] w-full object-contain" />
|
||||
</div>
|
||||
) : null}
|
||||
|
|
@ -566,7 +566,7 @@ export function SocialSharePage() {
|
|||
type="button"
|
||||
disabled={picking}
|
||||
onClick={() => fileRef.current?.click()}
|
||||
className="inline-flex h-11 w-full items-center justify-center rounded-lg border border-[#ddd] bg-white px-4 text-sm font-semibold text-[#333] hover:bg-[#fafafa] disabled:opacity-55"
|
||||
className="inline-flex h-11 w-full items-center justify-center rounded-lg border border-zinc-300 bg-zinc-100 px-4 text-sm font-semibold text-gray-600 disabled:opacity-55"
|
||||
>
|
||||
{picking ? "Processing…" : file ? "Change photo" : "Choose photo"}
|
||||
</button>
|
||||
|
|
@ -574,7 +574,7 @@ export function SocialSharePage() {
|
|||
type="button"
|
||||
disabled={!file || submitting}
|
||||
onClick={() => void onSubmit()}
|
||||
className="inline-flex h-11 w-full items-center justify-center rounded-lg bg-[#D32F2F] px-4 text-sm font-semibold text-white hover:bg-[#b71c1c] disabled:opacity-55"
|
||||
className="inline-flex h-11 w-full items-center justify-center rounded-lg theme-btn-bg px-4 text-sm font-semibold text-white disabled:opacity-55"
|
||||
>
|
||||
{submitting ? "Creating…" : "Create photo"}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export interface ResolveCodeResponse {
|
|||
trackId?: string;
|
||||
}
|
||||
|
||||
/** Page background preset from login-preview `theme`. */
|
||||
/** Page background preset from public visitor-theme API. */
|
||||
export type EventThemeMode = "simple" | "gradient" | "background" | "pattern" | string;
|
||||
|
||||
/** Single-family fonts driven by theme.font (same set as Eventbuzz_Web). */
|
||||
|
|
@ -33,7 +33,7 @@ export type EventThemeGradient = {
|
|||
color2?: string | null;
|
||||
};
|
||||
|
||||
/** Dynamic styling config from GET login-preview → CSS vars on `<html>`. */
|
||||
/** Dynamic styling config from GET /api/public/visitor-theme/:eventId → CSS vars on `<html>`. */
|
||||
export interface EventThemeConfig {
|
||||
theme: EventThemeMode;
|
||||
color?: string | null;
|
||||
|
|
@ -88,8 +88,6 @@ export interface LoginPreviewResponse {
|
|||
logoUrl: string;
|
||||
/** Star ratings for this event only (from feedback/survey submissions). */
|
||||
ratings?: { average: number; count: number };
|
||||
/** Dynamic page theme → CSS variables on `<html>`. */
|
||||
theme?: EventThemeConfig | null;
|
||||
}
|
||||
|
||||
export interface EbVisitorContext {
|
||||
|
|
|
|||
Loading…
Reference in New Issue