Files
BelgianBitcoinEmbassy/frontend/components/public/MeetupsSection.tsx
bbe 78271ea110 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
2026-04-04 21:55:34 +02:00

129 lines
5.2 KiB
TypeScript

import { MapPin, Clock, ArrowRight } from "lucide-react";
import Link from "next/link";
import { AddToCalendarButton } from "@/components/public/AddToCalendarDialog";
import { formatMeetupCivilDate } from "@/lib/meetupEventTime";
interface MeetupData {
id?: string;
title: string;
date: string;
time?: string;
location?: string;
link?: string;
description?: string;
organizer?: { name: string; slug?: string };
}
interface MeetupsSectionProps {
meetups: MeetupData[];
}
export function MeetupsSection({ meetups }: MeetupsSectionProps) {
return (
<section className="py-24 px-8 border-t border-zinc-800/50">
<div className="max-w-6xl mx-auto">
<div className="flex justify-between items-end mb-12">
<div>
<p className="uppercase tracking-[0.2em] text-primary mb-2 font-semibold text-xs">
Mark your calendar
</p>
<h2 className="text-3xl font-black tracking-tight">Upcoming Meetups</h2>
</div>
<div className="hidden md:flex items-center gap-4">
<AddToCalendarButton />
<Link
href="/events"
className="flex items-center gap-2 text-sm text-primary font-semibold hover:gap-3 transition-all"
>
All events <ArrowRight size={16} />
</Link>
</div>
</div>
{meetups.length === 0 ? (
<div className="border border-zinc-800 rounded-xl px-8 py-12 text-center">
<p className="text-on-surface-variant text-sm">
No upcoming meetups scheduled. Check back soon.
</p>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5">
{meetups.map((meetup, i) => {
const civil = formatMeetupCivilDate(meetup.date);
const month = civil?.monthShort ?? "—";
const day = civil?.day ?? "--";
const full = civil?.full ?? "";
const href = meetup.id ? `/events/${meetup.id}` : "#upcoming-meetups";
return (
<Link
key={meetup.id ?? i}
href={href}
className="group flex flex-col bg-zinc-900 border border-zinc-800 rounded-xl p-6 hover:border-zinc-700 hover:-translate-y-0.5 hover:shadow-xl transition-all duration-200"
>
<div className="flex items-start gap-4 mb-4">
<div className="bg-zinc-800 rounded-lg px-3 py-2 text-center shrink-0 min-w-[52px]">
<span className="block text-[10px] font-bold uppercase text-primary tracking-wider leading-none mb-0.5">
{month}
</span>
<span className="block text-2xl font-black leading-none">{day}</span>
</div>
<div className="min-w-0">
<h3 className="font-bold text-base leading-snug group-hover:text-primary transition-colors">
{meetup.title}
</h3>
<p className="text-on-surface-variant/60 text-xs mt-1">{full}</p>
</div>
</div>
{meetup.description && (
<p className="text-on-surface-variant text-sm leading-relaxed mb-4 flex-1 line-clamp-2">
{meetup.description}
</p>
)}
<p className="text-[11px] text-on-surface-variant/50 font-medium uppercase tracking-wide mb-2">
Organized by{" "}
<span className="text-on-surface-variant/70 normal-case tracking-normal">
{meetup.organizer?.name || "Belgian Bitcoin Embassy"}
</span>
</p>
<div className="flex flex-col gap-1.5 mt-auto pt-4 border-t border-zinc-800/60">
{meetup.location && (
<p className="flex items-center gap-1.5 text-xs text-on-surface-variant/60">
<MapPin size={12} className="shrink-0 text-primary/60" />
{meetup.location}
</p>
)}
{meetup.time && (
<p className="flex items-center gap-1.5 text-xs text-on-surface-variant/60">
<Clock size={12} className="shrink-0 text-primary/60" />
{meetup.time}
</p>
)}
</div>
<span className="flex items-center gap-1.5 text-primary text-xs font-semibold mt-4 group-hover:gap-2.5 transition-all">
View Details <ArrowRight size={12} />
</span>
</Link>
);
})}
</div>
)}
<div className="md:hidden flex flex-col items-center gap-3 mt-8">
<Link
href="/events"
className="flex items-center gap-2 text-primary font-semibold text-sm"
>
All events <ArrowRight size={16} />
</Link>
<AddToCalendarButton />
</div>
</div>
</section>
);
}