diff --git a/packages/web/app/src/components/layouts/organization.tsx b/packages/web/app/src/components/layouts/organization.tsx
index 7e3e20f83..3d6d78ee7 100644
--- a/packages/web/app/src/components/layouts/organization.tsx
+++ b/packages/web/app/src/components/layouts/organization.tsx
@@ -35,13 +35,12 @@ import { useLastVisitedOrganizationWriter } from '@/lib/last-visited-org';
import { cn } from '@/lib/utils';
import { zodResolver } from '@hookform/resolvers/zod';
import { Slot } from '@radix-ui/react-slot';
-import { Link, useRouter } from '@tanstack/react-router';
+import { useRouter } from '@tanstack/react-router';
import { ProPlanBilling } from '../organization/billing/ProPlanBillingWarm';
import { RateLimitWarn } from '../organization/billing/RateLimitWarn';
import { HiveLink } from '../ui/hive-link';
import { PlusIcon } from '../ui/icon';
import { QueryError } from '../ui/query-error';
-import { Tabs, TabsList, TabsTrigger } from '../ui/tabs';
import { OrganizationSelector } from './organization-selectors';
export enum Page {
@@ -65,6 +64,9 @@ const OrganizationLayoutQuery = graphql(`
viewerCanManageSupportTickets
viewerCanDescribeBilling
viewerCanSeeMembers
+ viewerCanAccessSettings
+ viewerCanManageAccessTokens
+ viewerCanManagePersonalAccessTokens
...UserMenu_OrganizationFragment
...ProPlanBilling_OrganizationFragment
...RateLimitWarn_OrganizationFragment
@@ -80,7 +82,8 @@ export function OrganizationLayout({
children,
page,
className,
- ...props
+ organizationSlug,
+ minimal,
}: {
page?: Page;
className?: string;
@@ -92,8 +95,8 @@ export function OrganizationLayout({
const [query] = useQuery({
query: OrganizationLayoutQuery,
variables: {
- organizationSlug: props.organizationSlug,
- minimal: props.minimal ?? false,
+ organizationSlug,
+ minimal: minimal ?? false,
},
requestPolicy: 'cache-first',
});
@@ -102,12 +105,12 @@ export function OrganizationLayout({
useLastVisitedOrganizationWriter(currentOrganization?.slug);
if (query.error) {
- return ;
+ return ;
}
// Only show the null state state if the query has finished fetching and data is not stale
// This prevents showing null state when switching between orgs with cached data
- const shouldShowNoOrg = !query.fetching && !query.stale && !currentOrganization && !props.minimal;
+ const shouldShowNoOrg = !query.fetching && !query.stale && !currentOrganization && !minimal;
return (
<>
@@ -115,7 +118,7 @@ export function OrganizationLayout({
@@ -125,84 +128,72 @@ export function OrganizationLayout({
organizations={query.data?.organizations ?? null}
/>
-
-
- {currentOrganization ? (
-
-
-
-
- Overview
-
-
- {currentOrganization.viewerCanSeeMembers && (
-
-
- Members
-
-
- )}
-
-
- Settings
-
-
- {currentOrganization.viewerCanManageSupportTickets && (
-
-
- Support
-
-
- )}
- {getIsStripeEnabled() && currentOrganization.viewerCanDescribeBilling && (
-
-
- Subscription
-
-
- )}
-
-
- ) : (
-
- )}
- {currentOrganization?.viewerCanCreateProject ? (
+
>
- ) : null}
-
-
+ ) : null
+ }
+ />
{currentOrganization ? (
<>
diff --git a/packages/web/app/src/components/layouts/project.tsx b/packages/web/app/src/components/layouts/project.tsx
index e53d25841..39b53640b 100644
--- a/packages/web/app/src/components/layouts/project.tsx
+++ b/packages/web/app/src/components/layouts/project.tsx
@@ -21,11 +21,10 @@ import { graphql } from '@/gql';
import { useToggle } from '@/lib/hooks';
import { useLastVisitedOrganizationWriter } from '@/lib/last-visited-org';
import { zodResolver } from '@hookform/resolvers/zod';
-import { Link, useRouter } from '@tanstack/react-router';
+import { useRouter } from '@tanstack/react-router';
import { ResourceNotFoundComponent } from '../resource-not-found';
import { HiveLink } from '../ui/hive-link';
import { PlusIcon } from '../ui/icon';
-import { Tabs, TabsList, TabsTrigger } from '../ui/tabs';
import { ProjectSelector } from './project-selector';
export enum Page {
@@ -53,6 +52,8 @@ const ProjectLayoutQuery = graphql(`
viewerCanModifySchemaPolicy
viewerCanCreateTarget
viewerCanModifyAlerts
+ viewerCanModifySettings
+ viewerCanManageProjectAccessTokens
}
...UserMenu_OrganizationFragment
}
@@ -63,7 +64,8 @@ export function ProjectLayout({
children,
page,
className,
- ...props
+ organizationSlug,
+ projectSlug,
}: {
page: Page;
organizationSlug: string;
@@ -71,14 +73,13 @@ export function ProjectLayout({
className?: string;
children: ReactNode;
}) {
+ const params = { organizationSlug, projectSlug };
+
const [isModalOpen, toggleModalOpen] = useToggle();
const [query] = useQuery({
query: ProjectLayoutQuery,
requestPolicy: 'cache-first',
- variables: {
- organizationSlug: props.organizationSlug,
- projectSlug: props.projectSlug,
- },
+ variables: params,
});
const me = query.data?.me;
@@ -93,8 +94,8 @@ export function ProjectLayout({
@@ -112,69 +113,54 @@ export function ProjectLayout({
) : (
<>
-
-
- {currentOrganization && currentProject ? (
-
-
-
-
- Targets
-
-
- {currentProject.viewerCanModifyAlerts && (
-
-
- Alerts
-
-
- )}
-
-
- Settings
-
-
-
-
- ) : (
-
- )}
- {currentProject?.viewerCanCreateTarget ? (
-
- ) : null}
-
-
-
+
+
+
+ >
+ ) : null
+ }
+ />
diff --git a/packages/web/app/src/components/layouts/target.tsx b/packages/web/app/src/components/layouts/target.tsx
index c8b881c92..52a4ab57c 100644
--- a/packages/web/app/src/components/layouts/target.tsx
+++ b/packages/web/app/src/components/layouts/target.tsx
@@ -29,7 +29,6 @@ import { useToggle } from '@/lib/hooks';
import { useResetState } from '@/lib/hooks/use-reset-state';
import { useLastVisitedOrganizationWriter } from '@/lib/last-visited-org';
import { cn } from '@/lib/utils';
-import { Link } from '@tanstack/react-router';
import { ResourceNotFoundComponent } from '../resource-not-found';
import { Label } from '../ui/label';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs';
@@ -123,7 +122,9 @@ export const TargetLayout = ({
children,
page,
className,
- ...props
+ organizationSlug,
+ projectSlug,
+ targetSlug,
}: {
page: Page;
organizationSlug: string;
@@ -132,15 +133,17 @@ export const TargetLayout = ({
className?: string;
children: ReactNode;
}): ReactElement | null => {
+ const params = {
+ organizationSlug,
+ projectSlug,
+ targetSlug,
+ };
+
const [isModalOpen, toggleModalOpen] = useToggle();
const [query] = useQuery({
query: TargetLayoutQuery,
requestPolicy: 'cache-first',
- variables: {
- organizationSlug: props.organizationSlug,
- projectSlug: props.projectSlug,
- targetSlug: props.targetSlug,
- },
+ variables: params,
});
const me = query.data?.me;
@@ -155,18 +158,18 @@ export const TargetLayout = ({
return (
@@ -184,153 +187,87 @@ export const TargetLayout = ({
) : (
<>
-
-
- {currentOrganization && currentProject && currentTarget ? (
-
-
-
-
- Schema
-
-
-
-
- Checks
-
-
-
-
- Explorer
-
-
-
-
- History
-
-
-
-
- Insights
-
-
- {currentTarget.viewerCanAccessTraces && (
-
-
- Traces
-
-
- )}
- {currentTarget.viewerCanViewAppDeployments && (
-
-
- Apps
-
-
- )}
- {currentTarget.viewerCanViewLaboratory && (
-
-
- Laboratory
-
-
- )}
- {currentTarget.viewerCanViewSchemaProposals && (
-
-
- Proposals
-
-
- )}
- {currentTarget.viewerCanAccessSettings && (
-
-
- Settings
-
-
- )}
-
-
- ) : (
-
- )}
- {currentTarget && isCDNEnabled && (
+
-
+ ) : null
+ }
+ />
{children}
>
)}
diff --git a/packages/web/app/src/components/navigation/secondary-nav-link.tsx b/packages/web/app/src/components/navigation/secondary-nav-link.tsx
new file mode 100644
index 000000000..50dcd98ed
--- /dev/null
+++ b/packages/web/app/src/components/navigation/secondary-nav-link.tsx
@@ -0,0 +1,26 @@
+import React, { ComponentProps } from 'react';
+import { createLink } from '@tanstack/react-router';
+import { TabsTrigger } from '../ui/tabs';
+
+const SecondaryNavLinkComponent = React.forwardRef<
+ HTMLAnchorElement,
+ React.AnchorHTMLAttributes
& {
+ value: string;
+ label: string;
+ visible?: boolean;
+ }
+>(({ value, label, visible = true, ...anchorProps }, ref) => {
+ if (!visible) return null;
+ return (
+
+
+ {label}
+
+
+ );
+});
+
+SecondaryNavLinkComponent.displayName = 'SecondaryNavLinkComponent';
+
+export const SecondaryNavLink = createLink(SecondaryNavLinkComponent);
+export type SecondaryNavLinkProps = ComponentProps;
diff --git a/packages/web/app/src/components/navigation/secondary-navigation.tsx b/packages/web/app/src/components/navigation/secondary-navigation.tsx
index 6f6d23797..dbef36e9e 100644
--- a/packages/web/app/src/components/navigation/secondary-navigation.tsx
+++ b/packages/web/app/src/components/navigation/secondary-navigation.tsx
@@ -1,11 +1,40 @@
-type SecondaryNavigationProps = {
- children?: React.ReactNode;
-};
+import { ReactNode } from 'react';
+import { Tabs, TabsList } from '../ui/tabs';
+import { SecondaryNavLink, type SecondaryNavLinkProps } from './secondary-nav-link';
-export function SecondaryNavigation({ children }: SecondaryNavigationProps) {
+export function SecondaryNavigation({
+ page,
+ loading,
+ actions,
+ className,
+ links,
+}: {
+ page?: string;
+ loading?: boolean;
+ actions?: ReactNode;
+ className?: string;
+ links: SecondaryNavLinkProps[];
+}) {
return (
- {children}
+
+ {loading ? (
+
+ ) : (
+
+
+ {links.map(link => (
+
+ ))}
+
+
+ )}
+ {actions}
+
);
}