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 + } + />
{children}
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} +
); }