feat: organizers, meetups UI, Plausible analytics, and migration tooling

- Add organizer model/API, admin and public organizer pages, meetup cards
- Refresh events/home/contact; add calendar dialog and carousel components
- Optional Plausible via NEXT_PUBLIC_PLAUSIBLE_* env vars in root layout
- Prisma migration, seed updates, baseline-and-migrate script

Made-with: Cursor
This commit is contained in:
bbe
2026-04-04 21:55:34 +02:00
parent 586b572f73
commit 78271ea110
37 changed files with 1555 additions and 301 deletions

View File

@@ -51,3 +51,44 @@ export function getMeetupStartUtc(dateStr: string, timeStr: string): Date {
const utcStartH = startH - BRUSSELS_OFFSET_HOURS;
return new Date(Date.UTC(year, month - 1, day, utcStartH, startM, 0));
}
const UTC_CAL_OPTS = { timeZone: "UTC" } as const;
/**
* Format the stored meetup calendar date (YYYY-MM-DD) for display without shifting
* by the viewer's timezone. Date-only strings parsed as new Date("YYYY-MM-DD") are
* UTC midnight and show the wrong local day in western zones.
*/
export function formatMeetupCivilDate(dateStr: string): {
monthShort: string;
day: string;
full: string;
} | null {
const key = normalizeMeetupDateKey(dateStr);
if (!key) return null;
const parts = key.split("-").map(Number);
const year = parts[0];
const month = parts[1];
const dayNum = parts[2];
if (!year || !month || !dayNum) return null;
const ref = new Date(Date.UTC(year, month - 1, dayNum, 12, 0, 0));
return {
monthShort: ref
.toLocaleString("en-US", { ...UTC_CAL_OPTS, month: "short" })
.toUpperCase(),
day: String(ref.getUTCDate()),
full: ref.toLocaleString("en-US", {
...UTC_CAL_OPTS,
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
}),
};
}
/** Long single-line civil date for event detail (same rules as formatMeetupCivilDate). */
export function formatMeetupCivilDateLong(dateStr: string): string {
return formatMeetupCivilDate(dateStr)?.full ?? "—";
}