generated from codersforcauses/django-nextjs-template
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ui): improve layout, sidebar, loading state
- feat(ui): create `WaitingLoader` component to show loading state during requests - feat(layout): get layout based on login status and role. Add breadcrumb navigation links - feat(sidebar): add logo to navigate landing page, add logout button - fix(sidebar): add missing `SheetTitle` and `SheetDescription` in Sidebar - feat(api): add `/users/me` mock data API - style(ui): add no-scrollbar CSS for better layout appearance
- Loading branch information
Showing
15 changed files
with
322 additions
and
89 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,58 @@ | ||
import React from "react"; | ||
import React, { useEffect, useState } from "react"; | ||
|
||
import Navbar from "@/components/navbar"; | ||
import { WaitingLoader } from "@/components/ui/loading"; | ||
import { useAuth } from "@/context/auth-provider"; | ||
import { useFetchData } from "@/hooks/use-fetch-data"; | ||
import { User } from "@/types/user"; | ||
|
||
import Sidebar from "./sidebar"; | ||
|
||
interface LayoutProps { | ||
children: React.ReactNode; | ||
} | ||
|
||
/** | ||
* Layout component that wraps the application with a Navbar or Sidebar based on user authentication status. | ||
* | ||
* @param {LayoutProps} props - The component props. | ||
* @param {React.ReactNode} props.children - The child components to render within the layout. | ||
* | ||
*/ | ||
export default function Layout({ children }: LayoutProps) { | ||
return ( | ||
<div> | ||
<Navbar /> | ||
<main>{children}</main> | ||
</div> | ||
); | ||
const [isAuthChecked, setIsAuthChecked] = useState(false); | ||
const { userId } = useAuth(); | ||
const isLoggedIn = Boolean(userId); | ||
|
||
// wait for auth to be checked before rendering | ||
useEffect(() => { | ||
setIsAuthChecked(true); | ||
}, []); | ||
|
||
const { | ||
data: user, | ||
error, | ||
isLoading, | ||
} = useFetchData<User>({ | ||
queryKey: ["user", userId], | ||
endpoint: "/users/me", | ||
staleTime: 5 * 60 * 1000, | ||
enabled: Boolean(userId), | ||
}); | ||
|
||
if (!isAuthChecked) return null; | ||
|
||
if (!isLoggedIn) { | ||
return ( | ||
<div> | ||
<Navbar /> | ||
<main>{children}</main> | ||
</div> | ||
); | ||
} | ||
|
||
if (isLoading) return <WaitingLoader />; | ||
if (!user) return <div>{error?.message || "Failed to load user data."}</div>; | ||
|
||
return <Sidebar role={user.role}>{children}</Sidebar>; | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import { useRouter } from "next/router"; | ||
import React from "react"; | ||
|
||
import AppSidebar from "@/components/ui/app-sidebar"; | ||
import { Separator } from "@/components/ui/separator"; | ||
import { | ||
SidebarInset, | ||
SidebarProvider, | ||
SidebarTrigger, | ||
} from "@/components/ui/sidebar"; | ||
import { Role } from "@/types/user"; | ||
|
||
import { | ||
Breadcrumb, | ||
BreadcrumbItem, | ||
BreadcrumbLink, | ||
BreadcrumbList, | ||
BreadcrumbSeparator, | ||
} from "./ui/breadcrumb"; | ||
|
||
interface LayoutProps { | ||
children: React.ReactNode; | ||
role: Role; | ||
} | ||
|
||
/** | ||
* Sidebar layout component that renders a sidebar with breadcrumb navigation. | ||
* Displays different content based on the user's role. | ||
* | ||
* @param {LayoutProps} props - The component props. | ||
* @param {React.ReactNode} props.children - The content to render inside the sidebar layout. | ||
* @param {Role} props.role - The role of the user, used to control sidebar behavior. | ||
*/ | ||
export default function Sidebar({ children, role }: LayoutProps) { | ||
const router = useRouter(); | ||
const pathSegments = router.pathname.split("/").filter(Boolean); | ||
|
||
const breadcrumbItems = pathSegments.map((segment, index) => { | ||
const formattedSegment = segment | ||
.replace(/_/g, " ") // replace underscores with spaces | ||
.replace(/\b\w/g, (char) => char.toUpperCase()); // capitalize first letter | ||
return { | ||
title: formattedSegment, | ||
url: `/${pathSegments.slice(0, index + 1).join("/")}`, | ||
}; | ||
}); | ||
|
||
return ( | ||
<div> | ||
<SidebarProvider> | ||
<AppSidebar className="z-50" Role={role} /> | ||
<SidebarInset> | ||
<header className="sticky top-0 z-40 flex h-16 shrink-0 items-center gap-2 border-b bg-background px-4"> | ||
<SidebarTrigger className="-ml-1" /> | ||
<Separator orientation="vertical" className="mr-2 h-5" /> | ||
<Breadcrumb className="ml-4"> | ||
<BreadcrumbList className="text-xl"> | ||
{breadcrumbItems.map((item, index) => ( | ||
<React.Fragment key={item.url}> | ||
<BreadcrumbItem> | ||
<BreadcrumbLink href={item.url}> | ||
{item.title} | ||
</BreadcrumbLink> | ||
</BreadcrumbItem> | ||
{index < breadcrumbItems.length - 1 && ( | ||
<BreadcrumbSeparator className="[&>svg]:h-5 [&>svg]:w-5" /> | ||
)} | ||
</React.Fragment> | ||
))} | ||
</BreadcrumbList> | ||
</Breadcrumb> | ||
</header> | ||
<main>{children}</main> | ||
</SidebarInset> | ||
</SidebarProvider> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { Loader } from "lucide-react"; | ||
|
||
import { Button } from "@/components/ui/button"; | ||
|
||
/** | ||
* WaitingLoader component displays a loading spinner inside a disabled button, indicating that content is being loaded. | ||
* | ||
* **Usage:** | ||
* Simply include where you need to display a loading state within the UI. | ||
* | ||
* @example | ||
* if (isLoading) return <WaitingLoader />; | ||
*/ | ||
export function WaitingLoader() { | ||
return ( | ||
<div className="flex justify-center pt-20"> | ||
<Button className="bg-transparent text-2xl" disabled> | ||
<Loader className="mr-2 animate-spin" /> Loading... | ||
</Button> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.