- Data export (Art. 15/20): GET /api/gdpr/export serves a full JSON export of the user's data (credentials/tokens excluded, exclusions declared) - Right to erasure (Art. 17): self-service deletion with 30-day grace period (Settings -> Privacy & Data), cancellable; daily /api/cron/gdpr drain cancels Stripe billing, purges Spaces files, cascade-deletes the account, anonymizes consent rows, and writes audit evidence - Migration 0011: account_deletion_requests (partial unique index = one pending per user) + FK-less consent_log (survives erasure) - Consent: terms/privacy acceptance logged at signup (email + Google); cookie banner with analytics opt-out (umami.disabled), choices logged server-side for signed-in users via POST /api/gdpr/consent - Admin deleteUser upgraded to the same full purge (was leaving Spaces files and Stripe subscriptions orphaned) - /gdpr legal page now points at the self-service tools - scripts/verify-gdpr.ts: end-to-end verification vs live dev DB (22/22) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
80 lines
2.4 KiB
TypeScript
80 lines
2.4 KiB
TypeScript
import type { Metadata } from "next"
|
||
import { Geist, Geist_Mono } from "next/font/google"
|
||
import { Toaster } from "@/components/ui/toaster"
|
||
import { UmamiAnalytics } from "@/components/analytics/umami"
|
||
import { CookieConsent } from "@/components/shared/cookie-consent"
|
||
import "./globals.css"
|
||
|
||
const geistSans = Geist({
|
||
variable: "--font-geist-sans",
|
||
subsets: ["latin"],
|
||
})
|
||
|
||
const geistMono = Geist_Mono({
|
||
variable: "--font-geist-mono",
|
||
subsets: ["latin"],
|
||
})
|
||
|
||
export const metadata: Metadata = {
|
||
metadataBase: new URL(process.env.NEXT_PUBLIC_APP_URL ?? "https://propertymanagement.network"),
|
||
applicationName: "Property Management Network",
|
||
title: {
|
||
default: "Property Management Network — Built for Independent Landlords",
|
||
template: "%s | Property Management Network",
|
||
},
|
||
description:
|
||
"Manage your rental properties, tenants, rent, maintenance, and leases from one dashboard. Built for independent landlords with 1–20 properties.",
|
||
keywords: ["property management", "landlord software", "rent tracker", "tenant management"],
|
||
openGraph: {
|
||
title: "Property Management Network — Built for Independent Landlords",
|
||
description: "One dashboard to manage all your rental properties.",
|
||
type: "website",
|
||
url: "/",
|
||
siteName: "Property Management Network",
|
||
locale: "en_US",
|
||
},
|
||
twitter: {
|
||
card: "summary_large_image",
|
||
title: "Property Management Network — Built for Independent Landlords",
|
||
description: "One dashboard to manage all your rental properties.",
|
||
creator: "@propertymgmtnet",
|
||
site: "@propertymgmtnet",
|
||
},
|
||
robots: {
|
||
index: true,
|
||
follow: true,
|
||
googleBot: {
|
||
index: true,
|
||
follow: true,
|
||
"max-image-preview": "large",
|
||
"max-snippet": -1,
|
||
"max-video-preview": -1,
|
||
},
|
||
},
|
||
verification: {
|
||
// Set GOOGLE_SITE_VERIFICATION in the environment to emit the
|
||
// <meta name="google-site-verification"> tag. Left undefined => tag omitted.
|
||
google: process.env.GOOGLE_SITE_VERIFICATION,
|
||
},
|
||
}
|
||
|
||
export default function RootLayout({
|
||
children,
|
||
}: Readonly<{
|
||
children: React.ReactNode
|
||
}>) {
|
||
return (
|
||
<html
|
||
lang="en"
|
||
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
|
||
>
|
||
<body suppressHydrationWarning className="min-h-full flex flex-col bg-[#09090b] text-white">
|
||
{children}
|
||
<Toaster />
|
||
<CookieConsent />
|
||
<UmamiAnalytics />
|
||
</body>
|
||
</html>
|
||
)
|
||
}
|