mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
Working on the Organization side.
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarHeader,
|
||||
SidebarMenu,
|
||||
SidebarMenuItem
|
||||
} from "@/components/ui/sidebar";
|
||||
import {SidebarLogo} from "@/components/wrappers/dashboard/common/sidebar/logo-sidebar";
|
||||
import {SidebarMenuCustomMain} from "@/components/wrappers/dashboard/common/sidebar/menu-sidebar-main";
|
||||
import {SideBarFooterCredit} from "@/components/wrappers/dashboard/common/sidebar/side-bar-footer-credit";
|
||||
import {LoggedInButton} from "@/components/wrappers/dashboard/loggedInButton/LoggedInButton";
|
||||
import {OrganizationCombobox} from "@/components/wrappers/dashboard/organization/organization-combobox";
|
||||
|
||||
export function AppSidebar() {
|
||||
|
||||
return (
|
||||
<Sidebar collapsible="icon">
|
||||
<SidebarHeader>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarLogo/>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<OrganizationCombobox/>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarHeader>
|
||||
|
||||
<SidebarContent>
|
||||
<SidebarMenuCustomMain/>
|
||||
</SidebarContent>
|
||||
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<LoggedInButton />
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
<SideBarFooterCredit />
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
"use client"
|
||||
|
||||
import { useSidebar } from "@/components/ui/sidebar";
|
||||
import Link from "next/link";
|
||||
import { env } from "@/env.mjs";
|
||||
import { useTheme } from "next-themes";
|
||||
import Image from "next/image";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export const SidebarLogo = () => {
|
||||
const { state, isMobile } = useSidebar();
|
||||
const { resolvedTheme } = useTheme();
|
||||
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
if (!mounted) return null;
|
||||
|
||||
const imageTheme = resolvedTheme === "dark" ? "/images/logo-white.png" : "/images/logo-black.png";
|
||||
|
||||
return (
|
||||
<div className="ml-1 flex items-center justify-center">
|
||||
<Link href={"/"}>
|
||||
{state === 'collapsed' && !isMobile ? (
|
||||
<Image
|
||||
loading="eager"
|
||||
src={"/images/logo.png"}
|
||||
alt={`Logo ${env.NEXT_PUBLIC_PROJECT_NAME}`}
|
||||
className="h-10 w-10 object-contain"
|
||||
height={10}
|
||||
width={10}
|
||||
/>
|
||||
) : (
|
||||
<div className="m-4">
|
||||
<Image
|
||||
loading="eager"
|
||||
src={imageTheme}
|
||||
alt={`Logo ${env.NEXT_PUBLIC_PROJECT_NAME}`}
|
||||
className="object-contain"
|
||||
width={190}
|
||||
height={90}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
"use client"
|
||||
|
||||
import {Home, Settings, Target, Users, Pickaxe, Euro, BanknoteX, Layers, ChartArea, ShieldHalf} from "lucide-react";
|
||||
import {SidebarGroupItem, SidebarMenuCustomBase} from "@/components/wrappers/dashboard/common/sidebar/menu-sidebar";
|
||||
import {authClient, useSession} from "@/lib/auth/auth-client";
|
||||
|
||||
|
||||
export const SidebarMenuCustomMain = () => {
|
||||
|
||||
const BASE_URL = `/dashboard`;
|
||||
|
||||
const { data: activeOrganization } = authClient.useActiveOrganization();
|
||||
const { data: organizations } = authClient.useListOrganizations();
|
||||
const {data: session, isPending, error} = authClient.useSession();
|
||||
const member = authClient.useActiveMember();
|
||||
|
||||
|
||||
if (isPending) return null;
|
||||
|
||||
if (error || !session) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const groupContent: SidebarGroupItem["group_content"] = [
|
||||
{ title: "Dashboard", url: "/home", icon: Home },
|
||||
{ title: "Projects", url: "/projects", icon: Layers, details:true },
|
||||
{ title: "Statistics", url: "/statistics", icon: ChartArea },
|
||||
];
|
||||
|
||||
if (activeOrganization && (member?.data?.role === "admin" || member?.data?.role === "owner")) {
|
||||
groupContent.push({ title: "Settings", url: "/settings", icon: Settings, details:true });
|
||||
}
|
||||
|
||||
const items: SidebarGroupItem[] = [
|
||||
{
|
||||
label: "Application",
|
||||
type: "list",
|
||||
group_content: groupContent,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
if (session?.user.role == "admin" || session?.user.role == "superadmin") {
|
||||
items.push({
|
||||
label: "Administration",
|
||||
type: "list",
|
||||
group_content: [
|
||||
{title: "Agents", url: "/agents", icon: ShieldHalf, details: true},
|
||||
{title: "Administration panel", url: "/admin", icon: Settings, details: true},
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return <SidebarMenuCustomBase baseUrl={BASE_URL} items={items}/>;
|
||||
};
|
||||
@@ -0,0 +1,208 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
SidebarGroupLabel,
|
||||
SidebarMenu,
|
||||
SidebarMenuAction,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarMenuSub,
|
||||
SidebarMenuSubButton,
|
||||
SidebarMenuSubItem
|
||||
} from "@/components/ui/sidebar";
|
||||
import {
|
||||
ChevronDown,
|
||||
MoreHorizontal
|
||||
} from "lucide-react";
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger
|
||||
} from "@radix-ui/react-collapsible";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
export type SidebarItem = {
|
||||
title: string;
|
||||
url: string;
|
||||
redirect?: boolean;
|
||||
not_from_base_url?: boolean;
|
||||
icon: React.ElementType;
|
||||
dropdown?: SidebarItem[];
|
||||
submenu?: SidebarItem[];
|
||||
details?: boolean;
|
||||
};
|
||||
|
||||
export type SidebarGroupItem = {
|
||||
label: string;
|
||||
type: "list" | "collapse";
|
||||
group_content: SidebarItem[];
|
||||
};
|
||||
|
||||
type SidebarMenuCustomBaseProps = {
|
||||
baseUrl: string;
|
||||
items: SidebarGroupItem[];
|
||||
};
|
||||
|
||||
export const SidebarMenuCustomBase = ({ baseUrl, items }: SidebarMenuCustomBaseProps) => {
|
||||
const pathname = usePathname();
|
||||
const [activeItem, setActiveItem] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const normalize = (url: string) => url.split("?")[0].replace(/\/$/, "");
|
||||
|
||||
const findActiveUrl = () => {
|
||||
const normalizedPathname = normalize(pathname);
|
||||
console.log(normalizedPathname);
|
||||
for (const group of items) {
|
||||
for (const item of group.group_content) {
|
||||
const itemUrl = normalize(item.url);
|
||||
|
||||
const fullUrl = item.not_from_base_url
|
||||
? itemUrl
|
||||
: normalize(`${baseUrl}${itemUrl}`);
|
||||
|
||||
if (
|
||||
normalizedPathname === fullUrl ||
|
||||
(item.details && normalizedPathname.startsWith(`${fullUrl}/`))
|
||||
) {
|
||||
return itemUrl;
|
||||
}
|
||||
|
||||
if (item.submenu) {
|
||||
for (const subItem of item.submenu) {
|
||||
const subItemUrl = normalize(subItem.url);
|
||||
const subFullUrl = normalize(`${baseUrl}${subItemUrl}`);
|
||||
|
||||
if (
|
||||
normalizedPathname === subFullUrl ||
|
||||
(subItem.details && normalizedPathname.startsWith(`${subFullUrl}/`))
|
||||
) {
|
||||
return subItemUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
};
|
||||
|
||||
const activeUrl = findActiveUrl();
|
||||
setActiveItem(activeUrl);
|
||||
}, [pathname, baseUrl, items]);
|
||||
|
||||
return (
|
||||
<SidebarMenu>
|
||||
{items.map((group, index) => (
|
||||
<Collapsible key={index} defaultOpen disabled={group.type === "list"}>
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel asChild>
|
||||
<CollapsibleTrigger>
|
||||
{group.label ?? "Missing label"}
|
||||
{group.type === "collapse" && (
|
||||
<ChevronDown className="ml-auto transition-transform group-data-[state=open]/collapsible:rotate-180" />
|
||||
)}
|
||||
</CollapsibleTrigger>
|
||||
</SidebarGroupLabel>
|
||||
<CollapsibleContent>
|
||||
<SidebarGroupContent>
|
||||
{group.group_content.map((item, index) => (
|
||||
<SidebarMenuItem key={index} className="mb-1">
|
||||
<SidebarMenuButton asChild>
|
||||
<Link
|
||||
href={
|
||||
item.redirect || item.not_from_base_url
|
||||
? item.url
|
||||
: `${baseUrl}${item.url}`
|
||||
}
|
||||
target={item.redirect ? "_blank" : ""}
|
||||
className={cn(
|
||||
buttonVariants({
|
||||
size: "lg",
|
||||
variant: activeItem === item.url ? "secondary" : "ghost"
|
||||
}),
|
||||
"justify-start p-0",
|
||||
"hover:bg-gray-700"
|
||||
)}
|
||||
onClick={() => setActiveItem(item.url)}
|
||||
>
|
||||
<item.icon />
|
||||
<span>{item.title}</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
|
||||
{item.submenu && (
|
||||
<SidebarMenuSub>
|
||||
{item.submenu.map((sub, idx) => (
|
||||
<SidebarMenuSubItem key={idx}>
|
||||
<SidebarMenuSubButton asChild>
|
||||
<Link
|
||||
href={`${baseUrl}${sub.url}`}
|
||||
className={cn(
|
||||
buttonVariants({
|
||||
size: "lg",
|
||||
variant: activeItem === sub.url ? "secondary" : "ghost"
|
||||
}),
|
||||
"justify-start p-0",
|
||||
"hover:bg-gray-700"
|
||||
)}
|
||||
onClick={() => setActiveItem(sub.url)}
|
||||
>
|
||||
<sub.icon />
|
||||
{sub.title}
|
||||
</Link>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
))}
|
||||
</SidebarMenuSub>
|
||||
)}
|
||||
|
||||
{item.dropdown && (
|
||||
<SidebarMenuAction>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<MoreHorizontal />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent side="right" align="start">
|
||||
{item.dropdown.map((dropdown, idx) => (
|
||||
<DropdownMenuItem key={idx} asChild>
|
||||
<Link
|
||||
href={
|
||||
dropdown.redirect || dropdown.not_from_base_url
|
||||
? dropdown.url
|
||||
: `${baseUrl}${dropdown.url}`
|
||||
}
|
||||
className="justify-start p-0"
|
||||
target={dropdown.redirect ? "_blank" : ""}
|
||||
onClick={() => setActiveItem(dropdown.url)}
|
||||
>
|
||||
<span>{dropdown.title}</span>
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</SidebarMenuAction>
|
||||
)}
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
</SidebarGroupContent>
|
||||
</CollapsibleContent>
|
||||
</SidebarGroup>
|
||||
</Collapsible>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
"use client";
|
||||
import { useSidebar } from "@/components/ui/sidebar";
|
||||
import { env } from "@/env.mjs";
|
||||
|
||||
export type SideBarFooterCreditProps = {};
|
||||
|
||||
export const SideBarFooterCredit = (props: SideBarFooterCreditProps) => {
|
||||
const { state } = useSidebar();
|
||||
|
||||
return (
|
||||
<>
|
||||
{state === "expanded" && (
|
||||
<div className="text-center">
|
||||
<h1 className="text-[10px]">Portabase Community Edition v{env.NEXT_PUBLIC_PROJECT_VERSION}</h1>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
"use client";
|
||||
import {useSidebar} from "@/components/ui/sidebar";
|
||||
import {useTheme} from "next-themes";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
export type SideBarLogoProps = {};
|
||||
|
||||
export const SideBarLogo = (props: SideBarLogoProps) => {
|
||||
const {state, isMobile} = useSidebar();
|
||||
const {resolvedTheme} = useTheme();
|
||||
return state === "collapsed" && !isMobile ? (
|
||||
<Link href={"/dashboard"}>
|
||||
|
||||
<div className="flex items-center justify-center">
|
||||
<Image
|
||||
src={"/images/logo/picto.png"}
|
||||
alt="Logo"
|
||||
width={32}
|
||||
height={32}
|
||||
loading="eager"
|
||||
className="object-contain"
|
||||
/>
|
||||
</div>
|
||||
</Link>
|
||||
) : (
|
||||
<Link href={"/dashboard"}>
|
||||
|
||||
<div className="w-full h-auto p-4 flex items-center justify-center">
|
||||
<Image
|
||||
loading="eager"
|
||||
src={"/images/logo/logo.png"}
|
||||
alt="Logo"
|
||||
width={190}
|
||||
height={190}
|
||||
className="object-contain"
|
||||
/>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user