Files
property-management-network/app/layout.tsx
Leon SerfatyandClaude Fable 5 5a555c715e Build GDPR compliance system: data export, account deletion, consent
- 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>
2026-07-03 06:03:27 -04:00

80 lines
2.4 KiB
TypeScript
Raw Permalink 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 { 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 120 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>
)
}