diff --git a/.hintrc b/.hintrc index 1f143282..9c1013f9 100644 --- a/.hintrc +++ b/.hintrc @@ -3,6 +3,12 @@ "development" ], "hints": { - "typescript-config/consistent-casing": "off" + "typescript-config/consistent-casing": "off", + "axe/forms": [ + "default", + { + "label": "off" + } + ] } } \ No newline at end of file diff --git a/.husky/pre-push b/.husky/pre-push index 735cac74..74b61eec 100644 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1,2 +1,2 @@ -bun run build -git pull --rebase origin main \ No newline at end of file +bun run build +# git pull --rebase origin main diff --git a/.husky/pre-rebase b/.husky/pre-rebase new file mode 100644 index 00000000..e1af8c1b --- /dev/null +++ b/.husky/pre-rebase @@ -0,0 +1,23 @@ +# #!/bin/sh +# . "$(dirname "$0")/_/husky.sh" + +# # Get the current branch name before rebase +# current_branch=$(git rev-parse --abbrev-ref HEAD) + +# # Fetch latest changes from origin/main +# git fetch origin main + +# # Count the number of commits ahead of origin/main +# commit_count=$(git rev-list --count origin/main..HEAD) + +# # If more than one commit exists, abort rebase +# if [ "$commit_count" -gt 1 ]; then +# echo "❌ Rebase aborted: More than one commit ahead of origin/main ($commit_count commits)." +# echo "🔄 Please squash your commits first, then run 'git rebase'." +# exit 1 +# fi + +# # Ensure we stay on the correct branch +# git checkout "$current_branch" 2>/dev/null || true + +# echo "✅ Rebase can proceed. Only one or no commits ahead of main." \ No newline at end of file diff --git a/apps/core/app/dashboard/_compnents/app-sidebar.tsx b/apps/core/app/dashboard/_compnents/app-sidebar.tsx new file mode 100644 index 00000000..5aada2e1 --- /dev/null +++ b/apps/core/app/dashboard/_compnents/app-sidebar.tsx @@ -0,0 +1,76 @@ +"use client"; + +import * as React from "react"; + +//components ui +import { + Sidebar, + SidebarContent, + SidebarHeader, + SidebarRail, + useSidebar, +} from "@repo/ui/components/sidebar"; + +//icons +import BoxPackage from "@repo/icons/box-package"; +import ChartBarPopular from "@repo/icons/chart-bar-popular"; +import LayoutDashboard from "@repo/icons/layout-dashboard"; +import ReportMoney from "@repo/icons/report-money"; +import Usericon from "@repo/icons/user"; + +//components sidebar +import Settingsicon from "@repo/icons/settings"; +import { LogoIconSwitcher } from "./logo-icon-switcher"; +import { NavMain } from "./nav-main"; + +// This is items of dashboard. +const data = { + navMain: [ + { + title: "Dashboard", + url: "/dashboard", + icon: LayoutDashboard, + }, + { + title: "Products", + url: "/products", + icon: BoxPackage, + }, + { + title: "Sales", + url: "/sales", + icon: ChartBarPopular, + }, + { + title: "Profile", + url: "/profile", + icon: Usericon, + }, + { + title: "Payouts", + url: "/payouts", + icon: ReportMoney, + }, + { + title: "Settings", + url: "/settings", + icon: Settingsicon, + }, + ], +}; + +export function AppSidebar({ ...props }: React.ComponentProps) { + const { open, isMobile } = useSidebar(); + + return ( + + + + + + + + + + ); +} diff --git a/apps/core/app/dashboard/_compnents/logo-icon-switcher.tsx b/apps/core/app/dashboard/_compnents/logo-icon-switcher.tsx new file mode 100644 index 00000000..e4c4b2b1 --- /dev/null +++ b/apps/core/app/dashboard/_compnents/logo-icon-switcher.tsx @@ -0,0 +1,46 @@ +import * as React from "react"; +import { motion, AnimatePresence } from "framer-motion"; + +// UI components +import { + SidebarMenu, + SidebarMenuItem, + useSidebar, +} from "@repo/ui/components/sidebar"; + +// Logo +import PixelIcon from "@repo/icons/pxiel"; +import { cn } from "@repo/ui/lib/utils"; + +export function LogoIconSwitcher() { + const { open } = useSidebar(); + + return ( + + + {/* Ensure icon remains centered */} +
+ +
+ + {/* Animate text visibility but maintain spacing */} + + {open && ( + + PixelGenius + + )} + +
+
+ ); +} diff --git a/apps/core/app/dashboard/_compnents/nav-main.tsx b/apps/core/app/dashboard/_compnents/nav-main.tsx new file mode 100644 index 00000000..a2657bed --- /dev/null +++ b/apps/core/app/dashboard/_compnents/nav-main.tsx @@ -0,0 +1,39 @@ +import { + SidebarGroup, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, +} from "@repo/ui/components/sidebar"; +import { usePathname } from "next/navigation"; + +export function NavMain({ + items, +}: { + items: { + title: string; + url: string; + icon: React.ElementType; + }[]; +}) { + const pathname = usePathname(); + + return ( + + + {items.map((item) => ( + + + + + {item.title} + + + + ))} + + + ); +} diff --git a/apps/core/app/dashboard/_compnents/navbar-dashboard.tsx b/apps/core/app/dashboard/_compnents/navbar-dashboard.tsx new file mode 100644 index 00000000..7e385d32 --- /dev/null +++ b/apps/core/app/dashboard/_compnents/navbar-dashboard.tsx @@ -0,0 +1,13 @@ +import { SidebarTrigger } from "@repo/ui/components/sidebar"; + +const NavbarDashboard = () => { + return ( + + ); +}; + +export default NavbarDashboard; diff --git a/apps/core/app/dashboard/_compnents/sidebar.tsx b/apps/core/app/dashboard/_compnents/sidebar.tsx deleted file mode 100644 index 82188eda..00000000 --- a/apps/core/app/dashboard/_compnents/sidebar.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import PixelIcon from "@repo/icons/pxiel"; -import Image from "next/image"; - -// icons -import LayoutDashboard from "../../../../../packages/icons/src/components/layout-dashboard"; -import BoxPackage from "../../../../../packages/icons/src/components/box-package"; -import ChartBarPopular from "../../../../../packages/icons/src/components/chart-bar-popular"; -import ReportMoney from "../../../../../packages/icons/src/components/report-money"; - -const DASHBOARD_SIDEBAR_ITEMS = [ - { - icon: , - title: "Dashboard", - url: "/dashboard", - }, - { - icon: , - title: "Products", - url: "/products", - }, - { - icon: , - title: "Sales", - url: "/sales", - }, - { - icon: , - title: "Payouts", - url: "/payouts", - }, -]; - -const SideBarDashboard = () => { - return ( -
-
-
- -

