import { ArrowUpRightIcon, MapPinIcon } from "lucide-react"; import type { Metadata } from "next"; import Link from "next/link"; import { CopyLinkCard } from "@/components/landing/CopyLinkCard"; import { dateBlock, describeWhen } from "@/lib/event-display"; import { normaliseUrl } from "@/lib/event-form"; import { API_BASE_URL } from "@/lib/env"; import { fetchUpcomingPublicEvents, googleCalendarUrl, icalUrl, webcalUrl } from "@/lib/public-calendar"; import type { CalendarEvent } from "@/lib/types"; export const metadata: Metadata = { title: "Nachklang e.V. – Veranstaltungskalender", description: "Abonniere den Kalender von Nachklang e.V. und verpasse keine Veranstaltung mehr.", }; // Static, refreshed every five minutes - matches the fetch's own revalidate. export const revalidate = 300; /** * Where the server fetches from. Optional and read at request time, like in * nachklang-feedback: it may point at an internal address. The links on the * page always use the public URL, since visitors' calendar apps fetch them. */ const serverApiBaseUrl = (): string => (process.env.API_BASE_URL || API_BASE_URL).replace(/\/+$/, ""); // Shared by the three subscribe options, so the two links and the copy button look alike. // A row on phones; icon above text in the three-column grid, where a row would wrap. const OPTION = "flex w-full items-center gap-3 rounded-xl border bg-card p-4 text-left transition-colors sm:flex-col sm:items-start " + "hover:bg-muted/50 focus-visible:ring-3 focus-visible:ring-ring/30 focus-visible:outline-none"; /** * The public page: how to subscribe, and what is coming up. Same look as the * admin area (design system, light, Inter) - one product, one visual language. */ export default async function Home() { const upcoming = await fetchUpcomingPublicEvents(serverApiBaseUrl(), new Date(), 4); return (
Nachklang Kalender Verwaltung

Veranstaltungskalender

Abonniere den Kalender von Nachklang e.V. – neue Termine erscheinen dann automatisch in deiner Kalender-App.

Abonnieren

{upcoming && }
); } function OptionText({ label, sub }: { label: string; sub: string }) { return ( {label} {sub} ); } /** * The next few public events, in the admin list's style. Hidden entirely when * the API could not be reached, so the page never shows an error where people * come to subscribe. */ function UpcomingEvents({ events }: { events: CalendarEvent[] }) { const now = new Date(); return (

Nächste Termine

{events.length === 0 ? (

Gerade stehen keine öffentlichen Termine an.

) : ( )}
); } function AppleIcon() { return ( 31 ); } function GoogleIcon() { return ( ); }