feat: redesign CMS and unify German admin UI

This commit is contained in:
2026-09-17 16:51:24 +02:00
parent a2b3e99174
commit 7ec0c51ff5
129 changed files with 952 additions and 550 deletions
+3 -3
View File
@@ -11,13 +11,13 @@ const WINDOW_FUTURE_MS = 400 * 24 * 60 * 60 * 1000;
/**
* Builds the calendar entries for Anna's personal ICS feed: confirmed
* bookings (never pending ones) plus standalone public events — onsite and
* bookings (never pending ones) plus standalone public events - onsite and
* online alike. Deliberately never includes the meeting password anywhere
* in the output, only a link to the protected join page.
*
* Local API is used with the default overrideAccess: true because this is
* only ever reached after the caller already verified the feed's secret
* token (see app/(frontend)/calendar/[tokenFile]/route.ts) — equivalent
* token (see app/(frontend)/calendar/[tokenFile]/route.ts) - equivalent
* trust to the existing admin calendar view.
*/
export async function getAdminFeedICSEvents(payload: Payload): Promise<ICSEvent[]> {
@@ -83,7 +83,7 @@ export async function getAdminFeedICSEvents(payload: Payload): Promise<ICSEvent[
end: new Date(b.endTime),
summary: offer?.title ?? "Termin",
description: description || undefined,
// Vor Ort: vollständige Adresse. Online: literal "Online" — never the
// Vor Ort: vollständige Adresse. Online: literal "Online" - never the
// meeting password, which is intentionally not part of this feed.
location: isOnline ? "Online" : fullAddress || undefined,
url,
+1 -1
View File
@@ -2,7 +2,7 @@ import { createHash, randomBytes } from "node:crypto";
/**
* Long-lived, random, rotatable secret for a personal calendar-subscription
* URL (/calendar/<token>.ics) — treated like an API key, not a
* URL (/calendar/<token>.ics) - treated like an API key, not a
* short-lived link: only its hash is stored, so a database leak alone
* can't be used to subscribe to anyone's calendar, and rotating it
* (overwriting the stored hash) immediately invalidates the old URL.
+3 -3
View File
@@ -68,7 +68,7 @@ export function berlinDayBounds(date: Date): { key: string; start: Date; end: Da
* representation for iCalendar's local `TZID` date-time form
* (`DTSTART;TZID=Europe/Berlin:YYYYMMDDTHHMMSS`). Uses the ICU timezone
* database via Intl rather than manual UTC+1/+2 arithmetic, so daylight
* saving transitions are always handled correctly — no naive UTC offset
* saving transitions are always handled correctly - no naive UTC offset
* math that would drift by an hour half the year.
*/
export function toBerlinICSDateTime(date: Date): string {
@@ -87,7 +87,7 @@ export function toBerlinICSDateTime(date: Date): string {
return `${get("year")}${get("month")}${get("day")}T${get("hour")}${get("minute")}${get("second")}`;
}
/** UTC timestamp form (`YYYYMMDDTHHMMSSZ`) — used only for DTSTAMP, which marks generation time, not a local event time. */
/** UTC timestamp form (`YYYYMMDDTHHMMSSZ`) - used only for DTSTAMP, which marks generation time, not a local event time. */
export function toUTCICSStamp(date: Date): string {
return date.toISOString().replace(/[-:]/g, "").split(".")[0] + "Z";
}
@@ -95,7 +95,7 @@ export function toUTCICSStamp(date: Date): string {
/**
* Standard IANA Europe/Berlin VTIMEZONE block (the same one commonly
* embedded by calendar exporters): CEST from the last Sunday in March,
* CET from the last Sunday in October — the actual EU DST rule, not an
* CET from the last Sunday in October - the actual EU DST rule, not an
* approximation.
*/
export const BERLIN_VTIMEZONE_LINES = [