- PixelGenius -

-
- {DASHBOARD_SIDEBAR_ITEMS.map((item) => ( -
-
{item.icon}
-

{item.title}

-
- ))} -
-
- Picture of space -
-
- ); -}; - -export default SideBarDashboard; diff --git a/apps/core/app/dashboard/layout.tsx b/apps/core/app/dashboard/layout.tsx index 9fa991d6..f4d01b0b 100644 --- a/apps/core/app/dashboard/layout.tsx +++ b/apps/core/app/dashboard/layout.tsx @@ -1,5 +1,7 @@ import React from "react"; -import SideBarDashboard from "./_compnents/sidebar"; +import { SidebarProvider } from "@repo/ui/components/sidebar"; +import { AppSidebar } from "./_compnents/app-sidebar"; +import NavbarDashboard from "./_compnents/navbar-dashboard"; export default function DashboardLayout({ children, @@ -7,9 +9,12 @@ export default function DashboardLayout({ children: React.ReactNode; }): JSX.Element { return ( -
- -
{children}
-
+ + +
+ + {children} +
+
); } diff --git a/apps/core/app/dashboard/page.tsx b/apps/core/app/dashboard/page.tsx index ae87ccd0..aa62d4ed 100644 --- a/apps/core/app/dashboard/page.tsx +++ b/apps/core/app/dashboard/page.tsx @@ -123,7 +123,7 @@ const DashboardPage = () => {
- + General Images diff --git a/apps/core/middleware.ts b/apps/core/middleware.ts index 654c0df6..60d851a2 100644 --- a/apps/core/middleware.ts +++ b/apps/core/middleware.ts @@ -9,11 +9,11 @@ export function middleware(request: NextRequest) { const isDashboard = pathname.startsWith("/dashboard"); // Redirect to login if unauthenticated - if (isDashboard) { - if (!accessToken || !refreshToken) { - return NextResponse.redirect(new URL("/auth/login", request.url)); - } - } + // if (isDashboard) { + // if (!accessToken || !refreshToken) { + // return NextResponse.redirect(new URL("/auth/login", request.url)); + // } + // } // Allow access to protected routes if authenticated return NextResponse.next(); diff --git a/apps/core/next.config.mjs b/apps/core/next.config.mjs index 14d89994..d1cbac29 100644 --- a/apps/core/next.config.mjs +++ b/apps/core/next.config.mjs @@ -2,11 +2,12 @@ // https://s3-alpha-sig.figma.com/img/ce79/f737/ea54458a8146c4935 //https://images.unsplash.com/photo-1730292422804-5bbb2bd2d3f0?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D +// https://github.com/shadcn.png // config src image const nextConfig = { transpilePackages: ["@repo/ui"], images: { - domains: ["images.unsplash.com"], + domains: ["images.unsplash.com", "github.com/shadcn.png"], }, output: "standalone", }; diff --git a/apps/core/public/images/clip-path-group-sidebar.svg b/apps/core/public/images/clip-path-group-sidebar.svg new file mode 100644 index 00000000..94be3d40 --- /dev/null +++ b/apps/core/public/images/clip-path-group-sidebar.svg @@ -0,0 +1,215 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bun.lockb b/bun.lockb index 24af6ab6..2b092209 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/packages/icons/package.json b/packages/icons/package.json index 52895241..eb7ee819 100644 --- a/packages/icons/package.json +++ b/packages/icons/package.json @@ -49,6 +49,7 @@ "./security": "./src/components/security.tsx", "./shopping-bag": "./src/components/shopping-bag.tsx", "./Usercircleicon": "./src/components/user-circle.tsx", + "./Usericon": "./src/components/user.tsx", "./Useractionpixelicon": "./src/components/user-action-pixel.tsx", "./Settingsicon": "./src/components/settings.tsx", "./Doorexiticon": "./src/components/door-exit.tsx", @@ -61,6 +62,7 @@ "./cinema4D": "./src/components/cinema4D.tsx", "./Asset3dIcon": "./src/components/3dAsset.tsx,", "./curveX": "./src/components/curveX.tsx", + "./Moonicon": "./src/components/moon.tsx", "./x": "./src/components/x.tsx", "./menu-2": "./src/components/menu-2.tsx", "./serach": "./src/components/serach.tsx", diff --git a/packages/icons/src/components/menu.tsx b/packages/icons/src/components/menu.tsx new file mode 100644 index 00000000..143f7e7c --- /dev/null +++ b/packages/icons/src/components/menu.tsx @@ -0,0 +1,33 @@ +import { IconProps } from "../types/types"; + +const MenuIcon = (props: IconProps) => { + const { size = 24, color = "currentColor", ...resProps } = props; + + return ( + + + + + + + + + + + ); +}; + +export default MenuIcon; diff --git a/packages/icons/src/components/moon.tsx b/packages/icons/src/components/moon.tsx new file mode 100644 index 00000000..37786f87 --- /dev/null +++ b/packages/icons/src/components/moon.tsx @@ -0,0 +1,25 @@ +import { IconProps } from "../types/types"; +const Moonicon = (props: IconProps) => { + const { size = 24, color = "currentColor", ...resProps } = props; + + return ( + + + + + ); +}; + +export default Moonicon; diff --git a/packages/icons/src/components/user.tsx b/packages/icons/src/components/user.tsx new file mode 100644 index 00000000..5bf317b2 --- /dev/null +++ b/packages/icons/src/components/user.tsx @@ -0,0 +1,26 @@ +import { IconProps } from "../types/types"; +const Usericon = (props: IconProps) => { + const { size = 24, color = "currentColor", ...resProps } = props; + + return ( + + + + + + ); +}; + +export default Usericon; diff --git a/packages/ui/package.json b/packages/ui/package.json index abff54fa..7038486a 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -34,7 +34,7 @@ "@radix-ui/react-checkbox": "^1.1.1", "@radix-ui/react-collapsible": "^1.1.0", "@radix-ui/react-context-menu": "^2.2.1", - "@radix-ui/react-dialog": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-hover-card": "^1.1.1", "@radix-ui/react-label": "^2.1.0", @@ -47,13 +47,13 @@ "@radix-ui/react-select": "^2.1.1", "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slider": "^1.2.0", - "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-switch": "^1.1.0", "@radix-ui/react-tabs": "^1.1.0", "@radix-ui/react-tooltip": "^1.1.0", "@tabler/icons-react": "^3.12.0", - "class-variance-authority": "^0.7.0", + "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "1.0.0", "date-fns": "^3.6.0", @@ -90,7 +90,9 @@ "./src/components/atoms/*.tsx", "./src/components/atoms/*.ts" ], - "./hooks/*": "./src/hooks/*.ts", + "./hooks/*": [ + "./src/hooks/*.ts" + ], "./constant/*": "./src/constant/*.ts" } } diff --git a/packages/ui/src/components/atoms/tooltip.tsx b/packages/ui/src/components/atoms/tooltip.tsx new file mode 100644 index 00000000..2246690d --- /dev/null +++ b/packages/ui/src/components/atoms/tooltip.tsx @@ -0,0 +1,30 @@ +"use client" + +import * as React from "react" +import * as TooltipPrimitive from "@radix-ui/react-tooltip" + +import { cn } from "@repo/ui/lib/utils" + +const TooltipProvider = TooltipPrimitive.Provider + +const Tooltip = TooltipPrimitive.Root + +const TooltipTrigger = TooltipPrimitive.Trigger + +const TooltipContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + +)) +TooltipContent.displayName = TooltipPrimitive.Content.displayName + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } diff --git a/packages/ui/src/components/molecules/sidebar.tsx b/packages/ui/src/components/molecules/sidebar.tsx new file mode 100644 index 00000000..1f87dc42 --- /dev/null +++ b/packages/ui/src/components/molecules/sidebar.tsx @@ -0,0 +1,870 @@ +"use client"; + +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { VariantProps, cva } from "class-variance-authority"; + +import { Skeleton } from "../atoms/skeleton"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "../ui/tooltip"; +import { cn } from "@repo/ui/lib/utils"; +import { useIsMobile } from "../../hooks/use-mobile"; +import { Button } from "../atoms/button"; +import { Input } from "./input"; +import { Separator } from "../atoms/separator"; +import { Sheet, SheetContent } from "../atoms/sheet"; + +import Bellicon from "@repo/icons/bell"; +import Doorexiticon from "@repo/icons/door-exit"; +import MenuIcon from "@repo/icons/menu"; +import Moonicon from "@repo/icons/moon"; +import Settingsicon from "@repo/icons/settings"; +import Shoppingbagicon from "@repo/icons/shopping-bag"; +import Usercircleicon from "@repo/icons/user-circle"; +import Useractionpixelicon from "@repo/icons/useractionpixel"; +import { + AvatarImage, + AvatarFallback, + Avatar, +} from "@repo/ui/components/avatar"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@repo/ui/components/dropdown-menu"; +import Link from "next/link"; + +const SIDEBAR_COOKIE_NAME = "sidebar:state"; +const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; +const SIDEBAR_WIDTH = "16rem"; +const SIDEBAR_WIDTH_MOBILE = "16rem"; +const SIDEBAR_WIDTH_ICON = "4.5rem"; +const SIDEBAR_KEYBOARD_SHORTCUT = "b"; + +type SidebarContext = { + state: "expanded" | "collapsed"; + open: boolean; + setOpen: (open: boolean) => void; + openMobile: boolean; + setOpenMobile: (open: boolean) => void; + isMobile: boolean; + toggleSidebar: () => void; +}; + +const SidebarContext = React.createContext(null); + +function useSidebar() { + const context = React.useContext(SidebarContext); + if (!context) { + throw new Error("useSidebar must be used within a SidebarProvider."); + } + + return context; +} + +const SidebarProvider = React.forwardRef< + HTMLDivElement, + React.ComponentProps<"div"> & { + defaultOpen?: boolean; + open?: boolean; + onOpenChange?: (open: boolean) => void; + } +>( + ( + { + defaultOpen = true, + open: openProp, + onOpenChange: setOpenProp, + className, + style, + children, + ...props + }, + ref, + ) => { + const isMobile = useIsMobile(); + const [openMobile, setOpenMobile] = React.useState(false); + + // This is the internal state of the sidebar. + // We use openProp and setOpenProp for control from outside the component. + const [_open, _setOpen] = React.useState(defaultOpen); + const open = openProp ?? _open; + const setOpen = React.useCallback( + (value: boolean | ((value: boolean) => boolean)) => { + const openState = typeof value === "function" ? value(open) : value; + if (setOpenProp) { + setOpenProp(openState); + } else { + _setOpen(openState); + } + + // This sets the cookie to keep the sidebar state. + document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`; + }, + [setOpenProp, open], + ); + + // Helper to toggle the sidebar. + const toggleSidebar = React.useCallback(() => { + return isMobile + ? setOpenMobile((open) => !open) + : setOpen((open) => !open); + }, [isMobile, setOpen, setOpenMobile]); + + // Adds a keyboard shortcut to toggle the sidebar. + React.useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if ( + event.key === SIDEBAR_KEYBOARD_SHORTCUT && + (event.metaKey || event.ctrlKey) + ) { + event.preventDefault(); + toggleSidebar(); + } + }; + + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [toggleSidebar]); + + // We add a state so that we can do data-state="expanded" or "collapsed". + // This makes it easier to style the sidebar with Tailwind classes. + const state = open ? "expanded" : "collapsed"; + + const contextValue = React.useMemo( + () => ({ + state, + open, + setOpen, + isMobile, + openMobile, + setOpenMobile, + toggleSidebar, + }), + [ + state, + open, + setOpen, + isMobile, + openMobile, + setOpenMobile, + toggleSidebar, + ], + ); + + return ( + + +
+ {children} +
+
+
+ ); + }, +); +SidebarProvider.displayName = "SidebarProvider"; + +const Sidebar = React.forwardRef< + HTMLDivElement, + React.ComponentProps<"div"> & { + side?: "left" | "right"; + variant?: "sidebar" | "floating" | "inset"; + collapsible?: "offcanvas" | "icon" | "none"; + } +>( + ( + { + side = "left", + variant = "sidebar", + collapsible = "offcanvas", + className, + children, + ...props + }, + ref, + ) => { + const { isMobile, state, openMobile, setOpenMobile } = useSidebar(); + + if (collapsible === "none") { + return ( +
+ {children} +
+ ); + } + + if (isMobile) { + return ( + + +
{children}
+
+
+ ); + } + + return ( +
+ {/* This is what handles the sidebar gap on desktop */} +
+ +
+ ); + }, +); +Sidebar.displayName = "Sidebar"; + +const SidebarTrigger = React.forwardRef< + React.ElementRef, + React.ComponentProps +>(({ className, onClick, ...props }, ref) => { + const { toggleSidebar } = useSidebar(); + + return ( + // + + ); +}); +SidebarTrigger.displayName = "SidebarTrigger"; + +const SidebarRail = React.forwardRef< + HTMLButtonElement, + React.ComponentProps<"button"> +>(({ className, ...props }, ref) => { + const { toggleSidebar } = useSidebar(); + + return ( +