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
+7 -1
View File
@@ -1,5 +1,6 @@
import { Router, Request, Response } from 'express';
import { prisma } from '../db/prisma';
import { respondIfOrganizerMigrationNeeded } from '../lib/prismaMigrationHint';
const router = Router();
@@ -92,6 +93,7 @@ router.get('/ics', async (_req: Request, res: Response) => {
const meetups = await prisma.meetup.findMany({
where: { date: { gte: cutoff } },
orderBy: { date: 'asc' },
include: { organizer: true },
});
const siteUrl = (process.env.FRONTEND_URL || 'https://belgianbitcoinembassy.org').replace(
@@ -129,8 +131,11 @@ router.get('/ics', async (_req: Request, res: Response) => {
lines.push(fold(`LOCATION:${escapeIcs(meetup.location)}`));
}
lines.push(fold(`URL:${eventUrl}`));
const orgName = meetup.organizer?.name || 'Belgian Bitcoin Embassy';
lines.push(
'ORGANIZER;CN=Belgian Bitcoin Embassy:mailto:info@belgianbitcoinembassy.org'
fold(
`ORGANIZER;CN=${escapeIcs(orgName)}:mailto:info@belgianbitcoinembassy.org`
)
);
// 15-minute reminder alarm
lines.push('BEGIN:VALARM');
@@ -156,6 +161,7 @@ router.get('/ics', async (_req: Request, res: Response) => {
res.send(icsBody);
} catch (err) {
console.error('Calendar ICS error:', err);
if (respondIfOrganizerMigrationNeeded(err, res)) return;
res.status(500).json({ error: 'Internal server error' });
}
});