diff --git a/frontend/app/[team]/layout.tsx b/frontend/app/[team]/layout.tsx index c0c4a0e63..7718eef7b 100644 --- a/frontend/app/[team]/layout.tsx +++ b/frontend/app/[team]/layout.tsx @@ -52,7 +52,7 @@ export default function RootLayout({ )} > {activeOrganisation && } - {showNav && } + {showNav && } {showNav && }
diff --git a/frontend/app/webauth/[requestCode]/page.tsx b/frontend/app/webauth/[requestCode]/page.tsx index eb0cc1312..9013555ed 100644 --- a/frontend/app/webauth/[requestCode]/page.tsx +++ b/frontend/app/webauth/[requestCode]/page.tsx @@ -2,7 +2,6 @@ import { OrganisationType } from '@/apollo/graphql' import { Button } from '@/components/common/Button' -import { HeroPattern } from '@/components/common/HeroPattern' import { Input } from '@/components/common/Input' import Spinner from '@/components/common/Spinner' import OnboardingNavbar from '@/components/layout/OnboardingNavbar' @@ -31,7 +30,6 @@ import { useSession } from 'next-auth/react' import { useRouter } from 'next/navigation' import { useContext, useEffect, useState } from 'react' import { FaChevronRight, FaExclamationTriangle, FaCheckCircle, FaShieldAlt } from 'react-icons/fa' -import { MdContentCopy } from 'react-icons/md' import { SiGithub, SiGnometerminal, SiSlack } from 'react-icons/si' import { toast } from 'react-toastify' @@ -325,7 +323,8 @@ export default function WebAuth({ params }: { params: { requestCode: string } }) CLI Authentication failed

- CLI authentication could not be completed from this page. Please follow these steps to retry the authentication: + CLI authentication could not be completed from this page. Please follow these steps to + retry the authentication:

@@ -336,7 +335,8 @@ export default function WebAuth({ params }: { params: { requestCode: string } }) 1

- Exit out of the CLI by pressing Ctrl+C + Exit out of the CLI by pressing{' '} + Ctrl+C

@@ -346,7 +346,10 @@ export default function WebAuth({ params }: { params: { requestCode: string } }) 2 -

Retry authentication manually via the token mode:

+

+ Retry authentication manually via the{' '} + token mode: +

