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,5 +1,5 @@
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import { useNavigate } from '@tanstack/react-router'
import { Link, useNavigate } from '@tanstack/react-router'
import clsx from 'clsx'
import { type ReactNode, useRef, useState } from 'react'
import { Icon, InputSearch, Popover, dropdownMenuItemVariants } from '@qovery/shared/ui'
Expand Down Expand Up @@ -70,22 +70,29 @@ export function BreadcrumbItem({ item, items }: BreadcrumbItemProps) {
// DropdownMenu.Root for entries.
// So both open state should be sync
return (
<DropdownMenu.Root open={open} onOpenChange={setOpen}>
<Popover.Root open={open} onOpenChange={setOpen}>
<Popover.Trigger>
<div className="group flex items-center justify-between gap-1">
<button
type="button"
className="flex items-center gap-1.5 rounded text-sm font-medium text-neutral-subtle transition-colors focus:outline-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-brand-11 group-hover:text-neutral"
>
{item.prefix}
{item.label}
</button>
<div className="flex items-center justify-between gap-1">
<Link
to={item.path}
className={twMerge(
clsx(
'flex items-center gap-1.5 rounded text-sm font-medium text-neutral-subtle transition-colors hover:text-neutral focus:outline-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-brand-11',
{
'text-neutral': open,
}
)
)}
>
{item.prefix}
{item.label}
</Link>
<DropdownMenu.Root open={open} onOpenChange={setOpen}>
<Popover.Root open={open} onOpenChange={setOpen}>
<Popover.Trigger>
<button
type="button"
className={twMerge(
clsx(
'relative top-[1px] flex h-6 w-6 items-center justify-center rounded text-neutral-disabled transition-colors focus:outline-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-brand-11 group-hover:text-neutral',
'relative top-[1px] flex h-6 w-6 items-center justify-center rounded text-neutral-disabled transition-colors hover:text-neutral focus:outline-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-brand-11',
{
'text-neutral': open,
}
Expand All @@ -101,58 +108,58 @@ export function BreadcrumbItem({ item, items }: BreadcrumbItemProps) {
></path>
</svg>
</button>
</div>
</Popover.Trigger>
<DropdownMenu.Content asChild>
<Popover.Content className="-ml-2.5 flex w-[340px] flex-col gap-3 p-3 pb-0">
{/*
</Popover.Trigger>
<DropdownMenu.Content asChild>
<Popover.Content className="-ml-2.5 flex w-[340px] flex-col gap-3 p-3 pb-0">
{/*
Transfer focus from input to list when using arrow keys
This enables keyboard navigation while keeping the input functional
https://github.com/radix-ui/primitives/issues/2193#issuecomment-1790564604
*/}
<div className="flex flex-col gap-3" onKeyDown={handleInputKeyDown} ref={inputContainerRef}>
<InputSearch placeholder="Search..." onChange={(value) => setSearchQuery(value)} autofocus />
</div>
<div className="max-h-64 overflow-y-auto" ref={firstItemRef} onKeyDown={handleListKeyDown}>
{filteredItems.length > 0 ? (
<div className="flex flex-col gap-1">
{filteredItems.map((listItem) => (
<DropdownMenu.Item
key={listItem.id}
onSelect={() => {
setOpen(false)
navigate({ to: listItem.path })
}}
className={twMerge(
dropdownMenuItemVariants({ color: 'brand' }),
'justify-between truncate last:mb-3'
)}
>
<div className="flex min-w-0 items-center gap-3">
<Icon
iconName="check"
className={twMerge(
clsx('text-positive opacity-0', {
'opacity-100': item.id === listItem.id,
})
)}
/>
<span className="truncate">{listItem.label}</span>
</div>
</DropdownMenu.Item>
))}
</div>
) : (
<div className="px-3 py-6 text-center">
<Icon iconName="wave-pulse" className="text-neutral-subtle" />
<p className="mt-1 text-xs font-medium text-neutral-subtle">No result for this search</p>
</div>
)}
</div>
</Popover.Content>
</DropdownMenu.Content>
</Popover.Root>
</DropdownMenu.Root>
<div className="flex flex-col gap-3" onKeyDown={handleInputKeyDown} ref={inputContainerRef}>
<InputSearch placeholder="Search..." onChange={(value) => setSearchQuery(value)} autofocus />
</div>
<div className="max-h-64 overflow-y-auto" ref={firstItemRef} onKeyDown={handleListKeyDown}>
{filteredItems.length > 0 ? (
<div className="flex flex-col gap-1">
{filteredItems.map((listItem) => (
<DropdownMenu.Item
key={listItem.id}
onSelect={() => {
setOpen(false)
navigate({ to: listItem.path })
}}
className={twMerge(
dropdownMenuItemVariants({ color: 'brand' }),
'justify-between truncate last:mb-3'
)}
>
<div className="flex min-w-0 items-center gap-3">
<Icon
iconName="check"
className={twMerge(
clsx('text-positive opacity-0', {
'opacity-100': item.id === listItem.id,
})
)}
/>
<span className="truncate">{listItem.label}</span>
</div>
</DropdownMenu.Item>
))}
</div>
) : (
<div className="px-3 py-6 text-center">
<Icon iconName="wave-pulse" className="text-neutral-subtle" />
<p className="mt-1 text-xs font-medium text-neutral-subtle">No result for this search</p>
</div>
)}
</div>
</Popover.Content>
</DropdownMenu.Content>
</Popover.Root>
</DropdownMenu.Root>
</div>
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
import { useParams, useRouter } from '@tanstack/react-router'
import { useMemo } from 'react'
import { useOrganizations } from '@qovery/domains/organizations/feature'
import { ClusterAvatar, useClusters } from '@qovery/domains/clusters/feature'
import { useOrganization, useOrganizations } from '@qovery/domains/organizations/feature'
import { Avatar } from '@qovery/shared/ui'
import { Separator } from '../header'
import { BreadcrumbItem, type BreadcrumbItemData } from './breadcrumb-item'

export function Breadcrumbs() {
const { buildLocation } = useRouter()
const { organizationId } = useParams({ strict: false })
const { organizationId, clusterId } = useParams({ strict: false })

const { data: organizations = [] } = useOrganizations({
enabled: true,
})
const { data: organization } = useOrganization({ organizationId, enabled: !!organizationId })
const { data: clusters = [] } = useClusters({ organizationId })

const orgItems: BreadcrumbItemData[] = organizations.map((organization) => ({
// Necessary to keep the organization from client by Qovery team
const allOrganizations =
organizations.find((org) => org.id !== organizationId) && organization
? [...organizations, organization]
: organizations

const orgItems: BreadcrumbItemData[] = allOrganizations.map((organization) => ({
id: organization.id,
label: organization.name,
path: buildLocation({ to: '/organization/$organizationId', params: { organizationId: organization.id } }).href,
Expand All @@ -25,12 +34,24 @@ export function Breadcrumbs() {
[organizationId, orgItems]
)

if (!currentOrg) {
return null
}
const clusterItems: BreadcrumbItemData[] = clusters.map((cluster) => ({
id: cluster.id,
label: cluster.name,
path: buildLocation({
to: '/organization/$organizationId/cluster/$clusterId',
params: { organizationId, clusterId: cluster.id },
}).href,
}))

const currentCluster = useMemo(
() => clusterItems.find((cluster) => cluster.id === clusterId),
[clusterId, clusterItems]
)

const breadcrumbData: Array<{ item: BreadcrumbItemData; items: BreadcrumbItemData[] }> = []

const breadcrumbData = [
{
if (currentOrg) {
breadcrumbData.push({
item: {
...currentOrg,
prefix: (
Expand All @@ -44,8 +65,18 @@ export function Breadcrumbs() {
),
},
items: orgItems,
},
]
})
}

if (currentCluster) {
breadcrumbData.push({
item: {
...currentCluster,
prefix: <ClusterAvatar cluster={clusters.find((cluster) => cluster.id === clusterId)} size="sm" />,
},
items: clusterItems,
})
}

return (
<div className="flex items-center gap-2">
Expand Down
5 changes: 5 additions & 0 deletions apps/console-v5/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ import {
QueryClientProvider,
} from '@tanstack/react-query'
import { RouterProvider, createRouter } from '@tanstack/react-router'
import axios from 'axios'
import { StrictMode } from 'react'
import * as ReactDOM from 'react-dom/client'
import { FlatProviders, makeProvider } from 'react-flat-providers'
import { LoaderSpinner, ModalProvider, ToastEnum, toast, toastError } from '@qovery/shared/ui'
import { QOVERY_API } from '@qovery/shared/util-node-env'
import { useAuthInterceptor } from '@qovery/shared/utils'
// TODO: Improve this import to use the shared/ui package
// eslint-disable-next-line @nx/enforce-module-boundaries
import '../../../libs/shared/ui/src/lib/styles/main.scss'
Expand Down Expand Up @@ -130,6 +133,8 @@ function InnerApp() {
// Create a new router instance
const router = createRouter({ routeTree, context: { auth, queryClient } })

useAuthInterceptor(axios, QOVERY_API)

if (auth.isLoading) {
return (
<div className="flex min-h-screen items-center justify-center">
Expand Down
Loading