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