diff --git a/frontend/app/dashboard/invoices/[id]/page.tsx b/frontend/app/dashboard/invoices/[id]/page.tsx
index 13ce1a4d..ee0b78f1 100644
--- a/frontend/app/dashboard/invoices/[id]/page.tsx
+++ b/frontend/app/dashboard/invoices/[id]/page.tsx
@@ -7,6 +7,7 @@ import { Button } from '@/components/ui/button';
import { ArrowLeft, Download } from 'lucide-react';
import Link from 'next/link';
import { Skeleton } from '@/components/ui/skeleton';
+import { PageBreadcrumb } from '@/components/layout/PageBreadcrumb';
export default function InvoiceDetailPage() {
const params = useParams();
@@ -51,7 +52,15 @@ export default function InvoiceDetailPage() {
};
return (
-
+
+
+
+
+ {/* Breadcrumb Navigation */}
+ {breadcrumbs.length > 0 && (
+
+
+
+ {breadcrumbs.map((item, index) => (
+
+
+
+ {item.label}
+
+
+ {index < breadcrumbs.length - 1 && }
+
+ ))}
+
+
+
+ )}
setThemeSettingsOpen(false)} />
>
);
-<<<<<<< feat/qr-code
-}
-=======
}
->>>>>>> main
diff --git a/frontend/components/layout/PageBreadcrumb.tsx b/frontend/components/layout/PageBreadcrumb.tsx
new file mode 100644
index 00000000..659fced5
--- /dev/null
+++ b/frontend/components/layout/PageBreadcrumb.tsx
@@ -0,0 +1,43 @@
+'use client';
+
+import React from 'react';
+import {
+ Breadcrumb,
+ BreadcrumbList,
+ BreadcrumbItem,
+ BreadcrumbLink,
+ BreadcrumbPage,
+ BreadcrumbSeparator,
+} from '@/components/ui/breadcrumb';
+
+export interface BreadcrumbItemData {
+ label: string;
+ href: string;
+}
+
+interface PageBreadcrumbProps {
+ items: BreadcrumbItemData[];
+ currentPage: string;
+}
+
+export function PageBreadcrumb({ items, currentPage }: PageBreadcrumbProps) {
+ return (
+
+
+ {items.map((item, index) => (
+
+
+
+ {item.label}
+
+
+
+
+ ))}
+
+ {currentPage}
+
+
+
+ );
+}
diff --git a/frontend/components/ui/breadcrumb.tsx b/frontend/components/ui/breadcrumb.tsx
new file mode 100644
index 00000000..d9d55031
--- /dev/null
+++ b/frontend/components/ui/breadcrumb.tsx
@@ -0,0 +1,130 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { cva, type VariantProps } from "class-variance-authority"
+import { ChevronRight } from "lucide-react"
+import { cn } from "@/lib/utils"
+import Link from "next/link"
+
+const breadcrumbVariants = cva("flex items-center gap-1.5")
+
+interface BreadcrumbProps
+ extends React.HTMLAttributes,
+ VariantProps {}
+
+const Breadcrumb = React.forwardRef(
+ ({ className, ...props }, ref) => (
+
+ )
+)
+Breadcrumb.displayName = "Breadcrumb"
+
+interface BreadcrumbListProps extends React.HTMLAttributes {}
+
+const BreadcrumbList = React.forwardRef(
+ ({ className, ...props }, ref) => (
+
+ )
+)
+BreadcrumbList.displayName = "BreadcrumbList"
+
+interface BreadcrumbItemProps extends React.HTMLAttributes {}
+
+const BreadcrumbItem = React.forwardRef(
+ ({ className, ...props }, ref) => (
+
+ )
+)
+BreadcrumbItem.displayName = "BreadcrumbItem"
+
+interface BreadcrumbLinkProps
+ extends React.AnchorHTMLAttributes {
+ asChild?: boolean
+ href: string
+}
+
+const BreadcrumbLink = React.forwardRef(
+ ({ className, href, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : Link
+
+ return (
+
+ )
+ }
+)
+BreadcrumbLink.displayName = "BreadcrumbLink"
+
+interface BreadcrumbPageProps
+ extends React.HTMLAttributes {}
+
+const BreadcrumbPage = React.forwardRef(
+ ({ className, ...props }, ref) => (
+
+ )
+)
+BreadcrumbPage.displayName = "BreadcrumbPage"
+
+interface BreadcrumbSeparatorProps
+ extends React.HTMLAttributes {
+ icon?: React.ReactNode
+}
+
+const BreadcrumbSeparator = ({ className, icon, ...props }: BreadcrumbSeparatorProps) =>
+ icon ? (
+
+ {icon}
+
+ ) : (
+
+
+
+ )
+BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
+
+export {
+ Breadcrumb,
+ BreadcrumbList,
+ BreadcrumbItem,
+ BreadcrumbLink,
+ BreadcrumbPage,
+ BreadcrumbSeparator,
+}
diff --git a/frontend/lib/breadcrumbs.ts b/frontend/lib/breadcrumbs.ts
new file mode 100644
index 00000000..19e8f77c
--- /dev/null
+++ b/frontend/lib/breadcrumbs.ts
@@ -0,0 +1,92 @@
+/**
+ * Utility functions for breadcrumb generation and path management
+ */
+
+export interface BreadcrumbItem {
+ label: string;
+ href: string;
+}
+
+/**
+ * Maps path segments to human-readable labels
+ */
+const pathLabelMap: Record = {
+ dashboard: 'Dashboard',
+ projects: 'Projects',
+ invoices: 'Invoices',
+ payments: 'Payments',
+ new: 'New Project',
+};
+
+/**
+ * Humanize a path segment into readable label
+ * Example: "projects" -> "Projects", "my-project" -> "My Project"
+ */
+export function humanizePathSegment(segment: string): string {
+ // Check if it's in our predefined map
+ if (pathLabelMap[segment]) {
+ return pathLabelMap[segment];
+ }
+
+ // Remove ID brackets and hyphens, capitalize words
+ if (segment.startsWith('[') && segment.endsWith(']')) {
+ return segment.slice(1, -1); // Return placeholder name without brackets
+ }
+
+ return segment
+ .split('-')
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
+ .join(' ');
+}
+
+/**
+ * Generate breadcrumb items from pathname
+ * Example: "/dashboard/projects/123" -> [
+ * { label: "Dashboard", href: "/dashboard" },
+ * { label: "Projects", href: "/dashboard/projects" }
+ * ]
+ */
+export function generateBreadcrumbs(pathname: string): BreadcrumbItem[] {
+ const segments = pathname.split('/').filter(Boolean);
+ const breadcrumbs: BreadcrumbItem[] = [];
+ let path = '';
+
+ for (let i = 0; i < segments.length - 1; i++) {
+ const segment = segments[i];
+ path += `/${segment}`;
+
+ // Skip ID segments (like [id])
+ if (segment.startsWith('[') && segment.endsWith(']')) {
+ continue;
+ }
+
+ const label = humanizePathSegment(segment);
+ breadcrumbs.push({
+ label,
+ href: path,
+ });
+ }
+
+ return breadcrumbs;
+}
+
+/**
+ * Get breadcrumb items for dashboard routes with custom labels
+ * Allows overriding default labels for dynamic content like project names or invoice IDs
+ */
+export function getDashboardBreadcrumbs(
+ pathname: string,
+ overrides?: Record
+): BreadcrumbItem[] {
+ const breadcrumbs = generateBreadcrumbs(pathname);
+
+ // Apply any label overrides
+ if (overrides) {
+ return breadcrumbs.map(item => ({
+ ...item,
+ label: overrides[item.href] || item.label,
+ }));
+ }
+
+ return breadcrumbs;
+}