From 79c1e4a353ff9fc9be2fd8e85d40be5bf43a43e7 Mon Sep 17 00:00:00 2001 From: Sukma Gladys Date: Tue, 10 Sep 2024 08:04:01 +0700 Subject: [PATCH 1/6] feat: update template to shadcn component --- apps/frontend/package.json | 5 + apps/frontend/src/assets/logos/amati-logo.png | Bin 0 -> 1991 bytes apps/frontend/src/components/AppHeader.tsx | 120 +++++---- apps/frontend/src/components/AppNavbar.tsx | 94 +++++-- .../src/components/DashboardTable.tsx | 119 +++++---- .../src/components/NavbarChildMenu.tsx | 11 +- .../src/components/NavbarMenuItem.tsx | 85 ++++--- apps/frontend/src/components/PageTemplate.tsx | 231 +++++++++++++----- .../usersManagement/tables/columns.tsx | 30 ++- apps/frontend/src/routes/_dashboardLayout.tsx | 88 ++++--- .../src/shadcn/components/ui/avatar.tsx | 50 ++++ .../src/shadcn/components/ui/badge.tsx | 36 +++ .../src/shadcn/components/ui/breadcrumb.tsx | 115 +++++++++ .../src/shadcn/components/ui/dialog.tsx | 122 +++++++++ .../shadcn/components/ui/dropdown-menu.tsx | 2 - .../src/shadcn/components/ui/pagination.tsx | 117 +++++++++ .../src/shadcn/components/ui/radio-group.tsx | 44 ++++ .../src/shadcn/components/ui/scroll-area.tsx | 48 ++++ .../src/shadcn/components/ui/select.tsx | 160 ++++++++++++ .../src/shadcn/components/ui/table.tsx | 117 +++++++++ .../src/shadcn/components/ui/textarea.tsx | 24 ++ pnpm-lock.yaml | 214 +++++++++++++++- 22 files changed, 1508 insertions(+), 324 deletions(-) create mode 100644 apps/frontend/src/assets/logos/amati-logo.png create mode 100644 apps/frontend/src/shadcn/components/ui/avatar.tsx create mode 100644 apps/frontend/src/shadcn/components/ui/badge.tsx create mode 100644 apps/frontend/src/shadcn/components/ui/breadcrumb.tsx create mode 100644 apps/frontend/src/shadcn/components/ui/dialog.tsx create mode 100644 apps/frontend/src/shadcn/components/ui/pagination.tsx create mode 100644 apps/frontend/src/shadcn/components/ui/radio-group.tsx create mode 100644 apps/frontend/src/shadcn/components/ui/scroll-area.tsx create mode 100644 apps/frontend/src/shadcn/components/ui/select.tsx create mode 100644 apps/frontend/src/shadcn/components/ui/table.tsx create mode 100644 apps/frontend/src/shadcn/components/ui/textarea.tsx diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 37d7123..6fd0053 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -16,9 +16,14 @@ "@mantine/form": "^7.10.2", "@mantine/hooks": "^7.10.2", "@mantine/notifications": "^7.10.2", + "@radix-ui/react-avatar": "^1.1.0", "@radix-ui/react-checkbox": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-radio-group": "^1.2.0", + "@radix-ui/react-scroll-area": "^1.1.0", + "@radix-ui/react-select": "^2.1.1", "@radix-ui/react-slot": "^1.1.0", "@tanstack/react-query": "^5.45.0", "@tanstack/react-router": "^1.38.1", diff --git a/apps/frontend/src/assets/logos/amati-logo.png b/apps/frontend/src/assets/logos/amati-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..bf15f563d4770d3700595058fdc9d3632219b803 GIT binary patch literal 1991 zcmV;&2RQhNP)@~0drDELIAGL9O(c600d`2O+f$vv5yPoW{?%E@-}mD>6?I1YtHb zUN=-4QXFs{)F*j4CJ4d|kdX8>#7~HS{=z^+Mi8Rp7H*C49&wX_FE9lJL71Eq+#6-g z-i!ucWD*F1FexM&Yig8taKD!nf*?$e0#9U8#19Ss3iQ8Gy0aI54?z$F9*<`uvC+7^ ziZ`X@=fC}DFBXvz1i=?8HobfYK@fxq5s?uDVNyh71VNY-nUe{NLNy7t!)2MfIooH{ zCc`}0HNInaPSc!!<`9I)2qiMA@iP|c4R9(ns#*3uh6AZcX#Otk;hZlQD9xm_-*6F^ zKJwDuqcOw3AiIXe!kD^Y??pLW>vBzm2=+!(mmNlSvN-s@T#d1-%vpVl{MRPVrP zUx~hV7e%gdMi7cOUSzB2=Fg{;kfJ1;x z##)iM9A?bl)K*};N3+MYd0f)kJl)4g^E$O9EoG50KL6a-y5eLGSnmo-ZUb9eStwLP zGGFLTT88z~CNe)@n~d#@opz_8z`Ryrn`8*ot6vcr0^M^x;x~bu z_*RG00PC32KBDRcsMn`4{vKhWs9_t?OW#5BBsbur&z28dr?BlIG?nx3*p4S>P(|$q zr$C%D+*8;Pt{Xo$tt-w#Ax!*Bf$kyB&~9a2cET{3N4nQ)3A$0J1o@iohigZS55%k6 zizlPu=f%`})TPh}?O#(qtqD%2@&+q|M21_DWFl=lNZkkn zXYJFESQ#WTTU}u)2y?*^-=DBDNM!y6k1d2%3w}u5XvZ+FRan{b9-niUq#_AjCIACb>==Ivxl%140upln{Q`b)?japWD`loPw^7|y9eLNxv=U5*hW|azWpq|j-w-WhruR*p40n5gxk(`> zDJ(^XxVKCoulj`e=LvJfzlhZCmt_6}Q;uRY1s__`%>@O0V z=a9p1GxWJIjsJ7YZ?5t-=s)5n2F7ke_FR^ItFTkVDKuHj?+n~ABmdsS$L%$YYkftP z(^$Cokd^ie;%gvT%kK<$7|e%3+(Oe9f5fR*&LX7{&*_qRM;>V`{^Xd(5yQB~4CU}c z!~LEU`0>E_4&S#33h|sSS)hA9=EO*238?%z&ub1#JMyX-X(V8hVA&%&>AU+H!<|DK z1>7bEwPT(X{5d(KU%(#)H{ne&9*t$ira=F*(2nT@iw*Hap(;nZpDznqI*`xIA&tUb Z{1tmmBwwEC<=_AS002ovPDHLkV1i_Wi7NmA literal 0 HcmV?d00001 diff --git a/apps/frontend/src/components/AppHeader.tsx b/apps/frontend/src/components/AppHeader.tsx index 836b93d..3b351e7 100644 --- a/apps/frontend/src/components/AppHeader.tsx +++ b/apps/frontend/src/components/AppHeader.tsx @@ -1,20 +1,13 @@ import { useState } from "react"; -import { - AppShell, - Avatar, - Burger, - Group, - Menu, - UnstyledButton, - Text, - rem, -} from "@mantine/core"; -import logo from "@/assets/logos/logo.png"; +import logo from "@/assets/logos/amati-logo.png"; import cx from "clsx"; import classNames from "./styles/appHeader.module.css"; -import { TbChevronDown } from "react-icons/tb"; +import { IoMdMenu } from "react-icons/io"; import { Link } from "@tanstack/react-router"; import useAuth from "@/hooks/useAuth"; +import { Avatar, AvatarFallback, AvatarImage } from "@/shadcn/components/ui/avatar"; +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/shadcn/components/ui/dropdown-menu"; +import { Button } from "@/shadcn/components/ui/button"; // import getUserMenus from "../actions/getUserMenus"; // import { useAuth } from "@/modules/auth/contexts/AuthContext"; // import UserMenuItem from "./UserMenuItem"; @@ -24,73 +17,74 @@ interface Props { toggle: () => void; } +interface User { + id: string; + name: string; + permissions: string[]; + photoProfile?: string; +} + // const mockUserData = { // name: "Fulan bin Fulanah", // email: "janspoon@fighter.dev", // image: "https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/avatars/avatar-5.png", // }; -export default function AppHeader(props: Props) { +export default function AppHeader({toggle}: Props) { const [userMenuOpened, setUserMenuOpened] = useState(false); - const { user } = useAuth(); + const { user }: { user: User | null } = useAuth(); // const userMenus = getUserMenus().map((item, i) => ( // // )); return ( - - - - - setUserMenuOpened(true)} - onClose={() => setUserMenuOpened(false)} - withinPortal +
+
+ - - - Logout - + - {/* {userMenus} */} - -
-
-
+ + + + + + + + Logout + + + + + ); } diff --git a/apps/frontend/src/components/AppNavbar.tsx b/apps/frontend/src/components/AppNavbar.tsx index d44397c..824d092 100644 --- a/apps/frontend/src/components/AppNavbar.tsx +++ b/apps/frontend/src/components/AppNavbar.tsx @@ -1,7 +1,10 @@ -import { AppShell, ScrollArea } from "@mantine/core"; import { useQuery } from "@tanstack/react-query"; import client from "../honoClient"; import MenuItem from "./NavbarMenuItem"; +import { useState, useEffect } from "react"; +import { useLocation } from "@tanstack/react-router"; +import { ScrollArea } from "@/shadcn/components/ui/scroll-area"; +import AppHeader from "./AppHeader"; // import MenuItem from "./SidebarMenuItem"; // import { useAuth } from "@/modules/auth/contexts/AuthContext"; @@ -12,33 +15,84 @@ import MenuItem from "./NavbarMenuItem"; * * @returns A React element representing the application's navigation bar. */ -export default function AppNavbar() { +export default function AppNavbar(){ // const {user} = useAuth(); + const { pathname } = useLocation(); + + const [isSidebarOpen, setSidebarOpen] = useState(true); + const toggleSidebar = () => { + setSidebarOpen(!isSidebarOpen); + }; + const { data } = useQuery({ queryKey: ["sidebarData"], queryFn: async () => { - const res = await client.dashboard.getSidebarItems.$get(); - if (res.ok) { - const data = await res.json(); - - return data; - } - console.error("Error:", res.status, res.statusText); - - //TODO: Handle error properly - throw new Error("Error fetching sidebar data"); + const res = await client.dashboard.getSidebarItems.$get(); + if (res.ok) { + const data = await res.json(); + return data; + } + console.error("Error:", res.status, res.statusText); + throw new Error("Error fetching sidebar data"); }, }); + useEffect(() => { + const handleResize = () => { + if (window.innerWidth < 768) { // Ganti 768 dengan breakpoint mobile Anda + setSidebarOpen(false); + } else { + setSidebarOpen(true); + } + }; + + window.addEventListener('resize', handleResize); + handleResize(); // Initial check + + return () => window.removeEventListener('resize', handleResize); + }, []); + + const [activeMenuId, setActiveMenuId] = useState(''); + + const handleMenuItemClick = (id: string) => { + setActiveMenuId(id); + if (window.innerWidth < 768) { + setSidebarOpen(false); + } + }; + return ( - - - {data?.map((menu, i) => )} - {/* {user?.sidebarMenus.map((menu, i) => ( - - )) ?? null} */} - - + <> +
+ {/* Header */} + + + {/* Sidebar */} +
+ + {data?.map((menu, i) => ( + + ))} + +
+ + {/* Overlay to close sidebar on mobile */} + {isSidebarOpen && ( +
+ )} +
+ ); } diff --git a/apps/frontend/src/components/DashboardTable.tsx b/apps/frontend/src/components/DashboardTable.tsx index 1b03a27..5301c59 100644 --- a/apps/frontend/src/components/DashboardTable.tsx +++ b/apps/frontend/src/components/DashboardTable.tsx @@ -1,5 +1,13 @@ -import { Table, Center, ScrollArea } from "@mantine/core"; -import { Table as ReactTable, flexRender } from "@tanstack/react-table"; +import { ScrollArea } from "@/shadcn/components/ui/scroll-area"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow +} from "@/shadcn/components/ui/table"; +import { flexRender, Table as ReactTable } from "@tanstack/react-table"; interface Props { table: ReactTable; @@ -7,68 +15,55 @@ interface Props { export default function DashboardTable({ table }: Props) { return ( - - - {/* Thead */} - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} - - ))} - + +
+ + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {header.isPlaceholder + ? null + : flexRender(header.column.columnDef.header, header.getContext())} + ))} - + + ))} + - {/* Tbody */} - - {table.getRowModel().rows.length > 0 ? ( - table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} - - ))} - - )) - ) : ( - - -
- No Data -
-
-
- )} -
+ + {table.getRowModel().rows.length > 0 ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + )) + ) : ( + + + - No Data - + + + )} +
-
+ ); } diff --git a/apps/frontend/src/components/NavbarChildMenu.tsx b/apps/frontend/src/components/NavbarChildMenu.tsx index 3f09f2f..5ed22ef 100644 --- a/apps/frontend/src/components/NavbarChildMenu.tsx +++ b/apps/frontend/src/components/NavbarChildMenu.tsx @@ -1,5 +1,3 @@ -import { Text } from "@mantine/core"; - import classNames from "./styles/navbarChildMenu.module.css"; import { SidebarMenu } from "backend/types"; @@ -22,13 +20,10 @@ export default function ChildMenu(props: Props) { : `/${props.item.link}`; return ( - - component="a" - className={classNames.link} - href={`${linkPath}`} - fw={props.active ? "bold" : "normal"} + {props.item.label} - + ); } diff --git a/apps/frontend/src/components/NavbarMenuItem.tsx b/apps/frontend/src/components/NavbarMenuItem.tsx index fc0e202..d1e114b 100644 --- a/apps/frontend/src/components/NavbarMenuItem.tsx +++ b/apps/frontend/src/components/NavbarMenuItem.tsx @@ -1,16 +1,5 @@ import { useState } from "react"; - -import { - Box, - Collapse, - Group, - ThemeIcon, - UnstyledButton, - rem, -} from "@mantine/core"; -import { TbChevronRight } from "react-icons/tb"; import * as TbIcons from "react-icons/tb"; - import classNames from "./styles/navbarMenuItem.module.css"; // import dashboardConfig from "../dashboard.config"; // import { usePathname } from "next/navigation"; @@ -19,9 +8,14 @@ import classNames from "./styles/navbarMenuItem.module.css"; import { SidebarMenu } from "backend/types"; import ChildMenu from "./NavbarChildMenu"; import { Link } from "@tanstack/react-router"; +import { Button } from "@/shadcn/components/ui/button"; +import { ChevronRightIcon} from "lucide-react"; +import { cn } from "@/lib/utils"; interface Props { menu: SidebarMenu; + isActive: boolean; + onClick: (link: string) => void; } //TODO: Make bold and collapsed when the item is active @@ -34,7 +28,7 @@ interface Props { * @param props.menu - The menu item data to display. * @returns A React element representing an individual menu item. */ -export default function MenuItem({ menu }: Props) { +export default function MenuItem({ menu, isActive, onClick }: Props) { const hasChildren = Array.isArray(menu.children); // const pathname = usePathname(); @@ -50,6 +44,13 @@ export default function MenuItem({ menu }: Props) { setOpened((prev) => !prev); }; + const handleClick = () => { + onClick(menu.link ?? ""); // Notify parent of the active menu item + if (!hasChildren) { + toggleOpenMenu(); // Collapse if no children + } + }; + // Mapping children menu items if available const subItems = (hasChildren ? menu.children! : []).map((child, index) => ( @@ -69,43 +70,41 @@ export default function MenuItem({ menu }: Props) { return ( <> {/* Main Menu Item */} - - onClick={toggleOpenMenu} - className={`${classNames.control} py-2`} - to={menu.link} - component={menu.link ? Link : "button"} + {/* Collapsible Sub-Menu */} - {hasChildren && {subItems}} + {hasChildren && ( +
+ {subItems} +
+ )} ); } diff --git a/apps/frontend/src/components/PageTemplate.tsx b/apps/frontend/src/components/PageTemplate.tsx index 3107229..c26a673 100644 --- a/apps/frontend/src/components/PageTemplate.tsx +++ b/apps/frontend/src/components/PageTemplate.tsx @@ -1,16 +1,4 @@ /* eslint-disable no-mixed-spaces-and-tabs */ -import { - Button, - Card, - Flex, - Pagination, - Select, - Stack, - Text, - TextInput, - Title, -} from "@mantine/core"; -import { Link } from "@tanstack/react-router"; import React, { ReactNode, useState } from "react"; import { TbPlus, TbSearch } from "react-icons/tb"; import DashboardTable from "./DashboardTable"; @@ -26,6 +14,26 @@ import { useQuery, } from "@tanstack/react-query"; import { useDebouncedCallback } from "@mantine/hooks"; +import { Button } from "@/shadcn/components/ui/button"; +import { useNavigate } from "@tanstack/react-router"; +import { Card } from "@/shadcn/components/ui/card"; +import { Input } from "@/shadcn/components/ui/input"; +import { + Pagination, + PaginationContent, + PaginationEllipsis, + PaginationItem, + PaginationLink, + PaginationNext, + PaginationPrevious, + } from "@/shadcn/components/ui/pagination" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/shadcn/components/ui/select" type PaginatedResponse> = { data: Array; @@ -71,30 +79,108 @@ const createCreateButton = ( property: Props["createButton"] = true ) => { if (property === true) { + const navigate = useNavigate(); return ( ); } else if (typeof property === "string") { + const navigate = useNavigate(); return ( + + + ); } else { return property; } }; +/** + * Pagination component for handling page navigation. + * + * @param props - The properties object. + * @returns The rendered Pagination component. + */ +const CustomPagination = ({ + currentPage, + totalPages, + onPageChange, + hasNextPage, + }: { + currentPage: number; + totalPages: number; + onPageChange: (page: number) => void; + hasNextPage: boolean; + }) => { + return ( + + + + onPageChange(currentPage - 1)} + className={`${ + currentPage === 1 + ? 'bg-white text-muted-foreground cursor-not-allowed' + : 'bg-white text-black hover:bg-muted hover:text-black' + }`} + aria-disabled={currentPage === 1} + > + Previous + + + + {Array.from({ length: totalPages }, (_, index) => index + 1).map((page) => ( + + onPageChange(page)} + className={page === currentPage ? 'border text-black' : ''} + > + {page} + + + ))} + + {totalPages > 1 && currentPage < totalPages && ( + + + + )} + + + onPageChange(currentPage + 1)} + className={`${ + !hasNextPage || currentPage === totalPages + ? 'bg-white text-muted-foreground cursor-not-allowed' + : 'bg-white text-black hover:bg-muted hover:text-black' + }`} + aria-disabled={!hasNextPage || currentPage === totalPages} + > + Next + + + + + ); + }; + /** * PageTemplate component for displaying a paginated table with search and filter functionality. @@ -131,7 +217,11 @@ export default function PageTemplate< columns: props.columnDefs, getCoreRowModel: getCoreRowModel(), defaultColumn: { - cell: (props) => {props.getValue() as ReactNode}, + cell: (props) => ( + + {props.getValue() as ReactNode} + + ), }, }); @@ -154,34 +244,50 @@ export default function PageTemplate< * @param page - The new page number. */ const handlePageChange = (page: number) => { - setFilterOptions((prev) => ({ - page: page - 1, + if (page >= 1 && page <= (query.data?._metadata.totalPages ?? 1)) { + setFilterOptions((prev) => ({ + page: page - 1, // Adjust for zero-based index limit: prev.limit, q: prev.q, - })); - }; + })); + } + }; + + // Default values when query.data is undefined + const totalPages = query.data?._metadata.totalPages ?? 1; + const hasNextPage = query.data + ? filterOptions.page < totalPages - 1 + : false; return ( - - {props.title} - +
+

