Update microsite rendering and add environment example.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ravi Gulhane 2026-07-13 15:11:22 +05:30
parent 709607aeca
commit d9270632dc
5 changed files with 84 additions and 43 deletions

8
.env.example Normal file
View File

@ -0,0 +1,8 @@
# Local development — Vite proxies /api to VITE_API_PROXY_TARGET
VITE_API_BASE_URL=/api
VITE_API_PROXY_TARGET=http://localhost:3001
VITE_PUBLIC_WEB_BASE=http://localhost:5175
# Production (see .env.production — used automatically by `npm run build`)
# VITE_API_BASE_URL=https://api.eventbuzz.live/api
# VITE_PUBLIC_WEB_BASE=https://web.eventbuzz.live

17
package-lock.json generated
View File

@ -8,6 +8,7 @@
"name": "eventbuzz-web", "name": "eventbuzz-web",
"version": "0.0.1", "version": "0.0.1",
"dependencies": { "dependencies": {
"bootstrap-icons": "^1.13.1",
"flatpickr": "^4.6.13", "flatpickr": "^4.6.13",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
@ -1275,6 +1276,22 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/bootstrap-icons": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.13.1.tgz",
"integrity": "sha512-ijombt4v6bv5CLeXvRWKy7CuM3TRTuPEuGaGKvTV5cz65rQSY8RQ2JcHt6b90cBBAC7s8fsf2EkQDldzCoXUjw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
],
"license": "MIT"
},
"node_modules/browserslist": { "node_modules/browserslist": {
"version": "4.28.4", "version": "4.28.4",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz",

View File

@ -9,6 +9,7 @@
"preview": "vite preview --port 5175" "preview": "vite preview --port 5175"
}, },
"dependencies": { "dependencies": {
"bootstrap-icons": "^1.13.1",
"flatpickr": "^4.6.13", "flatpickr": "^4.6.13",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",

View File

@ -12,6 +12,21 @@ function str(v: unknown, fallback = ''): string {
return v != null ? String(v).trim() : fallback; return v != null ? String(v).trim() : fallback;
} }
function isRegisterLabel(label: string): boolean {
return /\bregister\b/i.test(label);
}
function looksLikeRegistrationUrl(url: string): boolean {
return /\/register\/?$/i.test(url) || /\/form\/[a-f0-9]{24}\/?$/i.test(url);
}
function resolveRegistrationCtaUrl(label: string, configuredUrl: unknown, registrationUrl: string): string {
const configured = str(configuredUrl);
const wantsRegistration = isRegisterLabel(label) || looksLikeRegistrationUrl(configured);
if (wantsRegistration) return registrationUrl;
return configured || '#';
}
function HeaderSection({ section, ctx }: { section: MicrositeSection; ctx: Ctx }) { function HeaderSection({ section, ctx }: { section: MicrositeSection; ctx: Ctx }) {
const c = section.config; const c = section.config;
const logoText = str(c.logoText, ctx.data.event.name); const logoText = str(c.logoText, ctx.data.event.name);
@ -40,6 +55,7 @@ function HeaderSection({ section, ctx }: { section: MicrositeSection; ctx: Ctx }
const label = str(item.label); const label = str(item.label);
const url = str(item.url, '#'); const url = str(item.url, '#');
const isReg = label.toLowerCase() === 'register'; const isReg = label.toLowerCase() === 'register';
if (isReg && !ctx.registrationUrl) return null;
return ( return (
<li key={i}> <li key={i}>
<a <a
@ -59,13 +75,13 @@ function HeaderSection({ section, ctx }: { section: MicrositeSection; ctx: Ctx }
); );
} }
function BannerSection({ section }: { section: MicrositeSection }) { function BannerSection({ section, ctx }: { section: MicrositeSection; ctx: Ctx }) {
const c = section.config; const c = section.config;
const headline = str(c.headline, 'Welcome'); const headline = str(c.headline, 'Welcome');
const subtext = str(c.subtext); const subtext = str(c.subtext);
const heroMetaLine = str(c.heroMetaLine); const heroMetaLine = str(c.heroMetaLine);
const ctaText = str(c.ctaText); const ctaText = str(c.ctaText);
const ctaUrl = str(c.ctaUrl, '#'); const ctaUrl = resolveRegistrationCtaUrl(ctaText, c.ctaUrl, ctx.registrationUrl);
const bgImage = str(c.backgroundImage); const bgImage = str(c.backgroundImage);
const secText = str(c.secondaryCtaText); const secText = str(c.secondaryCtaText);
const secUrl = str(c.secondaryCtaUrl, '#schedule'); const secUrl = str(c.secondaryCtaUrl, '#schedule');
@ -80,7 +96,7 @@ function BannerSection({ section }: { section: MicrositeSection }) {
{subtext ? <p className="eb-hero-intro mb-4">{subtext}</p> : null} {subtext ? <p className="eb-hero-intro mb-4">{subtext}</p> : null}
{(ctaText || secText) && ( {(ctaText || secText) && (
<div className="eb-hero-cta"> <div className="eb-hero-cta">
{ctaText ? ( {ctaText && ctaUrl ? (
<a className="btn btn-primary btn-lg" href={ctaUrl}> <a className="btn btn-primary btn-lg" href={ctaUrl}>
{ctaText} {ctaText}
</a> </a>
@ -141,13 +157,13 @@ function CountdownSection({ section, ctx }: { section: MicrositeSection; ctx: Ct
); );
} }
function AboutSection({ section }: { section: MicrositeSection }) { function AboutSection({ section, ctx }: { section: MicrositeSection; ctx: Ctx }) {
const c = section.config; const c = section.config;
const title = str(c.title, 'About'); const title = str(c.title, 'About');
const body = str(c.body); const body = str(c.body) || str(c.lead);
const bullets = Array.isArray(c.bullets) ? (c.bullets as string[]) : []; const bullets = Array.isArray(c.bullets) ? (c.bullets as string[]) : [];
const ctaText = str(c.ctaText); const ctaText = str(c.ctaText) || str(c.ctaLabel);
const ctaUrl = str(c.ctaUrl, '#'); const ctaUrl = resolveRegistrationCtaUrl(ctaText, c.ctaUrl, ctx.registrationUrl);
return ( return (
<section id="about" className="py-5 eb-section-light"> <section id="about" className="py-5 eb-section-light">
@ -161,7 +177,7 @@ function AboutSection({ section }: { section: MicrositeSection }) {
))} ))}
</ul> </ul>
) : null} ) : null}
{ctaText ? ( {ctaText && ctaUrl ? (
<div className="text-center mt-4"> <div className="text-center mt-4">
<a className="btn btn-primary btn-lg" href={ctaUrl}> <a className="btn btn-primary btn-lg" href={ctaUrl}>
{ctaText} {ctaText}
@ -427,40 +443,27 @@ function AboutSection({ section }: { section: MicrositeSection }) {
// ); // );
// } // }
function AgendaSection({ function AgendaSection({
section, section,
items, items,
ctx,
}: { }: {
section: MicrositeSection; section: MicrositeSection;
items: AgendaItem[]; items: AgendaItem[];
ctx: Ctx;
}) { }) {
console.log("Items:",items); const title = str(section.config.title, 'Schedule');
const title = str(section.config.title, "Schedule");
const list = items.length const list = items.length
? items ? items
: [{ title: "Session 1", time: "9:00 AM", track: "Main", date: "" }]; : [{ title: 'Session 1', time: '9:00 AM', track: 'Main', date: '' }];
// Get unique dates
const dates = [...new Set(list.map((item) => item.date).filter(Boolean))]; const dates = [...new Set(list.map((item) => item.date).filter(Boolean))];
// Selected day const [selectedDate, setSelectedDate] = useState(dates.length ? dates[0]! : '');
const [selectedDate, setSelectedDate] = useState(
dates.length ? dates[0] : ""
);
// Expanded accordion
const [expandedIndex, setExpandedIndex] = useState<number | null>(null); const [expandedIndex, setExpandedIndex] = useState<number | null>(null);
// Filter sessions according to selected date const filteredList = dates.length ? list.filter((item) => item.date === selectedDate) : list;
const filteredList = list.filter((item) => item.date === selectedDate);
console.log("dates:", dates);
console.log("selectedDate:", selectedDate);
console.log("filteredList:", filteredList);
console.log("list:", list);
return ( return (
<section id="schedule" className="eb-schedule-section section py-5"> <section id="schedule" className="eb-schedule-section section py-5">
<div className="container"> <div className="container">
@ -728,9 +731,11 @@ console.log("list:", list);
{/* Action Buttons */} {/* Action Buttons */}
<div className="text-center mt-4"> <div className="text-center mt-4">
<a className="btn btn-primary btn-lg me-2" href="#register"> {ctx.registrationUrl ? (
Register <a className="btn btn-primary btn-lg me-2" href={ctx.registrationUrl}>
</a> Register
</a>
) : null}
<a className="btn btn-outline-secondary btn-lg" href="#hero"> <a className="btn btn-outline-secondary btn-lg" href="#hero">
Back to top Back to top
</a> </a>
@ -790,10 +795,12 @@ function SpeakersSection({ section, speakers }: { section: MicrositeSection; spe
} }
function RegisterLinksSection({ section, ctx }: { section: MicrositeSection; ctx: Ctx }) { function RegisterLinksSection({ section, ctx }: { section: MicrositeSection; ctx: Ctx }) {
if (!ctx.registrationUrl) return null;
const c = section.config; const c = section.config;
const title = str(c.title, 'Register'); const title = str(c.title, 'Register');
const primaryLabel = str(c.primaryLabel, 'Register Now'); const primaryLabel = str(c.primaryLabel, 'Register Now');
const primaryUrl = str(c.primaryUrl, ctx.registrationUrl); const primaryUrl = ctx.registrationUrl;
const secondaryLabel = str(c.secondaryLabel); const secondaryLabel = str(c.secondaryLabel);
const secondaryUrl = str(c.secondaryUrl, '#'); const secondaryUrl = str(c.secondaryUrl, '#');
@ -959,16 +966,18 @@ function CtaSection({ section, ctx }: { section: MicrositeSection; ctx: Ctx }) {
const headline = str(c.headline, 'Join us'); const headline = str(c.headline, 'Join us');
const subtext = str(c.subtext); const subtext = str(c.subtext);
const buttonLabel = str(c.buttonLabel, 'Register'); const buttonLabel = str(c.buttonLabel, 'Register');
const buttonUrl = str(c.buttonUrl, ctx.registrationUrl); const buttonUrl = resolveRegistrationCtaUrl(buttonLabel, c.buttonUrl, ctx.registrationUrl);
return ( return (
<section id="cta" className="py-5 text-white text-center" style={{ background: 'var(--eb-conf-primary)' }}> <section id="cta" className="py-5 text-white text-center" style={{ background: 'var(--eb-conf-primary)' }}>
<div className="container"> <div className="container">
<h2 className="eb-section-heading text-white mb-3">{headline}</h2> <h2 className="eb-section-heading text-white mb-3">{headline}</h2>
{subtext ? <p className="lead mb-4">{subtext}</p> : null} {subtext ? <p className="lead mb-4">{subtext}</p> : null}
<a className="btn btn-light btn-lg" href={buttonUrl}> {buttonUrl && buttonUrl !== '#' ? (
{buttonLabel} <a className="btn btn-light btn-lg" href={buttonUrl}>
</a> {buttonLabel}
</a>
) : null}
</div> </div>
</section> </section>
); );
@ -1068,9 +1077,11 @@ function TicketsSection({ section, ctx }: { section: MicrositeSection; ctx: Ctx
<h3 style={{ marginTop: 0 }}>{str(tier.name)}</h3> <h3 style={{ marginTop: 0 }}>{str(tier.name)}</h3>
<p style={{ fontSize: '1.5rem', fontWeight: 700, color: 'var(--eb-conf-primary)' }}>{str(tier.price)}</p> <p style={{ fontSize: '1.5rem', fontWeight: 700, color: 'var(--eb-conf-primary)' }}>{str(tier.price)}</p>
{tier.description ? <p className="text-muted">{tier.description}</p> : null} {tier.description ? <p className="text-muted">{tier.description}</p> : null}
<a className="btn btn-primary" href={ctx.registrationUrl}> {ctx.registrationUrl ? (
Register <a className="btn btn-primary" href={ctx.registrationUrl}>
</a> Register
</a>
) : null}
</div> </div>
</div> </div>
))} ))}
@ -1085,13 +1096,13 @@ export function SectionRenderer({ section, ctx }: { section: MicrositeSection; c
case 'header': case 'header':
return <HeaderSection section={section} ctx={ctx} />; return <HeaderSection section={section} ctx={ctx} />;
case 'banner': case 'banner':
return <BannerSection section={section} />; return <BannerSection section={section} ctx={ctx} />;
case 'countdown': case 'countdown':
return <CountdownSection section={section} ctx={ctx} />; return <CountdownSection section={section} ctx={ctx} />;
case 'about': case 'about':
return <AboutSection section={section} />; return <AboutSection section={section} ctx={ctx} />;
case 'agenda': case 'agenda':
return <AgendaSection section={section} items={ctx.data.agenda} />; return <AgendaSection section={section} items={ctx.data.agenda} ctx={ctx} />;
case 'speakers': case 'speakers':
return <SpeakersSection section={section} speakers={ctx.data.speakers} />; return <SpeakersSection section={section} speakers={ctx.data.speakers} />;
case 'register_links': case 'register_links':

View File

@ -42,7 +42,9 @@ export function EventMicrositePage() {
); );
} }
const registrationUrl = data.registrationUrl || `${PUBLIC_WEB_BASE}/event/${eventId}/register`; const registrationUrl = data.registrationFormId
? `${PUBLIC_WEB_BASE}/event/${eventId}/form/${data.registrationFormId}`
: '';
const ctx = { data, eventId, registrationUrl }; const ctx = { data, eventId, registrationUrl };
if (data.micrositeHtml) { if (data.micrositeHtml) {
@ -53,7 +55,9 @@ export function EventMicrositePage() {
); );
} }
const sections = [...(data.sections || [])].sort((a, b) => (a.order ?? 0) - (b.order ?? 0)); const sections = [...(data.sections || [])]
.filter((section) => section.type !== 'sponsors')
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
return ( return (
<MicrositeLayout data={data}> <MicrositeLayout data={data}>