diff --git a/frontend/src/app/(main)/dashboard/events/page.tsx b/frontend/src/app/(main)/dashboard/events/page.tsx
new file mode 100644
index 0000000..a9ce136
--- /dev/null
+++ b/frontend/src/app/(main)/dashboard/events/page.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { useEffect } from "react";
+import { useRouter } from "next/navigation";
+
+export default function EventsRedirectPage() {
+ const router = useRouter();
+
+ useEffect(() => {
+ // Redirect to dashboard home page
+ router.push("/dashboard");
+ }, [router]);
+
+ // Show a loading state while redirecting
+ return (
+
+
+
+
+ Redirecting to dashboard...
+
+
+
+ );
+}
diff --git a/frontend/src/app/(main)/dashboard/layout.tsx b/frontend/src/app/(main)/dashboard/layout.tsx
index e39c52e..c5ff5e1 100644
--- a/frontend/src/app/(main)/dashboard/layout.tsx
+++ b/frontend/src/app/(main)/dashboard/layout.tsx
@@ -1,9 +1,10 @@
"use client";
import { useAuth } from "@/context/auth-context";
-import { useRouter } from "next/navigation";
+import { useRouter, usePathname } from "next/navigation";
import { useEffect } from "react";
import Navbar from "@/components/layout/navbar";
+import Breadcrumbs from "@/components/layout/breadcrumb";
export default function MainLayout({
children,
@@ -12,6 +13,7 @@ export default function MainLayout({
}) {
const { isAuthenticated, isLoading } = useAuth();
const router = useRouter();
+ const pathname = usePathname();
useEffect(() => {
if (!isLoading && !isAuthenticated) {
@@ -38,10 +40,20 @@ export default function MainLayout({
);
}
+ // Don't show breadcrumbs on the main dashboard page
+ const showBreadcrumbs = pathname !== "/dashboard";
+
return (
<>
- {children}
+
+ {showBreadcrumbs && (
+
+
+
+ )}
+ {children}
+
>
);
}
diff --git a/frontend/src/app/(main)/dashboard/page.tsx b/frontend/src/app/(main)/dashboard/page.tsx
index d005835..02d48bd 100644
--- a/frontend/src/app/(main)/dashboard/page.tsx
+++ b/frontend/src/app/(main)/dashboard/page.tsx
@@ -1,27 +1,12 @@
"use client";
-import Navbar from "@/components/layout/navbar";
-import { useEvents } from "@/context/event-context";
import Link from "next/link";
import { Button } from "@/components/ui/button";
-import {
- Card,
- CardContent,
- CardFooter,
- CardHeader,
- CardTitle,
- CardDescription,
-} from "@/components/ui/card";
-import { Badge } from "@/components/ui/badge";
-import { Loader2Icon, CalendarIcon, MapPinIcon } from "lucide-react";
-import { EventResponse } from "@/client";
+import { useEvents } from "@/context/event-context";
+import EventsGrid from "@/components/events/events-grid";
export default function DashboardPage() {
- const {
- userEvents,
- isLoadingUserEvents,
- userEvents: eventsData,
- } = useEvents();
+ const { userEvents, isLoadingUserEvents } = useEvents();
return (
<>
@@ -45,72 +30,13 @@ export default function DashboardPage() {
- {isLoadingUserEvents && (
-
-
- Loading your events...
-
- )}
-
- {!isLoadingUserEvents &&
- userEvents?.items &&
- userEvents.items.length > 0 && (
-
- {userEvents.items.map((event: EventResponse) => (
-
-
- {event.title}
- {event.is_public ? (
- Public
- ) : (
- Private
- )}
-
-
-
- {event.description || "No description provided."}
-
- {event.location_address && (
-
-
- {event.location_address}
-
- )}
- {event.event_start_time && (
-
-
- {new Date(event.event_start_time).toLocaleString()}
-
- )}
-
-
-
-
-
- ))}
-
- )}
-
- {!isLoadingUserEvents &&
- (!userEvents?.items || userEvents.items.length === 0) && (
-
-
- You haven't created any events yet.
-
-
-
- )}
+
>
);
diff --git a/frontend/src/components/events/events-grid.tsx b/frontend/src/components/events/events-grid.tsx
new file mode 100644
index 0000000..9afb45f
--- /dev/null
+++ b/frontend/src/components/events/events-grid.tsx
@@ -0,0 +1,97 @@
+import React from "react";
+import Link from "next/link";
+import { Button } from "@/components/ui/button";
+import {
+ Card,
+ CardContent,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+ CardDescription,
+} from "@/components/ui/card";
+import { Badge } from "@/components/ui/badge";
+import { Loader2Icon, CalendarIcon, MapPinIcon } from "lucide-react";
+import { EventResponse } from "@/client";
+
+interface EventsGridProps {
+ events?: {
+ items: EventResponse[];
+ };
+ isLoading?: boolean;
+ emptyMessage?: string;
+ emptyActionLink?: string;
+ emptyActionText?: string;
+}
+
+const EventsGrid: React.FC = ({
+ events,
+ isLoading = false,
+ emptyMessage = "No events found.",
+ emptyActionLink,
+ emptyActionText,
+}) => {
+ return (
+ <>
+ {isLoading && (
+
+
+ Loading events...
+
+ )}
+
+ {!isLoading && events?.items && events.items.length > 0 && (
+
+ {events.items.map((event: EventResponse) => (
+
+
+ {event.title}
+ {event.is_public ? (
+ Public
+ ) : (
+ Private
+ )}
+
+
+
+ {event.description || "No description provided."}
+
+ {event.location_address && (
+
+
+ {event.location_address}
+
+ )}
+ {event.event_start_time && (
+
+
+ {new Date(event.event_start_time).toLocaleString()}
+
+ )}
+
+
+
+
+
+ ))}
+
+ )}
+
+ {!isLoading && (!events?.items || events.items.length === 0) && (
+
+
{emptyMessage}
+ {emptyActionLink && emptyActionText && (
+
+ )}
+
+ )}
+ >
+ );
+};
+
+export default EventsGrid;
diff --git a/frontend/src/components/layout/breadcrumb.tsx b/frontend/src/components/layout/breadcrumb.tsx
new file mode 100644
index 0000000..d01d99f
--- /dev/null
+++ b/frontend/src/components/layout/breadcrumb.tsx
@@ -0,0 +1,124 @@
+"use client";
+
+import React from "react";
+import Link from "next/link";
+import { usePathname } from "next/navigation";
+import { ChevronRight, HomeIcon } from "lucide-react";
+import {
+ Breadcrumb,
+ BreadcrumbItem,
+ BreadcrumbLink,
+ BreadcrumbList,
+ BreadcrumbPage,
+ BreadcrumbSeparator,
+} from "@/components/ui/breadcrumb";
+
+interface BreadcrumbItemData {
+ label: string;
+ href: string;
+ isActive: boolean;
+}
+
+interface BreadcrumbsProps {
+ homeHref?: string;
+ homeLabel?: React.ReactNode;
+ className?: string;
+ items?: BreadcrumbItemData[]; // Optional manual items
+}
+
+const Breadcrumbs = ({
+ homeHref = "/dashboard",
+ homeLabel = ,
+ className = "",
+ items,
+}: BreadcrumbsProps) => {
+ const pathname = usePathname();
+
+ // Only generate items from pathname if no items provided
+ const breadcrumbItems = items || generateBreadcrumbItems(pathname);
+
+ return (
+
+
+
+
+
+ {homeLabel}
+
+
+
+
+ {breadcrumbItems.map((item, index) => (
+
+
+
+
+
+ {item.isActive ? (
+ {item.label}
+ ) : (
+
+ {item.label}
+
+ )}
+
+
+ ))}
+
+
+ );
+};
+
+// Helper function to generate breadcrumb items from pathname
+function generateBreadcrumbItems(pathname: string): BreadcrumbItemData[] {
+ if (pathname === "/dashboard") return [];
+
+ // Remove leading /dashboard from pathname
+ const path = pathname.replace(/^\/dashboard\/?/, "");
+ if (!path) return [];
+
+ // Split path into segments
+ const segments = path.split("/").filter(Boolean);
+
+ // Map of slugs to human-readable labels
+ const segmentLabels: Record = {
+ events: "Events",
+ "event-themes": "Event Themes",
+ new: "New",
+ edit: "Edit",
+ gifts: "Gifts",
+ };
+
+ const items: BreadcrumbItemData[] = [];
+ let currentPath = "/dashboard";
+
+ segments.forEach((segment, index) => {
+ currentPath += `/${segment}`;
+
+ // Check if segment is a dynamic route (starts with [])
+ // In actual URLs, dynamic segments don't have brackets
+ let label =
+ segmentLabels[segment] ||
+ (segment.startsWith("[") && segment.endsWith("]")
+ ? segment.slice(1, -1).charAt(0).toUpperCase() + segment.slice(2, -1)
+ : segment.charAt(0).toUpperCase() + segment.slice(1));
+
+ // Special handling for IDs/slugs - use ID/Slug instead of the actual value
+ // This assumes IDs/slugs are at odd positions in the path (events/[id], themes/[id])
+ if (index % 2 === 1 && segments[index - 1] === "events") {
+ label = "Event Details";
+ } else if (index % 2 === 1 && segments[index - 1] === "event-themes") {
+ label = "Theme Details";
+ }
+
+ items.push({
+ label,
+ href: currentPath,
+ isActive: currentPath === pathname,
+ });
+ });
+
+ return items;
+}
+
+export default Breadcrumbs;
diff --git a/frontend/src/components/ui/breadcrumb.tsx b/frontend/src/components/ui/breadcrumb.tsx
new file mode 100644
index 0000000..eb88f32
--- /dev/null
+++ b/frontend/src/components/ui/breadcrumb.tsx
@@ -0,0 +1,109 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { ChevronRight, MoreHorizontal } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
+ return
+}
+
+function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
+ return (
+
+ )
+}
+
+function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
+ return (
+
+ )
+}
+
+function BreadcrumbLink({
+ asChild,
+ className,
+ ...props
+}: React.ComponentProps<"a"> & {
+ asChild?: boolean
+}) {
+ const Comp = asChild ? Slot : "a"
+
+ return (
+
+ )
+}
+
+function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
+ return (
+
+ )
+}
+
+function BreadcrumbSeparator({
+ children,
+ className,
+ ...props
+}: React.ComponentProps<"li">) {
+ return (
+ svg]:size-3.5", className)}
+ {...props}
+ >
+ {children ?? }
+
+ )
+}
+
+function BreadcrumbEllipsis({
+ className,
+ ...props
+}: React.ComponentProps<"span">) {
+ return (
+
+
+ More
+
+ )
+}
+
+export {
+ Breadcrumb,
+ BreadcrumbList,
+ BreadcrumbItem,
+ BreadcrumbLink,
+ BreadcrumbPage,
+ BreadcrumbSeparator,
+ BreadcrumbEllipsis,
+}