{props.title}

+ {/* Top Section */} - - {createCreateButton(props.createButton)} - + {/* Table Functionality */}
- {/* Search */} -
- } + {/* Search and Create Button */} +
+
+ + handleSearchQueryChange(e.target.value) } placeholder="Search..." /> + +
+
+ {createCreateButton(props.createButton)} +
{/* Table */} @@ -189,32 +295,43 @@ export default function PageTemplate< {/* Pagination */} {query.data && ( -
- setFilterOptions((prev) => ({ page: prev.page, limit: parseInt(value ?? "10"), q: prev.q, })) - } - checkIconPosition="right" - className="w-20" + } + defaultValue="10" + > + + + + + 5 + 10 + 50 + 100 + 500 + 1000 + + +
+ - - - Showing {query.data.data.length} of{" "} - {query.data._metadata.totalItems} - +
+ + Showing {query.data.data.length} of {query.data._metadata.totalItems} + +
)}
@@ -224,6 +341,6 @@ export default function PageTemplate< {modal} ))}
- +
); } diff --git a/apps/frontend/src/modules/usersManagement/tables/columns.tsx b/apps/frontend/src/modules/usersManagement/tables/columns.tsx index d29d65c..134f191 100644 --- a/apps/frontend/src/modules/usersManagement/tables/columns.tsx +++ b/apps/frontend/src/modules/usersManagement/tables/columns.tsx @@ -1,5 +1,4 @@ import { createColumnHelper } from "@tanstack/react-table"; -import { Badge, Flex, Group, Avatar, Text, Anchor } from "@mantine/core"; import { TbEye, TbPencil, TbTrash } from "react-icons/tb"; import { CrudPermission } from "@/types"; import stringToColorHex from "@/utils/stringToColorHex"; @@ -7,6 +6,8 @@ import createActionButtons from "@/utils/createActionButton"; import client from "@/honoClient"; import { InferResponseType } from "hono"; import { Link } from "@tanstack/react-router"; +import { Badge } from "@/shadcn/components/ui/badge"; +import { Avatar } from "@/shadcn/components/ui/avatar"; interface ColumnOptions { permissions: Partial; @@ -29,31 +30,28 @@ const createColumns = (options: ColumnOptions) => { columnHelper.accessor("name", { header: "Name", cell: (props) => ( - +
{props.getValue()?.[0].toUpperCase()} - + {props.getValue()} - - + +
), }), columnHelper.accessor("email", { header: "Email", cell: (props) => ( - + {props.getValue()} - + ), }), @@ -66,7 +64,7 @@ const createColumns = (options: ColumnOptions) => { id: "status", header: "Status", cell: (props) => ( - + {props.row.original.isEnabled ? "Active" : "Inactive"} ), @@ -80,7 +78,7 @@ const createColumns = (options: ColumnOptions) => { className: "w-fit", }, cell: (props) => ( - +
{createActionButtons([ { label: "Detail", @@ -104,7 +102,7 @@ const createColumns = (options: ColumnOptions) => { icon: , }, ])} - +
), }), ]; diff --git a/apps/frontend/src/routes/_dashboardLayout.tsx b/apps/frontend/src/routes/_dashboardLayout.tsx index e5d8517..49c8037 100644 --- a/apps/frontend/src/routes/_dashboardLayout.tsx +++ b/apps/frontend/src/routes/_dashboardLayout.tsx @@ -1,4 +1,3 @@ -import { AppShell } from "@mantine/core"; import { Navigate, Outlet, createFileRoute } from "@tanstack/react-router"; import { useDisclosure } from "@mantine/hooks"; import AppHeader from "../components/AppHeader"; @@ -9,60 +8,57 @@ import fetchRPC from "@/utils/fetchRPC"; import client from "@/honoClient"; export const Route = createFileRoute("/_dashboardLayout")({ - component: DashboardLayout, + component: DashboardLayout, - // beforeLoad: ({ location }) => { - // if (true) { - // throw redirect({ - // to: "/login", - // }); - // } - // }, + // beforeLoad: ({ location }) => { + // if (true) { + // throw redirect({ + // to: "/login", + // }); + // } + // }, }); function DashboardLayout() { - const { isAuthenticated, saveAuthData } = useAuth(); + const { isAuthenticated, saveAuthData } = useAuth(); - useQuery({ - queryKey: ["my-profile"], - queryFn: async () => { - const response = await fetchRPC(client.auth["my-profile"].$get()); + useQuery({ + queryKey: ["my-profile"], + queryFn: async () => { + const response = await fetchRPC(client.auth["my-profile"].$get()); - saveAuthData({ - id: response.id, - name: response.name, - permissions: response.permissions, - }); + saveAuthData({ + id: response.id, + name: response.name, + permissions: response.permissions, + }); - return response; - }, - enabled: isAuthenticated, - }); + return response; + }, + enabled: isAuthenticated, + }); - const [openNavbar, { toggle }] = useDisclosure(false); + const [openNavbar, { toggle }] = useDisclosure(false); - return isAuthenticated ? ( - - + return isAuthenticated ? ( +
+ {/* Header */} + - + {/* Main Content Area */} +
+ {/* Sidebar */} + - - - - - ) : ( - - ); + {/* Main Content */} +
+ +
+
+
+ ) : ( + + ); } diff --git a/apps/frontend/src/shadcn/components/ui/avatar.tsx b/apps/frontend/src/shadcn/components/ui/avatar.tsx new file mode 100644 index 0000000..51e507b --- /dev/null +++ b/apps/frontend/src/shadcn/components/ui/avatar.tsx @@ -0,0 +1,50 @@ +"use client" + +import * as React from "react" +import * as AvatarPrimitive from "@radix-ui/react-avatar" + +import { cn } from "@/lib/utils" + +const Avatar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Avatar.displayName = AvatarPrimitive.Root.displayName + +const AvatarImage = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarImage.displayName = AvatarPrimitive.Image.displayName + +const AvatarFallback = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/apps/frontend/src/shadcn/components/ui/badge.tsx b/apps/frontend/src/shadcn/components/ui/badge.tsx new file mode 100644 index 0000000..f000e3e --- /dev/null +++ b/apps/frontend/src/shadcn/components/ui/badge.tsx @@ -0,0 +1,36 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ) +} + +export { Badge, badgeVariants } diff --git a/apps/frontend/src/shadcn/components/ui/breadcrumb.tsx b/apps/frontend/src/shadcn/components/ui/breadcrumb.tsx new file mode 100644 index 0000000..71a5c32 --- /dev/null +++ b/apps/frontend/src/shadcn/components/ui/breadcrumb.tsx @@ -0,0 +1,115 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { ChevronRight, MoreHorizontal } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Breadcrumb = React.forwardRef< + HTMLElement, + React.ComponentPropsWithoutRef<"nav"> & { + separator?: React.ReactNode + } +>(({ ...props }, ref) =>