Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useProjectLayout } from "@/app/(app)/projects/[projectId]/layout-provider";
import { useProjectLayout } from "@/app/(app)/[workspaceSlug]/projects/[projectId]/layout-provider";
import { FilterCheckbox } from "@/components/logs/checkbox/filter-checkbox";
import { useLiveQuery } from "@tanstack/react-db";
import { useFilters } from "../../../../../hooks/use-filters";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";
import { useProjectLayout } from "@/app/(app)/projects/[projectId]/layout-provider";
import { useProjectLayout } from "@/app/(app)/[workspaceSlug]/projects/[projectId]/layout-provider";
import { type MenuItem, TableActionPopover } from "@/components/logs/table-action.popover";
import { useWorkspaceNavigation } from "@/hooks/use-workspace-navigation";
import type { Deployment, Environment } from "@/lib/collections";
import { eq, useLiveQuery } from "@tanstack/react-db";
import { ArrowDottedRotateAnticlockwise, ChevronUp, Layers3 } from "@unkey/icons";
Expand All @@ -20,6 +21,7 @@ export const DeploymentListTableActions = ({
selectedDeployment,
environment,
}: DeploymentListTableActionsProps) => {
const workspace = useWorkspaceNavigation();
const { collections } = useProjectLayout();
const { data } = useLiveQuery((q) =>
q
Expand Down Expand Up @@ -78,7 +80,7 @@ export const DeploymentListTableActions = ({
onClick: () => {
//INFO: This will produce a long query, but once we start using `contains` instead of `is` this will be a shorter query.
router.push(
`/projects/${selectedDeployment.projectId}/gateway-logs?host=${data
`${workspace.slug}/projects/${selectedDeployment.projectId}/gateway-logs?host=${data
.map((item) => `is:${item.host}`)
.join(",")}`,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@
import { QuickNavPopover } from "@/components/navbar-popover";
import { NavbarActionButton } from "@/components/navigation/action-button";
import { Navbar } from "@/components/navigation/navbar";
import { useWorkspaceNavigation } from "@/hooks/use-workspace-navigation";
import { collection } from "@/lib/collections";
import { eq, useLiveQuery } from "@tanstack/react-db";
import { ArrowDottedRotateAnticlockwise, Cube, Dots, ListRadio, Refresh3 } from "@unkey/icons";
import {
ArrowDottedRotateAnticlockwise,
ChevronExpandY,
Cube,
Dots,
ListRadio,
Refresh3,
} from "@unkey/icons";
import { Button, Separator } from "@unkey/ui";
import { RepoDisplay } from "../../_components/list/repo-display";

Expand All @@ -13,6 +21,7 @@ type ProjectNavigationProps = {
};

export const ProjectNavigation = ({ projectId }: ProjectNavigationProps) => {
const workspace = useWorkspaceNavigation();
const projects = useLiveQuery((q) =>
q.from({ project: collection.projects }).select(({ project }) => ({
id: project.id,
Expand All @@ -31,11 +40,12 @@ export const ProjectNavigation = ({ projectId }: ProjectNavigationProps) => {
})),
).data.at(0);

const basePath = `/${workspace.slug}/projects`;
if (projects.isLoading) {
return (
<Navbar>
<Navbar.Breadcrumbs icon={<Cube />}>
<Navbar.Breadcrumbs.Link href="/projects">Projects</Navbar.Breadcrumbs.Link>
<Navbar.Breadcrumbs.Link href={basePath}>Projects</Navbar.Breadcrumbs.Link>
<Navbar.Breadcrumbs.Link href="#" isIdentifier className="group max-md:hidden" noop>
<div className="h-6 w-24 bg-grayA-3 rounded animate-pulse transition-all" />
</Navbar.Breadcrumbs.Link>
Expand All @@ -47,13 +57,12 @@ export const ProjectNavigation = ({ projectId }: ProjectNavigationProps) => {
if (!activeProject) {
return <div className="h-full w-full flex items-center justify-center">Project not found</div>;
}

return (
<Navbar>
<Navbar.Breadcrumbs icon={<Cube />}>
<Navbar.Breadcrumbs.Link href="/projects">Projects</Navbar.Breadcrumbs.Link>
<Navbar.Breadcrumbs.Link href={basePath}>Projects</Navbar.Breadcrumbs.Link>
<Navbar.Breadcrumbs.Link
href={`/projects/${activeProject.id}`}
href={`${basePath}/${activeProject.id}`}
isIdentifier
isLast
active
Expand All @@ -64,11 +73,14 @@ export const ProjectNavigation = ({ projectId }: ProjectNavigationProps) => {
items={projects.data.map((project) => ({
id: project.id,
label: project.name,
href: `/projects/${project.id}`,
href: `${basePath}/${project.id}`,
}))}
shortcutKey="N"
>
<div className="truncate max-w-[120px] h-full">{activeProject.name}</div>
<div className="hover:bg-gray-3 rounded-lg flex items-center gap-1 p-1">
{activeProject.name}
<ChevronExpandY className="size-4" />
</div>
</QuickNavPopover>
</Navbar.Breadcrumbs.Link>
</Navbar.Breadcrumbs>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";

import { useWorkspaceNavigation } from "@/hooks/use-workspace-navigation";
import { cn } from "@/lib/utils";
import { Cloud, GridCircle, Layers3 } from "@unkey/icons";
import type { IconProps } from "@unkey/icons/src/props";
Expand All @@ -23,6 +24,7 @@ export const ProjectSubNavigation = ({
const router = useRouter();
const params = useParams();
const pathname = usePathname();
const workspace = useWorkspaceNavigation();
const projectId = params?.projectId as string;

const anchorRef = useRef<HTMLDivElement | null>(null);
Expand Down Expand Up @@ -51,24 +53,25 @@ export const ProjectSubNavigation = ({

const activeTab = getCurrentTab();

const basePath = `/${workspace.slug}/projects`;
const tabs: TabItem[] = [
{
id: "overview",
label: "Overview",
icon: GridCircle,
path: `/projects/${projectId}`,
path: `${basePath}/${projectId}`,
},
{
id: "deployments",
label: "Deployments",
icon: Cloud,
path: `/projects/${projectId}/deployments`,
path: `${basePath}/${projectId}/deployments`,
},
{
id: "gateway-logs",
label: "Gateway Logs",
icon: Layers3,
path: `/projects/${projectId}/gateway-logs`,
path: `${basePath}/${projectId}/gateway-logs`,
},
];

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useWorkspaceNavigation } from "@/hooks/use-workspace-navigation";
import { CodeBranch, Cube, User } from "@unkey/icons";
import { InfoTooltip, Loading, TimestampInfo } from "@unkey/ui";
import Link from "next/link";
Expand Down Expand Up @@ -30,17 +31,20 @@ export const ProjectCard = ({
actions,
projectId,
}: ProjectCardProps) => {
const workspace = useWorkspaceNavigation();
const [isNavigating, setIsNavigating] = useState(false);

const handleLinkClick = useCallback(() => {
setIsNavigating(true);
}, []);

const projectPath = `/${workspace.slug}/projects/${projectId}`;

return (
<div className="relative p-5 flex flex-col border border-grayA-4 hover:border-grayA-7 rounded-2xl w-full gap-5 group transition-all duration-300 [&_a]:z-10 [&_button]:z-10">
{/* Invisible base clickable layer - covers entire card */}
<Link
href={`/projects/${projectId}`}
href={projectPath}
className="absolute inset-0 z-0"
aria-label={`View ${name} project`}
onClick={handleLinkClick}
Expand All @@ -58,7 +62,7 @@ export const ProjectCard = ({
{/*Top Section > Project Name*/}
<InfoTooltip content={name} asChild position={{ align: "start", side: "top" }}>
<Link
href={`/projects/${projectId}`}
href={projectPath}
className="font-medium text-sm leading-[14px] text-accent-12 truncate hover:underline"
>
{name}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
"use client";
import { Navbar } from "@/components/navigation/navbar";
import { useWorkspaceNavigation } from "@/hooks/use-workspace-navigation";
import { Cube } from "@unkey/icons";
import { CreateProjectDialog } from "./_components/create-project/create-project-dialog";

export function ProjectsListNavigation() {
const workspace = useWorkspaceNavigation();
return (
<Navbar>
<Navbar.Breadcrumbs icon={<Cube size="md-medium" className="text-gray-12" />}>
<Navbar.Breadcrumbs.Link href="/projects" active>
<Navbar.Breadcrumbs.Link href={`/${workspace.slug}/projects`} active>
Projects
</Navbar.Breadcrumbs.Link>
</Navbar.Breadcrumbs>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ export const NamespaceNavbar = ({ namespaceId, activePage }: NamespaceNavbarProp
}))}
shortcutKey="N"
>
<div className="text-accent-10 group-hover:text-accent-12">{namespace?.name}</div>
<div className="hover:bg-gray-3 rounded-lg flex items-center gap-1 p-1">
{namespace?.name}
<ChevronExpandY className="size-4" />
</div>
</QuickNavPopover>
</Navbar.Breadcrumbs.Link>
<Navbar.Breadcrumbs.Link href={activePage.href} noop active>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
"use client";

import { Navbar } from "@/components/navigation/navbar";
import { useWorkspaceNavigation } from "@/hooks/use-workspace-navigation";
import { Gauge } from "@unkey/icons";
import { CreateNamespaceButton } from "./_components/create-namespace-button";

export function Navigation() {
const workspace = useWorkspaceNavigation();
return (
<Navbar>
<Navbar.Breadcrumbs icon={<Gauge />}>
<Navbar.Breadcrumbs.Link href="/ratelimits" active>
<Navbar.Breadcrumbs.Link href={`/${workspace.slug}/ratelimits`} active>
Ratelimits
</Navbar.Breadcrumbs.Link>
</Navbar.Breadcrumbs>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,28 +60,29 @@ export const NestedNavItem = ({
if (!hasChildren || !pathname) {
return;
}

const hasMatchingChild = item.items?.some(
(subItem) =>
subItem.href === pathname || subItem.items?.some((child) => child.href === pathname),
);

// Only auto-open children if user hasn't manually collapsed them
if (!childrenUserManuallyCollapsed) {
setIsChildrenOpen(!!hasMatchingChild);
setIsChildrenOpen(Boolean(hasMatchingChild));
}
if (typeof item.label === "string") {
const itemPath = `/${slugify(item.label, {
lower: true,
replacement: "-",
})}`;

// Check if current pathname matches this item's href path
// item.href is already workspace-aware (e.g., "/workspace-slug/projects")
if (item.href && pathname.startsWith(item.href)) {
// Only auto-open parent if user hasn't manually collapsed it AND not force collapsed
if (pathname.startsWith(itemPath) && !userManuallyCollapsed && !forceCollapsed) {
if (!userManuallyCollapsed && !forceCollapsed) {
setIsOpen(true);
}
}
}, [
pathname,
item.items,
item.label,
item.href,
hasChildren,
userManuallyCollapsed,
childrenUserManuallyCollapsed,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,21 @@ export const useApiNavigation = (baseNavItems: NavItem[]) => {

return data.pages.flatMap((page) =>
page.apiList.map((api) => {
const currentApiActive = segments.at(0) === "apis" && segments.at(1) === api.id;
const isExactlyApiRoot = currentApiActive && segments.length === 2;
const aIndex = segments.findIndex((s) => s === "apis");
const currentApiActive = aIndex !== -1 && segments.at(aIndex + 1) === api.id;

const settingsItem: NavItem = {
icon: Gear,
href: `/${workspace.slug}/apis/${api.id}/settings`,
label: "Settings",
active: currentApiActive && segments.at(2) === "settings",
active: currentApiActive && segments.at(3) === "settings",
};

const overviewItem: NavItem = {
icon: ArrowOppositeDirectionY,
href: `/${workspace.slug}/apis/${api.id}`,
label: "Requests",
active: isExactlyApiRoot || (currentApiActive && !segments.at(2)),
active: currentApiActive && !segments.at(3),
};

const subItems: NavItem[] = [overviewItem];
Expand All @@ -55,7 +55,7 @@ export const useApiNavigation = (baseNavItems: NavItem[]) => {
icon: Key,
href: `/${workspace.slug}/apis/${api.id}/keys/${api.keyspaceId}`,
label: "Keys",
active: currentApiActive && segments.at(2) === "keys",
active: currentApiActive && segments.at(3) === "keys",
};

subItems.push(keysItem);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
"use client";
import type { NavItem } from "@/components/navigation/sidebar/workspace-navigations";
import { useWorkspaceNavigation } from "@/hooks/use-workspace-navigation";
import { collection } from "@/lib/collections";
import { useLiveQuery } from "@tanstack/react-db";
import { useSelectedLayoutSegments } from "next/navigation";
import { useMemo } from "react";

export const useProjectNavigation = (baseNavItems: NavItem[]) => {
const segments = useSelectedLayoutSegments() ?? [];
const workspace = useWorkspaceNavigation();

const { data, isLoading } = useLiveQuery((q) =>
q.from({ project: collection.projects }).orderBy(({ project }) => project.id, "desc"),
);

const basePath = `/${workspace.slug}/projects`;
const projectNavItems = useMemo(() => {
if (!data) {
return [];
}
return data.map((project) => {
const currentProjectActive = segments.at(0) === "projects" && segments.at(1) === project.id;
const pIndex = segments.findIndex((s) => s === "projects");
const currentProjectActive = pIndex !== -1 && segments.at(pIndex + 1) === project.id;

const projectNavItem: NavItem = {
href: `/projects/${project.id}`,
href: `${basePath}/${project.id}`,
icon: null,
label: project.name,
active: currentProjectActive,
Expand All @@ -29,11 +33,11 @@ export const useProjectNavigation = (baseNavItems: NavItem[]) => {

return projectNavItem;
});
}, [data, segments]);
}, [data, segments, basePath]);

const enhancedNavItems = useMemo(() => {
const items = [...baseNavItems];
const projectsItemIndex = items.findIndex((item) => item.href === "/projects");
const projectsItemIndex = items.findIndex((item) => item.href === basePath);

if (projectsItemIndex !== -1) {
const projectsItem = { ...items[projectsItemIndex] };
Expand All @@ -44,7 +48,7 @@ export const useProjectNavigation = (baseNavItems: NavItem[]) => {
}

return items;
}, [baseNavItems, projectNavItems]);
}, [baseNavItems, projectNavItems, basePath]);

return {
enhancedNavItems,
Expand Down
Loading
Loading