125 lines
4.3 KiB
TypeScript
125 lines
4.3 KiB
TypeScript
import type { ReactNode } from 'react';
|
||
import type { PublicFormEventSummary } from '../../types';
|
||
import {
|
||
resolveFormEventHeaderDisplay,
|
||
type FormEventHeaderDisplay,
|
||
} from '../../utils/formEventHeaderDisplay';
|
||
|
||
const EVENT_TYPE_LABEL: Record<string, string> = {
|
||
conference: 'Conference',
|
||
workshop: 'Workshop',
|
||
summit: 'Summit',
|
||
cme: 'CME',
|
||
webinar: 'Webinar',
|
||
};
|
||
|
||
function formatEventType(t?: string): string {
|
||
if (!t) return '';
|
||
return EVENT_TYPE_LABEL[t] || t.replace(/^\w/, (c) => c.toUpperCase());
|
||
}
|
||
|
||
function formatLocationKind(locationType?: string): string {
|
||
if (locationType === 'virtual') return 'Virtual';
|
||
if (locationType === 'hybrid') return 'Hybrid';
|
||
return 'In person';
|
||
}
|
||
|
||
function formatEventDateRange(ev: PublicFormEventSummary): string {
|
||
const { startDate, endDate } = ev;
|
||
if (!startDate) return '—';
|
||
const s = new Date(startDate);
|
||
const e = endDate ? new Date(endDate) : s;
|
||
if (Number.isNaN(s.getTime())) return '—';
|
||
const sameDay = !Number.isNaN(e.getTime()) && s.toDateString() === e.toDateString();
|
||
const dOpt: Intl.DateTimeFormatOptions = { month: 'short', day: 'numeric', year: 'numeric' };
|
||
if (sameDay) return s.toLocaleDateString(undefined, { weekday: 'short', ...dOpt });
|
||
return `${s.toLocaleDateString(undefined, dOpt)} – ${e.toLocaleDateString(undefined, dOpt)}`;
|
||
}
|
||
|
||
function shouldShowLocation(ev: PublicFormEventSummary): boolean {
|
||
const loc = ev.location?.trim();
|
||
if (!loc) return false;
|
||
return ev.locationType === 'physical' || ev.locationType === 'hybrid';
|
||
}
|
||
|
||
function locationKindClass(locationType?: string): string {
|
||
if (locationType === 'virtual') return 'form-event-meta-kind--virtual';
|
||
if (locationType === 'hybrid') return 'form-event-meta-kind--hybrid';
|
||
return 'form-event-meta-kind--physical';
|
||
}
|
||
|
||
export function PublicFormEventHeader({
|
||
event,
|
||
variant = 'default',
|
||
display,
|
||
}: {
|
||
event: PublicFormEventSummary;
|
||
variant?: 'default' | 'embedded';
|
||
display?: FormEventHeaderDisplay | null;
|
||
}) {
|
||
const d = resolveFormEventHeaderDisplay(display);
|
||
const typeLabel = formatEventType(event.eventType);
|
||
const dateLine = formatEventDateRange(event);
|
||
const kind = formatLocationKind(event.locationType);
|
||
const showLoc = d.showVenue && shouldShowLocation(event);
|
||
const embedded = variant === 'embedded';
|
||
|
||
const showBanner = d.showLogo && !!event.imageUrl && !event.logoUrl;
|
||
const showLogo = d.showLogo && !!event.logoUrl;
|
||
const showTitle = d.showTitle && !!event.name;
|
||
const showSubtitle = d.showTitle && !!event.subtitle?.trim();
|
||
|
||
const metaItems: ReactNode[] = [];
|
||
if (d.showDateRange && dateLine) {
|
||
metaItems.push(<span className="form-event-meta-date">{dateLine}</span>);
|
||
}
|
||
if (d.showEventType && typeLabel) {
|
||
metaItems.push(<span>{typeLabel}</span>);
|
||
}
|
||
if (d.showLocationType && kind) {
|
||
metaItems.push(
|
||
<span className={`form-event-meta-kind ${locationKindClass(event.locationType)}`}>{kind}</span>,
|
||
);
|
||
}
|
||
if (showLoc) {
|
||
metaItems.push(<span className="form-event-meta-location">{event.location!.trim()}</span>);
|
||
}
|
||
|
||
if (!showBanner && !showLogo && !showTitle && !showSubtitle && metaItems.length === 0) {
|
||
return null;
|
||
}
|
||
|
||
return (
|
||
<div className={`form-event-header${embedded ? ' form-event-header--embedded' : ''}`}>
|
||
{showBanner ? (
|
||
<div className="form-event-banner">
|
||
<img src={event.imageUrl!} alt="" />
|
||
</div>
|
||
) : null}
|
||
{showLogo ? (
|
||
<img src={event.logoUrl!} alt={event.name} className="form-event-logo" />
|
||
) : null}
|
||
{showTitle || showSubtitle || metaItems.length > 0 ? (
|
||
<div>
|
||
{showTitle ? <h1 className="form-event-name">{event.name}</h1> : null}
|
||
{showSubtitle ? <p className="form-event-subtitle">{event.subtitle}</p> : null}
|
||
{metaItems.length > 0 ? (
|
||
<div className="form-event-meta">
|
||
{metaItems.map((item, index) => (
|
||
<span key={index} className="form-event-meta-item">
|
||
{index > 0 ? (
|
||
<span aria-hidden="true" className="form-event-meta-sep">
|
||
·
|
||
</span>
|
||
) : null}
|
||
{item}
|
||
</span>
|
||
))}
|
||
</div>
|
||
) : null}
|
||
</div>
|
||
) : null}
|
||
</div>
|
||
);
|
||
}
|