Files
anouma/collections/Redirects.ts
T
maroandClaude Sonnet 5 1cd15aff25 Add email verification, personal calendar feed, and full SEO implementation
- Customer accounts now require email verification (hashed, single-use,
  time-limited tokens) before they can request/confirm bookings, with
  resend flows on login/account/booking widget and rate limiting.
- Admins get a private, rotatable iCalendar (ICS) subscription feed of
  their confirmed bookings and public events, timezone-correct for
  Europe/Berlin including DST, never exposing meeting passwords.
- Adds a full SEO layer: per-page canonical/OG/Twitter metadata with
  CMS-editable overrides and content-derived fallbacks, a dynamic
  sitemap.xml and robots.txt driven by real published content, JSON-LD
  (Organization/LocalBusiness, WebSite, WebPage, BreadcrumbList, Service,
  Event, BlogPosting) that never fabricates data, and a CMS-managed
  redirect table for changed slugs.
- Global ANOUMA-naming audit: the brand name is never used to label
  personal account/calendar areas anywhere in the app, CMS, or emails.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-25 22:57:31 +02:00

77 lines
2.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { CollectionConfig } from "payload";
import { isAdmin } from "@/access";
function normalizePath(value: string): string {
const trimmed = value.trim();
const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
return withSlash.length > 1 ? withSlash.replace(/\/+$/, "") : withSlash;
}
/**
* Simple slug-change redirect table (see lib/seo/redirects.ts). Deliberately
* scoped to the three dynamic [slug] detail routes (Angebote/Termine/
* Aktuelles) — that's the only place slugs actually live and can change, so
* looking this up only there avoids adding a database lookup to every
* single request the way a catch-all middleware would.
*/
export const Redirects: CollectionConfig = {
slug: "redirects",
labels: {
singular: "Weiterleitung",
plural: "Weiterleitungen",
},
admin: {
useAsTitle: "fromPath",
defaultColumns: ["fromPath", "toPath", "type", "enabled"],
group: "SEO",
description: "Leitet eine alte URL (z. B. nach einer Slug-Änderung bei Angeboten, Terminen oder Aktuelles) dauerhaft auf eine neue weiter.",
},
access: {
read: () => true,
create: isAdmin,
update: isAdmin,
delete: isAdmin,
},
fields: [
{
name: "fromPath",
type: "text",
label: "Alte URL (Pfad)",
required: true,
unique: true,
index: true,
admin: { description: "Nur der Pfad, z. B. /angebote/doula (ohne Domain)." },
hooks: {
beforeValidate: [({ value }) => (typeof value === "string" && value ? normalizePath(value) : value)],
},
},
{
name: "toPath",
type: "text",
label: "Neue URL (Pfad)",
required: true,
admin: { description: "Ziel-Pfad, z. B. /angebote/doula-begleitung." },
hooks: {
beforeValidate: [({ value }) => (typeof value === "string" && value ? normalizePath(value) : value)],
},
},
{
name: "type",
type: "select",
label: "Art der Weiterleitung",
required: true,
defaultValue: "permanent",
options: [
{ label: "301 Dauerhaft", value: "permanent" },
{ label: "302 Vorübergehend", value: "temporary" },
],
},
{
name: "enabled",
type: "checkbox",
label: "Aktiv",
defaultValue: true,
},
],
};