chore: sync in-progress work across marketing, admin, API and tests
Snapshot of uncommitted work that had accumulated in the tree alongside the Turnstile changes: - marketing pages, SEO helpers (lib/seo.ts, lib/marketing/) and structured data - admin billing actions and a per-user portfolio view, plus an admin error boundary - rate limiting (lib/rate-limit.ts) applied across the /api/v1 surface - CSP and proxy adjustments, accounting/webhook lib updates - Playwright config and an e2e/unit test suite - next bumped to ^16.3.4 with the lockfile regenerated - generated AGENTS.md / CLAUDE.md Authored by other sessions working in this tree; committed here so the Turnstile work could be pushed without leaving the tree dirty. Typecheck passes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
8f90347659
commit
1d02598786
+71
@@ -0,0 +1,71 @@
|
||||
import type { Metadata } from "next"
|
||||
|
||||
export const SITE_NAME = "Property Management Network"
|
||||
|
||||
// The generated Open Graph card (app/opengraph-image.tsx), served at this path.
|
||||
// Declared explicitly because a page that sets its own `openGraph` object
|
||||
// replaces the inherited one — including the image Next.js attaches from the
|
||||
// file convention — which silently leaves that page with no share image.
|
||||
const OG_IMAGE = "/opengraph-image"
|
||||
|
||||
type PageSeo = {
|
||||
/**
|
||||
* Page title. Rendered through the root layout's "%s | Property Management
|
||||
* Network" template unless `absoluteTitle` is set.
|
||||
*/
|
||||
title: string
|
||||
/** Render `title` verbatim, without the site-name suffix. */
|
||||
absoluteTitle?: boolean
|
||||
description: string
|
||||
/** Canonical path, root-relative and without a trailing slash, e.g. "/terms". */
|
||||
path: string
|
||||
/**
|
||||
* Headline used for the Open Graph / Twitter card when the share copy should
|
||||
* differ from the page title. Defaults to the resolved page title.
|
||||
*/
|
||||
socialTitle?: string
|
||||
/** Set false for pages that must stay out of the index. */
|
||||
index?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a complete, self-consistent metadata block for a public page.
|
||||
*
|
||||
* Every public page must go through this helper so that canonical, og:url,
|
||||
* og:title, og:image and the Twitter card always agree with each other and with
|
||||
* the page's real URL. Setting these ad hoc per page is what previously left the
|
||||
* landing page with no share image and every legal page pointing og:url at "/".
|
||||
*/
|
||||
export function pageMetadata({
|
||||
title,
|
||||
absoluteTitle = false,
|
||||
description,
|
||||
path,
|
||||
socialTitle,
|
||||
index = true,
|
||||
}: PageSeo): Metadata {
|
||||
const fullTitle = absoluteTitle ? title : `${title} | ${SITE_NAME}`
|
||||
const social = socialTitle ?? fullTitle
|
||||
|
||||
return {
|
||||
title: absoluteTitle ? { absolute: title } : title,
|
||||
description,
|
||||
alternates: { canonical: path },
|
||||
openGraph: {
|
||||
title: social,
|
||||
description,
|
||||
url: path,
|
||||
siteName: SITE_NAME,
|
||||
locale: "en_US",
|
||||
type: "website",
|
||||
images: [OG_IMAGE],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: social,
|
||||
description,
|
||||
images: [OG_IMAGE],
|
||||
},
|
||||
...(index ? {} : { robots: { index: false, follow: true } }),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user