From 2c5ccff63ac55af37e458bc167ee3a27f54b5149 Mon Sep 17 00:00:00 2001 From: Amey Bhagwat Date: Fri, 14 Aug 2026 13:16:19 +0530 Subject: [PATCH 1/2] UI changes and theme setup --- src/App.tsx | 4 + src/components/AgendaRowTrackV2.tsx | 4 +- src/components/AskQuestionModal.tsx | 8 +- src/components/AudienceQuestionCard.tsx | 2 +- src/components/DynamicFeedbackForm.tsx | 10 +- src/components/EventLandingAgenda.tsx | 4 +- src/components/HeaderMobile.tsx | 8 +- src/components/MobileShell.tsx | 2 +- src/components/SessionQuestionsCard.tsx | 6 +- src/components/SessionQuizPollsSection.tsx | 8 +- src/hooks/useEventTheme.ts | 22 ++ src/index.css | 72 +++++-- src/lib/api.ts | 125 ++++++++++- src/pages/EventLandingPage.tsx | 12 +- src/pages/FeedbackPage.tsx | 4 +- src/pages/LeaderboardPage.tsx | 6 +- src/pages/LoginPage.tsx | 24 +-- src/pages/Questions.tsx | 49 ++--- src/pages/QuizPollTakePage.tsx | 51 ++--- src/pages/RatingsPage.tsx | 4 +- src/pages/RewardsPage.tsx | 18 +- src/pages/SingleEventPage.tsx | 28 +-- src/theme/eventTheme.ts | 229 +++++++++++++++++++++ src/types/visitorContext.ts | 39 ++++ 24 files changed, 593 insertions(+), 146 deletions(-) create mode 100644 src/hooks/useEventTheme.ts create mode 100644 src/theme/eventTheme.ts diff --git a/src/App.tsx b/src/App.tsx index cbf32d3..6ef182e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -21,8 +21,12 @@ import { QuestionsPresentPage } from "./pages/QuestionsPresentPage"; import { CodeEntryPage } from "./pages/CodeEntryPage"; import { PrivacyPolicyPage } from "./pages/PrivacyPolicyPage"; import { TermsOfServicePage } from "./pages/TermsOfServicePage"; +import { useEventTheme } from "./hooks/useEventTheme"; export default function App() { + // Re-apply theme from stored visitor context on refresh / navigation. + useEventTheme(); + return (
diff --git a/src/components/AgendaRowTrackV2.tsx b/src/components/AgendaRowTrackV2.tsx index 06e2bf9..60e5a34 100644 --- a/src/components/AgendaRowTrackV2.tsx +++ b/src/components/AgendaRowTrackV2.tsx @@ -35,8 +35,8 @@ export type AgendaRowTrackV2Data = { export function AgendaRowTrackV2({ row }: { row: AgendaRowTrackV2Data }) { const [detailsOpen, setDetailsOpen] = useState(false); const joinBtnClass = row.joinLive - ? "inline-flex h-8 w-[84px] items-center justify-between rounded-full bg-[#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 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 bg-[var(--theme-button-color)] pl-[18px] pr-2 text-xs font-semibold uppercase text-white shadow-[0_0_0_2px_rgba(239,141,29,0.5),0_4px_14px_rgba(239,141,29,0.45)] ring-2 ring-[var(--theme-button-color)]/40" + : "inline-flex h-8 w-[84px] items-center justify-between rounded-full bg-[var(--theme-button-color)] transition-colors duration-300 ease-in-out hover:bg-[var(--theme-button-color)]/90 pl-[18px] pr-2 text-xs font-semibold uppercase text-white shadow-[0_4px_4px_rgba(0,0,0,0.15)]"; return (
diff --git a/src/components/AskQuestionModal.tsx b/src/components/AskQuestionModal.tsx index dc23260..af90e79 100644 --- a/src/components/AskQuestionModal.tsx +++ b/src/components/AskQuestionModal.tsx @@ -79,7 +79,7 @@ export function AskQuestionModal({ role="dialog" aria-modal="true" aria-labelledby={`ask-question-title${idSuffix}`} - className="relative z-10 w-full max-w-[min(100%,402px)] rounded-t-2xl bg-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 bg-whit card-bg px-5 pb-8 pt-5 shadow-[0_-8px_32px_rgba(0,0,0,0.12)] sm:mx-4 sm:rounded-2xl sm:shadow-xl" >
@@ -92,7 +92,7 @@ export function AskQuestionModal({
diff --git a/src/components/AudienceQuestionCard.tsx b/src/components/AudienceQuestionCard.tsx index f4cd6d3..c86e12b 100644 --- a/src/components/AudienceQuestionCard.tsx +++ b/src/components/AudienceQuestionCard.tsx @@ -53,7 +53,7 @@ export function AudienceQuestionCard({ const shell = variant === "embedded" ? "flex flex-col gap-4 p-4" - : "flex flex-col gap-4 rounded-md border border-[#f2f2f7] bg-white p-4 shadow-[0_1px_4px_rgba(12,12,13,0.05)]"; + : "flex flex-col gap-4 rounded-md border border-[#f2f2f7] card-bg p-4 shadow-[0_1px_4px_rgba(12,12,13,0.05)]"; const doVote = useCallback( async (direction: "up" | "down") => { diff --git a/src/components/DynamicFeedbackForm.tsx b/src/components/DynamicFeedbackForm.tsx index cd34139..ce7213b 100644 --- a/src/components/DynamicFeedbackForm.tsx +++ b/src/components/DynamicFeedbackForm.tsx @@ -101,7 +101,7 @@ export function DynamicFeedbackForm({ const fieldShell = variant === "gradient" ? "rounded-xl bg-white p-6 shadow-[0_8px_12px_6px_rgba(0,0,0,0.15),0_4px_4px_rgba(0,0,0,0.3)]" - : "rounded-md bg-white p-4 shadow-[0_1px_4px_rgba(12,12,13,0.05)]"; + : "rounded-md bg-whit card-bg p-4 shadow-[0_1px_4px_rgba(12,12,13,0.05)]"; return (
@@ -120,7 +120,7 @@ export function DynamicFeedbackForm({ @@ -154,7 +154,7 @@ function FieldInput({ const t = (f.type || "text").toLowerCase(); 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") { return ( @@ -182,11 +182,11 @@ function FieldInput({ key={star} type="button" onClick={() => onChange(label, star)} - className="text-[#ef8d1d]" + className="text-[var(--theme-button-color)]" aria-label={`${star} star${star === 1 ? "" : "s"}`} > diff --git a/src/components/EventLandingAgenda.tsx b/src/components/EventLandingAgenda.tsx index 8443747..d01b79e 100644 --- a/src/components/EventLandingAgenda.tsx +++ b/src/components/EventLandingAgenda.tsx @@ -172,7 +172,7 @@ export function LandingAgendaCards({ return (
diff --git a/src/components/SessionQuizPollsSection.tsx b/src/components/SessionQuizPollsSection.tsx index a6df256..6d9b2a8 100644 --- a/src/components/SessionQuizPollsSection.tsx +++ b/src/components/SessionQuizPollsSection.tsx @@ -44,7 +44,7 @@ export function SessionQuizPollsSection({ eventId, sessionId }: SessionQuizPolls }).toString()}`; return ( -
+
{open ? ( - + ) : ( - + )} @@ -91,7 +91,7 @@ export function SessionQuizPollsSection({ eventId, sessionId }: SessionQuizPolls
{actionLabel(item.type)} diff --git a/src/hooks/useEventTheme.ts b/src/hooks/useEventTheme.ts new file mode 100644 index 0000000..f809296 --- /dev/null +++ b/src/hooks/useEventTheme.ts @@ -0,0 +1,22 @@ +import { useEffect } from "react"; +import { applyEventTheme, clearEventTheme } from "../theme/eventTheme"; +import type { EventThemeConfig } from "../types/visitorContext"; +import { readStoredVisitorContext } from "../types/visitorContext"; + +/** + * Applies event theme CSS variables to ``. + * When `theme` is omitted, uses theme from stored visitor context (if any). + */ +export function useEventTheme(theme?: EventThemeConfig | null) { + useEffect(() => { + const resolved = theme ?? readStoredVisitorContext()?.event?.theme ?? null; + if (resolved) { + applyEventTheme(resolved); + } + return () => { + // Keep vars while navigating between themed event pages; + // only clear when an explicit null theme is passed. + if (theme === null) clearEventTheme(); + }; + }, [theme]); +} diff --git a/src/index.css b/src/index.css index 47b3bee..d60dddc 100644 --- a/src/index.css +++ b/src/index.css @@ -15,15 +15,20 @@ html { 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 { margin: 0; min-height: 100dvh; - font-family: var(--font-sans); + font-family: var(--theme-body-font, var(--font-sans)); color: var(--color-eb-text); - background-color: #f2f2f7; + background: transparent; -webkit-font-smoothing: antialiased; } @@ -31,35 +36,42 @@ body { display: flex; flex-direction: column; min-height: 100dvh; - background-color: #f2f2f7; + background: transparent; } .eb-gradient-banner { background: linear-gradient( 34.8deg, - rgb(29, 102, 239) 0%, - rgb(30, 121, 240) 45%, - rgb(189, 53, 230) 100% + var(--theme-primary-color, rgb(29, 102, 239)) 0%, + var(--theme-primary-color, rgb(30, 121, 240)) 45%, + var(--theme-secondary-color, rgb(189, 53, 230)) 100% ); } .eb-gradient-fab { background: linear-gradient( 65deg, - rgb(29, 102, 239) 0%, - rgb(30, 121, 240) 40%, - rgb(189, 53, 230) 100% + var(--theme-primary-color, rgb(29, 102, 239)) 0%, + var(--theme-primary-color, rgb(30, 121, 240)) 40%, + var(--theme-secondary-color, rgb(189, 53, 230)) 100% ); } /* Auth / modal shells (Figma EventBuzz_Event_Mobile_V1_Register) */ .eb-gradient-auth { - background: linear-gradient( - 77.92deg, - rgb(29, 102, 239) 0%, - rgb(30, 121, 240) 45%, - rgb(189, 53, 230) 100% + background-color: var(--theme-bg-color, transparent); + background-image: var( + --theme-bg-image, + linear-gradient( + 77.92deg, + rgb(29, 102, 239) 0%, + rgb(30, 121, 240) 45%, + 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 */ @@ -116,6 +128,36 @@ body { 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 { 0%, 100% { diff --git a/src/lib/api.ts b/src/lib/api.ts index 440083b..0d3c0b5 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -1,7 +1,102 @@ import type { AttendeeParticipant } from "./attendeeAuth"; import { getAttendeeToken } from "./attendeeAuth"; +import { applyEventTheme } from "../theme/eventTheme"; import type { LoginPreviewResponse, ResolveCodeResponse, VisitorJoinType } from "../types/visitorContext"; +/** Temporary test payload for getVisitorLoginPreview — remove when wiring live API again. */ +const LOGIN_PREVIEW_SAMPLE: LoginPreviewResponse = { + _id: "69ce3c91be56b4a3b09b2333", + organization: "69b58b13aff81b3887b0026c", + name: "COPD Conference", + description: "COPD Conference in various locations joined using Zoom", + type: "conference", + status: "upcoming", + startDate: "2026-06-25T12:00:00.000Z", + endDate: "2026-09-04T12:00:00.000Z", + location: + "Stratawiz Technologies Pvt Ltd, First Floor, D-Building, Anant Shilp Society, DSK Raanwaara Rd, Patil Nagar, Bavdhan, Pune, Maharashtra 411021", + locationType: "virtual", + tags: [], + image: + "https://did94hhhcgq1d.cloudfront.net/eventbuzz/69b58b13aff81b3887b0026c/69ce3c91be56b4a3b09b2333/event/logo/event-logo-1775123603963.png", + bannerImage: + "https://did94hhhcgq1d.cloudfront.net/eventbuzz/69b58b13aff81b3887b0026c/69ce3c91be56b4a3b09b2333/event/banner/event-banner-1782452674436.png", + metadata: { + totalRevenue: 0, + rating: 3.9, + ratingCount: 8, + registrationCustomFields: [], + }, + category: undefined, + virtualLink: "https://zoom.us", + subtitle: undefined, + settings: { + registrationOpen: true, + requiresApproval: false, + registrationMode: "Open", + verifyEmail: false, + registrationDeadline: "2026-09-12T12:00:00.000Z", + enableFeedback: true, + enableRatings: true, + authenticationType: "none", + registrationBaseFields: "name_email", + }, + logoUrl: + "https://did94hhhcgq1d.cloudfront.net/eventbuzz/69b58b13aff81b3887b0026c/69ce3c91be56b4a3b09b2333/event/logo/event-logo-1775123603963.png", + ratings: { + average: 3.9, + count: 8, + }, + // theme: { + // "theme": "simple", + // "color": "#151937", + // "font": "Roboto", + // "primaryColor": "#303F9F", + // "secondaryColor": "#4CAF50", + // "headerColor": "", + // "buttonColor": "#2c357a", + // "textColor": "#ffffff", + // "textHighlightColor": "", + // "cardColor": "rgba(255,255,255,0.65)", + // "cardTextColor": "#ffffff" + // }, + // theme: { + // theme: "gradient", + // color: "#bd35e6", + // // "gradient": { + // // "color1": "#1e72ef", + // // "color2": "#ef8d1d" + // // }, + // font: "Open Sans", + // primaryColor: "#1e72ef", + // secondaryColor: "#bd35e6", + // headerColor: "", + // buttonColor: "#1e72ef", + // textColor: "#ffffff", + // textHighlightColor: "", + // cardColor: "rgba(255, 255, 255, 0.9)", + // cardTextColor: "#333333", + // }, + theme: { + theme: "background", + color: "#1e72ef", + background: { + url: "https://did94hhhcgq1d.cloudfront.net/rapport/backgrounds/eventbuzz-bg2.jpg", + size: "cover", + repeat: "no", + }, + font: "Poppins", + primaryColor: "#1e72ef", + secondaryColor: "#bd35e6", + headerColor: "", + buttonColor: "#1e72ef", + textColor: "#ffffff", + textHighlightColor: "", + cardColor: "rgba(255, 255, 255, 0.9)", + cardTextColor: "#333333", + }, +}; + function getBaseUrl(): string { const raw = import.meta.env.VITE_API_BASE_URL; if (typeof raw !== "string" || !raw.trim()) return ""; @@ -55,17 +150,25 @@ export async function postVisitorResolveCode(code: string): Promise { - const base = getBaseUrl(); - const url = `${base}/api/visitor/events/${encodeURIComponent(eventId)}/login-preview`; - const res = await fetch(url); - const body = await parseJson(res); - if (!res.ok) { - throw new Error(messageFromBody(body)); - } - if (!body || typeof body !== "object" || !("data" in body)) { - throw new Error("Invalid response"); - } - return (body as { data: LoginPreviewResponse }).data; + // TODO: restore live API call below once theme testing is done + void eventId; + const data = LOGIN_PREVIEW_SAMPLE; + if (data.theme) applyEventTheme(data.theme); + return data; + + // const base = getBaseUrl(); + // const url = `${base}/api/visitor/events/${encodeURIComponent(eventId)}/login-preview`; + // const res = await fetch(url); + // const body = await parseJson(res); + // if (!res.ok) { + // throw new Error(messageFromBody(body)); + // } + // if (!body || typeof body !== "object" || !("data" in body)) { + // throw new Error("Invalid response"); + // } + // const data = (body as { data: LoginPreviewResponse }).data; + // if (data?.theme) applyEventTheme(data.theme); + // return data; } export type VisitorEventTrackListItem = { diff --git a/src/pages/EventLandingPage.tsx b/src/pages/EventLandingPage.tsx index 6b14870..684b837 100644 --- a/src/pages/EventLandingPage.tsx +++ b/src/pages/EventLandingPage.tsx @@ -149,15 +149,15 @@ export function EventLandingPage() { -
+
-
+

{event.name}

{reg.enableRatings ? ( - + rate now ) : ( @@ -167,7 +167,7 @@ export function EventLandingPage() {

{event.description?.trim() || event.subtitle?.trim() || "Welcome to this event."}

-
@@ -188,7 +188,7 @@ export function EventLandingPage() { {bookmarkedIds.size > 0 ? (
-

My Sessions

+

My Sessions

{agendaLoading && !agendaSessions.length ? (

Loading…

@@ -205,7 +205,7 @@ export function EventLandingPage() {
-

Agenda

+

Agenda

{agendaErr ? (

{agendaErr}

diff --git a/src/pages/FeedbackPage.tsx b/src/pages/FeedbackPage.tsx index 3510dc2..ea52b8a 100644 --- a/src/pages/FeedbackPage.tsx +++ b/src/pages/FeedbackPage.tsx @@ -124,9 +124,9 @@ export function FeedbackPage() { return ( -
+
-

{schema?.name || "Feedback"}

+

{schema?.name || "Feedback"}

{schemaLoading ? (

Loading form…

) : schemaErr ? ( diff --git a/src/pages/LeaderboardPage.tsx b/src/pages/LeaderboardPage.tsx index 4eed419..b2e1a88 100644 --- a/src/pages/LeaderboardPage.tsx +++ b/src/pages/LeaderboardPage.tsx @@ -81,7 +81,7 @@ export function LeaderboardPage() {

Leaderboard

-
+

Your rank

@@ -89,7 +89,7 @@ export function LeaderboardPage() { {!signedIn ? "—" : loading ? "…" : yourRank != null ? yourRank : "—"} - + {!signedIn ? "Sign in" : loading @@ -133,7 +133,7 @@ export function LeaderboardPage() { Rewards diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx index 4415441..1c010f9 100644 --- a/src/pages/LoginPage.tsx +++ b/src/pages/LoginPage.tsx @@ -240,9 +240,9 @@ export function LoginPage() {
-
+
-
+
{preview?.logoUrl ? (
-

Log in to

+

Log in to

-

+

{headline}

{preview?.subtitle ? ( -

{preview.subtitle}

+

{preview.subtitle}

) : null}
{previewError ? ( @@ -280,7 +280,7 @@ export function LoginPage() { {schedule ? ( <> - {schedule} + {schedule} ) : null} {schedule && preview?.location?.trim() ? ( @@ -289,7 +289,7 @@ export function LoginPage() { {preview?.location?.trim() ? ( <> - {preview.location.trim()} + {preview.location.trim()} ) : null}
@@ -298,25 +298,25 @@ export function LoginPage() { {showInviteField ? ( ) : null} {showEmail ? ( ) : null} @@ -335,7 +335,7 @@ export function LoginPage() { diff --git a/src/pages/Questions.tsx b/src/pages/Questions.tsx index d06acb5..bfad81d 100644 --- a/src/pages/Questions.tsx +++ b/src/pages/Questions.tsx @@ -137,28 +137,31 @@ export function Questions() { -
-
-
- Audience questions +
+
+
+ +
+ Audience questions +
+

+ {sessionTitle ?? "Session questions"} +

+ {ev?.name ? ( +

{ev.name}

+ ) : null} + +
-

- {sessionTitle ?? "Session questions"} -

- {ev?.name ? ( -

{ev.name}

- ) : null} - -
{listLoading ? ( @@ -179,8 +182,8 @@ export function Questions() {
{sessionHref ? ( -

- +

+ Back to session

diff --git a/src/pages/QuizPollTakePage.tsx b/src/pages/QuizPollTakePage.tsx index a945a76..a10b681 100644 --- a/src/pages/QuizPollTakePage.tsx +++ b/src/pages/QuizPollTakePage.tsx @@ -731,9 +731,9 @@ export function QuizPollTakePage() {
-
+
-
+

Thank you

Your response was submitted.

{showRes && isQuiz && typeof scoreSubmitted === "number" ? ( @@ -925,7 +925,7 @@ export function QuizPollTakePage() { const footerBtnClass = "rounded-lg px-3 py-1.5 text-center text-xs font-semibold shadow-sm disabled:opacity-50 disabled:hover:bg-inherit"; - const footerSecondaryClass = `${footerBtnClass} flex-1 border border-[#e5e5ea] bg-white text-[#24282f] hover:bg-[#f2f2f7]`; + const footerSecondaryClass = `${footerBtnClass} flex-1 border border-[#e5e5ea] bg-white/40 text-[#24282f] hover:bg-white/40`; return ( @@ -934,20 +934,23 @@ export function QuizPollTakePage() {
-

{typeHeading(quiz.type)}

-

{quiz.title}

- {isQuizMasterMode && liveQuestion ? ( -

- Question {liveQuestionIndex + 1} of {totalQuestions} -

- ) : multiQuestion ? ( -

- Question {stepSafe + 1} of {totalQuestions} -

- ) : null} +
+

{typeHeading(quiz.type)}

+

{quiz.title}

+ {isQuizMasterMode && liveQuestion ? ( +

+ Question {liveQuestionIndex + 1} of {totalQuestions} +

+ ) : multiQuestion ? ( +

+ Question {stepSafe + 1} of {totalQuestions} +

+ ) : null} +
+ {timerActive && secondsLeft != null ? (
{ const qi = isQuizMasterMode ? liveQuestionIndex : multiQuestion ? stepSafe : offset; return ( -
  • +
  • {qi + 1}. {q.question} @@ -991,7 +994,7 @@ export function QuizPollTakePage() {

    : null} -

    - +

    + Back to session

    @@ -1096,7 +1099,7 @@ export function QuizPollTakePage() {