Initial import: property management SaaS + security hardening + admin dashboard
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>
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
"use client"
|
||||
|
||||
import Link from "next/link"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { ChevronRight, Home } from "lucide-react"
|
||||
|
||||
const SEGMENT_LABELS: Record<string, string> = {
|
||||
dashboard: "Dashboard",
|
||||
properties: "Properties",
|
||||
tenants: "Tenants",
|
||||
rent: "Rent Tracker",
|
||||
maintenance: "Maintenance",
|
||||
leases: "Leases",
|
||||
expenses: "Expenses",
|
||||
reports: "Reports",
|
||||
vendors: "Vendors",
|
||||
inspections: "Inspections",
|
||||
ai: "AI Assistant",
|
||||
settings: "Settings",
|
||||
profile: "Profile",
|
||||
billing: "Billing",
|
||||
demo: "Demo Data",
|
||||
new: "New",
|
||||
edit: "Edit",
|
||||
generate: "Generate",
|
||||
documents: "Documents",
|
||||
}
|
||||
|
||||
function isUUID(s: string) {
|
||||
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(s)
|
||||
}
|
||||
|
||||
export function Breadcrumbs() {
|
||||
const pathname = usePathname()
|
||||
const segments = pathname.split("/").filter(Boolean)
|
||||
|
||||
// Don't show on top-level pages (only 1 segment)
|
||||
if (segments.length <= 1) return null
|
||||
|
||||
const crumbs: { label: string; href: string }[] = []
|
||||
let acc = ""
|
||||
|
||||
for (const seg of segments) {
|
||||
acc += `/${seg}`
|
||||
if (isUUID(seg)) {
|
||||
crumbs.push({ label: "Detail", href: acc })
|
||||
} else {
|
||||
crumbs.push({ label: SEGMENT_LABELS[seg] ?? seg, href: acc })
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<nav className="mb-4 flex items-center gap-1.5 text-xs text-white/30">
|
||||
<Link href="/dashboard" className="flex items-center gap-1 transition hover:text-white/60">
|
||||
<Home className="h-3 w-3" />
|
||||
</Link>
|
||||
{crumbs.map((crumb, i) => {
|
||||
const isLast = i === crumbs.length - 1
|
||||
return (
|
||||
<span key={crumb.href} className="flex items-center gap-1.5">
|
||||
<ChevronRight className="h-3 w-3 text-white/15" />
|
||||
{isLast ? (
|
||||
<span className="font-medium text-white/60">{crumb.label}</span>
|
||||
) : (
|
||||
<Link href={crumb.href} className="transition hover:text-white/60">
|
||||
{crumb.label}
|
||||
</Link>
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user