- 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>
75 lines
3.0 KiB
TypeScript
75 lines
3.0 KiB
TypeScript
"use client"
|
|
|
|
import { usePathname } from "next/navigation"
|
|
import Link from "next/link"
|
|
import { Plus, CreditCard, Wrench, FileText, Receipt, CalendarDays } from "lucide-react"
|
|
import { NotificationsBell } from "@/components/dashboard/notifications-bell"
|
|
import { CommandTrigger } from "@/components/dashboard/command-palette"
|
|
|
|
const pageTitles: Record<string, string> = {
|
|
"/dashboard": "Dashboard",
|
|
"/properties": "Properties",
|
|
"/tenants": "Tenants",
|
|
"/rent": "Rent Tracker",
|
|
"/maintenance": "Maintenance",
|
|
"/leases": "Leases",
|
|
"/expenses": "Expenses",
|
|
"/settings/profile": "Settings",
|
|
"/settings/billing": "Billing",
|
|
"/settings/privacy": "Privacy & Data",
|
|
"/settings/demo": "Demo Data",
|
|
"/ai": "AI Assistant",
|
|
"/reports": "Reports",
|
|
"/rent/generate": "Generate Rent",
|
|
"/vendors": "Vendors",
|
|
"/inspections": "Inspections",
|
|
"/calendar": "Calendar",
|
|
}
|
|
|
|
const pageActions: Record<string, { label: string; href: string; icon?: React.ElementType }> = {
|
|
"/dashboard": { label: "Add Property", href: "/properties/new" },
|
|
"/properties": { label: "Add Property", href: "/properties/new" },
|
|
"/tenants": { label: "Add Tenant", href: "/tenants/new" },
|
|
"/rent/generate": { label: "Rent Tracker", href: "/rent", icon: CreditCard },
|
|
"/rent": { label: "Record Payment", href: "/rent/new", icon: CreditCard },
|
|
"/maintenance": { label: "New Request", href: "/maintenance/new", icon: Wrench },
|
|
"/leases": { label: "New Lease", href: "/leases/new", icon: FileText },
|
|
"/expenses": { label: "Add Expense", href: "/expenses/new", icon: Receipt },
|
|
}
|
|
|
|
export function Header() {
|
|
const pathname = usePathname()
|
|
|
|
const title = Object.entries(pageTitles).find(([path]) =>
|
|
path === "/dashboard" ? pathname === path : pathname.startsWith(path)
|
|
)?.[1] ?? "Property Management Network"
|
|
|
|
const action = Object.entries(pageActions).find(([path]) =>
|
|
path === "/dashboard" ? pathname === path : pathname.startsWith(path)
|
|
)?.[1]
|
|
|
|
const ActionIcon = action?.icon ?? Plus
|
|
|
|
return (
|
|
<header className="flex h-16 shrink-0 items-center justify-between border-b border-white/[0.06] bg-[#111118]/90 px-6 backdrop-blur-sm pl-16 md:pl-6">
|
|
<div>
|
|
<h1 className="text-sm font-semibold text-white leading-tight">{title}</h1>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2.5">
|
|
<CommandTrigger />
|
|
<NotificationsBell />
|
|
{action && (
|
|
<Link
|
|
href={action.href}
|
|
className="flex items-center gap-1.5 rounded-xl bg-indigo-600 px-3.5 py-2 text-xs font-semibold text-white transition-all hover:bg-indigo-500 hover:shadow-lg hover:shadow-indigo-500/25"
|
|
>
|
|
<ActionIcon className="h-3.5 w-3.5" />
|
|
<span className="hidden sm:inline">{action.label}</span>
|
|
</Link>
|
|
)}
|
|
</div>
|
|
</header>
|
|
)
|
|
}
|