feat: completed sidebar changes

This commit is contained in:
=
2025-01-20 22:58:59 +05:30
parent 4a6f759900
commit 03debcab5a
16 changed files with 794 additions and 378 deletions
+41 -41
View File
@@ -38,46 +38,46 @@ export const MenuItem = <T extends ElementType = "button">({
}: MenuItemProps<T> & ComponentPropsWithRef<T>): JSX.Element => { }: MenuItemProps<T> & ComponentPropsWithRef<T>): JSX.Element => {
const iconRef = useRef<DotLottie | null>(null); const iconRef = useRef<DotLottie | null>(null);
return ( return (
<div onMouseEnter={() => iconRef.current?.play()} onMouseLeave={() => iconRef.current?.stop()}> <li
<li onMouseEnter={() => iconRef.current?.play()}
className={twMerge( onMouseLeave={() => iconRef.current?.stop()}
"duration-50 group mt-0.5 flex cursor-pointer flex-col rounded px-1 py-2 font-inter text-sm text-bunker-100 transition-all hover:bg-mineshaft-700", className={twMerge(
isSelected && "bg-mineshaft-600 hover:bg-mineshaft-600", "duration-50 group mt-0.5 flex cursor-pointer flex-col rounded px-1 py-2 font-inter text-sm text-bunker-100 transition-all hover:bg-mineshaft-700",
isDisabled && "cursor-not-allowed hover:bg-transparent", isSelected && "bg-mineshaft-600 hover:bg-mineshaft-600",
className isDisabled && "cursor-not-allowed hover:bg-transparent",
)} className
> )}
<motion.span className="flex w-full flex-row items-center justify-start rounded-sm"> >
<Item <motion.span className="flex w-full flex-row items-center justify-start rounded-sm">
type="button" <Item
role="menuitem" type="button"
className="relative flex items-center" role="menuitem"
ref={inputRef} className="relative flex items-center"
{...props} ref={inputRef}
> {...props}
<div >
className={`${ <div
isSelected ? "visisble" : "invisible" className={`${
} absolute -left-[0.28rem] h-5 w-[0.07rem] rounded-md bg-primary`} isSelected ? "visisble" : "invisible"
/> } absolute -left-[0.28rem] h-5 w-[0.07rem] rounded-md bg-primary`}
{icon && ( />
<div style={{ width: "22px", height: "22px" }} className="my-auto ml-1 mr-3"> {icon && (
<DotLottieReact <div style={{ width: "22px", height: "22px" }} className="my-auto ml-1 mr-3">
dotLottieRefCallback={(el) => { <DotLottieReact
iconRef.current = el; dotLottieRefCallback={(el) => {
}} iconRef.current = el;
src={`/lotties/${icon}.json`} }}
loop src={`/lotties/${icon}.json`}
className="h-full w-full" loop
/> className="h-full w-full"
</div> />
)} </div>
<span className="flex-grow text-left">{children}</span> )}
</Item> <span className="flex-grow text-left">{children}</span>
{description && <span className="mt-2 text-xs">{description}</span>} </Item>
</motion.span> {description && <span className="mt-2 text-xs">{description}</span>}
</li> </motion.span>
</div> </li>
); );
}; };
@@ -90,7 +90,7 @@ export type MenuGroupProps = {
export const MenuGroup = ({ children, title }: MenuGroupProps): JSX.Element => ( export const MenuGroup = ({ children, title }: MenuGroupProps): JSX.Element => (
<> <>
<li className="p-2 text-xs text-gray-400">{title}</li> <li className="px-2 pt-3 text-xs uppercase text-gray-400">{title}</li>
{children} {children}
</> </>
); );
@@ -1,14 +1,16 @@
import { useState } from "react"; import { ReactNode, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { faMobile } from "@fortawesome/free-solid-svg-icons"; import { faMobile } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import { Link, Outlet, useNavigate, useRouter } from "@tanstack/react-router"; import { Link, Outlet, useNavigate, useRouter } from "@tanstack/react-router";
import { AnimatePresence, motion } from "framer-motion";
import { twMerge } from "tailwind-merge";
import { Mfa } from "@app/components/auth/Mfa"; import { Mfa } from "@app/components/auth/Mfa";
import { CreateOrgModal } from "@app/components/organization/CreateOrgModal"; import { CreateOrgModal } from "@app/components/organization/CreateOrgModal";
import SecurityClient from "@app/components/utilities/SecurityClient"; import SecurityClient from "@app/components/utilities/SecurityClient";
import { Menu, MenuItem } from "@app/components/v2"; import { Menu, MenuGroup, MenuItem } from "@app/components/v2";
import { useUser } from "@app/context"; import { useUser } from "@app/context";
import { usePopUp, useToggle } from "@app/hooks"; import { usePopUp, useToggle } from "@app/hooks";
import { useSelectOrganization, workspaceKeys } from "@app/hooks/api"; import { useSelectOrganization, workspaceKeys } from "@app/hooks/api";
@@ -18,10 +20,15 @@ import { ProjectType } from "@app/hooks/api/workspace/types";
import { navigateUserToOrg } from "@app/pages/auth/LoginPage/Login.utils"; import { navigateUserToOrg } from "@app/pages/auth/LoginPage/Login.utils";
import { InsecureConnectionBanner } from "./components/InsecureConnectionBanner"; import { InsecureConnectionBanner } from "./components/InsecureConnectionBanner";
import { MenuIconButton } from "./components/MenuIconButton";
import { SidebarFooter } from "./components/SidebarFooter"; import { SidebarFooter } from "./components/SidebarFooter";
import { SidebarHeader } from "./components/SidebarHeader"; import { SidebarHeader } from "./components/SidebarHeader";
export const OrganizationLayout = () => { type Props = {
children?: ReactNode;
};
export const OrganizationLayout = ({ children }: Props) => {
const [shouldShowMfa, toggleShowMfa] = useToggle(false); const [shouldShowMfa, toggleShowMfa] = useToggle(false);
const [requiredMfaMethod, setRequiredMfaMethod] = useState(MfaMethod.EMAIL); const [requiredMfaMethod, setRequiredMfaMethod] = useState(MfaMethod.EMAIL);
const [mfaSuccessCallback, setMfaSuccessCallback] = useState<() => void>(() => {}); const [mfaSuccessCallback, setMfaSuccessCallback] = useState<() => void>(() => {});
@@ -34,6 +41,9 @@ export const OrganizationLayout = () => {
const router = useRouter(); const router = useRouter();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const isNestedLayout = Boolean(children);
const [isCollapsed, setIsCollapsed] = useToggle(isNestedLayout);
const { t } = useTranslation(); const { t } = useTranslation();
const handleOrgChange = async (orgId: string) => { const handleOrgChange = async (orgId: string) => {
queryClient.removeQueries({ queryKey: authKeys.getAuthToken }); queryClient.removeQueries({ queryKey: authKeys.getAuthToken });
@@ -71,93 +81,171 @@ export const OrganizationLayout = () => {
return ( return (
<> <>
<div className="dark hidden h-screen w-full flex-col overflow-x-hidden md:flex"> <div className="dark hidden h-screen w-full flex-col overflow-x-hidden transition-all md:flex">
{!window.isSecureContext && <InsecureConnectionBanner />} {!window.isSecureContext && <InsecureConnectionBanner />}
<div className="flex flex-grow flex-col overflow-y-hidden md:flex-row"> <div className="flex flex-grow flex-col overflow-y-hidden md:flex-row">
<aside className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60"> <aside
className={twMerge(
"dark w-60 border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 transition-all duration-150",
isCollapsed && "w-16"
)}
>
<nav className="items-between flex h-full flex-col justify-between overflow-y-auto dark:[color-scheme:dark]"> <nav className="items-between flex h-full flex-col justify-between overflow-y-auto dark:[color-scheme:dark]">
<div> <div>
<SidebarHeader onChangeOrg={handleOrgChange} /> <SidebarHeader onChangeOrg={handleOrgChange} isCollapsed={isCollapsed} />
<div className="px-1"> <div className="px-1">
<Menu className="mt-4"> <AnimatePresence mode="wait">
<Link to={`/organization/${ProjectType.SecretManager}/overview` as const}> {isCollapsed ? (
{({ isActive }) => ( <motion.div
<MenuItem isSelected={isActive} icon="sliding-carousel"> key="menu-icons"
Secret Management className="space-y-1"
</MenuItem> animate={{ x: 0, opacity: 1 }}
)} exit={{ x: -300, opacity: 0 }}
</Link> transition={{ duration: 0.1 }}
<Link to={`/organization/${ProjectType.CertificateManager}/overview` as const}> >
{({ isActive }) => ( <Link to={`/organization/${ProjectType.SecretManager}/overview` as const}>
<MenuItem isSelected={isActive} icon="note"> <MenuIconButton
Cert Management isSelected={window.location.pathname.startsWith(
</MenuItem> `/${ProjectType.SecretManager}`
)} )}
</Link> icon="sliding-carousel"
<Link to={`/organization/${ProjectType.KMS}/overview` as const}> >
{({ isActive }) => ( Secret Manager
<MenuItem isSelected={isActive} icon="unlock"> </MenuIconButton>
Key Management </Link>
</MenuItem> <Link
)} to={`/organization/${ProjectType.CertificateManager}/overview` as const}
</Link> >
<Link to={`/organization/${ProjectType.SSH}/overview` as const}> <MenuIconButton
{({ isActive }) => ( isSelected={window.location.pathname.startsWith(
<MenuItem isSelected={isActive} icon="verified"> `/${ProjectType.CertificateManager}`
SSH )}
</MenuItem> icon="note"
)} >
</Link> Cert Manager
<Link to="/organization/access-management"> </MenuIconButton>
{({ isActive }) => ( </Link>
<MenuItem isSelected={isActive} icon="groups"> <Link to={`/organization/${ProjectType.KMS}/overview` as const}>
Access Control <MenuIconButton
</MenuItem> isSelected={window.location.pathname.startsWith(`/${ProjectType.KMS}`)}
)} icon="unlock"
</Link> >
<Link to="/organization/secret-scanning"> KMS
{({ isActive }) => ( </MenuIconButton>
<MenuItem isSelected={isActive} icon="secret-scan" className="text-white"> </Link>
Secret Scanning <Link to={`/organization/${ProjectType.SSH}/overview` as const}>
</MenuItem> <MenuIconButton
)} isSelected={window.location.pathname.startsWith(`/${ProjectType.SSH}`)}
</Link> icon="verified"
<Link to="/organization/secret-sharing"> >
{({ isActive }) => ( SSH
<MenuItem isSelected={isActive} icon="lock-closed"> </MenuIconButton>
Secret Sharing </Link>
</MenuItem> </motion.div>
)} ) : (
</Link> <motion.div
{(window.location.origin.includes("https://app.infisical.com") || key="menu-list-items"
window.location.origin.includes("https://eu.infisical.com") || initial={{ x: 300, opacity: 0 }}
window.location.origin.includes("https://gamma.infisical.com")) && ( animate={{ x: 0, opacity: 1 }}
<Link to="/organization/billing"> exit={{ x: -300, opacity: 0 }}
{({ isActive }) => ( transition={{ duration: 0.1 }}
<MenuItem isSelected={isActive} icon="spinning-coin"> >
Usage & Billing <Menu>
</MenuItem> <MenuGroup title="PRODUCTS">
)} <Link
</Link> to={`/organization/${ProjectType.SecretManager}/overview` as const}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="sliding-carousel">
Secret Management
</MenuItem>
)}
</Link>
<Link
to={
`/organization/${ProjectType.CertificateManager}/overview` as const
}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="note">
Cert Management
</MenuItem>
)}
</Link>
<Link to={`/organization/${ProjectType.KMS}/overview` as const}>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="unlock">
Key Management
</MenuItem>
)}
</Link>
<Link to={`/organization/${ProjectType.SSH}/overview` as const}>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="verified">
SSH
</MenuItem>
)}
</Link>
<Link to="/organization/secret-scanning">
{({ isActive }) => (
<MenuItem
isSelected={isActive}
icon="secret-scan"
className="text-white"
>
Secret Scanning
</MenuItem>
)}
</Link>
</MenuGroup>
<MenuGroup title="Others">
<Link to="/organization/access-management">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="groups">
Access Control
</MenuItem>
)}
</Link>
<Link to="/organization/secret-sharing">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="lock-closed">
Secret Sharing
</MenuItem>
)}
</Link>
{(window.location.origin.includes("https://app.infisical.com") ||
window.location.origin.includes("https://eu.infisical.com") ||
window.location.origin.includes("https://gamma.infisical.com")) && (
<Link to="/organization/billing">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="spinning-coin">
Usage & Billing
</MenuItem>
)}
</Link>
)}
<Link to="/organization/audit-logs">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="moving-block">
Audit Logs
</MenuItem>
)}
</Link>
<Link to="/organization/settings">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="toggle-settings">
Organization Settings
</MenuItem>
)}
</Link>
</MenuGroup>
</Menu>
</motion.div>
)} )}
<Link to="/organization/audit-logs"> </AnimatePresence>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="moving-block">
Audit Logs
</MenuItem>
)}
</Link>
<Link to="/organization/settings">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="toggle-settings">
Organization Settings
</MenuItem>
)}
</Link>
</Menu>
</div> </div>
</div> </div>
<SidebarFooter /> <SidebarFooter isCollapsed={isCollapsed} onToggleSidebar={setIsCollapsed.toggle} />
</nav> </nav>
</aside> </aside>
<CreateOrgModal <CreateOrgModal
@@ -165,7 +253,7 @@ export const OrganizationLayout = () => {
onClose={() => handlePopUpToggle("createOrg", false)} onClose={() => handlePopUpToggle("createOrg", false)}
/> />
<main className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 dark:[color-scheme:dark]"> <main className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 dark:[color-scheme:dark]">
<Outlet /> {children || <Outlet />}
</main> </main>
</div> </div>
</div> </div>
@@ -0,0 +1,55 @@
import { ComponentPropsWithRef, ElementType, useRef } from "react";
import { DotLottie, DotLottieReact } from "@lottiefiles/dotlottie-react";
import { MenuItemProps } from "@app/components/v2";
import { twMerge } from "tailwind-merge";
export const MenuIconButton = <T extends ElementType = "button">({
children,
icon,
className,
isDisabled,
isSelected,
as: Item = "button",
description,
// wrapping in forward ref with generic component causes the loss of ts definitions on props
inputRef,
...props
}: MenuItemProps<T> & ComponentPropsWithRef<T>): JSX.Element => {
const iconRef = useRef<DotLottie | null>(null);
return (
<Item
type="button"
role="menuitem"
className={twMerge(
"duration-50 group relative flex w-full cursor-pointer flex-col items-center justify-center rounded p-2 font-inter text-sm text-bunker-100 transition-all hover:bg-mineshaft-700",
isSelected && "bg-bunker-800 hover:bg-mineshaft-600",
isDisabled && "cursor-not-allowed hover:bg-transparent",
className
)}
onMouseEnter={() => iconRef.current?.play()}
onMouseLeave={() => iconRef.current?.stop()}
ref={inputRef}
{...props}
>
<div
className={`${
isSelected ? "visisble" : "invisible"
} absolute -left-[0.28rem] h-full w-[0.07rem] rounded-md bg-primary`}
/>
{icon && (
<div className="my-auto mb-2 h-6 w-6">
<DotLottieReact
dotLottieRefCallback={(el) => {
iconRef.current = el;
}}
src={`/lotties/${icon}.json`}
loop
className="h-full w-full"
/>
</div>
)}
<span className="flex-grow break-words text-center text-xxs">{children}</span>
</Item>
);
};
@@ -0,0 +1 @@
export { MenuIconButton } from "./MenuIconButton";
@@ -1,25 +1,33 @@
import { faGithub, faSlack } from "@fortawesome/free-brands-svg-icons"; import { faGithub, faSlack } from "@fortawesome/free-brands-svg-icons";
import { import {
faAnglesLeft,
faAnglesRight,
faArrowUpRightFromSquare,
faBook, faBook,
faEnvelope, faEnvelope,
faInfinity, faInfinity,
faInfo, faInfo,
faInfoCircle,
faPlus, faPlus,
faQuestion faQuestion,
faUser
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link } from "@tanstack/react-router"; import { Link, useNavigate } from "@tanstack/react-router";
import { WishForm } from "@app/components/features/WishForm"; import { WishForm } from "@app/components/features/WishForm";
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger DropdownMenuTrigger,
MenuItem
} from "@app/components/v2"; } from "@app/components/v2";
import { envConfig } from "@app/config/env"; import { envConfig } from "@app/config/env";
import { useOrganization, useSubscription } from "@app/context"; import { useOrganization, useSubscription, useUser } from "@app/context";
import { useGetOrgTrialUrl } from "@app/hooks/api"; import { useGetOrgTrialUrl, useLogoutUser } from "@app/hooks/api";
import { MenuIconButton } from "../MenuIconButton";
export const INFISICAL_SUPPORT_OPTIONS = [ export const INFISICAL_SUPPORT_OPTIONS = [
[ [
@@ -44,40 +52,68 @@ export const INFISICAL_SUPPORT_OPTIONS = [
] ]
]; ];
export const SidebarFooter = () => { type Props = {
isCollapsed?: boolean;
onToggleSidebar: () => void;
};
export const SidebarFooter = ({ isCollapsed, onToggleSidebar }: Props) => {
const { subscription } = useSubscription(); const { subscription } = useSubscription();
const { currentOrg } = useOrganization(); const { currentOrg } = useOrganization();
const { mutateAsync } = useGetOrgTrialUrl(); const { mutateAsync } = useGetOrgTrialUrl();
const { user } = useUser();
const navigate = useNavigate();
const logout = useLogoutUser();
const logOutUser = async () => {
try {
console.log("Logging out...");
await logout.mutateAsync();
navigate({ to: "/login" });
} catch (error) {
console.error(error);
}
};
return ( return (
<div <div
className={`relative mt-10 ${ className={`relative mt-10 ${
subscription && subscription.slug === "starter" && !subscription.has_used_trial subscription && subscription.slug === "starter" && !subscription.has_used_trial
? "mb-2" ? "mb-2"
: "mb-4" : "mb-4"
} flex w-full cursor-default flex-col items-center px-3 text-sm text-mineshaft-400`} } flex w-full cursor-default flex-col items-center ${isCollapsed ? "px-1" : "px-2"} text-sm text-mineshaft-400`}
> >
{(window.location.origin.includes("https://app.infisical.com") || {(window.location.origin.includes("https://app.infisical.com") ||
window.location.origin.includes("https://gamma.infisical.com")) && <WishForm />} window.location.origin.includes("https://gamma.infisical.com")) &&
<Link !isCollapsed && <WishForm />}
to="/organization/access-management" {!isCollapsed && (
search={{ <Link
action: "invite" to="/organization/access-management"
}} search={{
className="w-full" action: "invite"
> }}
<div className="mb-3 w-full pl-5 duration-200 hover:text-mineshaft-200"> className="w-full"
<FontAwesomeIcon icon={faPlus} className="mr-3" /> >
Invite people <div className="mb-3 w-full pl-2 duration-200 hover:text-mineshaft-200">
</div> <FontAwesomeIcon icon={faPlus} className="mr-3" />
</Link> Invite people
<DropdownMenu>
<DropdownMenuTrigger asChild>
<div className="mb-2 w-full pl-5 duration-200 hover:text-mineshaft-200">
<FontAwesomeIcon icon={faQuestion} className="mr-3 px-[0.1rem]" />
Help & Support
</div> </div>
</Link>
)}
<DropdownMenu>
<DropdownMenuTrigger className="w-full">
{isCollapsed ? (
<MenuIconButton>
<FontAwesomeIcon icon={faInfoCircle} className="mb-3 text-lg" />
Support
</MenuIconButton>
) : (
<div className="mb-4 flex w-full items-center pl-2 duration-200 hover:text-mineshaft-200">
<FontAwesomeIcon icon={faQuestion} className="mr-3 px-[0.1rem]" />
Help & Support
</div>
)}
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1"> <DropdownMenuContent align="start" className="p-1">
{INFISICAL_SUPPORT_OPTIONS.map(([icon, text, url]) => ( {INFISICAL_SUPPORT_OPTIONS.map(([icon, text, url]) => (
@@ -125,6 +161,91 @@ export const SidebarFooter = () => {
</div> </div>
</button> </button>
)} )}
{isCollapsed ? (
<MenuIconButton onClick={onToggleSidebar} className="p-4">
<FontAwesomeIcon icon={faAnglesRight} className="text-lg" />
</MenuIconButton>
) : (
<MenuItem onClick={onToggleSidebar} className="mb-2 w-full px-2 text-mineshaft-400">
<FontAwesomeIcon icon={faAnglesLeft} className="mr-3" />
Collapse
</MenuItem>
)}
<DropdownMenu>
<DropdownMenuTrigger className="w-full">
{isCollapsed ? (
<div>
<MenuIconButton>
<div className="my-1 flex h-6 w-6 items-center justify-center rounded-md bg-primary text-sm uppercase text-black">
{user?.firstName?.charAt(0)}
</div>
</MenuIconButton>
</div>
) : (
<div className="flex w-full cursor-pointer items-center rounded-md border border-mineshaft-600 p-2 px-1">
<div className="mr-2 flex h-6 w-6 items-center justify-center rounded-md bg-primary text-sm uppercase">
{user?.firstName?.charAt(0)}
</div>
<div className="max-w-40 flex-grow truncate text-ellipsis text-left text-sm capitalize text-white">
{user?.firstName} {user?.lastName}
</div>
<div>
<FontAwesomeIcon icon={faUser} className="text-xs text-mineshaft-400" />
</div>
</div>
)}
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1">
<div className="px-2 py-1 text-xs text-mineshaft-400">{user?.username}</div>
<Link to="/personal-settings">
<DropdownMenuItem>Personal Settings</DropdownMenuItem>
</Link>
<a
href="https://infisical.com/docs/documentation/getting-started/introduction"
target="_blank"
rel="noopener noreferrer"
className="mt-3 w-full text-sm font-normal leading-[1.2rem] text-mineshaft-300 hover:text-mineshaft-100"
>
<DropdownMenuItem>
Documentation
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.06rem] pl-1.5 text-xxs"
/>
</DropdownMenuItem>
</a>
<a
href="https://infisical.com/slack"
target="_blank"
rel="noopener noreferrer"
className="mt-3 w-full text-sm font-normal leading-[1.2rem] text-mineshaft-300 hover:text-mineshaft-100"
>
<DropdownMenuItem>
Join Slack Community
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.06rem] pl-1.5 text-xxs"
/>
</DropdownMenuItem>
</a>
{user?.superAdmin && (
<Link to="/admin">
<DropdownMenuItem className="mt-1 border-t border-mineshaft-600">
Server Admin Console
</DropdownMenuItem>
</Link>
)}
<Link to="/organization/admin">
<DropdownMenuItem className="mt-1 border-t border-mineshaft-600">
Organization Admin Console
</DropdownMenuItem>
</Link>
<div className="mt-1 h-1 border-t border-mineshaft-600" />
<button type="button" onClick={logOutUser} className="w-full">
<DropdownMenuItem>Log Out</DropdownMenuItem>
</button>
</DropdownMenuContent>
</DropdownMenu>
</div> </div>
); );
}; };
@@ -1,6 +1,6 @@
import { faAngleDown, faArrowUpRightFromSquare, faCheck } from "@fortawesome/free-solid-svg-icons"; import { faCheck, faSort } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, useNavigate } from "@tanstack/react-router"; import { useNavigate } from "@tanstack/react-router";
import { import {
Button, Button,
@@ -9,17 +9,18 @@ import {
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger DropdownMenuTrigger
} from "@app/components/v2"; } from "@app/components/v2";
import { useOrganization, useUser } from "@app/context"; import { useOrganization } from "@app/context";
import { useGetOrganizations, useLogoutUser } from "@app/hooks/api"; import { useGetOrganizations, useLogoutUser } from "@app/hooks/api";
import { AuthMethod } from "@app/hooks/api/users/types"; import { AuthMethod } from "@app/hooks/api/users/types";
import { twMerge } from "tailwind-merge";
type Prop = { type Prop = {
onChangeOrg: (orgId: string) => void; onChangeOrg: (orgId: string) => void;
isCollapsed?: boolean;
}; };
export const SidebarHeader = ({ onChangeOrg }: Prop) => { export const SidebarHeader = ({ onChangeOrg, isCollapsed }: Prop) => {
const { currentOrg } = useOrganization(); const { currentOrg } = useOrganization();
const { user } = useUser();
const navigate = useNavigate(); const navigate = useNavigate();
const { data: orgs } = useGetOrganizations(); const { data: orgs } = useGetOrganizations();
@@ -35,24 +36,42 @@ export const SidebarHeader = ({ onChangeOrg }: Prop) => {
}; };
return ( return (
<div className="flex h-12 cursor-default items-center px-3 pt-6"> <div
className={twMerge(
"flex cursor-pointer items-center p-2 pt-4",
isCollapsed && "transition-all duration-150 hover:bg-mineshaft-700"
)}
>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild className="max-w-[160px] data-[state=open]:bg-mineshaft-600"> <DropdownMenuTrigger asChild>
<div className="mr-auto flex items-center rounded-md py-1.5 pl-1.5 pr-2 hover:bg-mineshaft-600"> <div
<div className="flex h-5 w-5 min-w-[20px] items-center justify-center rounded-md bg-primary text-sm"> className={twMerge(
{currentOrg?.name.charAt(0)} "flex w-full items-center justify-center rounded-md border border-mineshaft-600 p-1 transition-all",
</div> isCollapsed ? "border-none" : "transition-all duration-150 hover:bg-mineshaft-700"
<div )}
className="overflow-hidden truncate text-ellipsis pl-2 text-sm text-mineshaft-100" >
style={{ maxWidth: "140px" }} {isCollapsed ? (
> <div className="flex h-8 w-8 items-center justify-center rounded-md bg-primary">
{currentOrg?.name} {currentOrg?.name.charAt(0)}
</div> </div>
<FontAwesomeIcon icon={faAngleDown} className="pl-1 pt-1 text-xs text-mineshaft-300" /> ) : (
<>
<div className="mr-2 flex h-8 w-8 items-center justify-center rounded-md bg-primary">
{currentOrg?.name.charAt(0)}
</div>
<div className="flex flex-grow flex-col text-white">
<div className="max-w-36 truncate text-ellipsis text-sm font-medium capitalize">
{currentOrg?.name}
</div>
<div className="text-xs text-mineshaft-400">Free Plan</div>
</div>
<FontAwesomeIcon icon={faSort} className="text-xs text-mineshaft-400" />
</>
)}
</div> </div>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1"> <DropdownMenuContent align="start" className="p-1">
<div className="px-2 py-1 text-xs text-mineshaft-400">{user?.username}</div> <div className="px-2 py-1 text-xs capitalize text-mineshaft-400">organizations</div>
{orgs?.map((org) => { {orgs?.map((org) => {
return ( return (
<DropdownMenuItem key={org.id}> <DropdownMenuItem key={org.id}>
@@ -93,71 +112,6 @@ export const SidebarHeader = ({ onChangeOrg }: Prop) => {
</DropdownMenuItem> </DropdownMenuItem>
); );
})} })}
<div className="mt-1 h-1 border-t border-mineshaft-600" />
<button type="button" onClick={logOutUser} className="w-full">
<DropdownMenuItem>Log Out</DropdownMenuItem>
</button>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger
asChild
className="p-1 hover:bg-primary-400 hover:text-black data-[state=open]:bg-primary-400 data-[state=open]:text-black"
>
<div
className="child flex items-center justify-center rounded-full bg-mineshaft pr-1 text-mineshaft-300 hover:bg-mineshaft-500"
style={{ fontSize: "11px", width: "26px", height: "26px" }}
>
{user?.firstName?.charAt(0)}
{user?.lastName && user?.lastName?.charAt(0)}
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1">
<div className="px-2 py-1 text-xs text-mineshaft-400">{user?.username}</div>
<Link to="/personal-settings">
<DropdownMenuItem>Personal Settings</DropdownMenuItem>
</Link>
<a
href="https://infisical.com/docs/documentation/getting-started/introduction"
target="_blank"
rel="noopener noreferrer"
className="mt-3 w-full text-sm font-normal leading-[1.2rem] text-mineshaft-300 hover:text-mineshaft-100"
>
<DropdownMenuItem>
Documentation
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.06rem] pl-1.5 text-xxs"
/>
</DropdownMenuItem>
</a>
<a
href="https://infisical.com/slack"
target="_blank"
rel="noopener noreferrer"
className="mt-3 w-full text-sm font-normal leading-[1.2rem] text-mineshaft-300 hover:text-mineshaft-100"
>
<DropdownMenuItem>
Join Slack Community
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.06rem] pl-1.5 text-xxs"
/>
</DropdownMenuItem>
</a>
{user?.superAdmin && (
<Link to="/admin">
<DropdownMenuItem className="mt-1 border-t border-mineshaft-600">
Server Admin Console
</DropdownMenuItem>
</Link>
)}
<Link to="/organization/admin">
<DropdownMenuItem className="mt-1 border-t border-mineshaft-600">
Organization Admin Console
</DropdownMenuItem>
</Link>
<div className="mt-1 h-1 border-t border-mineshaft-600" /> <div className="mt-1 h-1 border-t border-mineshaft-600" />
<button type="button" onClick={logOutUser} className="w-full"> <button type="button" onClick={logOutUser} className="w-full">
<DropdownMenuItem>Log Out</DropdownMenuItem> <DropdownMenuItem>Log Out</DropdownMenuItem>
@@ -167,3 +121,131 @@ export const SidebarHeader = ({ onChangeOrg }: Prop) => {
</div> </div>
); );
}; };
// <DropdownMenu>
// <DropdownMenuTrigger asChild className="max-w-[160px] data-[state=open]:bg-mineshaft-600">
// <div className="mr-auto flex items-center rounded-md py-1.5 pl-1.5 pr-2 hover:bg-mineshaft-600">
// <div className="flex h-5 w-5 min-w-[20px] items-center justify-center rounded-md bg-primary text-sm">
// {currentOrg?.name.charAt(0)}
// </div>
// <div
// className="overflow-hidden truncate text-ellipsis pl-2 text-sm text-mineshaft-100"
// style={{ maxWidth: "140px" }}
// >
// {currentOrg?.name}
// </div>
// <FontAwesomeIcon icon={faAngleDown} className="pl-1 pt-1 text-xs text-mineshaft-300" />
// </div>
// </DropdownMenuTrigger>
// <DropdownMenuContent align="start" className="p-1">
// <div className="px-2 py-1 text-xs text-mineshaft-400">{user?.username}</div>
// {orgs?.map((org) => {
// return (
// <DropdownMenuItem key={org.id}>
// <Button
// onClick={async () => {
// if (currentOrg?.id === org.id) return;
//
// if (org.authEnforced) {
// // org has an org-level auth method enabled (e.g. SAML)
// // -> logout + redirect to SAML SSO
//
// await logout.mutateAsync();
// if (org.orgAuthMethod === AuthMethod.OIDC) {
// window.open(`/api/v1/sso/oidc/login?orgSlug=${org.slug}`);
// } else {
// window.open(`/api/v1/sso/redirect/saml2/organizations/${org.slug}`);
// }
// window.close();
// return;
// }
//
// onChangeOrg(org?.id);
// }}
// variant="plain"
// colorSchema="secondary"
// size="xs"
// className="flex w-full items-center justify-start p-0 font-normal"
// leftIcon={
// currentOrg?.id === org.id && (
// <FontAwesomeIcon icon={faCheck} className="mr-3 text-primary" />
// )
// }
// >
// <div className="flex w-full max-w-[150px] items-center justify-between truncate">
// {org.name}
// </div>
// </Button>
// </DropdownMenuItem>
// );
// })}
// <div className="mt-1 h-1 border-t border-mineshaft-600" />
// <button type="button" onClick={logOutUser} className="w-full">
// <DropdownMenuItem>Log Out</DropdownMenuItem>
// </button>
// </DropdownMenuContent>
// </DropdownMenu>
// <DropdownMenu>
// <DropdownMenuTrigger
// asChild
// className="p-1 hover:bg-primary-400 hover:text-black data-[state=open]:bg-primary-400 data-[state=open]:text-black"
// >
// <div
// className="child flex items-center justify-center rounded-full bg-mineshaft pr-1 text-mineshaft-300 hover:bg-mineshaft-500"
// style={{ fontSize: "11px", width: "26px", height: "26px" }}
// >
// {user?.firstName?.charAt(0)}
// {user?.lastName && user?.lastName?.charAt(0)}
// </div>
// </DropdownMenuTrigger>
// <DropdownMenuContent align="start" className="p-1">
// <div className="px-2 py-1 text-xs text-mineshaft-400">{user?.username}</div>
// <Link to="/personal-settings">
// <DropdownMenuItem>Personal Settings</DropdownMenuItem>
// </Link>
// <a
// href="https://infisical.com/docs/documentation/getting-started/introduction"
// target="_blank"
// rel="noopener noreferrer"
// className="mt-3 w-full text-sm font-normal leading-[1.2rem] text-mineshaft-300 hover:text-mineshaft-100"
// >
// <DropdownMenuItem>
// Documentation
// <FontAwesomeIcon
// icon={faArrowUpRightFromSquare}
// className="mb-[0.06rem] pl-1.5 text-xxs"
// />
// </DropdownMenuItem>
// </a>
// <a
// href="https://infisical.com/slack"
// target="_blank"
// rel="noopener noreferrer"
// className="mt-3 w-full text-sm font-normal leading-[1.2rem] text-mineshaft-300 hover:text-mineshaft-100"
// >
// <DropdownMenuItem>
// Join Slack Community
// <FontAwesomeIcon
// icon={faArrowUpRightFromSquare}
// className="mb-[0.06rem] pl-1.5 text-xxs"
// />
// </DropdownMenuItem>
// </a>
// {user?.superAdmin && (
// <Link to="/admin">
// <DropdownMenuItem className="mt-1 border-t border-mineshaft-600">
// Server Admin Console
// </DropdownMenuItem>
// </Link>
// )}
// <Link to="/organization/admin">
// <DropdownMenuItem className="mt-1 border-t border-mineshaft-600">
// Organization Admin Console
// </DropdownMenuItem>
// </Link>
// <div className="mt-1 h-1 border-t border-mineshaft-600" />
// <button type="button" onClick={logOutUser} className="w-full">
// <DropdownMenuItem>Log Out</DropdownMenuItem>
// </button>
// </DropdownMenuContent>
// </DropdownMenu>
@@ -7,7 +7,7 @@ import { Link, Outlet, useNavigate, useRouter } from "@tanstack/react-router";
import { Mfa } from "@app/components/auth/Mfa"; import { Mfa } from "@app/components/auth/Mfa";
import SecurityClient from "@app/components/utilities/SecurityClient"; import SecurityClient from "@app/components/utilities/SecurityClient";
import { Menu, MenuItem } from "@app/components/v2"; import { Menu, MenuGroup, MenuItem } from "@app/components/v2";
import { useUser, useWorkspace } from "@app/context"; import { useUser, useWorkspace } from "@app/context";
import { useToggle } from "@app/hooks"; import { useToggle } from "@app/hooks";
import { import {
@@ -22,7 +22,6 @@ import { ProjectType } from "@app/hooks/api/workspace/types";
import { navigateUserToOrg } from "@app/pages/auth/LoginPage/Login.utils"; import { navigateUserToOrg } from "@app/pages/auth/LoginPage/Login.utils";
import { InsecureConnectionBanner } from "../OrganizationLayout/components/InsecureConnectionBanner"; import { InsecureConnectionBanner } from "../OrganizationLayout/components/InsecureConnectionBanner";
import { SidebarFooter } from "../OrganizationLayout/components/SidebarFooter";
import { ProjectSelect } from "./components/ProjectSelect"; import { ProjectSelect } from "./components/ProjectSelect";
import { SidebarHeader } from "./components/SidebarHeader"; import { SidebarHeader } from "./components/SidebarHeader";
@@ -105,144 +104,146 @@ export const ProjectLayout = () => {
<aside className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60"> <aside className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60">
<nav className="items-between flex h-full flex-col justify-between overflow-y-auto dark:[color-scheme:dark]"> <nav className="items-between flex h-full flex-col justify-between overflow-y-auto dark:[color-scheme:dark]">
<div> <div>
<SidebarHeader onChangeOrg={handleOrgChange} />
<ProjectSelect /> <ProjectSelect />
<div className="px-1"> <div className="px-1">
<Menu> <Menu>
{isSecretManager && ( <MenuGroup title="Main Menu">
<Link {isSecretManager && (
to={`/${ProjectType.SecretManager}/$projectId/overview` as const} <Link
params={{ to={`/${ProjectType.SecretManager}/$projectId/overview` as const}
projectId: currentWorkspace.id params={{
}} projectId: currentWorkspace.id
> }}
{({ isActive }) => ( >
<MenuItem isSelected={isActive} icon="lock-closed"> {({ isActive }) => (
{t("nav.menu.secrets")} <MenuItem isSelected={isActive} icon="lock-closed">
</MenuItem> {t("nav.menu.secrets")}
)} </MenuItem>
</Link> )}
)} </Link>
{isCertManager && (
<Link
to={`/${ProjectType.CertificateManager}/$projectId/overview` as const}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="lock-closed">
Overview
</MenuItem>
)}
</Link>
)}
{isCmek && (
<Link
to={`/${ProjectType.KMS}/$projectId/overview` as const}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="lock-closed">
Overview
</MenuItem>
)}
</Link>
)}
{isSSH && (
<Link
to={`/${ProjectType.SSH}/$projectId/overview` as const}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="lock-closed">
Overview
</MenuItem>
)}
</Link>
)}
<Link
to={`/${currentWorkspace.type}/$projectId/access-management` as const}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="groups">
Access Control
</MenuItem>
)} )}
</Link> {isCertManager && (
{isSecretManager && ( <Link
<Link to={`/${ProjectType.CertificateManager}/$projectId/overview` as const}
to={`/${ProjectType.SecretManager}/$projectId/integrations` as const} params={{
params={{ projectId: currentWorkspace.id
projectId: currentWorkspace.id }}
}} >
> {({ isActive }) => (
{({ isActive }) => ( <MenuItem isSelected={isActive} icon="lock-closed">
<MenuItem isSelected={isActive} icon="jigsaw-puzzle"> Overview
{t("nav.menu.integrations")} </MenuItem>
</MenuItem> )}
)} </Link>
</Link>
)}
{isSecretManager && (
<Link
to={`/${ProjectType.SecretManager}/$projectId/secret-rotation` as const}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="rotation">
Secret Rotation
</MenuItem>
)}
</Link>
)}
{isSecretManager && (
<Link
to={`/${ProjectType.SecretManager}/$projectId/approval` as const}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="circular-check">
Approvals
{Boolean(
secretApprovalReqCount?.open ||
accessApprovalRequestCount?.pendingCount
) && (
<span className="ml-2 rounded border border-primary-400 bg-primary-600 px-1 py-0.5 text-xs font-semibold text-black">
{pendingRequestsCount}
</span>
)}
</MenuItem>
)}
</Link>
)}
<Link
to={`/${currentWorkspace.type}/$projectId/settings` as const}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="toggle-settings">
{t("nav.menu.project-settings")}
</MenuItem>
)} )}
</Link> {isCmek && (
<Link
to={`/${ProjectType.KMS}/$projectId/overview` as const}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="lock-closed">
Overview
</MenuItem>
)}
</Link>
)}
{isSSH && (
<Link
to={`/${ProjectType.SSH}/$projectId/overview` as const}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="lock-closed">
Overview
</MenuItem>
)}
</Link>
)}
{isSecretManager && (
<Link
to={`/${ProjectType.SecretManager}/$projectId/integrations` as const}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="jigsaw-puzzle">
{t("nav.menu.integrations")}
</MenuItem>
)}
</Link>
)}
{isSecretManager && (
<Link
to={`/${ProjectType.SecretManager}/$projectId/secret-rotation` as const}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="rotation">
Secret Rotation
</MenuItem>
)}
</Link>
)}
{isSecretManager && (
<Link
to={`/${ProjectType.SecretManager}/$projectId/approval` as const}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="circular-check">
Approvals
{Boolean(
secretApprovalReqCount?.open ||
accessApprovalRequestCount?.pendingCount
) && (
<span className="ml-2 rounded border border-primary-400 bg-primary-600 px-1 py-0.5 text-xs font-semibold text-black">
{pendingRequestsCount}
</span>
)}
</MenuItem>
)}
</Link>
)}
</MenuGroup>
<MenuGroup title="Others">
<Link
to={`/${currentWorkspace.type}/$projectId/access-management` as const}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="groups">
Access Control
</MenuItem>
)}
</Link>
<Link
to={`/${currentWorkspace.type}/$projectId/settings` as const}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="toggle-settings">
{t("nav.menu.project-settings")}
</MenuItem>
)}
</Link>
</MenuGroup>
</Menu> </Menu>
</div> </div>
</div> </div>
<SidebarFooter />
</nav> </nav>
</aside> </aside>
<main className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 dark:[color-scheme:dark]"> <main className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 dark:[color-scheme:dark]">
@@ -167,7 +167,7 @@ export const ProjectSelect = () => {
}, [workspaces, projectFavorites, currentWorkspace]); }, [workspaces, projectFavorites, currentWorkspace]);
return ( return (
<div className="mb-4 mt-5 w-full p-3"> <div className="mt-2 w-full p-3">
<p className="mb-1 ml-1.5 text-xs font-semibold uppercase text-gray-400"> <p className="mb-1 ml-1.5 text-xs font-semibold uppercase text-gray-400">
{currentWorkspace?.type ? getProjectTitle(currentWorkspace?.type) : "Project"} {currentWorkspace?.type ? getProjectTitle(currentWorkspace?.type) : "Project"}
</p> </p>
+6 -1
View File
@@ -4,11 +4,16 @@ import { workspaceKeys } from "@app/hooks/api";
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries"; import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries"; import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
import { ProjectLayout } from "@app/layouts/ProjectLayout"; import { ProjectLayout } from "@app/layouts/ProjectLayout";
import { OrganizationLayout } from "@app/layouts/OrganizationLayout";
export const Route = createFileRoute( export const Route = createFileRoute(
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout" "/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout"
)({ )({
component: ProjectLayout, component: () => (
<OrganizationLayout>
<ProjectLayout />
</OrganizationLayout>
),
beforeLoad: async ({ params, context }) => { beforeLoad: async ({ params, context }) => {
await context.queryClient.ensureQueryData({ await context.queryClient.ensureQueryData({
queryKey: workspaceKeys.getWorkspaceById(params.projectId), queryKey: workspaceKeys.getWorkspaceById(params.projectId),
+6 -1
View File
@@ -3,12 +3,17 @@ import { createFileRoute } from "@tanstack/react-router";
import { workspaceKeys } from "@app/hooks/api"; import { workspaceKeys } from "@app/hooks/api";
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries"; import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries"; import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
import { OrganizationLayout } from "@app/layouts/OrganizationLayout";
import { ProjectLayout } from "@app/layouts/ProjectLayout"; import { ProjectLayout } from "@app/layouts/ProjectLayout";
export const Route = createFileRoute( export const Route = createFileRoute(
"/_authenticate/_inject-org-details/kms/$projectId/_kms-layout" "/_authenticate/_inject-org-details/kms/$projectId/_kms-layout"
)({ )({
component: ProjectLayout, component: () => (
<OrganizationLayout>
<ProjectLayout />
</OrganizationLayout>
),
beforeLoad: async ({ params, context }) => { beforeLoad: async ({ params, context }) => {
await context.queryClient.ensureQueryData({ await context.queryClient.ensureQueryData({
queryKey: workspaceKeys.getWorkspaceById(params.projectId), queryKey: workspaceKeys.getWorkspaceById(params.projectId),
@@ -1,9 +1,21 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { CertManagerOverviewPage } from "./CertManagerOverviewPage"; import { CertManagerOverviewPage } from "./CertManagerOverviewPage";
export const Route = createFileRoute( export const Route = createFileRoute(
"/_authenticate/_inject-org-details/organization/_layout/cert-manager/overview" "/_authenticate/_inject-org-details/organization/_layout/cert-manager/overview"
)({ )({
component: CertManagerOverviewPage component: CertManagerOverviewPage,
context: () => ({
breadcrumbs: [
{
label: "products",
link: linkOptions({ disabled: true, to: "/" })
},
{
label: "Cert Managers",
link: linkOptions({ to: "/organization/cert-manager/overview" })
}
]
})
}); });
@@ -1,9 +1,21 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { KmsOverviewPage } from "./KmsOverviewPage"; import { KmsOverviewPage } from "./KmsOverviewPage";
export const Route = createFileRoute( export const Route = createFileRoute(
"/_authenticate/_inject-org-details/organization/_layout/kms/overview" "/_authenticate/_inject-org-details/organization/_layout/kms/overview"
)({ )({
component: KmsOverviewPage component: KmsOverviewPage,
context: () => ({
breadcrumbs: [
{
label: "products",
link: linkOptions({ disabled: true, to: "/" })
},
{
label: "KMS",
link: linkOptions({ to: "/organization/kms/overview" })
}
]
})
}); });
@@ -1,9 +1,21 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { SecretManagerOverviewPage } from "./SecretManagerOverviewPage"; import { SecretManagerOverviewPage } from "./SecretManagerOverviewPage";
export const Route = createFileRoute( export const Route = createFileRoute(
"/_authenticate/_inject-org-details/organization/_layout/secret-manager/overview" "/_authenticate/_inject-org-details/organization/_layout/secret-manager/overview"
)({ )({
component: SecretManagerOverviewPage component: SecretManagerOverviewPage,
context: () => ({
breadcrumbs: [
{
label: "products",
link: linkOptions({ disabled: true, to: "/" })
},
{
label: "Secret Managers",
link: linkOptions({ to: "/organization/secret-manager/overview" })
}
]
})
}); });
@@ -1,9 +1,21 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { SshOverviewPage } from "./SshOverviewPage"; import { SshOverviewPage } from "./SshOverviewPage";
export const Route = createFileRoute( export const Route = createFileRoute(
"/_authenticate/_inject-org-details/organization/_layout/ssh/overview" "/_authenticate/_inject-org-details/organization/_layout/ssh/overview"
)({ )({
component: SshOverviewPage component: SshOverviewPage,
context: () => ({
breadcrumbs: [
{
label: "products",
link: linkOptions({ disabled: true, to: "/" })
},
{
label: "SSH",
link: linkOptions({ to: "/organization/ssh/overview" })
}
]
})
}); });
+6 -1
View File
@@ -4,11 +4,16 @@ import { workspaceKeys } from "@app/hooks/api";
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries"; import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries"; import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
import { ProjectLayout } from "@app/layouts/ProjectLayout"; import { ProjectLayout } from "@app/layouts/ProjectLayout";
import { OrganizationLayout } from "@app/layouts/OrganizationLayout";
export const Route = createFileRoute( export const Route = createFileRoute(
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout" "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout"
)({ )({
component: ProjectLayout, component: () => (
<OrganizationLayout>
<ProjectLayout />
</OrganizationLayout>
),
beforeLoad: async ({ params, context }) => { beforeLoad: async ({ params, context }) => {
await context.queryClient.ensureQueryData({ await context.queryClient.ensureQueryData({
queryKey: workspaceKeys.getWorkspaceById(params.projectId), queryKey: workspaceKeys.getWorkspaceById(params.projectId),
+6 -1
View File
@@ -4,11 +4,16 @@ import { workspaceKeys } from "@app/hooks/api";
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries"; import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries"; import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
import { ProjectLayout } from "@app/layouts/ProjectLayout"; import { ProjectLayout } from "@app/layouts/ProjectLayout";
import { OrganizationLayout } from "@app/layouts/OrganizationLayout";
export const Route = createFileRoute( export const Route = createFileRoute(
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout" "/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout"
)({ )({
component: ProjectLayout, component: () => (
<OrganizationLayout>
<ProjectLayout />
</OrganizationLayout>
),
beforeLoad: async ({ params, context }) => { beforeLoad: async ({ params, context }) => {
await context.queryClient.ensureQueryData({ await context.queryClient.ensureQueryData({
queryKey: workspaceKeys.getWorkspaceById(params.projectId), queryKey: workspaceKeys.getWorkspaceById(params.projectId),