diff --git a/app/about/layout.tsx b/app/about/layout.tsx index cfdc03b801..98956a52ad 100644 --- a/app/about/layout.tsx +++ b/app/about/layout.tsx @@ -1,7 +1,13 @@ -export default function AboutLayout({ children }: { children: React.ReactNode }) { +export default function AboutLayout({ + children, +}: { + children: React.ReactNode; +}) { return (
-
{children}
+
+ {children} +
); } diff --git a/app/blog/layout.tsx b/app/blog/layout.tsx index 365caee773..911d0bcf41 100644 --- a/app/blog/layout.tsx +++ b/app/blog/layout.tsx @@ -1,7 +1,13 @@ -export default function BlogLayout({ children }: { children: React.ReactNode }) { +export default function BlogLayout({ + children, +}: { + children: React.ReactNode; +}) { return (
-
{children}
+
+ {children} +
); } diff --git a/app/clouds/layout.tsx b/app/clouds/layout.tsx index 8c8daab55d..d9ca1e80e4 100644 --- a/app/clouds/layout.tsx +++ b/app/clouds/layout.tsx @@ -1,4 +1,8 @@ -export default function CloudsLayout({ children }: { children: React.ReactNode }) { +export default function CloudsLayout({ + children, +}: { + children: React.ReactNode; +}) { return (
{children}
diff --git a/app/clouds/page.tsx b/app/clouds/page.tsx index 48d18551f8..d546612378 100644 --- a/app/clouds/page.tsx +++ b/app/clouds/page.tsx @@ -22,13 +22,21 @@ import { title } from "@/components/primitives"; import { fetcher } from "@/utils/fetcher"; export default function CloudsPage() { - const getAccounts = useSWR("http://localhost:8080/api/v1/providers/aws/accounts", fetcher); + const getAccounts = useSWR( + "http://localhost:8080/api/v1/providers/aws/accounts", + fetcher, + ); - const getAudits = useSWR("http://localhost:8080/api/v1/providers/aws/audits", fetcher); + const getAudits = useSWR( + "http://localhost:8080/api/v1/providers/aws/audits", + fetcher, + ); // TODO FIX TYPE CHECKING const getScanDetails = (account_id: number, detail: string) => { - const scan = getAudits.data && getAudits.data.find((audit: any) => audit.account_id === account_id); + const scan = + getAudits.data && + getAudits.data.find((audit: any) => audit.account_id === account_id); if (detail === "status") { return scan?.audit_complete && "Completed"; @@ -70,8 +78,12 @@ export default function CloudsPage() {

Cloud Accounts

- {getAccounts.error && Failed to load} - {getAccounts.isLoading && Loading} + {getAccounts.error && ( + Failed to load + )} + {getAccounts.isLoading && ( + Loading + )}

{getAccounts.data && ( diff --git a/app/docs/layout.tsx b/app/docs/layout.tsx index e3d89f490a..eaf63f3b99 100644 --- a/app/docs/layout.tsx +++ b/app/docs/layout.tsx @@ -1,7 +1,13 @@ -export default function DocsLayout({ children }: { children: React.ReactNode }) { +export default function DocsLayout({ + children, +}: { + children: React.ReactNode; +}) { return (
-
{children}
+
+ {children} +
); } diff --git a/app/error.tsx b/app/error.tsx index 388b7fcc7b..9ed5104e8a 100644 --- a/app/error.tsx +++ b/app/error.tsx @@ -2,7 +2,13 @@ import { useEffect } from "react"; -export default function Error({ error, reset }: { error: Error; reset: () => void }) { +export default function Error({ + error, + reset, +}: { + error: Error; + reset: () => void; +}) { useEffect(() => { // Log the error to an error reporting service /* eslint-disable no-console */ diff --git a/app/layout.tsx b/app/layout.tsx index 39517f2bf1..c414a0bf59 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -2,11 +2,12 @@ import "@/styles/globals.css"; import clsx from "clsx"; import { Metadata, Viewport } from "next"; +import React from "react"; -import { Navbar } from "@/components/navbar"; import { fontSans } from "@/config/fonts"; import { siteConfig } from "@/config/site"; +import { SidebarWrap } from "../components/sidebar/SidebarWrap"; import { Providers } from "./providers"; export const metadata: Metadata = { @@ -27,15 +28,24 @@ export const viewport: Viewport = { ], }; -export default function RootLayout({ children }: { children: React.ReactNode }) { +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { return ( - + -
- -
{children}
+
+ +
{children}
diff --git a/app/login/layout.tsx b/app/login/layout.tsx index 561d6bcc49..d3a4a58ea4 100644 --- a/app/login/layout.tsx +++ b/app/login/layout.tsx @@ -1,7 +1,13 @@ -export default function LoginLayout({ children }: { children: React.ReactNode }) { +export default function LoginLayout({ + children, +}: { + children: React.ReactNode; +}) { return (
-
{children}
+
+ {children} +
); } diff --git a/app/login/page.tsx b/app/login/page.tsx index c348917daf..f07535d803 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -2,7 +2,15 @@ import { EyeIcon } from "@heroicons/react/24/solid"; import { EyeSlashIcon } from "@heroicons/react/24/solid"; -import { Button, Card, CardBody, CardFooter, CardHeader, Input, Link } from "@nextui-org/react"; +import { + Button, + Card, + CardBody, + CardFooter, + CardHeader, + Input, + Link, +} from "@nextui-org/react"; import React from "react"; export default function LoginPage() { @@ -18,13 +26,22 @@ export default function LoginPage() {

Login

- + + - } - type={isVisible ? "text" : "password"} - className="max-w-xs" + <> +
+
+ - - -

This is a page with "use client", useState

-
- + +

Overview

+ +
+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+
+ + ); } diff --git a/app/pricing/layout.tsx b/app/pricing/layout.tsx index fa11447c67..dc3db6af64 100644 --- a/app/pricing/layout.tsx +++ b/app/pricing/layout.tsx @@ -1,7 +1,13 @@ -export default function PricingLayout({ children }: { children: React.ReactNode }) { +export default function PricingLayout({ + children, +}: { + children: React.ReactNode; +}) { return (
-
{children}
+
+ {children} +
); } diff --git a/components/icons.tsx b/components/Icons.tsx similarity index 86% rename from components/icons.tsx rename to components/Icons.tsx index c684f44d78..a3d73323b4 100644 --- a/components/icons.tsx +++ b/components/Icons.tsx @@ -2,8 +2,19 @@ import * as React from "react"; import { IconSvgProps } from "@/types"; -export const Logo: React.FC = ({ size = 36, width, height, ...props }) => ( - +export const Logo: React.FC = ({ + size = 36, + width, + height, + ...props +}) => ( + = ({ size = 36, width, height, ...prop ); -export const DiscordIcon: React.FC = ({ size = 24, width, height, ...props }) => { +export const DiscordIcon: React.FC = ({ + size = 24, + width, + height, + ...props +}) => { return ( - + = ({ size = 24, width, height, ); }; -export const TwitterIcon: React.FC = ({ size = 24, width, height, ...props }) => { +export const TwitterIcon: React.FC = ({ + size = 24, + width, + height, + ...props +}) => { return ( - + = ({ size = 24, width, height, ); }; -export const GithubIcon: React.FC = ({ size = 24, width, height, ...props }) => { +export const GithubIcon: React.FC = ({ + size = 24, + width, + height, + ...props +}) => { return ( - + = ({ size = 24, width, height, . ); }; -export const MoonFilledIcon = ({ size = 24, width, height, ...props }: IconSvgProps) => ( +export const MoonFilledIcon = ({ + size = 24, + width, + height, + ...props +}: IconSvgProps) => (

PROWLER

diff --git a/components/theme-switch.tsx b/components/ThemeSwitch.tsx similarity index 70% rename from components/theme-switch.tsx rename to components/ThemeSwitch.tsx index 2c2cd9e85a..9c58062ce2 100644 --- a/components/theme-switch.tsx +++ b/components/ThemeSwitch.tsx @@ -6,15 +6,19 @@ import { VisuallyHidden } from "@react-aria/visually-hidden"; import clsx from "clsx"; import { useTheme } from "next-themes"; import { FC } from "react"; +import React from "react"; -import { MoonFilledIcon, SunFilledIcon } from "@/components/icons"; +import { MoonFilledIcon, SunFilledIcon } from "./Icons"; export interface ThemeSwitchProps { className?: string; classNames?: SwitchProps["classNames"]; } -export const ThemeSwitch: FC = ({ className, classNames }) => { +export const ThemeSwitch: FC = ({ + className, + classNames, +}) => { const { theme, setTheme } = useTheme(); const isSSR = useIsSSR(); @@ -22,7 +26,14 @@ export const ThemeSwitch: FC = ({ className, classNames }) => theme === "light" ? setTheme("dark") : setTheme("light"); }; - const { Component, slots, isSelected, getBaseProps, getInputProps, getWrapperProps } = useSwitch({ + const { + Component, + slots, + isSelected, + getBaseProps, + getInputProps, + getWrapperProps, + } = useSwitch({ isSelected: theme === "light" || isSSR, "aria-label": `Switch to ${theme === "light" || isSSR ? "dark" : "light"} mode`, onChange, @@ -31,7 +42,11 @@ export const ThemeSwitch: FC = ({ className, classNames }) => return ( @@ -56,7 +71,11 @@ export const ThemeSwitch: FC = ({ className, classNames }) => ), })} > - {!isSelected || isSSR ? : } + {!isSelected || isSSR ? ( + + ) : ( + + )} ); diff --git a/components/acme.tsx b/components/acme.tsx new file mode 100644 index 0000000000..9478723fa5 --- /dev/null +++ b/components/acme.tsx @@ -0,0 +1,25 @@ +import React from "react"; + +import { IconSvgProps } from "../types/index"; + +export const AcmeIcon: React.FC = ({ + size = 32, + width, + height, + ...props +}) => ( + + + +); diff --git a/components/counter.tsx b/components/counter.tsx deleted file mode 100644 index 3fe2d87743..0000000000 --- a/components/counter.tsx +++ /dev/null @@ -1,14 +0,0 @@ -"use client"; - -import { Button } from "@nextui-org/react"; -import { useState } from "react"; - -export const Counter = () => { - const [count, setCount] = useState(0); - - return ( - - ); -}; diff --git a/components/primitives.ts b/components/primitives.ts index e6095fdeae..472973cbe9 100644 --- a/components/primitives.ts +++ b/components/primitives.ts @@ -26,7 +26,15 @@ export const title = tv({ }, compoundVariants: [ { - color: ["violet", "yellow", "blue", "cyan", "green", "pink", "foreground"], + color: [ + "violet", + "yellow", + "blue", + "cyan", + "green", + "pink", + "foreground", + ], class: "bg-clip-text text-transparent bg-gradient-to-b", }, ], diff --git a/components/sidebar.tsx b/components/sidebar.tsx deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/components/sidebar/Sidebar.tsx b/components/sidebar/Sidebar.tsx new file mode 100644 index 0000000000..49df2deac7 --- /dev/null +++ b/components/sidebar/Sidebar.tsx @@ -0,0 +1,327 @@ +"use client"; + +import { Icon } from "@iconify/react"; +import { + Accordion, + AccordionItem, + type ListboxProps, + type ListboxSectionProps, + type Selection, +} from "@nextui-org/react"; +import { + Listbox, + ListboxItem, + ListboxSection, + Tooltip, +} from "@nextui-org/react"; +import React from "react"; + +import { cn } from "@/utils/cn"; + +export enum SidebarItemType { + Nest = "nest", +} + +export type SidebarItem = { + key: string; + title: string; + icon?: string; + href?: string; + type?: SidebarItemType.Nest; + startContent?: React.ReactNode; + endContent?: React.ReactNode; + items?: SidebarItem[]; + className?: string; +}; + +export type SidebarProps = Omit, "children"> & { + items: SidebarItem[]; + isCompact?: boolean; + hideEndContent?: boolean; + iconClassName?: string; + sectionClasses?: ListboxSectionProps["classNames"]; + classNames?: ListboxProps["classNames"]; + defaultSelectedKey: string; + onSelect?: (key: string) => void; +}; + +const Sidebar = React.forwardRef( + ( + { + items, + isCompact, + defaultSelectedKey, + onSelect, + hideEndContent, + sectionClasses: sectionClassesProp = {}, + itemClasses: itemClassesProp = {}, + iconClassName, + classNames, + className, + ...props + }, + ref, + ) => { + const [selected, setSelected] = + React.useState(defaultSelectedKey); + + const sectionClasses = { + ...sectionClassesProp, + base: cn(sectionClassesProp?.base, "w-full", { + "p-0 max-w-[44px]": isCompact, + }), + group: cn(sectionClassesProp?.group, { + "flex flex-col gap-1": isCompact, + }), + heading: cn(sectionClassesProp?.heading, { + hidden: isCompact, + }), + }; + + const itemClasses = { + ...itemClassesProp, + base: cn(itemClassesProp?.base, { + "w-11 h-11 gap-0 p-0": isCompact, + }), + }; + + const renderNestItem = React.useCallback( + (item: SidebarItem) => { + const isNestType = + item.items && + item.items?.length > 0 && + item?.type === SidebarItemType.Nest; + + if (isNestType) { + // Is a nest type item , so we need to remove the href + delete item.href; + } + + return ( + + ) : ( + item.startContent ?? null + ) + } + title={isCompact || isNestType ? null : item.title} + > + {isCompact ? ( + +
+ {item.icon ? ( + + ) : ( + item.startContent ?? null + )} +
+
+ ) : null} + {!isCompact && isNestType ? ( + + + + + {item.title} + + + ) : ( + item.startContent ?? null + ) + } + > + {item.items && item.items?.length > 0 ? ( + + {item.items.map(renderItem)} + + ) : ( + renderItem(item) + )} + + + ) : null} +
+ ); + }, + [isCompact, hideEndContent, iconClassName, items], + ); + + const renderItem = React.useCallback( + (item: SidebarItem) => { + const isNestType = + item.items && + item.items?.length > 0 && + item?.type === SidebarItemType.Nest; + + if (isNestType) { + return renderNestItem(item); + } + + return ( + + ) : ( + item.startContent ?? null + ) + } + textValue={item.title} + title={isCompact ? null : item.title} + > + {isCompact ? ( + +
+ {item.icon ? ( + + ) : ( + item.startContent ?? null + )} +
+
+ ) : null} +
+ ); + }, + [isCompact, hideEndContent, iconClassName, itemClasses?.base], + ); + + return ( + { + const key = Array.from(keys)[0]; + + setSelected(key as React.Key); + onSelect?.(key as string); + }} + {...props} + > + {(item) => { + return item.items && + item.items?.length > 0 && + item?.type === SidebarItemType.Nest ? ( + renderNestItem(item) + ) : item.items && item.items?.length > 0 ? ( + + {item.items.map(renderItem)} + + ) : ( + renderItem(item) + ); + }} + + ); + }, +); + +Sidebar.displayName = "Sidebar"; + +export default Sidebar; diff --git a/components/sidebar/SidebarItems.tsx b/components/sidebar/SidebarItems.tsx new file mode 100644 index 0000000000..53ac6ef72f --- /dev/null +++ b/components/sidebar/SidebarItems.tsx @@ -0,0 +1,503 @@ +import { Icon } from "@iconify/react"; +import { Chip } from "@nextui-org/react"; + +import { SidebarItem, SidebarItemType } from "./Sidebar"; +import TeamAvatar from "./TeamAvatar"; + +/** + * Please check the https://nextui.org/docs/guide/routing to have a seamless router integration + */ + +export const items: SidebarItem[] = [ + { + key: "home", + href: "#", + icon: "solar:home-2-linear", + title: "Home", + }, + { + key: "projects", + href: "#", + icon: "solar:widget-2-outline", + title: "Projects", + endContent: ( + + ), + }, + { + key: "tasks", + href: "#", + icon: "solar:checklist-minimalistic-outline", + title: "Tasks", + endContent: ( + + ), + }, + { + key: "team", + href: "#", + icon: "solar:users-group-two-rounded-outline", + title: "Team", + }, + { + key: "tracker", + href: "#", + icon: "solar:sort-by-time-linear", + title: "Tracker", + endContent: ( + + New + + ), + }, + { + key: "analytics", + href: "#", + icon: "solar:chart-outline", + title: "Analytics", + }, + { + key: "perks", + href: "#", + icon: "solar:gift-linear", + title: "Perks", + endContent: ( + + 3 + + ), + }, + { + key: "expenses", + href: "#", + icon: "solar:bill-list-outline", + title: "Expenses", + }, + { + key: "settings", + href: "#", + icon: "solar:settings-outline", + title: "Settings", + }, +]; + +export const sectionItems: SidebarItem[] = [ + { + key: "dashboards", + title: "Dashboards", + items: [ + { + key: "home", + href: "/", + icon: "solar:home-2-linear", + title: "Overview", + }, + // { + // key: "projects", + // href: "#", + // icon: "solar:widget-2-outline", + // title: "Projects", + // endContent: ( + // + // ), + // }, + // { + // key: "tasks", + // href: "#", + // icon: "solar:checklist-minimalistic-outline", + // title: "Tasks", + // endContent: ( + // + // ), + // }, + { + key: "services", + href: "/services", + icon: "solar:users-group-two-rounded-outline", + title: "Services", + }, + { + key: "compliance", + href: "/compliance", + icon: "solar:sort-by-time-linear", + title: "Compliance", + endContent: ( + + New + + ), + }, + ], + }, + { + key: "scan", + title: "Scan", + items: [ + { + key: "findings", + href: "/findings", + title: "Findings", + icon: "solar:pie-chart-2-outline", + items: [ + { + key: "shareholders", + href: "#", + title: "Shareholders", + }, + { + key: "note_holders", + href: "#", + title: "Note Holders", + }, + { + key: "transactions_log", + href: "#", + title: "Transactions Log", + }, + ], + }, + ], + }, + { + key: "accounts", + title: "Accounts", + items: [ + { + key: "cloud", + href: "/cloud", + icon: "solar:gift-linear", + title: "Cloud", + endContent: ( + + 3 + + ), + }, + { + key: "integration", + href: "/integration", + icon: "solar:bill-list-outline", + title: "Integration", + }, + { + key: "settings", + href: "/settings", + icon: "solar:settings-outline", + title: "Settings", + }, + ], + }, +]; + +export const sectionItemsWithTeams: SidebarItem[] = [ + ...sectionItems, + { + key: "team", + title: "Team", + items: [ + { + key: "users", + href: "#", + title: "Users", + startContent: , + }, + { + key: "roles", + href: "#", + title: "Roles", + startContent: , + }, + ], + }, +]; + +export const brandItems: SidebarItem[] = [ + { + key: "overview", + title: "Overview", + items: [ + { + key: "home", + href: "/", + icon: "solar:home-2-linear", + title: "Home", + }, + { + key: "projects", + href: "#", + icon: "solar:widget-2-outline", + title: "Projects", + endContent: ( + + ), + }, + { + key: "tasks", + href: "#", + icon: "solar:checklist-minimalistic-outline", + title: "Tasks", + endContent: ( + + ), + }, + { + key: "team", + href: "#", + icon: "solar:users-group-two-rounded-outline", + title: "Team", + }, + { + key: "tracker", + href: "#", + icon: "solar:sort-by-time-linear", + title: "Tracker", + endContent: ( + + New + + ), + }, + ], + }, + { + key: "your-teams", + title: "Your Teams", + items: [ + { + key: "nextui", + href: "#", + title: "NextUI", + startContent: ( + + ), + }, + { + key: "tailwind-variants", + href: "#", + title: "Tailwind Variants", + startContent: ( + + ), + }, + { + key: "nextui-pro", + href: "#", + title: "NextUI Pro", + startContent: ( + + ), + }, + ], + }, +]; + +export const sectionLongList: SidebarItem[] = [ + ...sectionItems, + { + key: "payments", + title: "Payments", + items: [ + { + key: "payroll", + href: "#", + title: "Payroll", + icon: "solar:dollar-minimalistic-linear", + }, + { + key: "invoices", + href: "#", + title: "Invoices", + icon: "solar:file-text-linear", + }, + { + key: "billing", + href: "#", + title: "Billing", + icon: "solar:card-outline", + }, + { + key: "payment-methods", + href: "#", + title: "Payment Methods", + icon: "solar:wallet-money-outline", + }, + { + key: "payouts", + href: "#", + title: "Payouts", + icon: "solar:card-transfer-outline", + }, + ], + }, + { + key: "your-teams", + title: "Your Teams", + items: [ + { + key: "nextui", + href: "#", + title: "NextUI", + startContent: , + }, + { + key: "tailwind-variants", + href: "#", + title: "Tailwind Variants", + startContent: , + }, + { + key: "nextui-pro", + href: "#", + title: "NextUI Pro", + startContent: , + }, + ], + }, +]; + +export const sectionNestedItems: SidebarItem[] = [ + { + key: "home", + href: "#", + icon: "solar:home-2-linear", + title: "Home", + }, + { + key: "projects", + href: "#", + icon: "solar:widget-2-outline", + title: "Projects", + endContent: ( + + ), + }, + { + key: "tasks", + href: "#", + icon: "solar:checklist-minimalistic-outline", + title: "Tasks", + endContent: ( + + ), + }, + { + key: "team", + href: "#", + icon: "solar:users-group-two-rounded-outline", + title: "Team", + }, + { + key: "tracker", + href: "#", + icon: "solar:sort-by-time-linear", + title: "Tracker", + endContent: ( + + New + + ), + }, + { + key: "analytics", + href: "#", + icon: "solar:chart-outline", + title: "Analytics", + }, + { + key: "perks", + href: "#", + icon: "solar:gift-linear", + title: "Perks", + endContent: ( + + 3 + + ), + }, + { + key: "cap_table", + title: "Cap Table", + icon: "solar:pie-chart-2-outline", + type: SidebarItemType.Nest, + items: [ + { + key: "shareholders", + icon: "solar:users-group-rounded-linear", + href: "#", + title: "Shareholders", + }, + { + key: "note_holders", + icon: "solar:notes-outline", + href: "#", + title: "Note Holders", + }, + { + key: "transactions_log", + icon: "solar:clipboard-list-linear", + href: "#", + title: "Transactions Log", + }, + ], + }, + { + key: "expenses", + href: "#", + icon: "solar:bill-list-outline", + title: "Expenses", + }, +]; diff --git a/components/sidebar/SidebarWrap.tsx b/components/sidebar/SidebarWrap.tsx new file mode 100644 index 0000000000..81f2e195aa --- /dev/null +++ b/components/sidebar/SidebarWrap.tsx @@ -0,0 +1,207 @@ +"use client"; + +import { Icon } from "@iconify/react"; +import { Button, ScrollShadow, Spacer, Tooltip } from "@nextui-org/react"; +import React, { useCallback, useEffect, useState } from "react"; +import { useMediaQuery } from "usehooks-ts"; + +import { cn } from "@/utils/cn"; + +import { AcmeIcon } from "../acme"; +import { ThemeSwitch } from "../ThemeSwitch"; +import Sidebar from "./Sidebar"; +import { sectionItemsWithTeams } from "./SidebarItems"; +import UserAvatar from "./UserAvatar"; + +export const SidebarWrap = () => { + const [isCollapsed, setIsCollapsed] = useState(false); + const [isLoaded, setIsLoaded] = useState(false); + + const isMobile = useMediaQuery("(max-width: 768px)"); + + const isCompact = isCollapsed || isMobile; + + useEffect(() => { + const savedState = localStorage.getItem("isCollapsed"); + if (savedState !== null) { + setIsCollapsed(JSON.parse(savedState)); + } + setIsLoaded(true); + }, []); + + const onToggle = useCallback(() => { + setIsCollapsed((prev) => { + const newState = !prev; + localStorage.setItem("isCollapsed", JSON.stringify(newState)); + return newState; + }); + }, []); + + if (!isLoaded) return null; + + return ( +
+
+
+ +
+ + Prowler + +
+ + + + + + + + +
+ + + + + + +
+
+ +
+ +
+
+ + + +
+
+ ); +}; diff --git a/components/sidebar/TeamAvatar.tsx b/components/sidebar/TeamAvatar.tsx new file mode 100644 index 0000000000..e3af5cd985 --- /dev/null +++ b/components/sidebar/TeamAvatar.tsx @@ -0,0 +1,38 @@ +"use client"; + +import type { AvatarProps } from "@nextui-org/react"; +import { Avatar } from "@nextui-org/react"; +import React from "react"; + +import { cn } from "@/utils/cn"; + +const TeamAvatar = React.forwardRef( + ({ name, className, classNames = {}, ...props }, ref) => ( + + (name[0] || "") + (name[name.lastIndexOf(" ") + 1] || "").toUpperCase() + } + name={name} + radius="md" + size="sm" + /> + ), +); + +TeamAvatar.displayName = "TeamAvatar"; + +export default TeamAvatar; diff --git a/components/sidebar/UserAvatar.tsx b/components/sidebar/UserAvatar.tsx new file mode 100644 index 0000000000..4f37a3155d --- /dev/null +++ b/components/sidebar/UserAvatar.tsx @@ -0,0 +1,35 @@ +"use client"; + +import { Avatar } from "@nextui-org/react"; +import React from "react"; + +import { cn } from "@/utils/cn"; + +interface UserAvatarProps { + userName: string; + position: string; + isCompact: boolean; +} +const UserAvatar: React.FC = ({ + userName, + position, + isCompact = false, +}) => { + return ( +
+ +
+

+ {userName} +

+

{position}

+
+
+ ); +}; + +export default UserAvatar; diff --git a/package.json b/package.json index c7ba81e435..3756a3d8e8 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "swr": "^2.2.5" }, "devDependencies": { + "@iconify/react": "^5.0.1", "@types/node": "20.5.7", "@types/react": "18.3.3", "@types/react-dom": "18.3.0", @@ -39,7 +40,8 @@ "postcss": "8.4.38", "tailwind-variants": "0.1.20", "tailwindcss": "3.4.3", - "typescript": "5.0.4" + "typescript": "5.0.4", + "usehooks-ts": "^3.1.0" }, "name": "prowler-next-app", "private": true, diff --git a/tailwind.config.js b/tailwind.config.js index 03a4e22e67..5a6f937328 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,11 +1,11 @@ -import {nextui} from '@nextui-org/theme' +import { nextui } from "@nextui-org/theme"; /** @type {import('tailwindcss').Config} */ module.exports = { content: [ - './components/**/*.{js,ts,jsx,tsx,mdx}', - './app/**/*.{js,ts,jsx,tsx,mdx}', - './node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}' + "./components/**/*.{js,ts,jsx,tsx,mdx}", + "./app/**/*.{js,ts,jsx,tsx,mdx}", + "./node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}", ], theme: { extend: { @@ -13,8 +13,11 @@ module.exports = { sans: ["var(--font-sans)"], mono: ["var(--font-geist-mono)"], }, + height: { + "dvh-minus-16": "calc(100dvh - 116px)", + }, }, }, darkMode: "class", plugins: [nextui()], -} +}; diff --git a/utils/cn.ts b/utils/cn.ts new file mode 100644 index 0000000000..9b89275543 --- /dev/null +++ b/utils/cn.ts @@ -0,0 +1,22 @@ +import type { ClassValue } from "clsx"; +import clsx from "clsx"; +import { twMerge } from "tailwind-merge"; + +// Definition of custom classes you want to combine +const customClasses = new Map([ + ["text-small", "text-small"], + ["text-default-500", "text-default-500"], + // Add more custom classes as needed +]); + +export function cn(...inputs: ClassValue[]) { + // Filter and combine custom classes before passing them to twMerge + const filteredInputs = inputs.map((input) => { + if (typeof input === "string") { + return customClasses.get(input) || input; + } + return input; + }); + + return twMerge(clsx(filteredInputs)); +}