@@ -356,7 +359,13 @@ export default function WebAuth({ params }: { params: { requestCode: string } }) Choose your Phase instance type as: ☁️ Phase Cloud
  • - Enter your email address: handleCopy(session?.user?.email || '')}>{session?.user?.email} + Enter your email address:{' '} + handleCopy(session?.user?.email || '')} + > + {session?.user?.email} +
  • ) : ( @@ -365,10 +374,22 @@ export default function WebAuth({ params }: { params: { requestCode: string } }) Choose your Phase instance type as: 🛠️ Self Hosted
  • - Enter the host: handleCopy(getHostname() || '')}>{getHostname()} + Enter the host:{' '} + handleCopy(getHostname() || '')} + > + {getHostname()} +
  • - Enter your email address: handleCopy(session?.user?.email || '')}>{session?.user?.email} + Enter your email address:{' '} + handleCopy(session?.user?.email || '')} + > + {session?.user?.email} +
  • )} @@ -384,20 +405,16 @@ export default function WebAuth({ params }: { params: { requestCode: string } })

    - +
    - diff --git a/frontend/components/layout/Navbar.tsx b/frontend/components/layout/Navbar.tsx index 728b0efcf..6350df6ef 100644 --- a/frontend/components/layout/Navbar.tsx +++ b/frontend/components/layout/Navbar.tsx @@ -1,21 +1,31 @@ -import UserMenu from '../UserMenu' +'use client' + import { useLazyQuery, useQuery } from '@apollo/client' +import { useContext, useEffect, useMemo } from 'react' +import Link from 'next/link' +import { startCase } from 'lodash' +import { useSearchParams } from 'next/navigation' + import { GetApps } from '@/graphql/queries/getApps.gql' import { GetAppEnvironments } from '@/graphql/queries/secrets/getAppEnvironments.gql' -import { usePathname } from 'next/navigation' -import { useContext, useEffect } from 'react' import { AppType, EnvironmentType } from '@/apollo/graphql' -import Link from 'next/link' +import { organisationContext } from '@/contexts/organisationContext' +import { userHasPermission } from '@/utils/access/permissions' +import { generateBreadcrumbs, generatePageTitle, NavigationContext } from '@/utils/navigation' + import { Button } from '../common/Button' import { StatusIndicator } from '../common/StatusIndicator' -import { organisationContext } from '@/contexts/organisationContext' -import clsx from 'clsx' import { LogoMark } from '../common/LogoMark' import CommandPalette from '../common/CommandPalette' -import { userHasPermission } from '@/utils/access/permissions' +import UserMenu from '../UserMenu' + +import { useParsedRoute } from '@/utils/route' -export const NavBar = (props: { team: string }) => { +export const NavBar = () => { const { activeOrganisation: organisation } = useContext(organisationContext) + const { team, context, appId, envId, page, subPage } = useParsedRoute() + const searchParams = useSearchParams() + const tab = searchParams?.get('tab') const userCanReadApps = userHasPermission(organisation?.role?.permissions, 'Apps', 'read') @@ -25,87 +35,74 @@ export const NavBar = (props: { team: string }) => { }, skip: !organisation || !userCanReadApps, }) - const [getAppEnvs, { data: appEnvsData }] = useLazyQuery(GetAppEnvironments) - - const orgContext = usePathname()?.split('/')[2] - const apps = appsData?.apps as AppType[] + const [getAppEnvs, { data: appEnvsData }] = useLazyQuery(GetAppEnvironments) + const apps = (appsData?.apps as AppType[]) || [] const envs: EnvironmentType[] = appEnvsData?.appEnvironments ?? [] - const appId = usePathname()?.split('/')[3] - - const envId = usePathname()?.split('/')[5] - - const appPage = usePathname()?.split('/')[4] - - const activeApp = orgContext === 'apps' ? apps?.find((app) => app.id === appId) : undefined + const activeApp = context === 'apps' ? apps.find((app) => app.id === appId) : undefined + const activeEnv = activeApp ? envs.find((env) => env.id === envId) : undefined - useEffect(() => { - if (activeApp) getAppEnvs({ variables: { appId: activeApp.id } }) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [activeApp]) + // Create navigation context + const navigationContext: NavigationContext = useMemo( + () => ({ + team, + context, + appId, + envId, + page, + subPage, + activeApp, + activeEnv, + }), + [team, context, appId, envId, page, subPage, activeApp, activeEnv] + ) - const activeEnv = activeApp ? envs.find((env) => env.id === envId) : undefined + const breadcrumbs = generateBreadcrumbs(navigationContext) - return ( -
    + const BreadCrumbs = () => { + return (
    - / - - - {props.team} - - {activeApp && /} - - {activeApp && - (appPage ? ( - - {activeApp.name} - - ) : ( - - {activeApp.name} - - ))} - - {activeApp && appPage && /} - - {activeApp && appPage && ( - ( +
    + / + {crumb.isLink && crumb.href ? ( + + {startCase(crumb.label)} + + ) : ( + + {startCase(crumb.label)} + )} - > - {appPage} - - )} +
    + ))} +
    + ) + } - {activeEnv && /} + useEffect(() => { + if (activeApp) { + getAppEnvs({ variables: { appId: activeApp.id } }) + } + }, [activeApp, getAppEnvs]) - {activeEnv && ( - - {activeEnv.name} - - )} + // Update page title using the utility + useEffect(() => { + document.title = generatePageTitle(navigationContext) + }, [navigationContext, tab]) - {!activeApp && orgContext && /} - {!activeApp && {orgContext}} -
    + return ( +
    +
    @@ -113,7 +110,6 @@ export const NavBar = (props: { team: string }) => {
    - diff --git a/frontend/components/layout/OnboardingNavbar.tsx b/frontend/components/layout/OnboardingNavbar.tsx index 9393f59e7..e171cd348 100644 --- a/frontend/components/layout/OnboardingNavbar.tsx +++ b/frontend/components/layout/OnboardingNavbar.tsx @@ -1,8 +1,33 @@ +'use client' + import Link from 'next/link' +import { useEffect } from 'react' +import { usePathname } from 'next/navigation' import UserMenu from '../UserMenu' import { LogoWordMark } from '../common/LogoWordMark' +import { generatePageTitle } from '@/utils/navigation' const OnboardingNavbar = () => { + const pathname = usePathname() + + useEffect(() => { + // Parse the current route to determine page title + const pathSegments = (pathname ?? '').split('/').filter(Boolean) + let route = null + let routeParam = null + + if (pathSegments.length > 0) { + route = pathSegments[0] + if (pathSegments.length > 1) { + routeParam = pathSegments[1] + } + } + + // Set page title based on route + const title = generatePageTitle({ route, routeParam }) + document.title = title + }, [pathname]) + return (