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>
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Formats a JS Date as an ISO 8601 string carrying its actual Europe/Berlin
|
||||
* UTC offset (e.g. "2026-06-15T09:00:00+02:00" in summer,
|
||||
* "...+01:00" in winter) — what schema.org's Event startDate/endDate
|
||||
* expect. Uses Intl's timezone database rather than hardcoded +1/+2 math,
|
||||
* so it's correct across the DST transition automatically.
|
||||
*/
|
||||
export function toBerlinOffsetISOString(date: Date): string {
|
||||
const parts = new Intl.DateTimeFormat("en-US", {
|
||||
timeZone: "Europe/Berlin",
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
hourCycle: "h23",
|
||||
timeZoneName: "shortOffset",
|
||||
}).formatToParts(date);
|
||||
|
||||
const get = (type: string) => parts.find((p) => p.type === type)?.value ?? "00";
|
||||
const tzName = parts.find((p) => p.type === "timeZoneName")?.value ?? "GMT+0";
|
||||
const match = /GMT([+-])(\d+)(?::(\d+))?/.exec(tzName);
|
||||
const sign = match?.[1] ?? "+";
|
||||
const offsetHours = (match?.[2] ?? "0").padStart(2, "0");
|
||||
const offsetMinutes = (match?.[3] ?? "0").padStart(2, "0");
|
||||
|
||||
return `${get("year")}-${get("month")}-${get("day")}T${get("hour")}:${get("minute")}:${get("second")}${sign}${offsetHours}:${offsetMinutes}`;
|
||||
}
|
||||
Reference in New Issue
Block a user