diff --git a/.env.example b/.env.example index d402b116..f0c162ea 100644 --- a/.env.example +++ b/.env.example @@ -2,13 +2,22 @@ # Backend API base URL (used by services/api/client.ts) VITE_API_URL=http://localhost:3001/api +# Virtual Concierge public API (website embed snippet). Must end with /api. +# VITE_CONCIERGE_URL=http://localhost:3010/api +# Production: https://concierge.eventbuzz.live/api + # Optional: public base URL for published custom microsites (no trailing slash). Links to {base}/{folder}/index.html # VITE_MICROSITE_PUBLIC_BASE=https://web.eventbuzz.live # Public web app for default microsites, forms, and registration (no trailing slash) # VITE_PUBLIC_WEB_BASE=https://web.eventbuzz.live +# Participant SPA base (join codes /code/{code}, quiz-present). Not microsites. +# Production: https://join.eventbuzz.live — local eventbuzz Vite often :5174 +# VITE_EVENTBUZZ_PUBLIC_URL=https://join.eventbuzz.live + # GrapesJS microsite builder (Vite app in /website_builder). Iframe src + postMessage target origin. + VITE_WEBSITE_BUILDER_URL=http://localhost:5173 # Optional: exact origin for postMessage (defaults to origin of VITE_WEBSITE_BUILDER_URL) # VITE_WEBSITE_BUILDER_ORIGIN=http://localhost:5173 diff --git a/dist/index.html b/dist/index.html index be891495..2b772bc4 100644 --- a/dist/index.html +++ b/dist/index.html @@ -12,8 +12,8 @@ rel="stylesheet" /> - - + +
diff --git a/public/fonts/Swiss921BT.ttf b/public/fonts/Swiss921BT.ttf new file mode 100644 index 00000000..b7fe5f51 Binary files /dev/null and b/public/fonts/Swiss921BT.ttf differ diff --git a/src/app/components/EventBuzzBrandLogo.tsx b/src/app/components/EventBuzzBrandLogo.tsx new file mode 100644 index 00000000..b4348076 --- /dev/null +++ b/src/app/components/EventBuzzBrandLogo.tsx @@ -0,0 +1,47 @@ +import type { CSSProperties } from "react"; +import logoUrl from "../../assets/logo.png"; +import logoSmallUrl from "../../assets/logo-small.png"; + +type EventBuzzBrandLogoProps = { + /** `full` = wordmark (`logo.png`); `small` = mark (`logo-small.png`). */ + variant?: "full" | "small"; + /** Visual height in px (width scales with image aspect). */ + height?: number; + /** Optional max width clamp. */ + maxWidth?: number; + className?: string; + style?: CSSProperties; + alt?: string; +}; + +/** + * Product logo from `src/assets/logo.png` / `logo-small.png`. + */ +export function EventBuzzBrandLogo({ + variant = "full", + height = 28, + maxWidth, + className, + style, + alt = "EventBuzz", +}: EventBuzzBrandLogoProps) { + const src = variant === "small" ? logoSmallUrl : logoUrl; + return ( ++ Loading exhibitors… +
+ ) : null} + {error ? ( ++ {error} +
+ ) : null} + + {!loading && !error && filtered.length === 0 ? ( ++ {exhibitors.length === 0 ? "No exhibitors yet" : "No matches"} +
++ {exhibitors.length === 0 + ? "Add companies exhibiting at this event to build the exhibitor directory." + : "Try another search."} +
+ {canWrite && exhibitors.length === 0 ? ( ++ {ex.name} + {ex.isFeatured ? ( + + Featured + + ) : null} +
+ + {statusLabel(ex.status)} + ++ {[ + ex.boothNumber ? `Booth ${ex.boothNumber}` : null, + ex.hall || null, + ex.category || null, + ] + .filter(Boolean) + .join(" · ") || "No booth details yet"} +
+{err}
} {msg &&{msg}
}+ Used even when custom SMTP is off. +
+ Required when custom SMTP is on. Ignored when off (platform From email is used). +
+ Used even when custom SMTP is off. When custom SMTP is on and this is blank, defaults to From email. +
++ {error} +
+ ) : null} ++ {saveErr} +
+ ) : null} + ++ Primary contact +
++ Social links +
+- Select from your organization's directory or add someone new. + Select from your organization's directory or add someone new. Assign a role for each person (defaults to Speaker).
- {selectedSpeakerIds.length > 0 && ( -{err}
+ )} + +
+ Required: email
+ {mode === "NEW_INSERT" || mode === "UPSERT" || mode === "SYNC" ? (
+ <>
+ , firstName, lastName (for new rows)
+ >
+ ) : null}
+ {mode === "STATUS_UPDATE" ? (
+ <>
+ , status (pending, confirmed, checked_in,{" "}
+ cancelled, rejected)
+ >
+ ) : null}
+
+ Optional: phone, organization, designation, tags, notes + {mode !== "STATUS_UPDATE" ? ". Headers like “First name” / “Last name” are accepted." : "."} +
+
+ Example:{" "}
+
+ {mode === "STATUS_UPDATE"
+ ? "email,status"
+ : "email,firstName,lastName,phone,organization,designation,tags,notes"}
+
+
+ Palette overrides +
++
{error}
) : null} diff --git a/src/app/components/panels/badgeBatchFormState.ts b/src/app/components/panels/badgeBatchFormState.ts index 43953599..58df57d4 100644 --- a/src/app/components/panels/badgeBatchFormState.ts +++ b/src/app/components/panels/badgeBatchFormState.ts @@ -24,7 +24,9 @@ export type BadgeFormState = { layoutElements: BadgeLayoutElement[]; customTexts: { id: string; text: string }[]; customImages: { id: string; imageUrl: string }[]; - outputFormat: "pdf" | "png" | "jpeg"; + outputFormat: "pdf" | "pdf_per_person" | "png" | "jpeg"; + printDpi: number; + colorSpace: "rgb" | "cmyk"; badgeTypeLabel: string; }; @@ -115,7 +117,12 @@ export function formStateFromBadgeBatch(batch: BadgeBatchRecord): BadgeFormState const scope = str(c.participantScope, "all") === "participant_list" ? "participant_list" : "all"; const out = str(c.outputFormat, "pdf"); const outputFormat: BadgeFormState["outputFormat"] = - out === "png" || out === "jpeg" ? out : "pdf"; + out === "png" || out === "jpeg" || out === "pdf_per_person" ? out : "pdf"; + const dpiRaw = Number(c.printDpi); + const printDpi = + Number.isFinite(dpiRaw) ? Math.min(600, Math.max(72, Math.round(dpiRaw))) : 300; + const colorSpace: BadgeFormState["colorSpace"] = + str(c.colorSpace, "rgb").toLowerCase() === "cmyk" ? "cmyk" : "rgb"; return { formName: duplicateExportName(batch.name), @@ -137,6 +144,8 @@ export function formStateFromBadgeBatch(batch: BadgeBatchRecord): BadgeFormState customTexts, customImages, outputFormat, + printDpi, + colorSpace, badgeTypeLabel: str(c.badgeTypeLabel, "Attendee"), }; } diff --git a/src/app/components/panels/badgeLayout.ts b/src/app/components/panels/badgeLayout.ts index 507112da..452197c5 100644 --- a/src/app/components/panels/badgeLayout.ts +++ b/src/app/components/panels/badgeLayout.ts @@ -82,6 +82,10 @@ export type BadgeLayoutElement = { /** Default true — reduce font size when text overflows */ shrinkToFit?: boolean; textAlign?: BadgeTextAlign; + /** Optional string before the field value (default empty) */ + prefix?: string; + /** Optional string after the field value (default empty) */ + postfix?: string; }; export const DEFAULT_TEXT_LAYOUT = { @@ -213,6 +217,7 @@ export const BADGE_FONT_FAMILIES = [ { id: "Arial", label: "Arial", css: "Arial, sans-serif" }, { id: "Georgia", label: "Georgia", css: "Georgia, serif" }, { id: "Times New Roman", label: "Times New Roman", css: "'Times New Roman', Times, serif" }, + { id: "Swiss921 BT", label: "Swiss921 BT", css: "'Swiss921 BT', sans-serif" }, ] as const; export const DEFAULT_TEXT_COLOR = "#0f172a"; @@ -389,23 +394,37 @@ export function previewAvatarColor(seed: string): string { return PREVIEW_AVATAR_COLORS[n % PREVIEW_AVATAR_COLORS.length]; } +export function applyTextAffixes(el: PickImport finished
- Required columns: email, firstName (or First name), lastName (or Last name). Optional: phone, organization, designation, tags, notes.
-
Sell tickets on the public checkout page. Buyers are added to the participant list you choose. Online card/UPI uses your - Razorpay (or platform) gateway when configured; if not, you can enable per-event UPI QR and review payment screenshots + Razorpay (and other organisation gateways you enable below) when configured; you can also enable per-event UPI QR and + review payment screenshots below.
+ Choose organisation payment gateways (from Settings → Payments) that buyers can use when purchasing tickets for this + event. +
+ {onlineErr ?{onlineErr}
: null} + {!manualLoading && onlineSettings ? ( ++ No gateways on this organisation yet. Add Razorpay (or another provider) under{" "} + Settings → Payments, then return here to enable it for tickets. +
+ ) : ( + (onlineSettings.orgGateways || []).map((g) => { + const supported = g.ticketCheckoutSupported && g.isActive; + return ( + + ); + }) + )} ++ Online card/UPI via your connected gateway will not be offered at ticket checkout for this event. +
+ )} + +- For events without an online payment gateway. Buyers pay you directly, then upload proof for you to approve. + Buyers pay you directly via UPI, then upload proof for you to approve. Can be used alone or alongside online gateways.
{manualErr &&{manualErr}
} - {manualLoading &&Loading UPI settings…
} + {manualLoading &&Loading payment settings…
} {!manualLoading && manualSettings && (+ Configure answers for fixed bot FAQ prompts. Questions are not editable. Empty answers fall back to event data or the help desk in the concierge. +
+ {(bot.faqs || []).length === 0 && ( ++ No questions yet — open this tab after loading the bot (templates seed automatically). +
+ )} + {(bot.faqs || []).map((f: ConciergeFaqItem, index: number) => { + const format = f.answerFormat || "short_text"; + const options = Array.isArray(f.options) ? f.options : []; + const selected = Array.isArray(f.selectedOptions) ? f.selectedOptions : []; + const showMcq = format === "mcq" || format === "mcq_short_text"; + const showMulti = format === "mcq_multi"; + const showText = + format === "short_text" || format === "mcq_short_text"; + const showContact = format === "contact_details"; + const showMedia = format === "media_upload"; + return ( ++ {f.question} +
+ + {faqFormatLabel(format)} + ++ These details are shared with visitors who ask for human / emergency help. +
+ + + ++ Upload a floor plan image or PDF. Visitors will see a preview and download option. +
+ { + const file = e.target.files?.[0]; + if (file) void handleFaqMediaUpload(index, file); + e.target.value = ""; + }} + /> + {f.mediaUrl ? ( ++ Select all that apply. +
+ {options.map((opt) => ( + + ))} +No tickets are available for this form.
+Step 2 of 2 — Choose your ticket
+UPI ID: {manualUpi.upiId}
: null} + {manualUpi.instructions ?{manualUpi.instructions}
: null} +Select a ticket to generate the UPI QR with the exact amount.
+ ) + ) : manualUpi.upiQrImageUrl ? ( +{quoteError}
: null} + {quote ? ( ++ Total: {formatTicketPrice(quote.totalMinor, quote.currency)} + {quote.taxMinor > 0 ? ( + (incl. tax {formatTicketPrice(quote.taxMinor, quote.currency)}) + ) : null} +
+ ) : null} + {payError ?{payError}
: null} + +- You will pay through the organiser's connected Razorpay account (not a platform wallet). + You will pay through the organiser's connected payment gateway.
} + {!isManualCheckout && onlineGateways.length > 1 ? ( +