Property Management Network — Next.js 16 (App Router), Better Auth, Drizzle ORM over PostgreSQL, Stripe, OpenAI, Resend. Includes: - Security hardening: access-control/IDOR fixes, TLS-by-default DB layer, constant-time cron auth, strict security headers, atomic AI quota gating, HTML/email output encoding, demo-backdoor disabled in production. - Superadmin dashboard at /admin (overview/MRR, server-paginated users with ban/impersonate/plan/delete, billing, platform activity + admin audit log, AI usage, system health) via the Better Auth admin plugin. - Seed/migration utility scripts under scripts/. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
74 lines
2.9 KiB
TypeScript
74 lines
2.9 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/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>
|
|
)
|
|
}
|