Merge branch 'main' of http://192.168.1.200:3000/Stratawiz/EventBuzz_Website
This commit is contained in:
commit
432842ee13
|
|
@ -26,9 +26,14 @@ import { AiAvatarPage } from "./pages/AiAvatarPage";
|
||||||
import { CodeEntryPage } from "./pages/CodeEntryPage";
|
import { CodeEntryPage } from "./pages/CodeEntryPage";
|
||||||
import { PrivacyPolicyPage } from "./pages/PrivacyPolicyPage";
|
import { PrivacyPolicyPage } from "./pages/PrivacyPolicyPage";
|
||||||
import { TermsOfServicePage } from "./pages/TermsOfServicePage";
|
import { TermsOfServicePage } from "./pages/TermsOfServicePage";
|
||||||
|
import { useEventTheme } from "./hooks/useEventTheme";
|
||||||
|
import { ThemeGate } from "./components/ThemeGate";
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
|
const { ready: themeReady } = useEventTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<ThemeGate ready={themeReady}>
|
||||||
<div className="flex min-h-dvh flex-1 flex-col">
|
<div className="flex min-h-dvh flex-1 flex-col">
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Home />} />
|
<Route path="/" element={<Home />} />
|
||||||
|
|
@ -62,5 +67,6 @@ export default function App() {
|
||||||
<Route path="*" element={<Navigate to="/" replace />} />
|
<Route path="*" element={<Navigate to="/" replace />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
|
</ThemeGate>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,17 +13,17 @@ export type AgendaRowData = {
|
||||||
|
|
||||||
function SpeakerLineView({ name, role }: SpeakerLine) {
|
function SpeakerLineView({ name, role }: SpeakerLine) {
|
||||||
const pill =
|
const pill =
|
||||||
role === "Speaker" ? "bg-[#f0fbd7]" : "bg-[#fbefd7]";
|
role === "Speaker" ? "bg-emerald-50" : "bg-yellow-100";
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
<div className="flex 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]">
|
<div className="flex size-6 shrink-0 items-center justify-center rounded-full bg-zinc-100">
|
||||||
<User className="size-3 text-[#24282f]/70" strokeWidth={2} />
|
<User className="size-3 text-gray-600" strokeWidth={2} />
|
||||||
</div>
|
</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>
|
</div>
|
||||||
<span
|
<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}
|
{role}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -39,37 +39,36 @@ export function AgendaRow({ row }: AgendaRowProps) {
|
||||||
return (
|
return (
|
||||||
<div className="relative flex gap-2 pb-3">
|
<div className="relative flex gap-2 pb-3">
|
||||||
<div className="relative z-[1] flex shrink-0 flex-col items-center">
|
<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]">
|
<div className="flex size-10 items-center justify-center rounded-full bg-[var(--theme-button-color)]/15">
|
||||||
<User className="size-4 text-[#24282f]/80" strokeWidth={2} />
|
<User className="size-4 text-[var(--theme-card-text-color)]" strokeWidth={2} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex min-w-0 flex-1 flex-col gap-2">
|
<div className="flex min-w-0 flex-1 flex-col gap-2">
|
||||||
<div className="flex items-start justify-between gap-2">
|
<div className="flex items-start justify-between gap-2">
|
||||||
<div className="flex min-w-0 flex-col gap-1">
|
<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 text-[#24282f]">
|
<div className="flex items-center gap-1 text-[10px] tracking-wide">
|
||||||
<span>{row.time}</span>
|
<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>
|
<span>{row.duration}</span>
|
||||||
</div>
|
</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}
|
{row.trackTitle}
|
||||||
</h3>
|
</h3>
|
||||||
<div
|
<div
|
||||||
className="inline-flex w-fit items-center rounded px-2 py-0.5 text-[10px] tracking-wide text-[#24282f]"
|
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)]"
|
||||||
style={{ backgroundColor: "rgba(94, 147, 239, 0.14)" }}
|
|
||||||
>
|
>
|
||||||
{row.sessionType}
|
{row.sessionType}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="shrink-0 text-[#1d66ef]"
|
className="shrink-0 text-[var(--theme-button-color)]"
|
||||||
aria-label="Bookmark session"
|
aria-label="Bookmark session"
|
||||||
>
|
>
|
||||||
<Bookmark className="size-5" strokeWidth={1.75} />
|
<Bookmark className="size-5" strokeWidth={1.75} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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}
|
{row.description}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
|
|
|
||||||
|
|
@ -35,39 +35,39 @@ export type AgendaRowTrackV2Data = {
|
||||||
export function AgendaRowTrackV2({ row }: { row: AgendaRowTrackV2Data }) {
|
export function AgendaRowTrackV2({ row }: { row: AgendaRowTrackV2Data }) {
|
||||||
const [detailsOpen, setDetailsOpen] = useState(false);
|
const [detailsOpen, setDetailsOpen] = useState(false);
|
||||||
const joinBtnClass = row.joinLive
|
const joinBtnClass = row.joinLive
|
||||||
? "inline-flex h-8 w-[84px] items-center justify-between rounded-full bg-[#e67e00] 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-[#ffb347]/40"
|
? "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 bg-[#ef8d1d] 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 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 (
|
return (
|
||||||
<div className="relative w-full pb-4">
|
<div className="relative w-full pb-4">
|
||||||
<div className="flex min-w-0 w-full flex-col gap-2">
|
<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-col gap-1">
|
||||||
<div className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1">
|
<div className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1">
|
||||||
<span
|
<span
|
||||||
className="inline-flex shrink-0 rounded px-2 py-0.5 text-[10px] text-[#24282f]"
|
className="inline-flex shrink-0 rounded px-2 py-0.5 text-[10px] bg-zinc-200 text-gray-600"
|
||||||
style={{ backgroundColor: "rgba(94, 147, 239, 0.14)" }}
|
|
||||||
>
|
>
|
||||||
{row.sessionType}
|
{row.sessionType}
|
||||||
</span>
|
</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 ? (
|
{row.sessionDateLabel ? (
|
||||||
<>
|
<>
|
||||||
<span className="shrink-0">{row.sessionDateLabel}</span>
|
<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}
|
) : null}
|
||||||
<span className="shrink-0">{row.time}</span>
|
<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>
|
<span className="shrink-0">{row.duration}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-sm font-semibold leading-[22px] text-[#24282f]">
|
<h3 className="text-sm font-semibold leading-[22px]">
|
||||||
{row.trackTitle}
|
{row.trackTitle}
|
||||||
</h3>
|
</h3>
|
||||||
{row.trackLocation?.trim() ? (
|
{row.trackLocation?.trim() ? (
|
||||||
<div className="flex items-start gap-1.5 text-xs text-[rgba(36,40,47,0.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-[#1e72ef]" strokeWidth={1.75} />
|
<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>
|
<span className="leading-snug">{row.trackLocation.trim()}</span>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
@ -93,7 +93,7 @@ export function AgendaRowTrackV2({ row }: { row: AgendaRowTrackV2Data }) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
row.bookmark!.onToggle();
|
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-label={row.bookmark.isBookmarked ? "Remove bookmark" : "Bookmark session"}
|
||||||
aria-pressed={row.bookmark.isBookmarked}
|
aria-pressed={row.bookmark.isBookmarked}
|
||||||
>
|
>
|
||||||
|
|
@ -111,7 +111,7 @@ export function AgendaRowTrackV2({ row }: { row: AgendaRowTrackV2Data }) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setDetailsOpen((o) => !o);
|
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-expanded={detailsOpen}
|
||||||
aria-label={detailsOpen ? "Hide session details" : "Show session details"}
|
aria-label={detailsOpen ? "Hide session details" : "Show session details"}
|
||||||
>
|
>
|
||||||
|
|
@ -123,15 +123,15 @@ export function AgendaRowTrackV2({ row }: { row: AgendaRowTrackV2Data }) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{detailsOpen ? (
|
{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() ? (
|
{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}
|
) : null}
|
||||||
<div className="flex min-w-0 items-center gap-1.5">
|
<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
|
Speaker
|
||||||
</span>
|
</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(", ")}
|
{row.speakerNames.join(", ")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -79,20 +79,20 @@ export function AskQuestionModal({
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
aria-labelledby={`ask-question-title${idSuffix}`}
|
aria-labelledby={`ask-question-title${idSuffix}`}
|
||||||
className="relative z-10 w-full max-w-[min(100%,402px)] rounded-t-2xl bg-white 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="mb-4 flex items-start justify-between gap-3">
|
||||||
<div className="min-w-0">
|
<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
|
Ask a Question
|
||||||
</h3>
|
</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.
|
Submit your question to the session moderator.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="shrink-0 rounded-full p-1.5 text-[#24282f] hover:bg-[#f2f2f7]"
|
className="shrink-0 rounded-full p-1.5 text-[var(--theme-card-text-color)] transition-colors duration-300 ease-in-out"
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
onClick={close}
|
onClick={close}
|
||||||
>
|
>
|
||||||
|
|
@ -110,13 +110,13 @@ export function AskQuestionModal({
|
||||||
placeholder="Type your question here..."
|
placeholder="Type your question here..."
|
||||||
rows={5}
|
rows={5}
|
||||||
disabled={submitting}
|
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)] focus:border-[#ef8d1d] focus:outline-none focus:ring-2 focus:ring-[#ef8d1d]/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}
|
{submitError ? <p className="text-sm text-red-600">{submitError}</p> : null}
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={submitting}
|
disabled={submitting}
|
||||||
className="w-full rounded-xl bg-[#ef8d1d] py-3 text-sm font-semibold text-white shadow-[0_4px_4px_rgba(0,0,0,0.12)] transition hover:bg-[#e0800c] 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"}
|
{submitting ? "Submitting…" : "Submit Question"}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ export function AudienceQuestionCard({
|
||||||
const shell =
|
const shell =
|
||||||
variant === "embedded"
|
variant === "embedded"
|
||||||
? "flex flex-col gap-4 p-4"
|
? "flex flex-col gap-4 p-4"
|
||||||
: "flex flex-col gap-4 rounded-md border border-[#f2f2f7] bg-white 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(
|
const doVote = useCallback(
|
||||||
async (direction: "up" | "down") => {
|
async (direction: "up" | "down") => {
|
||||||
|
|
@ -86,7 +86,7 @@ export function AudienceQuestionCard({
|
||||||
type="button"
|
type="button"
|
||||||
disabled={!canVote || voting}
|
disabled={!canVote || voting}
|
||||||
onClick={() => void doVote("up")}
|
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"
|
userVote === "up"
|
||||||
? "border-[#34c759] bg-[#34c759]/10 text-[#34c759]"
|
? "border-[#34c759] bg-[#34c759]/10 text-[#34c759]"
|
||||||
: "border-[#34c759] bg-white text-[#34c759] hover:bg-[#34c759]/5"
|
: "border-[#34c759] bg-white text-[#34c759] hover:bg-[#34c759]/5"
|
||||||
|
|
@ -94,7 +94,7 @@ export function AudienceQuestionCard({
|
||||||
aria-label={canVote ? "Upvote" : undefined}
|
aria-label={canVote ? "Upvote" : undefined}
|
||||||
>
|
>
|
||||||
<ThumbsUp className="size-4 shrink-0" strokeWidth={2} />
|
<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>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -103,14 +103,14 @@ export function AudienceQuestionCard({
|
||||||
type="button"
|
type="button"
|
||||||
disabled={!canVote || voting}
|
disabled={!canVote || voting}
|
||||||
onClick={() => void doVote("down")}
|
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"
|
userVote === "down"
|
||||||
? "border-[#ff3b30] bg-[#ff3b30]/10 text-[#ff3b30]"
|
? "border-[#ff3b30] bg-[#ff3b30]/10 text-[#ff3b30]"
|
||||||
: "border-[#ff3b30] bg-white text-[#ff3b30] hover:bg-[#ff3b30]/5"
|
: "border-[#ff3b30] bg-white text-[#ff3b30] hover:bg-[#ff3b30]/5"
|
||||||
} disabled:cursor-not-allowed disabled:opacity-45`}
|
} disabled:cursor-not-allowed disabled:opacity-45`}
|
||||||
aria-label={canVote ? "Downvote" : undefined}
|
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} />
|
<ThumbsDown className="size-4 shrink-0" strokeWidth={2} />
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
@ -118,12 +118,12 @@ export function AudienceQuestionCard({
|
||||||
return (
|
return (
|
||||||
<div className={shell}>
|
<div className={shell}>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex size-8 items-center justify-center rounded-full bg-[#f2f2f7]">
|
<div className="flex size-10 items-center justify-center rounded-full bg-[var(--theme-button-color)]/10">
|
||||||
<User className="size-4 text-[#24282f]/70" strokeWidth={2} />
|
<User className="size-5 text-[var(theme-card-text-color)]/70" strokeWidth={2} />
|
||||||
</div>
|
</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>
|
</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 flex-col items-end gap-1">
|
||||||
<div className="flex justify-end gap-2">
|
<div className="flex justify-end gap-2">
|
||||||
{canVote ? (
|
{canVote ? (
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,12 @@ export function DynamicFeedbackForm({
|
||||||
eventId,
|
eventId,
|
||||||
schema,
|
schema,
|
||||||
variant = "light",
|
variant = "light",
|
||||||
|
className = '',
|
||||||
}: {
|
}: {
|
||||||
eventId: string;
|
eventId: string;
|
||||||
schema: VisitorPublicFeedbackForm;
|
schema: VisitorPublicFeedbackForm;
|
||||||
variant?: "light" | "gradient";
|
variant?: "light" | "gradient";
|
||||||
|
className?: string | '';
|
||||||
}) {
|
}) {
|
||||||
const [values, setValues] = useState<Record<string, unknown>>({});
|
const [values, setValues] = useState<Record<string, unknown>>({});
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
@ -86,11 +88,11 @@ export function DynamicFeedbackForm({
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
variant === "gradient"
|
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 card-bg p-4 text-center shadow-md"
|
||||||
: "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"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<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]">
|
<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."}
|
{schema.thankYouMessage?.trim() || "Your response has been recorded."}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -100,13 +102,13 @@ export function DynamicFeedbackForm({
|
||||||
|
|
||||||
const fieldShell =
|
const fieldShell =
|
||||||
variant === "gradient"
|
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 p-4 pb-2 ${className}`
|
||||||
: "rounded-md bg-white p-4 shadow-[0_1px_4px_rgba(12,12,13,0.05)]";
|
: `rounded-md p-4 pb-2 ${className}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={onSubmit} className={fieldShell}>
|
<form onSubmit={onSubmit} className={fieldShell}>
|
||||||
{schema.description?.trim() ? (
|
{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}
|
) : null}
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
{fields.map((f) => (
|
{fields.map((f) => (
|
||||||
|
|
@ -120,7 +122,7 @@ export function DynamicFeedbackForm({
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={submitting}
|
disabled={submitting}
|
||||||
className="mt-4 w-full rounded-full 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="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"}
|
{submitting ? "Submitting…" : "Submit"}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -154,12 +156,12 @@ function FieldInput({
|
||||||
const t = (f.type || "text").toLowerCase();
|
const t = (f.type || "text").toLowerCase();
|
||||||
|
|
||||||
const baseInput =
|
const baseInput =
|
||||||
"w-full rounded-lg border border-[rgba(116,112,112,0.3)] bg-[rgba(157,157,172,0.07)] px-3 py-2 font-inter text-sm text-[#24282f] placeholder:text-[rgba(36,40,47,0.45)]";
|
"w-full rounded-lg border border-[rgba(116,112,112,0.3)] bg-[rgba(157,157,172,0.07)] px-3 py-2 font-inter text-sm text-[#24282f] 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 placeholder:text-[rgba(36,40,47,0.45)]";
|
||||||
|
|
||||||
if (t === "file") {
|
if (t === "file") {
|
||||||
return (
|
return (
|
||||||
<p className="text-sm text-[rgba(36,40,47,0.67)]">
|
<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>
|
<span className="mt-1 block">File upload isn't supported in the mobile app yet.</span>
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
|
|
@ -170,11 +172,11 @@ function FieldInput({
|
||||||
const selected = Number.isFinite(n) ? n : 0;
|
const selected = Number.isFinite(n) ? n : 0;
|
||||||
return (
|
return (
|
||||||
<fieldset className="flex flex-col gap-2">
|
<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}
|
{label}
|
||||||
{f.required ? <span className="text-red-600"> *</span> : null}
|
{f.required ? <span className="text-red-600"> *</span> : null}
|
||||||
</legend>
|
</legend>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2 mt-1">
|
||||||
{Array.from({ length: 5 }, (_, i) => {
|
{Array.from({ length: 5 }, (_, i) => {
|
||||||
const star = i + 1;
|
const star = i + 1;
|
||||||
return (
|
return (
|
||||||
|
|
@ -182,11 +184,11 @@ function FieldInput({
|
||||||
key={star}
|
key={star}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onChange(label, star)}
|
onClick={() => onChange(label, star)}
|
||||||
className="text-[#ef8d1d]"
|
className="text-[var(--theme-button-color)]"
|
||||||
aria-label={`${star} star${star === 1 ? "" : "s"}`}
|
aria-label={`${star} star${star === 1 ? "" : "s"}`}
|
||||||
>
|
>
|
||||||
<Star
|
<Star
|
||||||
className={`size-7 ${star <= selected ? "fill-[#ef8d1d]" : "fill-transparent"}`}
|
className={`size-7 ${star <= selected ? "fill-[var(--theme-button-color)]" : "fill-transparent"}`}
|
||||||
strokeWidth={1.75}
|
strokeWidth={1.75}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -223,13 +225,13 @@ function FieldInput({
|
||||||
if (t === "radio") {
|
if (t === "radio") {
|
||||||
return (
|
return (
|
||||||
<fieldset className="flex flex-col gap-2">
|
<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}
|
{label}
|
||||||
{f.required ? <span className="text-red-600"> *</span> : null}
|
{f.required ? <span className="text-red-600"> *</span> : null}
|
||||||
</legend>
|
</legend>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
{(opts.length ? opts : ["Yes", "No"]).map((o) => (
|
{(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)} />
|
<input type="radio" name={label} checked={value === o} onChange={() => onChange(label, o)} />
|
||||||
{o}
|
{o}
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -247,13 +249,13 @@ function FieldInput({
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<fieldset className="flex flex-col gap-2">
|
<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}
|
{label}
|
||||||
{f.required ? <span className="text-red-600"> *</span> : null}
|
{f.required ? <span className="text-red-600"> *</span> : null}
|
||||||
</legend>
|
</legend>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
{opts.map((o) => (
|
{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)} />
|
<input type="checkbox" checked={arr.includes(o)} onChange={() => toggle(o)} />
|
||||||
{o}
|
{o}
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -265,7 +267,7 @@ function FieldInput({
|
||||||
|
|
||||||
if (t === "checkbox") {
|
if (t === "checkbox") {
|
||||||
return (
|
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
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={value === true || value === "yes" || value === "Yes"}
|
checked={value === true || value === "yes" || value === "Yes"}
|
||||||
|
|
@ -282,7 +284,7 @@ function FieldInput({
|
||||||
if (t === "email" || t === "phone") {
|
if (t === "email" || t === "phone") {
|
||||||
return (
|
return (
|
||||||
<label className="flex flex-col gap-2">
|
<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}
|
{label}
|
||||||
{f.required ? <span className="text-red-600"> *</span> : null}
|
{f.required ? <span className="text-red-600"> *</span> : null}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -300,7 +302,7 @@ function FieldInput({
|
||||||
if (t === "textarea") {
|
if (t === "textarea") {
|
||||||
return (
|
return (
|
||||||
<label className="flex flex-col gap-2">
|
<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}
|
{label}
|
||||||
{f.required ? <span className="text-red-600"> *</span> : null}
|
{f.required ? <span className="text-red-600"> *</span> : null}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -324,7 +326,7 @@ function FieldInput({
|
||||||
if (t === "text" && longText) {
|
if (t === "text" && longText) {
|
||||||
return (
|
return (
|
||||||
<label className="flex flex-col gap-2">
|
<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}
|
{label}
|
||||||
{f.required ? <span className="text-red-600"> *</span> : null}
|
{f.required ? <span className="text-red-600"> *</span> : null}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -341,7 +343,7 @@ function FieldInput({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<label className="flex flex-col gap-2">
|
<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}
|
{label}
|
||||||
{f.required ? <span className="text-red-600"> *</span> : null}
|
{f.required ? <span className="text-red-600"> *</span> : null}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,14 @@ export type EventBannerProps = {
|
||||||
/** Event banner URL from login-preview (`bannerImage` or `image`) */
|
/** Event banner URL from login-preview (`bannerImage` or `image`) */
|
||||||
imageUrl?: string | null;
|
imageUrl?: string | null;
|
||||||
alt?: string;
|
alt?: string;
|
||||||
|
className?: string | '';
|
||||||
};
|
};
|
||||||
|
|
||||||
export function EventBanner({ imageUrl, alt = "" }: EventBannerProps) {
|
export function EventBanner({ imageUrl, alt = "", className = "" }: EventBannerProps) {
|
||||||
const src = imageUrl?.trim();
|
const src = imageUrl?.trim();
|
||||||
if (src) {
|
if (src) {
|
||||||
return (
|
return (
|
||||||
<div className="w-full shrink-0 overflow-hidden bg-[#e5e5ea]">
|
<div className={`w-full shrink-0 overflow-hidden bg-[#e5e5ea] ${className}`}>
|
||||||
<img
|
<img
|
||||||
src={src}
|
src={src}
|
||||||
alt={alt}
|
alt={alt}
|
||||||
|
|
@ -22,8 +23,8 @@ export function EventBanner({ imageUrl, alt = "" }: EventBannerProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<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"
|
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"
|
data-name="Event Logo ${className}`}
|
||||||
>
|
>
|
||||||
<EventBuzzLogo
|
<EventBuzzLogo
|
||||||
variant="white"
|
variant="white"
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,7 @@ export function LandingAgendaCards({
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={day.dateKey}
|
key={day.dateKey}
|
||||||
className="w-full rounded-lg border-x-0 border-[rgba(116,112,112,0.2)] bg-white shadow-sm sm:border-x"
|
className="w-full rounded-lg border border-[var(--theme-card-text-color)]/5"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -181,34 +181,34 @@ export function LandingAgendaCards({
|
||||||
aria-expanded={isOpen}
|
aria-expanded={isOpen}
|
||||||
>
|
>
|
||||||
<div className="flex min-w-0 items-center gap-2">
|
<div className="flex min-w-0 items-center gap-2">
|
||||||
<Calendar className="size-4 shrink-0 text-[#1e72ef]" strokeWidth={1.75} />
|
<Calendar className="size-4 shrink-0 text-[var(--theme-button-color)]" strokeWidth={1.75} />
|
||||||
<span className="font-inter text-sm font-semibold text-[#24282f]">{day.dateLabel}</span>
|
<span className="text-sm font-semibold">{day.dateLabel}</span>
|
||||||
</div>
|
</div>
|
||||||
<ChevronDown
|
<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}
|
strokeWidth={2}
|
||||||
aria-hidden
|
aria-hidden
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
{isOpen ? (
|
{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) => (
|
{day.trackGroups.map((tg, tgIdx) => (
|
||||||
<div key={tg.trackId} className="w-full">
|
<div key={tg.trackId} className="w-full">
|
||||||
{tgIdx > 0 ? (
|
{tgIdx > 0 ? (
|
||||||
<div
|
<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"
|
role="separator"
|
||||||
aria-hidden
|
aria-hidden
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<div className="px-4 pt-2">
|
<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}
|
{tg.trackName}
|
||||||
</p>
|
</p>
|
||||||
{tg.trackHall ? (
|
{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
|
<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}
|
strokeWidth={1.75}
|
||||||
/>
|
/>
|
||||||
<span className="font-inter leading-snug">{tg.trackHall}</span>
|
<span className="font-inter leading-snug">{tg.trackHall}</span>
|
||||||
|
|
@ -216,7 +216,7 @@ export function LandingAgendaCards({
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="relative mt-3 w-full max-w-none px-0">
|
<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">
|
<div className="flex w-full flex-col gap-4">
|
||||||
{tg.sessions.map((s) => (
|
{tg.sessions.map((s) => (
|
||||||
<AgendaRowTrackV2
|
<AgendaRowTrackV2
|
||||||
|
|
@ -257,7 +257,7 @@ export function LandingMySessionsList({
|
||||||
return (
|
return (
|
||||||
<div className="mt-3 w-full max-w-none">
|
<div className="mt-3 w-full max-w-none">
|
||||||
<div className="relative w-full">
|
<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">
|
<div className="flex w-full flex-col gap-4">
|
||||||
{sessions.map((s) => (
|
{sessions.map((s) => (
|
||||||
<AgendaRowTrackV2
|
<AgendaRowTrackV2
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export function EventRatingBadge({ ratings }: { ratings: EventRatingsSummary | n
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<span className="font-inter text-[#8e8e93]">
|
<span className="text-[var(--theme-card-text-color)]/60">
|
||||||
{average.toFixed(1)} ({count})
|
{average.toFixed(1)} ({count})
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,7 @@ export function HeaderMobile({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<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"
|
data-name="Header_Mobile"
|
||||||
>
|
>
|
||||||
<div className="flex w-full items-center justify-between gap-2">
|
<div className="flex w-full items-center justify-between gap-2">
|
||||||
|
|
@ -140,13 +140,13 @@ export function HeaderMobile({
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => (onBack ? onBack() : navigate(-1))}
|
onClick={() => (onBack ? onBack() : navigate(-1))}
|
||||||
className="flex size-10 shrink-0 items-center justify-center rounded-full bg-[#f2f2f7] text-[#1d66ef]"
|
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"
|
aria-label="Back"
|
||||||
>
|
>
|
||||||
<ChevronLeft className="size-6" strokeWidth={2} />
|
<ChevronLeft className="size-6" strokeWidth={2} />
|
||||||
</button>
|
</button>
|
||||||
) : null}
|
) : 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 ? (
|
{logoSrc ? (
|
||||||
<img
|
<img
|
||||||
src={logoSrc}
|
src={logoSrc}
|
||||||
|
|
@ -169,17 +169,17 @@ export function HeaderMobile({
|
||||||
<div className="flex shrink-0 items-center justify-end gap-2 sm:gap-4">
|
<div className="flex shrink-0 items-center justify-end gap-2 sm:gap-4">
|
||||||
<Link
|
<Link
|
||||||
to={rewardsHref}
|
to={rewardsHref}
|
||||||
className="flex items-center gap-1.5 rounded-full border border-[#ef8d1d] px-3 py-2 sm:gap-2 sm:px-4"
|
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-[#ef8d1d] sm:size-6" strokeWidth={1.75} />
|
<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}
|
{pointsLabel}
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleLogout}
|
onClick={handleLogout}
|
||||||
className="flex size-10 shrink-0 items-center justify-center rounded-full bg-[#ef8d1d] text-white"
|
className="flex size-10 shrink-0 items-center justify-center rounded-full bg-[var(--theme-button-color)] transition-colors duration-300 ease-in-out hover:bg-[var(--theme-button-color)]/90 text-white"
|
||||||
aria-label="Sign out"
|
aria-label="Sign out"
|
||||||
>
|
>
|
||||||
<LogOut className="size-5 sm:size-6" strokeWidth={2} />
|
<LogOut className="size-5 sm:size-6" strokeWidth={2} />
|
||||||
|
|
|
||||||
|
|
@ -61,19 +61,19 @@ export function HyperNavigationFAB({
|
||||||
const ratingsTo = eventId ? `/ratings?${ratingsQs.toString()}` : "/ratings";
|
const ratingsTo = eventId ? `/ratings?${ratingsQs.toString()}` : "/ratings";
|
||||||
|
|
||||||
const items: MenuItem[] = [
|
const items: MenuItem[] = [
|
||||||
{ label: "Event Home", to: eventHome, icon: <Home 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-[#1e72ef]" 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-[#1e72ef]" strokeWidth={1.75} /> },
|
{ label: "Leaderboard", to: leaderboard, icon: <Trophy className="size-4 shrink-0 text-[var(--theme-button-color)]" strokeWidth={1.75} /> },
|
||||||
...(showFeedbackNav
|
...(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
|
...(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",
|
label: "All Questions",
|
||||||
to: allQuestions,
|
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-expanded={open}
|
||||||
aria-haspopup="menu"
|
aria-haspopup="menu"
|
||||||
aria-label={open ? "Close quick navigation" : "Open quick navigation"}
|
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
|
<span
|
||||||
className="pointer-events-none absolute inset-[-2px] rounded-full shadow-[inset_0_6px_4px_rgba(0,0,0,0.25)]"
|
className="pointer-events-none absolute inset-[-2px] rounded-full shadow-[inset_0_6px_4px_rgba(0,0,0,0.25)]"
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ export function MobileShell({
|
||||||
}: MobileShellProps) {
|
}: MobileShellProps) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`mx-auto flex min-h-dvh w-full max-w-[402px] flex-1 flex-col bg-[#f2f2f7] ${className}`}
|
className={`mx-auto flex min-h-dvh w-full max-w-[402px] flex-1 flex-col bg-[#f2f2f7 bg-transparent ${className}`}
|
||||||
>
|
>
|
||||||
<div className="flex min-h-0 flex-1 flex-col">{children}</div>
|
<div className="flex min-h-0 flex-1 flex-col">{children}</div>
|
||||||
{showPoweredByFooter ? <PoweredByFooter /> : null}
|
{showPoweredByFooter ? <PoweredByFooter /> : null}
|
||||||
|
|
|
||||||
|
|
@ -47,16 +47,16 @@ export function PhotoWallCropModal({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex items-end justify-center bg-black/55 p-4 sm:items-center">
|
<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 bg-white shadow-xl">
|
<div className="flex w-full max-w-[420px] flex-col overflow-hidden rounded-2xl card-bg shadow-xl">
|
||||||
<div className="border-b border-[#f2f2f7] px-4 py-3">
|
<div className="border-b border-[var(--theme-card-border-color)]/5 px-4 py-3">
|
||||||
<p className="font-inter text-base font-semibold text-[#24282f]">Crop your photo</p>
|
<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-[rgba(36,40,47,0.65)]">
|
<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.
|
Adjust to match the wall photo ratio before uploading.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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
|
<Cropper
|
||||||
image={imageSrc}
|
image={imageSrc}
|
||||||
crop={crop}
|
crop={crop}
|
||||||
|
|
@ -70,7 +70,7 @@ export function PhotoWallCropModal({
|
||||||
|
|
||||||
<div className="flex flex-col gap-3 px-4 py-4">
|
<div className="flex flex-col gap-3 px-4 py-4">
|
||||||
<label className="flex flex-col gap-1.5">
|
<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
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
min={1}
|
min={1}
|
||||||
|
|
@ -78,7 +78,7 @@ export function PhotoWallCropModal({
|
||||||
step={0.05}
|
step={0.05}
|
||||||
value={zoom}
|
value={zoom}
|
||||||
onChange={(e) => setZoom(Number(e.target.value))}
|
onChange={(e) => setZoom(Number(e.target.value))}
|
||||||
className="w-full accent-[#ef8d1d]"
|
className="w-full accent-[var(--theme-button-color)]"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
|
@ -93,7 +93,7 @@ export function PhotoWallCropModal({
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onCancel}
|
onClick={onCancel}
|
||||||
disabled={working}
|
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
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -101,7 +101,7 @@ export function PhotoWallCropModal({
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => void confirm()}
|
onClick={() => void confirm()}
|
||||||
disabled={working || !croppedAreaPixels}
|
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"}
|
{working ? "Cropping…" : "Use photo"}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ export function PhotoWallEventHeader({
|
||||||
if (d.showDateRange && dateLine) {
|
if (d.showDateRange && dateLine) {
|
||||||
metaItems.push(
|
metaItems.push(
|
||||||
<span key="date" className="inline-flex items-center gap-1.5">
|
<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}
|
{dateLine}
|
||||||
</span>,
|
</span>,
|
||||||
);
|
);
|
||||||
|
|
@ -85,7 +85,7 @@ export function PhotoWallEventHeader({
|
||||||
if (showLoc) {
|
if (showLoc) {
|
||||||
metaItems.push(
|
metaItems.push(
|
||||||
<span key="loc" className="inline-flex items-center gap-1.5">
|
<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}
|
{venue}
|
||||||
</span>,
|
</span>,
|
||||||
);
|
);
|
||||||
|
|
@ -105,7 +105,7 @@ export function PhotoWallEventHeader({
|
||||||
return (
|
return (
|
||||||
<header className="flex w-full flex-col items-center gap-3 text-center">
|
<header className="flex w-full flex-col items-center gap-3 text-center">
|
||||||
{showLogo ? (
|
{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
|
<img
|
||||||
src={event.logoUrl!}
|
src={event.logoUrl!}
|
||||||
alt={event.name}
|
alt={event.name}
|
||||||
|
|
@ -121,22 +121,22 @@ export function PhotoWallEventHeader({
|
||||||
{showTitle || showSubtitle || metaItems.length > 0 ? (
|
{showTitle || showSubtitle || metaItems.length > 0 ? (
|
||||||
<div className="flex w-full flex-col items-center gap-2">
|
<div className="flex w-full flex-col items-center gap-2">
|
||||||
{eyebrowText ? (
|
{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}
|
) : null}
|
||||||
{showTitle ? (
|
{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}
|
{event.name}
|
||||||
</h1>
|
</h1>
|
||||||
) : null}
|
) : null}
|
||||||
{showSubtitle ? (
|
{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}
|
) : null}
|
||||||
{metaItems.length > 0 ? (
|
{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) => (
|
{metaItems.map((item, index) => (
|
||||||
<span key={index} className="inline-flex items-center gap-1.5 font-inter">
|
<span key={index} className="inline-flex items-center gap-1.5 font-inter">
|
||||||
{index > 0 ? (
|
{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>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
@ -147,7 +147,7 @@ export function PhotoWallEventHeader({
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
) : eyebrowText ? (
|
) : 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}
|
) : null}
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -93,16 +93,16 @@ export function SessionQuestionsCard({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<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-white 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"
|
data-name="Session Questions"
|
||||||
>
|
>
|
||||||
<div className="flex h-8 items-center justify-between gap-2 px-4">
|
<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">
|
<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}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
{total > 0 ? (
|
{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}
|
{safeIndex + 1}/{total}
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
@ -111,26 +111,26 @@ export function SessionQuestionsCard({
|
||||||
<div className="flex shrink-0 gap-1">
|
<div className="flex shrink-0 gap-1">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="rounded-full bg-[#f2f2f7] p-1"
|
className="rounded-full bg-zinc-300 p-1"
|
||||||
aria-label="Previous question"
|
aria-label="Previous question"
|
||||||
onClick={goPrev}
|
onClick={goPrev}
|
||||||
>
|
>
|
||||||
<ChevronLeft className="size-4 text-[#24282f]" />
|
<ChevronLeft className="size-4 text-zinc-700" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="rounded-full bg-[#f2f2f7] p-1"
|
className="rounded-full bg-zinc-300 p-1"
|
||||||
aria-label="Next question"
|
aria-label="Next question"
|
||||||
onClick={goNext}
|
onClick={goNext}
|
||||||
>
|
>
|
||||||
<ChevronRight className="size-4 text-[#24282f]" />
|
<ChevronRight className="size-4 text-zinc-700" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="border-t border-[#f2f2f7]">
|
<div className="border-t border-[var(--theme-card-text-color)]/5">
|
||||||
{live && loading ? (
|
{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 ? (
|
) : live && error ? (
|
||||||
<p className="px-4 py-6 text-center text-sm text-red-600">{error}</p>
|
<p className="px-4 py-6 text-center text-sm text-red-600">{error}</p>
|
||||||
) : current ? (
|
) : current ? (
|
||||||
|
|
@ -143,19 +143,19 @@ export function SessionQuestionsCard({
|
||||||
/>
|
/>
|
||||||
</div>
|
</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>
|
||||||
<div className="flex items-center justify-between gap-3 px-4">
|
<div className="flex items-center justify-between gap-3 px-4">
|
||||||
{viewAllTo ? (
|
{viewAllTo ? (
|
||||||
<Link
|
<Link
|
||||||
to={viewAllTo}
|
to={viewAllTo}
|
||||||
className="font-inter text-xs font-semibold uppercase tracking-wider text-[#1e72ef]"
|
className="text-xs font-semibold uppercase tracking-wider theme-btn-link"
|
||||||
>
|
>
|
||||||
View All
|
View All
|
||||||
</Link>
|
</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
|
View All
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
@ -163,7 +163,7 @@ export function SessionQuestionsCard({
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => live && setAskModalOpen(true)}
|
onClick={() => live && setAskModalOpen(true)}
|
||||||
disabled={!live}
|
disabled={!live}
|
||||||
className="shrink-0 rounded-lg bg-[#ef8d1d] px-3 py-2 text-xs font-semibold text-white shadow-[0_2px_4px_rgba(0,0,0,0.1)] transition hover:bg-[#e0800c] 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
|
Ask your Question
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ function typeBadgeClass(type: string): string {
|
||||||
const t = type.toLowerCase();
|
const t = type.toLowerCase();
|
||||||
if (t === "poll") return "bg-[#eaddff]";
|
if (t === "poll") return "bg-[#eaddff]";
|
||||||
if (t === "survey") return "bg-[#e5e5ea]";
|
if (t === "survey") return "bg-[#e5e5ea]";
|
||||||
return "bg-[#ebffee]";
|
return "bg-green-100";
|
||||||
}
|
}
|
||||||
|
|
||||||
function typeLabel(type: string): string {
|
function typeLabel(type: string): string {
|
||||||
|
|
@ -44,7 +44,7 @@ export function SessionQuizPollsSection({ eventId, sessionId }: SessionQuizPolls
|
||||||
}).toString()}`;
|
}).toString()}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-none rounded-lg border border-[rgba(116,112,112,0.2)] bg-white shadow-sm">
|
<div className="w-full max-w-none rounded-lg card-bg shadow-md">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="flex w-full items-center justify-between gap-2 px-4 py-3 text-left"
|
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}
|
aria-expanded={open}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-sm font-semibold text-[#24282f]">Quiz & Polls</span>
|
<span className="text-md font-semibold text-[var(--theme-card-text-color)]">Quiz & Polls</span>
|
||||||
<span className="rounded bg-[#ebffee] px-2 py-0.5 text-[10px] uppercase text-[#24282f]">
|
<span className="rounded bg-green-100 px-2 py-0.5 text-[10px] uppercase text-gray-800">
|
||||||
{loading ? "…" : `${items.length} Active`}
|
{loading ? "…" : `${items.length} Active`}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{open ? (
|
{open ? (
|
||||||
<ChevronUp className="size-4 shrink-0 text-[#1d66ef]" strokeWidth={2} />
|
<ChevronUp className="size-4 shrink-0 text-[var(--theme-card-text-color)]/70" strokeWidth={2} />
|
||||||
) : (
|
) : (
|
||||||
<ChevronDown className="size-4 shrink-0 text-[#1d66ef]" strokeWidth={2} />
|
<ChevronDown className="size-4 shrink-0 text-[var(--theme-card-text-color)]/70" strokeWidth={2} />
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{open ? (
|
{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 ? (
|
{!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 ? (
|
) : error ? (
|
||||||
<p className="py-3 text-center text-sm text-red-600">{error}</p>
|
<p className="py-3 text-center text-sm text-red-600">{error}</p>
|
||||||
) : loading ? (
|
) : 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 ? (
|
) : 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">
|
<ul className="flex flex-col gap-0">
|
||||||
{items.map((item) => (
|
{items.map((item) => (
|
||||||
<li
|
<li
|
||||||
key={item._id}
|
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">
|
<div className="min-w-0 flex-1">
|
||||||
<span
|
<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()}
|
{typeLabel(item.type).toUpperCase()}
|
||||||
</span>
|
</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>
|
</div>
|
||||||
<Link
|
<Link
|
||||||
to={takeHref(item._id)}
|
to={takeHref(item._id)}
|
||||||
className="shrink-0 rounded bg-[#0b4dd1] 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)}
|
{actionLabel(item.type)}
|
||||||
</Link>
|
</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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
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;
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
ready: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 };
|
||||||
|
}
|
||||||
|
|
@ -15,15 +15,20 @@
|
||||||
|
|
||||||
html {
|
html {
|
||||||
min-height: 100dvh;
|
min-height: 100dvh;
|
||||||
background-color: #f2f2f7;
|
background-color: var(--theme-bg-color, #f2f2f7);
|
||||||
|
background-image: var(--theme-bg-image, none);
|
||||||
|
background-size: var(--theme-bg-size, auto);
|
||||||
|
background-repeat: var(--theme-bg-repeat, no-repeat);
|
||||||
|
background-position: var(--theme-bg-position, center center);
|
||||||
|
background-attachment: fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
min-height: 100dvh;
|
min-height: 100dvh;
|
||||||
font-family: var(--font-sans);
|
font-family: var(--theme-body-font, var(--font-sans));
|
||||||
color: var(--color-eb-text);
|
color: var(--color-eb-text);
|
||||||
background-color: #f2f2f7;
|
background: transparent;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,35 +36,42 @@ body {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 100dvh;
|
min-height: 100dvh;
|
||||||
background-color: #f2f2f7;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.eb-gradient-banner {
|
.eb-gradient-banner {
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
34.8deg,
|
34.8deg,
|
||||||
rgb(29, 102, 239) 0%,
|
var(--theme-primary-color, rgb(29, 102, 239)) 0%,
|
||||||
rgb(30, 121, 240) 45%,
|
var(--theme-primary-color, rgb(30, 121, 240)) 45%,
|
||||||
rgb(189, 53, 230) 100%
|
var(--theme-secondary-color, rgb(189, 53, 230)) 100%
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
.eb-gradient-fab {
|
.eb-gradient-fab {
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
65deg,
|
65deg,
|
||||||
rgb(29, 102, 239) 0%,
|
var(--theme-primary-color, rgb(29, 102, 239)) 0%,
|
||||||
rgb(30, 121, 240) 40%,
|
var(--theme-primary-color, rgb(30, 121, 240)) 40%,
|
||||||
rgb(189, 53, 230) 100%
|
var(--theme-secondary-color, rgb(189, 53, 230)) 100%
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Auth / modal shells (Figma EventBuzz_Event_Mobile_V1_Register) */
|
/* Auth / modal shells (Figma EventBuzz_Event_Mobile_V1_Register) */
|
||||||
.eb-gradient-auth {
|
.eb-gradient-auth {
|
||||||
background: linear-gradient(
|
background-color: var(--theme-bg-color, transparent);
|
||||||
|
background-image: var(
|
||||||
|
--theme-bg-image,
|
||||||
|
linear-gradient(
|
||||||
77.92deg,
|
77.92deg,
|
||||||
rgb(29, 102, 239) 0%,
|
rgb(29, 102, 239) 0%,
|
||||||
rgb(30, 121, 240) 45%,
|
rgb(30, 121, 240) 45%,
|
||||||
rgb(189, 53, 230) 100%
|
rgb(189, 53, 230) 100%
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
background-size: var(--theme-bg-size, cover);
|
||||||
|
background-repeat: var(--theme-bg-repeat, no-repeat);
|
||||||
|
background-position: var(--theme-bg-position, center center);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Rewards / Leaderboard “Total Points” card */
|
/* Rewards / Leaderboard “Total Points” card */
|
||||||
|
|
@ -116,6 +128,36 @@ body {
|
||||||
animation: eb-live-blink 1.2s ease-in-out infinite;
|
animation: eb-live-blink 1.2s ease-in-out infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-bg {
|
||||||
|
background-color: var(--theme-card-color, #ffffff);
|
||||||
|
color: var(--theme-card-text-color, #333333);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
.theme-btn-bg {
|
||||||
|
background-color: var(--theme-button-color, #ef8d1d);
|
||||||
|
color: var(--theme-button-text-color, #ffffff);
|
||||||
|
}
|
||||||
|
.theme-btn-link {
|
||||||
|
color: var(--theme-button-color, #ef8d1d);
|
||||||
|
text-decoration: underline;
|
||||||
|
text-underline-offset: 2px;
|
||||||
|
text-decoration-thickness: 1px;
|
||||||
|
text-decoration-color: var(--theme-button-color, #ef8d1d);
|
||||||
|
text-decoration-style: solid;
|
||||||
|
text-decoration-skip-ink: none;
|
||||||
|
text-decoration-skip-ink: none;
|
||||||
|
}
|
||||||
|
/* @layer utilities {
|
||||||
|
.btn-bg {
|
||||||
|
background-color: var(--theme-button-color, #ef8d1d);
|
||||||
|
color: var(--theme-button-text-color, #ffffff);
|
||||||
|
}
|
||||||
|
.btn-bg:hover {
|
||||||
|
background-color: var(--theme-primary-color, #ef8d1d);
|
||||||
|
color: var(--theme-button-text-color, #ffffff);
|
||||||
|
}
|
||||||
|
} */
|
||||||
|
|
||||||
@keyframes eb-live-blink {
|
@keyframes eb-live-blink {
|
||||||
0%,
|
0%,
|
||||||
100% {
|
100% {
|
||||||
|
|
@ -127,3 +169,14 @@ body {
|
||||||
transform: scale(0.85);
|
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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
import type { AttendeeParticipant } from "./attendeeAuth";
|
import type { AttendeeParticipant } from "./attendeeAuth";
|
||||||
import { getAttendeeToken } from "./attendeeAuth";
|
import { getAttendeeToken } from "./attendeeAuth";
|
||||||
import type { LoginPreviewResponse, ResolveCodeResponse, VisitorJoinType } from "../types/visitorContext";
|
import type {
|
||||||
|
EventThemeConfig,
|
||||||
|
LoginPreviewResponse,
|
||||||
|
ResolveCodeResponse,
|
||||||
|
VisitorJoinType,
|
||||||
|
} from "../types/visitorContext";
|
||||||
|
|
||||||
function getBaseUrl(): string {
|
function getBaseUrl(): string {
|
||||||
const raw = import.meta.env.VITE_API_BASE_URL;
|
const raw = import.meta.env.VITE_API_BASE_URL;
|
||||||
|
|
@ -68,6 +73,30 @@ export async function getVisitorLoginPreview(eventId: string): Promise<LoginPrev
|
||||||
return (body as { data: LoginPreviewResponse }).data;
|
return (body as { data: LoginPreviewResponse }).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 = {
|
export type VisitorEventTrackListItem = {
|
||||||
_id: string;
|
_id: string;
|
||||||
event: string;
|
event: string;
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,8 @@ import {
|
||||||
function WhiteCard({ children }: { children: ReactNode }) {
|
function WhiteCard({ children }: { children: ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full max-w-[354px] flex-col items-center">
|
<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="w-full rounded-xl shadow-md">
|
||||||
<div className="rounded-xl bg-white px-6 py-6">{children}</div>
|
<div className="rounded-xl bg-whit card-bg px-6 py-6">{children}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -410,24 +410,24 @@ export function AiAvatarPage() {
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<div className="flex flex-col items-center py-4 text-center">
|
<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]">
|
<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-[#D32F2F]" aria-hidden />
|
<Loader2 className="h-8 w-8 animate-spin text-[var(--theme-button-color)]" aria-hidden />
|
||||||
</div>
|
</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
|
<p
|
||||||
key={pipelineMsgIndex}
|
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"
|
aria-live="polite"
|
||||||
>
|
>
|
||||||
{GENERATING_PIPELINE_MESSAGES[pipelineMsgIndex]}
|
{GENERATING_PIPELINE_MESSAGES[pipelineMsgIndex]}
|
||||||
</p>
|
</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
|
<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}%` }}
|
style={{ width: `${progressPct}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</WhiteCard>
|
</WhiteCard>
|
||||||
</AuthShell>
|
</AuthShell>
|
||||||
|
|
@ -447,12 +447,12 @@ export function AiAvatarPage() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<h1 className="mb-2 text-center text-[22px] font-semibold text-[#1a1a1a]">Your avatar</h1>
|
<h1 className="mb-2 text-center text-[22px] font-semibold">Your avatar</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">
|
||||||
Here’s your AI avatar. You can download or share it.
|
Here's your AI avatar. You can download or share it.
|
||||||
</p>
|
</p>
|
||||||
{(done.fileUrl || done.previewUrl) ? (
|
{(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
|
<img
|
||||||
ref={resultImgRef}
|
ref={resultImgRef}
|
||||||
crossOrigin="anonymous"
|
crossOrigin="anonymous"
|
||||||
|
|
@ -477,7 +477,7 @@ export function AiAvatarPage() {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={resetForNew}
|
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
|
Create another
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -485,7 +485,7 @@ export function AiAvatarPage() {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => void onDownload()}
|
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 className="h-4 w-4" />
|
||||||
Download photo
|
Download photo
|
||||||
|
|
@ -494,14 +494,14 @@ export function AiAvatarPage() {
|
||||||
</div>
|
</div>
|
||||||
{showShare ? (
|
{showShare ? (
|
||||||
<div className="mt-4">
|
<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
|
Share
|
||||||
</p>
|
</p>
|
||||||
{useNativeShare ? (
|
{useNativeShare ? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => void onNativeShare()}
|
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" />
|
<Share2 className="h-4 w-4" />
|
||||||
Share
|
Share
|
||||||
|
|
@ -513,7 +513,7 @@ export function AiAvatarPage() {
|
||||||
key={item.key}
|
key={item.key}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => void onShare(item.key)}
|
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} />
|
<ShareIcon platform={item.key} />
|
||||||
{item.label}
|
{item.label}
|
||||||
|
|
@ -544,22 +544,22 @@ export function AiAvatarPage() {
|
||||||
|
|
||||||
{meta?.accessMode === "login" && !loggedIn ? (
|
{meta?.accessMode === "login" && !loggedIn ? (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<h1 className="mb-1 text-center text-[22px] font-semibold text-[#1a1a1a]">AI Avatar</h1>
|
<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-[#666]">Sign in to create your AI avatar.</p>
|
<p className="text-center text-sm text-[var(--theme-card-text-color)]/80">Sign in to create your AI avatar.</p>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => void goLogin()}
|
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
|
Sign in to continue
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
) : !selectedAvatar ? (
|
) : !selectedAvatar ? (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h1 className="mb-1 text-center text-[22px] font-semibold text-[#1a1a1a]">Choose an avatar</h1>
|
<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-[#666]">Pick an avatar, then upload your selfie.</p>
|
<p className="text-center text-sm text-[var(--theme-card-text-color)]/80">Pick an avatar, then upload your selfie.</p>
|
||||||
{!meta?.avatars?.length ? (
|
{!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">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
{meta.avatars.map((avatar) => (
|
{meta.avatars.map((avatar) => (
|
||||||
|
|
@ -567,9 +567,9 @@ export function AiAvatarPage() {
|
||||||
key={avatar._id}
|
key={avatar._id}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setSelectedAvatar(avatar)}
|
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 ? (
|
{avatar.imageUrl ? (
|
||||||
<img
|
<img
|
||||||
src={avatar.imageUrl}
|
src={avatar.imageUrl}
|
||||||
|
|
@ -577,12 +577,12 @@ export function AiAvatarPage() {
|
||||||
className="h-full w-full object-contain"
|
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
|
No preview
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</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}
|
{avatar.name}
|
||||||
</p>
|
</p>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -592,7 +592,7 @@ export function AiAvatarPage() {
|
||||||
{err ? <p className="text-center text-sm text-red-600">{err}</p> : null}
|
{err ? <p className="text-center text-sm text-red-600">{err}</p> : null}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4 text-center">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
@ -600,13 +600,13 @@ export function AiAvatarPage() {
|
||||||
clearSelectedPhoto();
|
clearSelectedPhoto();
|
||||||
setErr(null);
|
setErr(null);
|
||||||
}}
|
}}
|
||||||
className="text-sm font-semibold text-[#D32F2F]"
|
className="text-sm font-semibold theme-btn-link"
|
||||||
>
|
>
|
||||||
← Change avatar
|
← Change avatar
|
||||||
</button>
|
</button>
|
||||||
<h1 className="mb-1 text-center text-[22px] font-semibold text-[#1a1a1a]">Upload selfie</h1>
|
<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-[#666]">
|
<p className="text-center text-sm text-[var(--theme-card-text-color)]/80">
|
||||||
Avatar: <span className="font-semibold text-[#333]">{selectedAvatar.name}</span>
|
Avatar: <span className="font-semibold">{selectedAvatar.name}</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
|
|
@ -618,7 +618,7 @@ export function AiAvatarPage() {
|
||||||
onChange={(e) => void handleFileChange(e.target.files?.[0] || null)}
|
onChange={(e) => void handleFileChange(e.target.files?.[0] || null)}
|
||||||
/>
|
/>
|
||||||
{previewUrl ? (
|
{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" />
|
<img src={previewUrl} alt="Your selfie" className="mx-auto max-h-[240px] w-full object-contain" />
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
@ -626,7 +626,7 @@ export function AiAvatarPage() {
|
||||||
type="button"
|
type="button"
|
||||||
disabled={picking}
|
disabled={picking}
|
||||||
onClick={() => fileRef.current?.click()}
|
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"}
|
{picking ? "Processing…" : file ? "Change selfie" : "Choose selfie"}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -634,7 +634,7 @@ export function AiAvatarPage() {
|
||||||
type="button"
|
type="button"
|
||||||
disabled={!file || submitting}
|
disabled={!file || submitting}
|
||||||
onClick={() => void onSubmit()}
|
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"}
|
{submitting ? "Creating avatar…" : "Create avatar"}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -121,9 +121,9 @@ export function EventLandingPage() {
|
||||||
|
|
||||||
if (!eventId) {
|
if (!eventId) {
|
||||||
return (
|
return (
|
||||||
<MobileShell className="relative">
|
<MobileShell className="relative bg-transparent">
|
||||||
<HeaderMobile showRewardsAndLogout={false} showBack={false} />
|
<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>
|
<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">
|
<Link to="/" className="mt-4 text-sm font-semibold text-[#1e72ef] underline">
|
||||||
Home
|
Home
|
||||||
|
|
@ -135,9 +135,9 @@ export function EventLandingPage() {
|
||||||
|
|
||||||
if (!event) {
|
if (!event) {
|
||||||
return (
|
return (
|
||||||
<MobileShell className="relative">
|
<MobileShell className="relative bg-transparent">
|
||||||
<HeaderMobile logoUrl={ctx?.event?.logoUrl} eventId={eventId || null} showBack={false} />
|
<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>
|
<p className="text-sm text-[#24282f]">Loading event…</p>
|
||||||
</main>
|
</main>
|
||||||
</MobileShell>
|
</MobileShell>
|
||||||
|
|
@ -145,40 +145,40 @@ export function EventLandingPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MobileShell className="relative">
|
<MobileShell className="relative bg-transparent">
|
||||||
<HeaderMobile logoUrl={event.logoUrl} eventId={eventId || null} showBack={false} />
|
<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-[#f2f2f7] pb-8 pt-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">
|
<div className="flex w-full max-w-none flex-col gap-6 px-4">
|
||||||
<section className="w-full">
|
<section className="w-full">
|
||||||
<div className="w-full rounded-lg border border-[rgba(116,112,112,0.2)] bg-white p-4 shadow-sm">
|
<div className="w-full rounded-lg card-bg p-4 shadow-md">
|
||||||
<h1 className="text-lg font-semibold text-[#24282f]">{event.name}</h1>
|
<h1 className="text-lg font-semibold">{event.name}</h1>
|
||||||
<div className="mt-2 flex flex-wrap items-center gap-2 text-xs">
|
<div className="mt-2 flex flex-wrap items-center gap-2 text-xs">
|
||||||
<EventRatingBadge ratings={event.ratings} />
|
<EventRatingBadge ratings={event.ratings} />
|
||||||
{reg.enableRatings ? (
|
{reg.enableRatings ? (
|
||||||
<Link to={ratingsHref} className="font-inter font-semibold uppercase tracking-wide text-[#1e72ef]">
|
<Link to={ratingsHref} className="font-semibold uppercase tracking-wide theme-btn-link">
|
||||||
rate now
|
rate now
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<span className="font-inter text-[#8e8e93]">Ratings off</span>
|
<span className="text-[var(--theme-card-text-color)]/50">Ratings off</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</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."}
|
{event.description?.trim() || event.subtitle?.trim() || "Welcome to this event."}
|
||||||
</p>
|
</p>
|
||||||
<button type="button" className="mt-2 font-inter text-xs font-semibold uppercase tracking-wide text-[#1e72ef]">
|
{/* <button type="button" className="mt-2 text-xs font-semibold uppercase tracking-wide text-[#1e72e theme-btn-link">
|
||||||
Read More
|
Read More
|
||||||
</button>
|
</button> */}
|
||||||
<div className="mt-4 flex flex-col gap-2 border-t border-[#f2f2f7] pt-4 text-xs text-[#24282f]">
|
<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">
|
<div className="flex items-center gap-2">
|
||||||
<Calendar className="size-4 shrink-0 text-[#8e8e93]" strokeWidth={1.75} />
|
<Calendar className="size-4 shrink-0 text-[var(--theme-button-color)]" strokeWidth={1.75} />
|
||||||
<span className="font-inter">{schedule || "—"}</span>
|
<span className="">{schedule || "—"}</span>
|
||||||
</div>
|
</div>
|
||||||
{event.location?.trim() ? (
|
{event.location?.trim() ? (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<MapPin className="size-4 shrink-0 text-[#8e8e93]" strokeWidth={1.75} />
|
<MapPin className="size-4 shrink-0 text-[var(--theme-button-color)]" strokeWidth={1.75} />
|
||||||
<span className="font-inter">{event.location.trim()}</span>
|
<span className="">{event.location.trim()}</span>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -186,12 +186,12 @@ export function EventLandingPage() {
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{bookmarkedIds.size > 0 ? (
|
{bookmarkedIds.size > 0 ? (
|
||||||
<section className="w-full">
|
<section className="w-full card-bg rounded-lg shadow-md p-4">
|
||||||
<div className="w-full px-4">
|
|
||||||
<h2 className="font-inter text-base font-semibold text-[#24282f]">My Sessions</h2>
|
<h2 className="text-md font-semibold">My Sessions</h2>
|
||||||
</div>
|
|
||||||
{agendaLoading && !agendaSessions.length ? (
|
{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
|
<LandingMySessionsList
|
||||||
sessions={bookmarkedSessionsSorted}
|
sessions={bookmarkedSessionsSorted}
|
||||||
|
|
@ -203,17 +203,17 @@ export function EventLandingPage() {
|
||||||
</section>
|
</section>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<section className="w-full">
|
<section className="w-full card-bg rounded-lg shadow-md p-4">
|
||||||
<div className="w-full px-4">
|
|
||||||
<h2 className="font-inter text-base font-semibold text-[#24282f]">Agenda</h2>
|
<h2 className="text-base font-semibold">Agenda</h2>
|
||||||
</div>
|
|
||||||
{agendaErr ? (
|
{agendaErr ? (
|
||||||
<p className="mt-3 px-4 text-sm text-red-600">{agendaErr}</p>
|
<p className="mt-3 px-4 text-sm text-red-600">{agendaErr}</p>
|
||||||
) : null}
|
) : null}
|
||||||
{agendaLoading && !agendaSessions.length ? (
|
{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 ? (
|
) : 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
|
<LandingAgendaCards
|
||||||
days={agendaDays}
|
days={agendaDays}
|
||||||
|
|
|
||||||
|
|
@ -109,11 +109,11 @@ export function EventTrackPage() {
|
||||||
|
|
||||||
if (!eventId || !trackId) {
|
if (!eventId || !trackId) {
|
||||||
return (
|
return (
|
||||||
<MobileShell className="relative">
|
<MobileShell className="relative bg-transparent">
|
||||||
<HeaderMobile eventId={eventId || null} />
|
<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>
|
<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
|
Home
|
||||||
</Link>
|
</Link>
|
||||||
</main>
|
</main>
|
||||||
|
|
@ -123,10 +123,10 @@ export function EventTrackPage() {
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<MobileShell className="relative">
|
<MobileShell className="relative bg-transparent">
|
||||||
<HeaderMobile eventId={eventId || null} />
|
<HeaderMobile eventId={eventId || null} />
|
||||||
<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 track…</p>
|
<p className="text-sm text-[var(--theme-card-text-color)]">Loading track…</p>
|
||||||
</main>
|
</main>
|
||||||
</MobileShell>
|
</MobileShell>
|
||||||
);
|
);
|
||||||
|
|
@ -134,9 +134,9 @@ export function EventTrackPage() {
|
||||||
|
|
||||||
if (err || !track) {
|
if (err || !track) {
|
||||||
return (
|
return (
|
||||||
<MobileShell className="relative">
|
<MobileShell className="relative bg-transparent">
|
||||||
<HeaderMobile eventId={eventId || null} />
|
<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>
|
<p className="text-center text-sm text-red-600">{err ?? "Track not found."}</p>
|
||||||
</main>
|
</main>
|
||||||
</MobileShell>
|
</MobileShell>
|
||||||
|
|
@ -160,42 +160,44 @@ export function EventTrackPage() {
|
||||||
(ctx?.eventId === eventId && ctx.event?.logoUrl?.trim() ? ctx.event.logoUrl.trim() : undefined);
|
(ctx?.eventId === eventId && ctx.event?.logoUrl?.trim() ? ctx.event.logoUrl.trim() : undefined);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MobileShell className="relative">
|
<MobileShell className="relative bg-transparent">
|
||||||
<HeaderMobile logoUrl={headerLogo} eventId={eventId || null} />
|
<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="mx-auto w-full max-w-[370px]">
|
||||||
<div className="mb-6 flex flex-col gap-4">
|
<div className="mb-6 flex flex-col gap-4 card-bg p-4 rounded-lg">
|
||||||
<div className="inline-flex w-fit rounded bg-[#e5e5ea] px-2 py-0.5">
|
<div className="inline-flex w-fit rounded bg-zinc-200 px-2 py-0.5">
|
||||||
<span className="text-[10px] tracking-wide text-[#24282f]">{eventName.toUpperCase()}</span>
|
<span className="text-[10px] tracking-wide">{eventName.toUpperCase()}</span>
|
||||||
</div>
|
</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() ? (
|
{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}
|
) : null}
|
||||||
<p className="text-[10px] uppercase tracking-wide text-[#8e8e93]">{trackTypeLabel}</p>
|
<p className="text-xs uppercase font-semibold tracking-wide text-[var(--theme-card-text-color)]/70">{trackTypeLabel}</p>
|
||||||
<div className="flex flex-col gap-1 text-xs text-[#24282f]">
|
<div className="flex flex-col gap-2 text-xs text-[var(--theme-card-text-color)]/80">
|
||||||
<div className="flex items-center gap-2">
|
<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>
|
<span className="font-inter tracking-wide">{schedule || "—"}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<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>
|
<span className="font-inter tracking-wide">See sessions below</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<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>
|
<span className="font-inter tracking-wide">Track agenda (sample)</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-4 flex w-full max-w-[370px] flex-col gap-2">
|
<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
|
<button
|
||||||
type="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">
|
<div className="flex items-center gap-2">
|
||||||
<Calendar className="size-4 shrink-0 text-[#24282f]" strokeWidth={1.75} />
|
<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} />
|
<ChevronDown className="size-4 shrink-0 text-[#1d66ef]" strokeWidth={2} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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">
|
<div className="flex flex-col gap-6">
|
||||||
{agendaRows.map((row) => (
|
{agendaRows.map((row) => (
|
||||||
<AgendaRow key={row.time + row.sessionType} row={row} />
|
<AgendaRow key={row.time + row.sessionType} row={row} />
|
||||||
|
|
@ -213,9 +216,9 @@ export function EventTrackPage() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="mt-6 text-center text-xs text-[#24282f]/60">
|
<p className="mt-6 text-center text-sm">
|
||||||
<Link
|
<Link
|
||||||
className="text-[#1e72ef] underline"
|
className="text-[var(--theme-text-color)] underline"
|
||||||
to={`/questions?event_id=${encodeURIComponent(eventId)}`}
|
to={`/questions?event_id=${encodeURIComponent(eventId)}`}
|
||||||
>
|
>
|
||||||
Ask & session questions
|
Ask & session questions
|
||||||
|
|
|
||||||
|
|
@ -43,50 +43,50 @@ export function EventTrackV2Page() {
|
||||||
const [tab, setTab] = useState(0);
|
const [tab, setTab] = useState(0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MobileShell className="relative">
|
<MobileShell className="relative bg-transparent">
|
||||||
<HeaderMobile
|
<HeaderMobile
|
||||||
logoUrl={ctx?.eventId === eventId ? ctx.event?.logoUrl : undefined}
|
logoUrl={ctx?.eventId === eventId ? ctx.event?.logoUrl : undefined}
|
||||||
eventId={eventId || null}
|
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="mx-auto w-full max-w-[370px]">
|
||||||
<div className="mb-4 flex flex-col gap-4">
|
<div className="mb-4 flex flex-col gap-4 card-bg p-4 rounded-lg">
|
||||||
<span className="inline-flex w-fit rounded bg-[#e5e5ea] px-2 py-0.5 text-[10px] text-[#24282f]">
|
<span className="inline-flex w-fit rounded bg-zinc-200 px-2 py-0.5 text-[10px] text-gray-600">
|
||||||
NAPCON JAIPUR 2026
|
NAPCON JAIPUR 2026
|
||||||
</span>
|
</span>
|
||||||
<h1 className="text-lg font-semibold text-[#24282f]">This will be Track Name</h1>
|
<h1 className="text-lg font-semibold">This will be Track Name</h1>
|
||||||
<div className="flex items-center gap-2 text-xs text-[#24282f]">
|
<div className="flex items-center gap-2 text-xs">
|
||||||
<MapPin className="size-4 shrink-0" strokeWidth={1.75} />
|
<MapPin className="size-4 shrink-0 text-[var(--theme-button-color)]" strokeWidth={1.75} />
|
||||||
<span className="font-inter">Health and Wellness Expo</span>
|
<span className="font-inter">Health and Wellness Expo</span>
|
||||||
</div>
|
</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">
|
<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>
|
<span className="font-inter">November 22, 2025 - November 23, 2025</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<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>
|
<span className="font-inter">6:00 PM - 10:00 PM</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<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>
|
<span className="font-inter">400 Participants (Capacity)</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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) => (
|
{dates.map((d, i) => (
|
||||||
<button
|
<button
|
||||||
key={d}
|
key={d}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setTab(i)}
|
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
|
tab === i
|
||||||
? "border-b-2 border-[#1e72ef] font-semibold text-[#24282f]"
|
? "border-b-2 border-[var(--theme-button-color)] font-semibold"
|
||||||
: "font-normal text-[#24282f]"
|
: "font-normal"
|
||||||
}`}
|
}`}
|
||||||
style={{ marginBottom: -2 }}
|
style={{ marginBottom: -2 }}
|
||||||
>
|
>
|
||||||
|
|
@ -95,7 +95,7 @@ export function EventTrackV2Page() {
|
||||||
))}
|
))}
|
||||||
</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-md card-bg p-4 shadow-md">
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
{rowsV2.map((row) => (
|
{rowsV2.map((row) => (
|
||||||
<AgendaRowTrackV2 key={row.time + row.sessionType} row={row} />
|
<AgendaRowTrackV2 key={row.time + row.sessionType} row={row} />
|
||||||
|
|
@ -103,8 +103,8 @@ export function EventTrackV2Page() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="mt-4 text-center text-xs">
|
<p className="mt-4 text-center text-sm">
|
||||||
<Link className="text-[#1e72ef] underline" to="/event">
|
<Link className="text-[var(--theme-text-color)] underline" to="/event">
|
||||||
Back to event
|
Back to event
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -98,14 +98,16 @@ export function FeedbackPage() {
|
||||||
return (
|
return (
|
||||||
<MobileShell className="relative">
|
<MobileShell className="relative">
|
||||||
<HeaderMobile logoUrl={ctx?.event?.logoUrl} eventId={null} />
|
<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]">
|
<div className="mx-auto w-full max-w-[370px]">
|
||||||
<h1 className="font-inter text-xl font-semibold text-[#24282f]">Feedback</h1>
|
<div className="card-bg rounded-lg shadow-md p-4">
|
||||||
<p className="mt-3 text-sm text-[rgba(36,40,47,0.67)]">Open feedback from your event or session link.</p>
|
<h1 className="font-inter text-xl font-semibold text-[var(--theme-card-text-color)]">Feedback</h1>
|
||||||
<Link to="/" className="mt-4 inline-block text-sm font-semibold text-[#1e72ef] underline">
|
<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
|
Home
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</MobileShell>
|
</MobileShell>
|
||||||
);
|
);
|
||||||
|
|
@ -115,14 +117,16 @@ export function FeedbackPage() {
|
||||||
return (
|
return (
|
||||||
<MobileShell className="relative">
|
<MobileShell className="relative">
|
||||||
<HeaderMobile logoUrl={eventSnapshot?.logoUrl} eventId={eventId || null} />
|
<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]">
|
<div className="mx-auto w-full max-w-[370px]">
|
||||||
<h1 className="font-inter text-xl font-semibold text-[#24282f]">Feedback</h1>
|
<div className="card-bg rounded-lg shadow-md p-4">
|
||||||
<p className="mt-3 text-sm text-[rgba(36,40,47,0.67)]">Feedback is disabled for this event.</p>
|
<h1 className="text-xl font-semibold text-[var(--theme-card-text-color)]">Feedback</h1>
|
||||||
<Link to="/event" className="mt-4 inline-block text-sm font-semibold text-[#1e72ef] underline">
|
<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
|
Back to event
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</MobileShell>
|
</MobileShell>
|
||||||
);
|
);
|
||||||
|
|
@ -131,27 +135,29 @@ export function FeedbackPage() {
|
||||||
return (
|
return (
|
||||||
<MobileShell className="relative">
|
<MobileShell className="relative">
|
||||||
<HeaderMobile logoUrl={eventSnapshot?.logoUrl} eventId={eventId || null} />
|
<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]">
|
<div className="mx-auto w-full max-w-[370px]">
|
||||||
<h1 className="font-inter text-xl font-semibold text-[#24282f]">{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 ? (
|
{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 ? (
|
) : schemaErr ? (
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<p className="text-sm text-red-600">{schemaErr}</p>
|
<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.
|
Ask the organizer to publish an active feedback form for this event.
|
||||||
</p>
|
</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
|
Back to event
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
) : schema ? (
|
) : schema ? (
|
||||||
<div className="mt-6">
|
<div className="mt-2">
|
||||||
<DynamicFeedbackForm eventId={eventId} schema={schema} variant="light" />
|
<DynamicFeedbackForm eventId={eventId} schema={schema} variant="light" className="px-0" />
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</MobileShell>
|
</MobileShell>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -9,25 +9,22 @@ export function Home() {
|
||||||
return (
|
return (
|
||||||
<AuthShell>
|
<AuthShell>
|
||||||
<div className="flex w-full max-w-[354px] flex-col items-center">
|
<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="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 />
|
{/* <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">
|
<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 flex-col items-center gap-3 text-center">
|
||||||
<div
|
<div
|
||||||
className="flex size-12 items-center justify-center rounded-full"
|
className="flex size-12 items-center justify-center rounded-full bg-[var(--theme-button-color)]/10"
|
||||||
style={{
|
|
||||||
background: "linear-gradient(135deg, rgba(52,167,242,0.12) 0%, rgba(29,102,239,0.16) 100%)",
|
|
||||||
}}
|
|
||||||
aria-hidden
|
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>
|
||||||
<div className="space-y-1.5">
|
<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
|
Join with your code
|
||||||
</h1>
|
</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
|
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.
|
invite, on the event screen, or on a poster at the venue.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -35,7 +32,7 @@ export function Home() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label className="flex flex-col gap-2">
|
<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
|
<input
|
||||||
id="join-event-code"
|
id="join-event-code"
|
||||||
type="text"
|
type="text"
|
||||||
|
|
@ -46,7 +43,7 @@ export function Home() {
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
autoCapitalize="characters"
|
autoCapitalize="characters"
|
||||||
spellCheck={false}
|
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>
|
</label>
|
||||||
|
|
||||||
|
|
@ -59,7 +56,7 @@ export function Home() {
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={submitting || !code.trim()}
|
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"}
|
{submitting ? "Joining…" : "Join"}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -77,19 +77,42 @@ export function LeaderboardPage() {
|
||||||
return (
|
return (
|
||||||
<MobileShell className="relative">
|
<MobileShell className="relative">
|
||||||
<HeaderMobile logoUrl={ctx?.event?.logoUrl} eventId={eventId || null} />
|
<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="eb-gradient-auth flex flex-1 flex-col items-center px-6 pb-10 pt-2">
|
||||||
<Award className="size-[68px] text-amber-300" strokeWidth={1.25} />
|
<div className="mt-6 w-full max-w-[354px] rounded-xl card-bg shadow-md">
|
||||||
<h1 className="mt-1 font-inter text-xl font-semibold text-white">Leaderboard</h1>
|
|
||||||
|
|
||||||
<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="flex gap-2 justify-between w-full p-4">
|
||||||
<div className="p-4">
|
<div className="flex gap-2 justify-between w-full">
|
||||||
<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 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>
|
<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">
|
<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)]">
|
<span className="font-inter text-5xl font-bold drop-shadow-[0_4px_4px_rgba(0,0,0,0.25)]">
|
||||||
{!signedIn ? "—" : loading ? "…" : yourRank != null ? yourRank : "—"}
|
{!signedIn ? "—" : loading ? "…" : yourRank != null ? yourRank : "—"}
|
||||||
</span>
|
</span>
|
||||||
<span className="rounded-xl bg-[#cff7d3] px-4 py-3 text-xs font-medium text-[#009951] shadow-sm">
|
<span className="rounded-xl bg-[#cff7d3] px-4 py-2 text-sm font-medium text-[#009951] shadow-sm">
|
||||||
{!signedIn
|
{!signedIn
|
||||||
? "Sign in"
|
? "Sign in"
|
||||||
: loading
|
: loading
|
||||||
|
|
@ -99,14 +122,14 @@ export function LeaderboardPage() {
|
||||||
: "0 points"}
|
: "0 points"}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> */}
|
||||||
<h2 className="mt-6 font-inter text-base font-semibold text-[#24282f]">Top participants</h2>
|
<h2 className="mt-6 font-inter text-base font-semibold">Leaderboard</h2>
|
||||||
{err ? (
|
{err ? (
|
||||||
<p className="mt-3 text-xs text-red-600">{err}</p>
|
<p className="mt-3 text-xs text-red-600">{err}</p>
|
||||||
) : loading && rows.length === 0 ? (
|
) : 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 ? (
|
) : 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.
|
No scores yet. Complete activities and earn points when organizers enable rewards.
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -116,12 +139,12 @@ export function LeaderboardPage() {
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
key={row.participantId}
|
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
|
#{row.rank} · {row.total} pts
|
||||||
</span>
|
</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}
|
{medal ? <Medal tier={medal} /> : null}
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
|
|
@ -133,7 +156,7 @@ export function LeaderboardPage() {
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
to={eventId ? `/rewards?event_id=${encodeURIComponent(eventId)}` : "/rewards"}
|
to={eventId ? `/rewards?event_id=${encodeURIComponent(eventId)}` : "/rewards"}
|
||||||
className="mt-6 font-inter font-semibold text-white underline"
|
className="mt-6 font-inter font-semibold text-[var(--theme-text-color)] underline"
|
||||||
>
|
>
|
||||||
Rewards
|
Rewards
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
||||||
|
|
@ -240,9 +240,9 @@ export function LoginPage() {
|
||||||
<AuthShell>
|
<AuthShell>
|
||||||
<div className="flex w-full max-w-[354px] flex-col items-center">
|
<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="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">
|
<div className="rounded-xl card-bg">
|
||||||
<div className="px-6 pb-2 pt-6">
|
<div className="px-6 pb-2 pt-6">
|
||||||
<div className="flex h-[103px] items-center justify-center rounded-xl border border-[#f2f2f7] px-4">
|
<div className="flex h-[103px] items-center justify-center rounded-xl border border-[var(--theme-card-color)]/25 px-4">
|
||||||
{preview?.logoUrl ? (
|
{preview?.logoUrl ? (
|
||||||
<img
|
<img
|
||||||
src={preview.logoUrl}
|
src={preview.logoUrl}
|
||||||
|
|
@ -256,13 +256,13 @@ export function LoginPage() {
|
||||||
</div>
|
</div>
|
||||||
<form onSubmit={handleSubmit} className="flex flex-col gap-8 px-6 pb-8 pt-2">
|
<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">
|
<div className="flex flex-col items-center gap-4 text-center">
|
||||||
<p className="font-inter 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">
|
<div className="min-w-0 space-y-1">
|
||||||
<p className="font-inter 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}
|
{headline}
|
||||||
</p>
|
</p>
|
||||||
{preview?.subtitle ? (
|
{preview?.subtitle ? (
|
||||||
<p className="font-inter 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}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
{previewError ? (
|
{previewError ? (
|
||||||
|
|
@ -276,11 +276,11 @@ export function LoginPage() {
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
{showMeta ? (
|
{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 ? (
|
{schedule ? (
|
||||||
<>
|
<>
|
||||||
<Calendar className="size-4 shrink-0" strokeWidth={1.75} />
|
<Calendar className="size-4 shrink-0" strokeWidth={1.75} />
|
||||||
<span className="font-inter">{schedule}</span>
|
<span className="">{schedule}</span>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
{schedule && preview?.location?.trim() ? (
|
{schedule && preview?.location?.trim() ? (
|
||||||
|
|
@ -289,7 +289,7 @@ export function LoginPage() {
|
||||||
{preview?.location?.trim() ? (
|
{preview?.location?.trim() ? (
|
||||||
<>
|
<>
|
||||||
<MapPin className="size-4 shrink-0" strokeWidth={1.75} />
|
<MapPin className="size-4 shrink-0" strokeWidth={1.75} />
|
||||||
<span className="font-inter">{preview.location.trim()}</span>
|
<span className="">{preview.location.trim()}</span>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -298,25 +298,25 @@ export function LoginPage() {
|
||||||
|
|
||||||
{showInviteField ? (
|
{showInviteField ? (
|
||||||
<label className="flex flex-col gap-2">
|
<label className="flex flex-col gap-2">
|
||||||
<span className="font-inter 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
|
<input
|
||||||
value={inviteInput}
|
value={inviteInput}
|
||||||
onChange={(e) => setInviteInput(e.target.value)}
|
onChange={(e) => setInviteInput(e.target.value)}
|
||||||
placeholder="Enter your invite code"
|
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]"
|
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>
|
</label>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{showEmail ? (
|
{showEmail ? (
|
||||||
<label className="flex flex-col gap-2">
|
<label className="flex flex-col gap-2">
|
||||||
<span className="font-inter 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
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
value={email}
|
value={email}
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
placeholder="Enter your email address"
|
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-inter text-sm tracking-wide text-[#24282f] placeholder:text-[rgba(36,40,47,0.6)]"
|
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>
|
</label>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
@ -335,7 +335,7 @@ export function LoginPage() {
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={loading || loadingPreview}
|
disabled={loading || loadingPreview}
|
||||||
className="w-full 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="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}
|
{loading ? "Please wait…" : reg.primaryCtaLabel}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ function WhiteCard({ children, wide }: { children: ReactNode; wide?: boolean })
|
||||||
return (
|
return (
|
||||||
<div className={`flex w-full ${wide ? "max-w-[480px]" : "max-w-[354px]"} flex-col items-center`}>
|
<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="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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -432,18 +432,18 @@ export function PhotoSearchPage() {
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<h1 className="mb-1 text-center text-[22px] font-semibold text-[#1a1a1a]">Find Photos</h1>
|
<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-[#666]">
|
<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"}.
|
Upload a selfie to find photos of you from {meta?.eventName || "the event"}.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{meta?.accessMode === "login" && !loggedIn ? (
|
{meta?.accessMode === "login" && !loggedIn ? (
|
||||||
<div className="space-y-3">
|
<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
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => void goLogin()}
|
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
|
Sign in to continue
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -460,14 +460,14 @@ export function PhotoSearchPage() {
|
||||||
onChange={(e) => onPick(e.target.files?.[0] || null)}
|
onChange={(e) => onPick(e.target.files?.[0] || null)}
|
||||||
/>
|
/>
|
||||||
{previewUrl ? (
|
{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" />
|
<img src={previewUrl} alt="Your selfie" className="mx-auto max-h-[240px] w-full object-contain" />
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => fileRef.current?.click()}
|
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"}
|
{file ? "Change photo" : "Choose photo"}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -477,7 +477,7 @@ export function PhotoSearchPage() {
|
||||||
type="button"
|
type="button"
|
||||||
disabled={!file || searching}
|
disabled={!file || searching}
|
||||||
onClick={() => void onSearch()}
|
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" />
|
<Search className="h-4 w-4" />
|
||||||
{searching ? "Searching…" : "Find photos"}
|
{searching ? "Searching…" : "Find photos"}
|
||||||
|
|
|
||||||
|
|
@ -59,9 +59,9 @@ export function PhotoWallPresentPage() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="eb-gradient-auth min-h-dvh text-white">
|
<div className="eb-gradient-auth min-h-dvh text-white">
|
||||||
<div className="px-4 pb-3 pt-4 text-center">
|
<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-white/70">Photo Wall</p>
|
<p className="text-xs uppercase tracking-[0.2em] text-[var(--theme-card-text-color)]/70">Photo Wall</p>
|
||||||
<h1 className="font-inter text-xl font-semibold sm:text-2xl">{data.eventName}</h1>
|
<h1 className="text-xl font-semibold sm:text-2xl text-[var(--theme-card-text-color)]">{data.eventName}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{data.photos.length === 0 ? (
|
{data.photos.length === 0 ? (
|
||||||
|
|
@ -98,7 +98,7 @@ export function PhotoWallPresentPage() {
|
||||||
/>
|
/>
|
||||||
{showNames && p.displayName ? (
|
{showNames && p.displayName ? (
|
||||||
<p
|
<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}
|
title={p.displayName}
|
||||||
>
|
>
|
||||||
{p.displayName}
|
{p.displayName}
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ function WhiteCard({ children }: { children: ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full max-w-[354px] flex-col items-center">
|
<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="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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -371,7 +371,7 @@ export function PhotoWallUploadPage() {
|
||||||
return (
|
return (
|
||||||
<AuthShell showLogo={false}>
|
<AuthShell showLogo={false}>
|
||||||
<WhiteCard>
|
<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>
|
</WhiteCard>
|
||||||
</AuthShell>
|
</AuthShell>
|
||||||
);
|
);
|
||||||
|
|
@ -398,10 +398,10 @@ export function PhotoWallUploadPage() {
|
||||||
<PhotoWallEventHeader event={meta.event} display={meta.eventHeader || null} />
|
<PhotoWallEventHeader event={meta.event} display={meta.eventHeader || null} />
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : 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"}
|
{done.framed ? "Your framed photo" : "Photo uploaded"}
|
||||||
</h1>
|
</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
|
{done.awaitingApproval
|
||||||
? "Thanks! Your photo is waiting for approval."
|
? "Thanks! Your photo is waiting for approval."
|
||||||
: done.framed
|
: done.framed
|
||||||
|
|
@ -435,7 +435,7 @@ export function PhotoWallUploadPage() {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={resetForNewUpload}
|
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" />
|
<ImagePlus className="h-4 w-4" />
|
||||||
Edit / Upload new photo
|
Edit / Upload new photo
|
||||||
|
|
@ -453,7 +453,7 @@ export function PhotoWallUploadPage() {
|
||||||
</div>
|
</div>
|
||||||
{showShare ? (
|
{showShare ? (
|
||||||
<div className="mt-4">
|
<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
|
Share
|
||||||
</p>
|
</p>
|
||||||
{useNativeShare ? (
|
{useNativeShare ? (
|
||||||
|
|
@ -496,18 +496,18 @@ export function PhotoWallUploadPage() {
|
||||||
<PhotoWallEventHeader event={meta.event} display={meta.eventHeader || null} />
|
<PhotoWallEventHeader event={meta.event} display={meta.eventHeader || null} />
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<h1 className="mb-1 text-center text-[22px] font-semibold text-[#1a1a1a]">Upload your photo</h1>
|
<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-[#666]">
|
<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."}
|
{meta?.form?.description || "Add a selfie to the event photo wall."}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{meta?.accessMode === "login" && !loggedIn ? (
|
{meta?.accessMode === "login" && !loggedIn ? (
|
||||||
<div className="space-y-3">
|
<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
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => void goLogin()}
|
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
|
Sign in to continue
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -539,20 +539,20 @@ export function PhotoWallUploadPage() {
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
{file ? (
|
{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}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{meta?.askUploaderName ? (
|
{meta?.askUploaderName ? (
|
||||||
<label className="block text-sm">
|
<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>
|
Your name <span className="text-red-500">*</span>
|
||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={uploaderName}
|
value={uploaderName}
|
||||||
onChange={(e) => setUploaderName(e.target.value)}
|
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"
|
placeholder="Name shown on the wall"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
|
@ -566,7 +566,7 @@ export function PhotoWallUploadPage() {
|
||||||
if (f.type === "textarea") {
|
if (f.type === "textarea") {
|
||||||
return (
|
return (
|
||||||
<label key={key} className="block text-sm">
|
<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}
|
{f.label}
|
||||||
{required ? <span className="text-red-500"> *</span> : null}
|
{required ? <span className="text-red-500"> *</span> : null}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -575,14 +575,14 @@ export function PhotoWallUploadPage() {
|
||||||
onChange={(e) => setValues((v) => ({ ...v, [key]: e.target.value }))}
|
onChange={(e) => setValues((v) => ({ ...v, [key]: e.target.value }))}
|
||||||
required={required}
|
required={required}
|
||||||
rows={3}
|
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>
|
</label>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<label key={key} className="block text-sm">
|
<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}
|
{f.label}
|
||||||
{required ? <span className="text-red-500"> *</span> : null}
|
{required ? <span className="text-red-500"> *</span> : null}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -591,7 +591,7 @@ export function PhotoWallUploadPage() {
|
||||||
value={String(values[key] ?? "")}
|
value={String(values[key] ?? "")}
|
||||||
onChange={(e) => setValues((v) => ({ ...v, [key]: e.target.value }))}
|
onChange={(e) => setValues((v) => ({ ...v, [key]: e.target.value }))}
|
||||||
required={required}
|
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>
|
</label>
|
||||||
);
|
);
|
||||||
|
|
@ -602,7 +602,7 @@ export function PhotoWallUploadPage() {
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={!file || submitting || picking}
|
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"}
|
{submitting ? "Uploading…" : "Upload"}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -103,15 +103,18 @@ export function Questions() {
|
||||||
|
|
||||||
if (!eventId) {
|
if (!eventId) {
|
||||||
return (
|
return (
|
||||||
<MobileShell className="relative">
|
<MobileShell className="relative bg-transparent">
|
||||||
<HeaderMobile eventId={eventId || null} />
|
<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]">
|
<p className="text-center text-sm text-[#24282f]">
|
||||||
Add <span className="font-mono">event_id</span> to the URL to open questions.
|
Add <span className="font-mono">event_id</span> to the URL to open questions.
|
||||||
</p>
|
</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
|
Home
|
||||||
</Link>
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
</MobileShell>
|
</MobileShell>
|
||||||
);
|
);
|
||||||
|
|
@ -119,21 +122,24 @@ export function Questions() {
|
||||||
|
|
||||||
if (!sessionId) {
|
if (!sessionId) {
|
||||||
return (
|
return (
|
||||||
<MobileShell className="relative">
|
<MobileShell className="relative bg-transparent">
|
||||||
<HeaderMobile logoUrl={headerLogoUrl} eventId={eventId || null} />
|
<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]">
|
<div className="mx-auto w-full max-w-[370px]">
|
||||||
<h1 className="text-lg font-semibold text-[#24282f]">{ev?.name ?? "Event"}</h1>
|
<div className="card-bg p-4 rounded-lg">
|
||||||
<p className="mt-3 text-sm leading-relaxed text-[rgba(36,40,47,0.75)]">
|
|
||||||
|
<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.
|
Open a session from the agenda to view all audience questions and ask your own.
|
||||||
</p>
|
</p>
|
||||||
<Link
|
<Link
|
||||||
to={trackHref}
|
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
|
Browse sessions
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<HyperNavigationFAB eventId={eventId} sessionId={sessionId || undefined} />
|
<HyperNavigationFAB eventId={eventId} sessionId={sessionId || undefined} />
|
||||||
</MobileShell>
|
</MobileShell>
|
||||||
|
|
@ -141,31 +147,34 @@ export function Questions() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MobileShell className="relative">
|
<MobileShell className="relative bg-transparent">
|
||||||
<HeaderMobile logoUrl={headerLogoUrl} eventId={eventId || null} />
|
<HeaderMobile logoUrl={headerLogoUrl} eventId={eventId || null} />
|
||||||
|
|
||||||
<main className="relative z-0 flex flex-1 flex-col bg-[#f2f2f7] 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-4">
|
<div className="mx-auto w-full max-w-[370px] px-3">
|
||||||
<div className="mb-1 inline-flex w-fit rounded bg-[#e5e5ea] px-2 py-0.5">
|
<div className="flex flex-col gap-2 card-bg p-4 rounded-lg">
|
||||||
<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>
|
</div>
|
||||||
<h1 className="text-lg font-semibold text-[#24282f]">
|
<h1 className="text-lg font-semibold">
|
||||||
{sessionTitle ?? "Session questions"}
|
{sessionTitle ?? "Session questions"}
|
||||||
</h1>
|
</h1>
|
||||||
{ev?.name ? (
|
{ev?.name ? (
|
||||||
<p className="mt-1 text-xs text-[rgba(36,40,47,0.65)]">{ev.name}</p>
|
<p className="mt-1 text-xs text-[var(--theme-card-text-color)]/80">{ev.name}</p>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setAskOpen(true)}
|
onClick={() => setAskOpen(true)}
|
||||||
className="mt-4 flex h-11 w-full items-center justify-between rounded-full bg-[#ef8d1d] px-[18px] text-left shadow-sm transition hover:bg-[#e0800c]"
|
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
|
Ask a question
|
||||||
</span>
|
</span>
|
||||||
<ChevronRight className="size-4 text-white" strokeWidth={2.5} />
|
<ChevronRight className="size-4 text-white" strokeWidth={2.5} />
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="mt-6 flex flex-col gap-3">
|
<div className="mt-6 flex flex-col gap-3">
|
||||||
{listLoading ? (
|
{listLoading ? (
|
||||||
|
|
@ -186,8 +195,8 @@ export function Questions() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{sessionHref ? (
|
{sessionHref ? (
|
||||||
<p className="mt-6 text-center text-xs">
|
<p className="mt-6 text-center text-md">
|
||||||
<Link className="text-[#1e72ef] underline" to={sessionHref}>
|
<Link className="text-[var(--theme-text-color)] underline" to={sessionHref}>
|
||||||
Back to session
|
Back to session
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -636,11 +636,13 @@ export function QuizPollTakePage() {
|
||||||
return (
|
return (
|
||||||
<MobileShell className="relative">
|
<MobileShell className="relative">
|
||||||
<HeaderMobile eventId={null} />
|
<HeaderMobile 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]">Invalid quiz link.</p>
|
<div className="mx-auto w-full max-w-[370px] rounded-lg card-bg p-4 text-center shadow-sm">
|
||||||
<Link to="/" className="mt-4 text-sm font-semibold text-[#1e72ef] underline">
|
<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
|
Home
|
||||||
</Link>
|
</Link>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</MobileShell>
|
</MobileShell>
|
||||||
);
|
);
|
||||||
|
|
@ -650,8 +652,10 @@ export function QuizPollTakePage() {
|
||||||
return (
|
return (
|
||||||
<MobileShell className="relative">
|
<MobileShell className="relative">
|
||||||
<HeaderMobile logoUrl={headerLogo} eventId={eventId} />
|
<HeaderMobile logoUrl={headerLogo} eventId={eventId} />
|
||||||
<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…</p>
|
<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>
|
</main>
|
||||||
</MobileShell>
|
</MobileShell>
|
||||||
);
|
);
|
||||||
|
|
@ -661,11 +665,13 @@ export function QuizPollTakePage() {
|
||||||
return (
|
return (
|
||||||
<MobileShell className="relative">
|
<MobileShell className="relative">
|
||||||
<HeaderMobile logoUrl={headerLogo} eventId={eventId} />
|
<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>
|
<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
|
Back to session
|
||||||
</Link>
|
</Link>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</MobileShell>
|
</MobileShell>
|
||||||
);
|
);
|
||||||
|
|
@ -676,8 +682,10 @@ export function QuizPollTakePage() {
|
||||||
return (
|
return (
|
||||||
<MobileShell className="relative">
|
<MobileShell className="relative">
|
||||||
<HeaderMobile logoUrl={headerLogo} eventId={eventId} />
|
<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">
|
||||||
<p className="text-center text-sm text-[#24282f]">Returning to session…</p>
|
<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>
|
</main>
|
||||||
</MobileShell>
|
</MobileShell>
|
||||||
);
|
);
|
||||||
|
|
@ -687,15 +695,17 @@ export function QuizPollTakePage() {
|
||||||
return (
|
return (
|
||||||
<MobileShell className="relative">
|
<MobileShell className="relative">
|
||||||
<HeaderMobile logoUrl={headerLogo} eventId={eventId} />
|
<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">
|
||||||
<p className="font-inter text-lg font-semibold text-[#24282f]">Thank you</p>
|
<div className="mx-auto w-full max-w-[370px] rounded-lg card-bg p-4 text-center shadow-sm">
|
||||||
<p className="mt-2 text-center text-sm text-[#24282f]">Your response was submitted.</p>
|
<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
|
<Link
|
||||||
to={sessionBackHref}
|
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
|
Back to session
|
||||||
</Link>
|
</Link>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</MobileShell>
|
</MobileShell>
|
||||||
);
|
);
|
||||||
|
|
@ -712,20 +722,20 @@ export function QuizPollTakePage() {
|
||||||
<div className="flex shrink-0 flex-col">
|
<div className="flex shrink-0 flex-col">
|
||||||
<HeaderMobile logoUrl={headerLogo} eventId={eventId} />
|
<HeaderMobile logoUrl={headerLogo} eventId={eventId} />
|
||||||
</div>
|
</div>
|
||||||
<main className="min-h-0 flex-1 overflow-y-auto overscroll-contain bg-[#f2f2f7] 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="mx-auto w-full max-w-[370px]">
|
||||||
<div className="rounded-lg border border-[rgba(116,112,112,0.2)] bg-white p-5 text-center shadow-sm">
|
<div className="rounded-lg card-bg p-5 text-center shadow-md">
|
||||||
<p className="font-inter text-lg font-semibold text-[#24282f]">Thank you</p>
|
<p className="font-inter text-lg font-semibold text-[var(--theme-card-text-color)]]">Thank you</p>
|
||||||
<p className="mt-1 text-sm text-[#24282f]">Your response was submitted.</p>
|
<p className="mt-1 text-sm text-[var(--theme-card-text-color)]/80">Your response was submitted.</p>
|
||||||
{showRes && isQuiz && typeof scoreSubmitted === "number" ? (
|
{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}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showRes && isQuiz ? (
|
{showRes && isQuiz ? (
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<p className="text-[10px] font-semibold uppercase tracking-wide text-[#1e72ef]">Results</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-[rgba(36,40,47,0.55)]">Questions with correct answers</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">
|
<ul className="mt-4 flex flex-col gap-4">
|
||||||
{quiz.questions.map((q, qi) => {
|
{quiz.questions.map((q, qi) => {
|
||||||
const raw = answers[q._id];
|
const raw = answers[q._id];
|
||||||
|
|
@ -734,13 +744,13 @@ export function QuizPollTakePage() {
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
key={q._id}
|
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]">
|
<p className="text-sm font-semibold text-[var(--theme-card-text-color)]">
|
||||||
<span className="text-[#8e8e93]">{qi + 1}. </span>
|
<span className="text-[var(--theme-card-text-color)]">{qi + 1}. </span>
|
||||||
{q.question}
|
{q.question}
|
||||||
</p>
|
</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
|
Your answer
|
||||||
</p>
|
</p>
|
||||||
<p
|
<p
|
||||||
|
|
@ -749,7 +759,7 @@ export function QuizPollTakePage() {
|
||||||
? "text-green-700"
|
? "text-green-700"
|
||||||
: graded === false
|
: graded === false
|
||||||
? "text-red-600"
|
? "text-red-600"
|
||||||
: "text-[#24282f]"
|
: "text-[var(--theme-card-text-color)]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{formatUserAnswerLine(q, raw)}
|
{formatUserAnswerLine(q, raw)}
|
||||||
|
|
@ -757,17 +767,17 @@ export function QuizPollTakePage() {
|
||||||
</p>
|
</p>
|
||||||
{corr ? (
|
{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
|
Correct answer
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm font-semibold text-green-800">{corr}</p>
|
<p className="text-sm font-semibold text-green-800">{corr}</p>
|
||||||
</>
|
</>
|
||||||
) : q.type === "text" ? (
|
) : 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.
|
Open-ended — no single correct answer.
|
||||||
</p>
|
</p>
|
||||||
) : q.type === "rating" ? (
|
) : 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.
|
Rating scale — no correct answer key.
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
@ -780,16 +790,16 @@ export function QuizPollTakePage() {
|
||||||
|
|
||||||
{showRes && isPollOrSurvey ? (
|
{showRes && isPollOrSurvey ? (
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<p className="text-[10px] font-semibold uppercase tracking-wide text-[#1e72ef]">Results</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-[rgba(36,40,47,0.55)]">Share of responses per option</p>
|
<p className="mt-0.5 text-xs text-[var(--theme-card-text-color)]">Share of responses per option</p>
|
||||||
{pollResultsLoading ? (
|
{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}
|
) : null}
|
||||||
{pollResultsErr ? (
|
{pollResultsErr ? (
|
||||||
<p className="mt-4 text-center text-sm text-red-600">{pollResultsErr}</p>
|
<p className="mt-4 text-center text-sm text-red-600">{pollResultsErr}</p>
|
||||||
) : null}
|
) : null}
|
||||||
{pollResults && !pollResultsLoading ? (
|
{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
|
Based on {pollResults.totalSubmissions} submission
|
||||||
{pollResults.totalSubmissions === 1 ? "" : "s"}
|
{pollResults.totalSubmissions === 1 ? "" : "s"}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -799,11 +809,11 @@ export function QuizPollTakePage() {
|
||||||
{pollResults.questions.map((pq) => (
|
{pollResults.questions.map((pq) => (
|
||||||
<li
|
<li
|
||||||
key={`${pq.questionIndex}-${pq.questionId}`}
|
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" ? (
|
{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
|
{(pq.answeredCount ?? 0) === 0
|
||||||
? "No text answers yet."
|
? "No text answers yet."
|
||||||
: `${pq.answeredCount} of ${pq.totalResponses} answered (${pq.percentAnswered ?? 0}%)`}
|
: `${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">
|
<div className="mt-3 flex flex-col gap-3">
|
||||||
{(pq.options || []).map((opt) => (
|
{(pq.options || []).map((opt) => (
|
||||||
<div key={opt.optionId}>
|
<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="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}%
|
{opt.percent}%
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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
|
<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))}%` }}
|
style={{ width: `${Math.min(100, Math.max(0, opt.percent))}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -837,7 +847,7 @@ export function QuizPollTakePage() {
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
to={sessionBackHref}
|
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
|
Back to session
|
||||||
</Link>
|
</Link>
|
||||||
|
|
@ -853,8 +863,10 @@ export function QuizPollTakePage() {
|
||||||
<div className="flex shrink-0 flex-col">
|
<div className="flex shrink-0 flex-col">
|
||||||
<HeaderMobile logoUrl={headerLogo} eventId={eventId} />
|
<HeaderMobile logoUrl={headerLogo} eventId={eventId} />
|
||||||
</div>
|
</div>
|
||||||
<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…</p>
|
<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>
|
</main>
|
||||||
</MobileShell>
|
</MobileShell>
|
||||||
);
|
);
|
||||||
|
|
@ -868,14 +880,16 @@ export function QuizPollTakePage() {
|
||||||
<div className="flex shrink-0 flex-col">
|
<div className="flex shrink-0 flex-col">
|
||||||
<HeaderMobile logoUrl={headerLogo} eventId={eventId} />
|
<HeaderMobile logoUrl={headerLogo} eventId={eventId} />
|
||||||
</div>
|
</div>
|
||||||
<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 border border-[rgba(116,112,112,0.2)] bg-white p-5 text-center shadow-sm">
|
<div className="mx-auto w-full max-w-[370px] rounded-lg border text-center shadow-sm">
|
||||||
<p className="font-inter text-lg font-semibold text-[#24282f]">Answer submitted</p>
|
<div className="card-bg rounded-lg p-4">
|
||||||
<p className="mt-2 text-sm text-[rgba(36,40,47,0.75)]">
|
<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.
|
You have already submitted this question. Please wait for the host to show the next one.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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
|
Back to session
|
||||||
</Link>
|
</Link>
|
||||||
</main>
|
</main>
|
||||||
|
|
@ -889,14 +903,14 @@ export function QuizPollTakePage() {
|
||||||
<div className="flex shrink-0 flex-col">
|
<div className="flex shrink-0 flex-col">
|
||||||
<HeaderMobile logoUrl={headerLogo} eventId={eventId} />
|
<HeaderMobile logoUrl={headerLogo} eventId={eventId} />
|
||||||
</div>
|
</div>
|
||||||
<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 border border-[rgba(116,112,112,0.2)] bg-white p-5 text-center shadow-sm">
|
<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 text-[#24282f]">{quiz.title}</p>
|
<p className="font-inter text-lg font-semibold">{quiz.title}</p>
|
||||||
<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">
|
||||||
The host will start the next question soon.
|
The host will start the next question soon.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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
|
Back to session
|
||||||
</Link>
|
</Link>
|
||||||
</main>
|
</main>
|
||||||
|
|
@ -906,7 +920,7 @@ export function QuizPollTakePage() {
|
||||||
|
|
||||||
const footerBtnClass =
|
const footerBtnClass =
|
||||||
"rounded-lg px-3 py-1.5 text-center text-xs font-semibold shadow-sm disabled:opacity-50 disabled:hover:bg-inherit";
|
"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 text-[#24282f] hover:bg-[#f2f2f7]`;
|
const footerSecondaryClass = `${footerBtnClass} flex-1 border border-zinc-300 bg-zinc-100 text-gray-700`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MobileShell className="relative h-dvh min-h-0 flex-col">
|
<MobileShell className="relative h-dvh min-h-0 flex-col">
|
||||||
|
|
@ -915,33 +929,36 @@ export function QuizPollTakePage() {
|
||||||
</div>
|
</div>
|
||||||
<main
|
<main
|
||||||
ref={mainScrollRef}
|
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="mx-auto w-full max-w-[370px] pb-4">
|
||||||
<p className="text-[10px] font-semibold uppercase tracking-wide text-[#1e72ef]">{typeHeading(quiz.type)}</p>
|
<div className="flex flex-col gap-1 card-bg p-4 rounded-lg shadow-sm">
|
||||||
<h1 className="mt-1 font-inter text-xl font-semibold text-[#24282f]">{quiz.title}</h1>
|
<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">{quiz.title}</h1>
|
||||||
{isQuizMasterMode && liveQuestion ? (
|
{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}
|
Question {liveQuestionIndex + 1} of {totalQuestions}
|
||||||
</p>
|
</p>
|
||||||
) : multiQuestion ? (
|
) : 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}
|
Question {stepSafe + 1} of {totalQuestions}
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{timerActive && secondsLeft != null ? (
|
{timerActive && secondsLeft != null ? (
|
||||||
<div
|
<div
|
||||||
className={`mt-4 flex items-center justify-between rounded-lg border px-3 py-2.5 ${
|
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"}
|
{perQuestionTimer && isQuizMasterMode ? "Time left for this question" : "Time left"}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className={`font-mono text-lg font-bold tabular-nums ${
|
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)}
|
{formatCountdown(secondsLeft)}
|
||||||
|
|
@ -958,9 +975,9 @@ export function QuizPollTakePage() {
|
||||||
).map((q, offset) => {
|
).map((q, offset) => {
|
||||||
const qi = isQuizMasterMode ? liveQuestionIndex : multiQuestion ? stepSafe : offset;
|
const qi = isQuizMasterMode ? liveQuestionIndex : multiQuestion ? stepSafe : offset;
|
||||||
return (
|
return (
|
||||||
<li key={q._id} className="rounded-lg border border-[rgba(116,112,112,0.2)] bg-white p-4 shadow-sm">
|
<li key={q._id} className="rounded-lg card-bg p-4 shadow-sm">
|
||||||
<p className="text-sm font-semibold text-[#24282f]">
|
<p className="text-sm font-semibold text-[var(--theme-card-text-color)]">
|
||||||
<span className="text-[#8e8e93]">{qi + 1}. </span>
|
<span className="text-[var(--theme-card-text-color)]">{qi + 1}. </span>
|
||||||
{q.question}
|
{q.question}
|
||||||
</p>
|
</p>
|
||||||
<QuestionMedia url={q.imageUrl} alt="" />
|
<QuestionMedia url={q.imageUrl} alt="" />
|
||||||
|
|
@ -972,7 +989,7 @@ export function QuizPollTakePage() {
|
||||||
<label
|
<label
|
||||||
key={opt._id}
|
key={opt._id}
|
||||||
className={`flex cursor-pointer items-start gap-2 rounded-md border px-3 py-2 text-sm ${
|
className={`flex cursor-pointer items-start gap-2 rounded-md border px-3 py-2 text-sm ${
|
||||||
selected ? "border-[#1e72ef] bg-[#1e72ef]/5" : "border-[#e5e5ea]"
|
selected ? "border-[var(--theme-button-color)] bg-[var(--theme-card-color)]/10 text-[var(--theme-button-color)]" : "bg-[var(--theme-card-color)]/30 border-[var(--theme-card-color)]/15 text-[var(--theme-card-text-color)]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
|
|
@ -994,7 +1011,7 @@ export function QuizPollTakePage() {
|
||||||
value={typeof answers[q._id] === "string" ? answers[q._id] : ""}
|
value={typeof answers[q._id] === "string" ? answers[q._id] : ""}
|
||||||
onChange={(e) => setText(q, e.target.value)}
|
onChange={(e) => setText(q, e.target.value)}
|
||||||
rows={3}
|
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"
|
placeholder="Your answer"
|
||||||
/>
|
/>
|
||||||
) : q.type === "rating" ? (
|
) : q.type === "rating" ? (
|
||||||
|
|
@ -1006,7 +1023,7 @@ export function QuizPollTakePage() {
|
||||||
onClick={() => setSingle(q, String(n))}
|
onClick={() => setSingle(q, String(n))}
|
||||||
className={`size-10 rounded-full border text-sm font-semibold ${
|
className={`size-10 rounded-full border text-sm font-semibold ${
|
||||||
answers[q._id] === String(n)
|
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]"
|
: "border-[#e5e5ea] bg-white text-[#24282f]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|
@ -1020,7 +1037,7 @@ export function QuizPollTakePage() {
|
||||||
<label
|
<label
|
||||||
key={opt._id}
|
key={opt._id}
|
||||||
className={`flex cursor-pointer items-start gap-2 rounded-md border px-3 py-2 text-sm ${
|
className={`flex cursor-pointer items-start gap-2 rounded-md border px-3 py-2 text-sm ${
|
||||||
answers[q._id] === opt._id ? "border-[#1e72ef] bg-[#1e72ef]/5" : "border-[#e5e5ea]"
|
answers[q._id] === opt._id ? "border-[var(--theme-button-color)] bg-[var(--theme-card-color)]/10 text-[var(--theme-button-color)]" : "bg-[var(--theme-card-color)]/30 border-[var(--theme-card-color)]/15 text-[var(--theme-card-text-color)]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
|
|
@ -1044,20 +1061,20 @@ export function QuizPollTakePage() {
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
{!signedIn ? (
|
{!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 ? (
|
{multiQuestion ? (
|
||||||
<>
|
<>
|
||||||
Use <span className="font-semibold text-[#24282f]">Next</span> or{" "}
|
Use <span className="font-semibold">Next</span> or{" "}
|
||||||
<span className="font-semibold text-[#24282f]">Submit</span> below after{" "}
|
<span className="font-semibold">Submit</span> below after{" "}
|
||||||
<Link className="font-semibold text-[#1e72ef] underline" to={visitorEventUrl(eventId)}>
|
<Link className="font-semibold theme-btn-link" to={visitorEventUrl(eventId)}>
|
||||||
signing in
|
signing in
|
||||||
</Link>
|
</Link>
|
||||||
.
|
.
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
Tap <span className="font-semibold text-[#24282f]">Submit</span> below after{" "}
|
Tap <span className="font-semibold">Submit</span> below after{" "}
|
||||||
<Link className="font-semibold text-[#1e72ef] underline" to={visitorEventUrl(eventId)}>
|
<Link className="font-semibold theme-btn-link" to={visitorEventUrl(eventId)}>
|
||||||
signing in
|
signing in
|
||||||
</Link>
|
</Link>
|
||||||
.
|
.
|
||||||
|
|
@ -1068,8 +1085,8 @@ export function QuizPollTakePage() {
|
||||||
|
|
||||||
{submitError ? <p className="mt-4 text-center text-sm text-red-600">{submitError}</p> : null}
|
{submitError ? <p className="mt-4 text-center text-sm text-red-600">{submitError}</p> : null}
|
||||||
|
|
||||||
<p className="mt-6 text-center text-xs">
|
<p className="mt-6 text-center text-md">
|
||||||
<Link className="text-[#1e72ef] underline" to={sessionBackHref}>
|
<Link className="text-[var(--theme-text-color)] underline" to={sessionBackHref}>
|
||||||
Back to session
|
Back to session
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -1077,7 +1094,7 @@ export function QuizPollTakePage() {
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer
|
<footer
|
||||||
className="z-40 shrink-0 border-t border-[#e5e5ea] bg-white 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 }}
|
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">
|
<div className="mx-auto flex w-full max-w-[370px] gap-2">
|
||||||
|
|
@ -1096,7 +1113,7 @@ export function QuizPollTakePage() {
|
||||||
type="button"
|
type="button"
|
||||||
disabled={submitting}
|
disabled={submitting}
|
||||||
onClick={() => void runSubmit()}
|
onClick={() => void runSubmit()}
|
||||||
className={`${footerBtnClass} flex-1 bg-orange-500 text-white hover:bg-orange-600 disabled:hover:bg-orange-500`}
|
className={`${footerBtnClass} flex-1 theme-btn-bg text-white disabled:hover:bg-[var(--theme-button-color)]/50`}
|
||||||
>
|
>
|
||||||
{submitLabel}
|
{submitLabel}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -1104,7 +1121,7 @@ export function QuizPollTakePage() {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setQuestionStep((s) => Math.min(totalQuestions - 1, s + 1))}
|
onClick={() => setQuestionStep((s) => Math.min(totalQuestions - 1, s + 1))}
|
||||||
className={`${footerBtnClass} flex-1 bg-orange-500 text-white hover:bg-orange-600`}
|
className={`${footerBtnClass} flex-1 theme-btn-bg text-white disabled:hover:bg-[var(--theme-button-color)]/50`}
|
||||||
>
|
>
|
||||||
Next
|
Next
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -1115,7 +1132,7 @@ export function QuizPollTakePage() {
|
||||||
type="button"
|
type="button"
|
||||||
disabled={submitting}
|
disabled={submitting}
|
||||||
onClick={() => void runSubmit()}
|
onClick={() => void runSubmit()}
|
||||||
className="w-full rounded-lg bg-orange-500 px-3 py-1.5 text-center text-xs font-semibold text-white shadow-sm hover:bg-orange-600 disabled:opacity-60 disabled:hover:bg-orange-500"
|
className="w-full rounded-lg theme-btn-bg px-3 py-1.5 text-center text-xs font-semibold text-white shadow-sm disabled:opacity-60 disabled:hover:bg-[var(--theme-button-color)]/50"
|
||||||
>
|
>
|
||||||
{submitLabel}
|
{submitLabel}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -98,14 +98,16 @@ export function RatingsPage() {
|
||||||
return (
|
return (
|
||||||
<MobileShell className="relative">
|
<MobileShell className="relative">
|
||||||
<HeaderMobile logoUrl={ctx?.event?.logoUrl} eventId={null} />
|
<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]">
|
<div className="mx-auto w-full max-w-[370px]">
|
||||||
<h1 className="font-inter text-xl font-semibold text-[#24282f]">Ratings</h1>
|
<div className="card-bg rounded-lg shadow-md p-4">
|
||||||
<p className="mt-3 text-sm text-[rgba(36,40,47,0.67)]">Open ratings from your event or session link.</p>
|
<h1 className="text-xl font-semibold text-[var(--theme-card-text-color)]">Ratings</h1>
|
||||||
<Link to="/" className="mt-4 inline-block text-sm font-semibold text-[#1e72ef] underline">
|
<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
|
Home
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</MobileShell>
|
</MobileShell>
|
||||||
);
|
);
|
||||||
|
|
@ -115,14 +117,16 @@ export function RatingsPage() {
|
||||||
return (
|
return (
|
||||||
<MobileShell className="relative">
|
<MobileShell className="relative">
|
||||||
<HeaderMobile logoUrl={eventSnapshot?.logoUrl} eventId={eventId || null} />
|
<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]">
|
<div className="mx-auto w-full max-w-[370px]">
|
||||||
<h1 className="font-inter text-xl font-semibold text-[#24282f]">Ratings</h1>
|
<div className="card-bg rounded-lg shadow-md p-4">
|
||||||
<p className="mt-3 text-sm text-[rgba(36,40,47,0.67)]">Ratings are disabled for this event.</p>
|
<h1 className="text-xl font-semibold text-[var(--theme-card-text-color)]">Ratings</h1>
|
||||||
<Link to="/event" className="mt-4 inline-block text-sm font-semibold text-[#1e72ef] underline">
|
<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
|
Back to event
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</MobileShell>
|
</MobileShell>
|
||||||
);
|
);
|
||||||
|
|
@ -131,27 +135,29 @@ export function RatingsPage() {
|
||||||
return (
|
return (
|
||||||
<MobileShell className="relative">
|
<MobileShell className="relative">
|
||||||
<HeaderMobile logoUrl={eventSnapshot?.logoUrl} eventId={eventId || null} />
|
<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]">
|
<div className="mx-auto w-full max-w-[370px]">
|
||||||
<h1 className="font-inter text-xl font-semibold text-[#24282f]">{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 ? (
|
{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 ? (
|
) : schemaErr ? (
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<p className="text-sm text-red-600">{schemaErr}</p>
|
<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.
|
Ask the organizer to publish an active ratings form for this event.
|
||||||
</p>
|
</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
|
Back to event
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
) : schema ? (
|
) : schema ? (
|
||||||
<div className="mt-6">
|
<div className="mt-2">
|
||||||
<DynamicFeedbackForm eventId={eventId} schema={schema} variant="light" />
|
<DynamicFeedbackForm eventId={eventId} schema={schema} variant="light" className="px-0" />
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</MobileShell>
|
</MobileShell>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -70,31 +70,56 @@ export function RewardsPage() {
|
||||||
return (
|
return (
|
||||||
<MobileShell className="relative">
|
<MobileShell className="relative">
|
||||||
<HeaderMobile logoUrl={ctx?.event?.logoUrl} eventId={eventId || null} />
|
<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="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} />
|
<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>
|
<h1 className="mt-1 font-inter text-xl font-semibold text-white">Your Rewards</h1>
|
||||||
<Link
|
<Link
|
||||||
to={eventId ? `/leaderboard?event_id=${encodeURIComponent(eventId)}` : "/leaderboard"}
|
to={eventId ? `/leaderboard?event_id=${encodeURIComponent(eventId)}` : "/leaderboard"}
|
||||||
className="mt-6 w-full max-w-[354px] rounded-full border border-[rgba(214,200,200,0.6)] bg-[#ef8d1d] py-3 text-center text-xs font-semibold uppercase tracking-wide text-white shadow-[0_4px_4px_rgba(0,0,0,0.15)]"
|
className="mt-6 w-full max-w-[354px] rounded-full 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
|
Leaderboard
|
||||||
</Link>
|
</Link>
|
||||||
|
</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="mt-6 w-full max-w-[354px] rounded-xl bg-whit card-bg shadow-md">
|
||||||
<div className="p-4">
|
<div className="flex gap-2 justify-between w-full 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)]">
|
<div className="flex items-center gap-2 shrink-0">
|
||||||
<p className="font-inter text-base font-semibold uppercase tracking-wide">Total Points</p>
|
<div className="bg-[var(--theme-button-color)]/12 rounded-full p-3">
|
||||||
<p className="font-inter text-5xl font-bold tabular-nums drop-shadow-[0_4px_4px_rgba(0,0,0,0.25)]">
|
<Trophy className="size-10 text-[var(--theme-button-color)]" strokeWidth={1.25} />
|
||||||
{displayTotal}
|
|
||||||
</p>
|
|
||||||
</div>
|
</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">
|
<ul className="mt-3 flex flex-col gap-3">
|
||||||
{historyLoading && (
|
{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 && (
|
{!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 &&
|
{!historyLoading &&
|
||||||
historyItems.map((row) => {
|
historyItems.map((row) => {
|
||||||
|
|
@ -108,13 +133,13 @@ export function RewardsPage() {
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
key={row._id}
|
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}`}>
|
<span className={`shrink-0 rounded px-3 py-2 text-xs font-medium shadow-sm ${cls}`}>
|
||||||
{signed}
|
{signed}
|
||||||
{row.points} pts
|
{row.points} pts
|
||||||
</span>
|
</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)}
|
{historyLabel(row.source, row.description)}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
||||||
|
|
@ -363,13 +363,13 @@ export function SingleEventPage() {
|
||||||
|
|
||||||
if (!eventId || !sessionId) {
|
if (!eventId || !sessionId) {
|
||||||
return (
|
return (
|
||||||
<MobileShell className="relative">
|
<MobileShell className="relative bg-transparent">
|
||||||
<HeaderMobile
|
<HeaderMobile
|
||||||
eventName={headerEventNameForEvent(eventId, null, ctx)}
|
eventName={headerEventNameForEvent(eventId, null, ctx)}
|
||||||
eventId={eventId || null}
|
eventId={eventId || null}
|
||||||
showBack={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]">{err}</p>
|
<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 text-[#1e72ef] underline">
|
||||||
Home
|
Home
|
||||||
|
|
@ -381,14 +381,14 @@ export function SingleEventPage() {
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<MobileShell className="relative">
|
<MobileShell className="relative bg-transparent">
|
||||||
<HeaderMobile
|
<HeaderMobile
|
||||||
logoUrl={headerLogoForEvent(eventId, null, ctx)}
|
logoUrl={headerLogoForEvent(eventId, null, ctx)}
|
||||||
eventName={headerEventNameForEvent(eventId, null, ctx)}
|
eventName={headerEventNameForEvent(eventId, null, ctx)}
|
||||||
eventId={eventId || null}
|
eventId={eventId || null}
|
||||||
showBack={false}
|
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>
|
<p className="text-sm text-[#24282f]">Loading session…</p>
|
||||||
</main>
|
</main>
|
||||||
</MobileShell>
|
</MobileShell>
|
||||||
|
|
@ -397,14 +397,14 @@ export function SingleEventPage() {
|
||||||
|
|
||||||
if (err || !session) {
|
if (err || !session) {
|
||||||
return (
|
return (
|
||||||
<MobileShell className="relative">
|
<MobileShell className="relative bg-transparent">
|
||||||
<HeaderMobile
|
<HeaderMobile
|
||||||
logoUrl={headerLogoForEvent(eventId, eventInfo, ctx)}
|
logoUrl={headerLogoForEvent(eventId, eventInfo, ctx)}
|
||||||
eventName={headerEventNameForEvent(eventId, eventInfo, ctx)}
|
eventName={headerEventNameForEvent(eventId, eventInfo, ctx)}
|
||||||
eventId={eventId || null}
|
eventId={eventId || null}
|
||||||
showBack={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-red-600">{err ?? "Session not found."}</p>
|
<p className="text-center text-sm text-red-600">{err ?? "Session not found."}</p>
|
||||||
</main>
|
</main>
|
||||||
</MobileShell>
|
</MobileShell>
|
||||||
|
|
@ -451,7 +451,7 @@ export function SingleEventPage() {
|
||||||
Boolean(sessionNotes || sessionLocation || trackHall || hasCapacity);
|
Boolean(sessionNotes || sessionLocation || trackHall || hasCapacity);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MobileShell className="relative">
|
<MobileShell className="relative bg-transparent">
|
||||||
<HeaderMobile
|
<HeaderMobile
|
||||||
logoUrl={headerLogoUrl}
|
logoUrl={headerLogoUrl}
|
||||||
eventName={headerEventName}
|
eventName={headerEventName}
|
||||||
|
|
@ -469,7 +469,7 @@ export function SingleEventPage() {
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<div className="eb-gradient-banner relative flex flex-col items-center justify-center gap-1.5 px-6 pb-4 pt-3 text-center text-white">
|
<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">
|
<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 ? (
|
{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">
|
<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
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => toggleBookmark(sessionId)}
|
onClick={() => toggleBookmark(sessionId)}
|
||||||
className="shrink-0 rounded-md p-1 text-white hover:bg-white/15"
|
className="shrink-0 rounded-md p-1 text-[var(--theme-button-color)]"
|
||||||
aria-label={sessionBookmarked ? "Remove bookmark" : "Bookmark session"}
|
aria-label={sessionBookmarked ? "Remove bookmark" : "Bookmark session"}
|
||||||
aria-pressed={sessionBookmarked}
|
aria-pressed={sessionBookmarked}
|
||||||
>
|
>
|
||||||
<Bookmark className={`size-6 ${sessionBookmarked ? "fill-current" : ""}`} strokeWidth={1.75} />
|
<Bookmark className={`size-6 ${sessionBookmarked ? "fill-current" : ""}`} strokeWidth={1.75} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex max-w-full flex-wrap items-center justify-center gap-x-2 gap-y-1 px-2 text-[10px] text-white/90">
|
<div className="flex max-w-full flex-wrap items-center gap-x-2 gap-y-1 pe-2 text-xs">
|
||||||
<span className="rounded bg-white/20 px-2 py-0.5">{typeLabel}</span>
|
<span className="rounded bg-[var(--theme-button-color)]/20 px-2 py-0.5">{typeLabel}</span>
|
||||||
{timeLine ? <span>{timeLine}</span> : null}
|
{timeLine ? <span>{timeLine}</span> : null}
|
||||||
{trackTitle !== typeLabel ? <span className="opacity-90">{trackTitle}</span> : null}
|
{trackTitle !== typeLabel ? <span className="opacity-90">{trackTitle}</span> : null}
|
||||||
</div>
|
</div>
|
||||||
<h1 className="font-inter text-2xl font-bold tracking-tight">{session.title}</h1>
|
<h1 className="text-xl font-semibold 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-white/95">
|
<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 ? (
|
{duration ? (
|
||||||
<span className="flex items-center gap-1.5">
|
<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})
|
{startT} ({duration})
|
||||||
</span>
|
</span>
|
||||||
) : startT ? (
|
) : startT ? (
|
||||||
<span className="flex items-center gap-1.5">
|
<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}
|
{startT}
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
{sessionDate ? (
|
{sessionDate ? (
|
||||||
<span className="flex items-center gap-1.5">
|
<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}
|
{sessionDate}
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-end gap-1.5">
|
||||||
{hasExpandableSessionMeta ? (
|
{hasExpandableSessionMeta ? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -519,7 +522,7 @@ export function SingleEventPage() {
|
||||||
aria-label={
|
aria-label={
|
||||||
sessionMetaOpen ? "Hide session notes and location" : "Show session notes and location"
|
sessionMetaOpen ? "Hide session notes and location" : "Show session notes and location"
|
||||||
}
|
}
|
||||||
className="inline-flex shrink-0 items-center rounded-md p-0.5 text-white hover:bg-white/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
|
<ChevronDown
|
||||||
className={`size-5 transition-transform ${sessionMetaOpen ? "rotate-180" : ""}`}
|
className={`size-5 transition-transform ${sessionMetaOpen ? "rotate-180" : ""}`}
|
||||||
|
|
@ -530,27 +533,29 @@ export function SingleEventPage() {
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
{sessionMetaOpen && hasExpandableSessionMeta ? (
|
{sessionMetaOpen && hasExpandableSessionMeta ? (
|
||||||
<div className="mt-2 w-full max-w-full border-t border-white/25 px-1 pt-3 text-left text-sm text-white/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 ? (
|
{sessionNotes ? (
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<p className="text-[10px] font-semibold uppercase tracking-wide text-white/70">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>
|
<p className="mt-1.5 whitespace-pre-wrap leading-snug">{sessionNotes}</p>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{sessionLocation || trackHall ? (
|
{sessionLocation || trackHall ? (
|
||||||
<div>
|
<div>
|
||||||
<p className="text-[10px] font-semibold uppercase tracking-wide text-white/70">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">
|
<ul className="mt-1.5 flex flex-col gap-1.5">
|
||||||
{sessionLocation ? (
|
{sessionLocation ? (
|
||||||
<li className="flex items-start gap-2 leading-snug">
|
<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>{sessionLocation}</span>
|
<span>{sessionLocation}</span>
|
||||||
</li>
|
</li>
|
||||||
) : null}
|
) : null}
|
||||||
{trackHall ? (
|
{trackHall ? (
|
||||||
<li className="flex items-start gap-2 leading-snug">
|
<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>
|
<span>{trackHall}</span>
|
||||||
</li>
|
</li>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
@ -558,13 +563,13 @@ export function SingleEventPage() {
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{hasCapacity ? (
|
{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}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<main className="relative z-0 w-full max-w-none flex-1 bg-[#f2f2f7] 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">
|
<div className="flex w-full max-w-none flex-col gap-4">
|
||||||
{session && session.enableQnA !== false ? (
|
{session && session.enableQnA !== false ? (
|
||||||
<SessionQuestionsCard
|
<SessionQuestionsCard
|
||||||
|
|
@ -578,33 +583,33 @@ export function SingleEventPage() {
|
||||||
<SessionQuizPollsSection eventId={eventId} sessionId={sessionId} />
|
<SessionQuizPollsSection eventId={eventId} sessionId={sessionId} />
|
||||||
|
|
||||||
{showSessionFeedback || showSessionRatings ? (
|
{showSessionFeedback || showSessionRatings ? (
|
||||||
<div className="flex w-full max-w-none flex-col gap-2">
|
<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-[10px] font-semibold uppercase tracking-wide text-[#8e8e93]">
|
<p className="px-1 text-md font-semibold uppercas tracking-wide">
|
||||||
Feedback & ratings
|
Feedback & ratings
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
{showSessionFeedback ? (
|
{showSessionFeedback ? (
|
||||||
<Link
|
<Link
|
||||||
to={sessionFeedbackHref}
|
to={sessionFeedbackHref}
|
||||||
className="flex items-center justify-between gap-3 rounded-lg border border-[rgba(116,112,112,0.2)] bg-white 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">
|
<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 className="truncate">Session feedback</span>
|
||||||
</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>
|
</Link>
|
||||||
) : null}
|
) : null}
|
||||||
{showSessionRatings ? (
|
{showSessionRatings ? (
|
||||||
<Link
|
<Link
|
||||||
to={sessionRatingsHref}
|
to={sessionRatingsHref}
|
||||||
className="flex items-center justify-between gap-3 rounded-lg border border-[rgba(116,112,112,0.2)] bg-white 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">
|
<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 className="truncate">Rate this session</span>
|
||||||
</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>
|
</Link>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -44,8 +44,8 @@ import {
|
||||||
function WhiteCard({ children }: { children: ReactNode }) {
|
function WhiteCard({ children }: { children: ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full max-w-[354px] flex-col items-center">
|
<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="w-full rounded-xl shadow-md">
|
||||||
<div className="rounded-xl bg-white px-6 py-6">{children}</div>
|
<div className="rounded-xl card-bg px-6 py-6">{children}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -387,12 +387,12 @@ export function SocialSharePage() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<h1 className="mb-2 text-center text-[22px] font-semibold text-[#1a1a1a]">Your photo</h1>
|
<h1 className="mb-2 text-center text-[22px] font-semibold">Your photo</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">
|
||||||
Here’s your framed photo. You can download or share it.
|
Here's your framed photo. You can download or share it.
|
||||||
</p>
|
</p>
|
||||||
{(done.fileUrl || done.previewUrl) ? (
|
{(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
|
<img
|
||||||
ref={resultImgRef}
|
ref={resultImgRef}
|
||||||
crossOrigin="anonymous"
|
crossOrigin="anonymous"
|
||||||
|
|
@ -417,7 +417,7 @@ export function SocialSharePage() {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={resetForNew}
|
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
|
Create another
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -425,7 +425,7 @@ export function SocialSharePage() {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => void onDownload()}
|
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 className="h-4 w-4" />
|
||||||
Download photo
|
Download photo
|
||||||
|
|
@ -434,14 +434,14 @@ export function SocialSharePage() {
|
||||||
</div>
|
</div>
|
||||||
{showShare ? (
|
{showShare ? (
|
||||||
<div className="mt-4">
|
<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
|
Share
|
||||||
</p>
|
</p>
|
||||||
{useNativeShare ? (
|
{useNativeShare ? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => void onNativeShare()}
|
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" />
|
<Share2 className="h-4 w-4" />
|
||||||
Share
|
Share
|
||||||
|
|
@ -453,7 +453,7 @@ export function SocialSharePage() {
|
||||||
key={item.key}
|
key={item.key}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => void onShare(item.key)}
|
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} />
|
<ShareIcon platform={item.key} />
|
||||||
{item.label}
|
{item.label}
|
||||||
|
|
@ -484,22 +484,22 @@ export function SocialSharePage() {
|
||||||
|
|
||||||
{meta?.accessMode === "login" && !loggedIn ? (
|
{meta?.accessMode === "login" && !loggedIn ? (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<h1 className="mb-1 text-center text-[22px] font-semibold text-[#1a1a1a]">Social Share</h1>
|
<h1 className="mb-1 text-center text-[22px] font-semibold">Social Share</h1>
|
||||||
<p className="text-center text-sm text-[#666]">Sign in to create and share your photo.</p>
|
<p className="text-center text-sm text-[var(--theme-card-text-color)]/80">Sign in to create and share your photo.</p>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => void goLogin()}
|
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
|
Sign in to continue
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
) : !selectedFrame ? (
|
) : !selectedFrame ? (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h1 className="mb-1 text-center text-[22px] font-semibold text-[#1a1a1a]">Choose a frame</h1>
|
<h1 className="mb-1 text-center text-[22px] font-semibold">Choose a frame</h1>
|
||||||
<p className="text-center text-sm text-[#666]">Pick a frame, then upload your photo.</p>
|
<p className="text-center text-sm text-[var(--theme-card-text-color)]/80">Pick a frame, then upload your photo.</p>
|
||||||
{!meta?.frames?.length ? (
|
{!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">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
{meta.frames.map((frame) => (
|
{meta.frames.map((frame) => (
|
||||||
|
|
@ -507,9 +507,9 @@ export function SocialSharePage() {
|
||||||
key={frame._id}
|
key={frame._id}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setSelectedFrame(frame)}
|
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 ? (
|
{frame.frameImageUrl ? (
|
||||||
<img
|
<img
|
||||||
src={frame.frameImageUrl}
|
src={frame.frameImageUrl}
|
||||||
|
|
@ -517,12 +517,12 @@ export function SocialSharePage() {
|
||||||
className="h-full w-full object-contain"
|
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
|
No preview
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</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}
|
{frame.name}
|
||||||
</p>
|
</p>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -532,7 +532,7 @@ export function SocialSharePage() {
|
||||||
{err ? <p className="text-center text-sm text-red-600">{err}</p> : null}
|
{err ? <p className="text-center text-sm text-red-600">{err}</p> : null}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4 text-center">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
@ -540,13 +540,13 @@ export function SocialSharePage() {
|
||||||
clearSelectedPhoto();
|
clearSelectedPhoto();
|
||||||
setErr(null);
|
setErr(null);
|
||||||
}}
|
}}
|
||||||
className="text-sm font-semibold text-[#D32F2F]"
|
className="text-sm font-semibold theme-btn-link"
|
||||||
>
|
>
|
||||||
← Change frame
|
← Change frame
|
||||||
</button>
|
</button>
|
||||||
<h1 className="mb-1 text-center text-[22px] font-semibold text-[#1a1a1a]">Upload photo</h1>
|
<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-[#666]">
|
<p className="text-center text-sm text-[var(--theme-card-text-color)]/80">
|
||||||
Frame: <span className="font-semibold text-[#333]">{selectedFrame.name}</span>
|
Frame: <span className="font-semibold">{selectedFrame.name}</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
|
|
@ -558,7 +558,7 @@ export function SocialSharePage() {
|
||||||
onChange={(e) => void handleFileChange(e.target.files?.[0] || null)}
|
onChange={(e) => void handleFileChange(e.target.files?.[0] || null)}
|
||||||
/>
|
/>
|
||||||
{previewUrl ? (
|
{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" />
|
<img src={previewUrl} alt="Your photo" className="mx-auto max-h-[240px] w-full object-contain" />
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
@ -566,7 +566,7 @@ export function SocialSharePage() {
|
||||||
type="button"
|
type="button"
|
||||||
disabled={picking}
|
disabled={picking}
|
||||||
onClick={() => fileRef.current?.click()}
|
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"}
|
{picking ? "Processing…" : file ? "Change photo" : "Choose photo"}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -574,7 +574,7 @@ export function SocialSharePage() {
|
||||||
type="button"
|
type="button"
|
||||||
disabled={!file || submitting}
|
disabled={!file || submitting}
|
||||||
onClick={() => void onSubmit()}
|
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"}
|
{submitting ? "Creating…" : "Create photo"}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,229 @@
|
||||||
|
import type { EventThemeConfig } from "../types/visitorContext";
|
||||||
|
|
||||||
|
const DEFAULT_THEME_FONT = "Open Sans";
|
||||||
|
|
||||||
|
type FontFaceSpec = { href: string; heading: string; body: string };
|
||||||
|
|
||||||
|
/** Single-family fonts for theme.font (heading + body share the same family). */
|
||||||
|
export const THEME_FONT_SPECS: Record<string, FontFaceSpec> = {
|
||||||
|
"Open Sans": {
|
||||||
|
href: "https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;600;700;800&display=swap",
|
||||||
|
heading: '"Open Sans", system-ui, sans-serif',
|
||||||
|
body: '"Open Sans", system-ui, sans-serif',
|
||||||
|
},
|
||||||
|
Montserrat: {
|
||||||
|
href: "https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;600;700;800&display=swap",
|
||||||
|
heading: '"Montserrat", system-ui, sans-serif',
|
||||||
|
body: '"Montserrat", system-ui, sans-serif',
|
||||||
|
},
|
||||||
|
Roboto: {
|
||||||
|
href: "https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap",
|
||||||
|
heading: '"Roboto", system-ui, sans-serif',
|
||||||
|
body: '"Roboto", system-ui, sans-serif',
|
||||||
|
},
|
||||||
|
Poppins: {
|
||||||
|
href: "https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600;700;800&display=swap",
|
||||||
|
heading: '"Poppins", system-ui, sans-serif',
|
||||||
|
body: '"Poppins", system-ui, sans-serif',
|
||||||
|
},
|
||||||
|
Raleway: {
|
||||||
|
href: "https://fonts.googleapis.com/css2?family=Raleway:wght@300;400;600;700;800&display=swap",
|
||||||
|
heading: '"Raleway", system-ui, sans-serif',
|
||||||
|
body: '"Raleway", system-ui, sans-serif',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const THEME_FONT_ALIASES: Record<string, string> = Object.fromEntries(
|
||||||
|
Object.keys(THEME_FONT_SPECS).map((name) => [name.toLowerCase(), name]),
|
||||||
|
);
|
||||||
|
|
||||||
|
const THEME_VAR_KEYS = [
|
||||||
|
"--theme-heading-font",
|
||||||
|
"--theme-body-font",
|
||||||
|
"--theme-primary-color",
|
||||||
|
"--theme-secondary-color",
|
||||||
|
"--theme-header-color",
|
||||||
|
"--theme-button-color",
|
||||||
|
"--theme-text-color",
|
||||||
|
"--theme-text-highlight-color",
|
||||||
|
"--theme-card-color",
|
||||||
|
"--theme-card-text-color",
|
||||||
|
"--theme-bg-color",
|
||||||
|
"--theme-bg-image",
|
||||||
|
"--theme-bg-size",
|
||||||
|
"--theme-bg-repeat",
|
||||||
|
"--theme-bg-position",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
function normalizeHex(input?: string | null): string | null {
|
||||||
|
if (input == null) return null;
|
||||||
|
let s = String(input).trim();
|
||||||
|
if (!s) return null;
|
||||||
|
if (!s.startsWith("#")) s = `#${s}`;
|
||||||
|
if (/^#([0-9a-fA-F]{3})$/.test(s)) {
|
||||||
|
const x = s.slice(1);
|
||||||
|
s = `#${x[0]}${x[0]}${x[1]}${x[1]}${x[2]}${x[2]}`;
|
||||||
|
}
|
||||||
|
if (!/^#([0-9a-fA-F]{6})$/.test(s)) return null;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Accepts hex / rgb() / rgba() / named colors as CSS color strings. */
|
||||||
|
function normalizeCssColor(input?: string | null): string | null {
|
||||||
|
if (input == null) return null;
|
||||||
|
const s = String(input).trim();
|
||||||
|
if (!s) return null;
|
||||||
|
const hex = normalizeHex(s);
|
||||||
|
if (hex) return hex;
|
||||||
|
if (/^(rgb|rgba|hsl|hsla)\(/i.test(s) || /^[a-z]+$/i.test(s)) return s;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeBgRepeat(repeat?: string | null): string {
|
||||||
|
const raw = String(repeat ?? "").trim().toLowerCase();
|
||||||
|
if (!raw || raw === "no" || raw === "no-repeat" || raw === "false") return "no-repeat";
|
||||||
|
if (raw === "yes" || raw === "true" || raw === "repeat") return "repeat";
|
||||||
|
if (raw === "repeat-x" || raw === "repeat-y" || raw === "space" || raw === "round") return raw;
|
||||||
|
return "no-repeat";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves theme.font → font face. Defaults to Open Sans.
|
||||||
|
* Unknown values fall back to Open Sans.
|
||||||
|
*/
|
||||||
|
export function resolveThemeFont(config?: EventThemeConfig | null): FontFaceSpec {
|
||||||
|
const raw = config?.font != null ? String(config.font).trim() : "";
|
||||||
|
const key = raw ? THEME_FONT_ALIASES[raw.toLowerCase()] : DEFAULT_THEME_FONT;
|
||||||
|
return THEME_FONT_SPECS[key] || THEME_FONT_SPECS[DEFAULT_THEME_FONT];
|
||||||
|
}
|
||||||
|
|
||||||
|
function themeBackgroundVars(theme?: EventThemeConfig | null): Record<string, string> {
|
||||||
|
if (!theme) return {};
|
||||||
|
const mode = String(theme.theme || "").trim().toLowerCase();
|
||||||
|
const vars: Record<string, string> = {
|
||||||
|
"--theme-bg-color": "transparent",
|
||||||
|
"--theme-bg-image": "none",
|
||||||
|
"--theme-bg-size": "auto",
|
||||||
|
"--theme-bg-repeat": "no-repeat",
|
||||||
|
"--theme-bg-position": "center center",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (mode === "background") {
|
||||||
|
const url = theme.background?.url != null ? String(theme.background.url).trim() : "";
|
||||||
|
if (url) {
|
||||||
|
vars["--theme-bg-image"] = `url("${url.replace(/"/g, '\\"')}")`;
|
||||||
|
vars["--theme-bg-size"] = String(theme.background?.size || "cover").trim() || "cover";
|
||||||
|
vars["--theme-bg-repeat"] = normalizeBgRepeat(theme.background?.repeat);
|
||||||
|
vars["--theme-bg-position"] = "center center";
|
||||||
|
}
|
||||||
|
return vars;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode === "gradient") {
|
||||||
|
const c1 =
|
||||||
|
normalizeCssColor(theme.gradient?.color1) ||
|
||||||
|
normalizeCssColor(theme.primaryColor) ||
|
||||||
|
"#1e79f0";
|
||||||
|
const c2 =
|
||||||
|
normalizeCssColor(theme.gradient?.color2) ||
|
||||||
|
normalizeCssColor(theme.secondaryColor) ||
|
||||||
|
"#bd35e6";
|
||||||
|
vars["--theme-bg-image"] = `linear-gradient(135deg, ${c1} 0%, ${c2} 100%)`;
|
||||||
|
vars["--theme-bg-size"] = "cover";
|
||||||
|
vars["--theme-bg-repeat"] = "no-repeat";
|
||||||
|
return vars;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode === "simple") {
|
||||||
|
const color =
|
||||||
|
normalizeCssColor(theme.color) ||
|
||||||
|
normalizeCssColor(theme.primaryColor) ||
|
||||||
|
"#1e79f0";
|
||||||
|
vars["--theme-bg-color"] = color;
|
||||||
|
vars["--theme-bg-image"] = "none";
|
||||||
|
return vars;
|
||||||
|
}
|
||||||
|
|
||||||
|
return vars;
|
||||||
|
}
|
||||||
|
|
||||||
|
function themePaletteVars(theme?: EventThemeConfig | null): Record<string, string> {
|
||||||
|
if (!theme) return {};
|
||||||
|
const vars: Record<string, string> = {};
|
||||||
|
const primary = normalizeCssColor(theme.primaryColor);
|
||||||
|
const secondary = normalizeCssColor(theme.secondaryColor);
|
||||||
|
const header = normalizeCssColor(theme.headerColor);
|
||||||
|
const button = normalizeCssColor(theme.buttonColor);
|
||||||
|
const text = normalizeCssColor(theme.textColor);
|
||||||
|
const textHighlight = normalizeCssColor(theme.textHighlightColor);
|
||||||
|
const card = normalizeCssColor(theme.cardColor);
|
||||||
|
const cardText = normalizeCssColor(theme.cardTextColor);
|
||||||
|
|
||||||
|
if (primary) vars["--theme-primary-color"] = primary;
|
||||||
|
if (secondary) vars["--theme-secondary-color"] = secondary;
|
||||||
|
if (header) vars["--theme-header-color"] = header;
|
||||||
|
if (button) vars["--theme-button-color"] = button;
|
||||||
|
if (text) vars["--theme-text-color"] = text;
|
||||||
|
if (textHighlight) vars["--theme-text-highlight-color"] = textHighlight;
|
||||||
|
if (card) vars["--theme-card-color"] = card;
|
||||||
|
if (cardText) vars["--theme-card-text-color"] = cardText;
|
||||||
|
|
||||||
|
return vars;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Build CSS custom properties for an event theme (fonts + palette + page background). */
|
||||||
|
export function eventThemeCssVars(theme?: EventThemeConfig | null): Record<string, string> {
|
||||||
|
const spec = resolveThemeFont(theme);
|
||||||
|
return {
|
||||||
|
"--theme-heading-font": spec.heading,
|
||||||
|
"--theme-body-font": spec.body,
|
||||||
|
...themePaletteVars(theme),
|
||||||
|
...themeBackgroundVars(theme),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadEventThemeFont(theme?: EventThemeConfig | null): void {
|
||||||
|
if (typeof document === "undefined") return;
|
||||||
|
const spec = resolveThemeFont(theme);
|
||||||
|
const id = "eb-event-theme-fonts";
|
||||||
|
let link = document.getElementById(id) as HTMLLinkElement | null;
|
||||||
|
if (!link) {
|
||||||
|
link = document.createElement("link");
|
||||||
|
link.id = id;
|
||||||
|
link.rel = "stylesheet";
|
||||||
|
document.head.appendChild(link);
|
||||||
|
}
|
||||||
|
if (link.href !== spec.href) {
|
||||||
|
link.href = spec.href;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyCssVars(el: HTMLElement, vars: Record<string, string>) {
|
||||||
|
for (const [key, value] of Object.entries(vars)) {
|
||||||
|
el.style.setProperty(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearCssVars(el: HTMLElement, keys: readonly string[]) {
|
||||||
|
for (const key of keys) {
|
||||||
|
el.style.removeProperty(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Apply theme CSS variables + font to `<html>`. Pass null/undefined to clear. */
|
||||||
|
export function applyEventTheme(theme?: EventThemeConfig | null): void {
|
||||||
|
if (typeof document === "undefined") return;
|
||||||
|
const html = document.documentElement;
|
||||||
|
clearCssVars(html, THEME_VAR_KEYS);
|
||||||
|
if (!theme) return;
|
||||||
|
loadEventThemeFont(theme);
|
||||||
|
applyCssVars(html, eventThemeCssVars(theme));
|
||||||
|
html.dataset.ebTheme = String(theme.theme || "").trim().toLowerCase() || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearEventTheme(): void {
|
||||||
|
if (typeof document === "undefined") return;
|
||||||
|
const html = document.documentElement;
|
||||||
|
clearCssVars(html, THEME_VAR_KEYS);
|
||||||
|
delete html.dataset.ebTheme;
|
||||||
|
}
|
||||||
|
|
@ -15,6 +15,42 @@ export interface ResolveCodeResponse {
|
||||||
trackId?: string;
|
trackId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 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). */
|
||||||
|
export type EventThemeFont = "Open Sans" | "Montserrat" | "Roboto" | "Poppins" | "Raleway";
|
||||||
|
|
||||||
|
export type EventThemeBackground = {
|
||||||
|
url?: string | null;
|
||||||
|
size?: string | null;
|
||||||
|
/** `"no"` / `"no-repeat"` → no-repeat; `"yes"` / `"repeat"` → repeat */
|
||||||
|
repeat?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type EventThemeGradient = {
|
||||||
|
color1?: string | null;
|
||||||
|
color2?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Dynamic styling config from GET /api/public/visitor-theme/:eventId → CSS vars on `<html>`. */
|
||||||
|
export interface EventThemeConfig {
|
||||||
|
theme: EventThemeMode;
|
||||||
|
color?: string | null;
|
||||||
|
pattern?: string | null;
|
||||||
|
background?: EventThemeBackground | null;
|
||||||
|
gradient?: EventThemeGradient | null;
|
||||||
|
font?: EventThemeFont | string | null;
|
||||||
|
primaryColor?: string | null;
|
||||||
|
secondaryColor?: string | null;
|
||||||
|
headerColor?: string | null;
|
||||||
|
buttonColor?: string | null;
|
||||||
|
textColor?: string | null;
|
||||||
|
textHighlightColor?: string | null;
|
||||||
|
cardColor?: string | null;
|
||||||
|
cardTextColor?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
/** Visitor login-preview: full event (no website / createdAt / updatedAt) + computed logoUrl. */
|
/** Visitor login-preview: full event (no website / createdAt / updatedAt) + computed logoUrl. */
|
||||||
export interface LoginPreviewResponse {
|
export interface LoginPreviewResponse {
|
||||||
_id: string;
|
_id: string;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue