Files
supabase/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.tsx
T
9fd85e76fb feature: warehouse (#23714)
* add event collection ui, queries, routing

* fix alignment issue

* renaming

* wip

* feat: initial data hooks and integration with feature flagging

* update with naming, correct mocks, etc

* feat: add source crud integration

* mock data, fix form, add motion to drawer

* feat: optimize warehouse fetch behaviour

* feat: add in valid query

* chore: ermove collection mocks

* feat: optimize tenant fetching

* cleanup + update openapi client + fix fetch calls

* cleanup imporst

* on delete success, redirect to main logs page

* fix dropdown event propagating to link and opening page

* Refactor code to use useParams and useRouter in CreateWarehouseCollection.tsx, WarehouseCollectionDetail.tsx, and LogsLayout.tsx

* clean warehouse query

* warehouse access tokens create, list

* add warehouse access token revokal

* fix access tokens table, fix access tokens revoke feature

* format date in access tokens table

* fix some state issues and warnings in access tokens forms

* warehouse/feat: introduction when creating event collections (#23244)

feat: add introduction for event collections

* warehouse/feat: rename headers and nav items (#23243)

* feat: rename headers

* feat: use feature flag for navigation bar

* move header name flag to component

* fix typeerror in create collection query

---------

Co-authored-by: Jordi Enric <jordi.err@gmail.com>

* ui fixes, rm deprecated icons

* render table with events

* fix typeerror results arr

* add pagination

* some ui fixes

* fix loading states, format dates, test connection dialog

* patch type errors

* Fix access token selection in TestCollectionDialog and update column name in WarehouseAccessTokens

* rm unused querytype

* rm unused querytype in logtable

* patch type errors in warhouse collection mutations

* rm unused component

* mr unused import

* show connection dialog in staging

* fix Run btn now showing up

* fix undef argument

* show pagination only when there are results

* fix issues in connection dialog, improve ux

* Update apps/studio/components/interfaces/DataWarehouse/CreateWarehouseAccessToken.tsx

Co-authored-by: Ziinc <Ziinc@users.noreply.github.com>

* Update apps/studio/components/interfaces/DataWarehouse/CreateWarehouseAccessToken.tsx

Co-authored-by: Ziinc <Ziinc@users.noreply.github.com>

* fix infinite render loop, adapt component to render log metadata on log selection

* rm unused import, prevent ui from breaking when some data may be missing from an event

* update WAT create mutation to use template

* follow mutation template in delete access token mutation

* refactor naming, improve code in some areas

* rm unused mocks for msw

* skip tests - this will get fixed on the vitest PR

* Update apps/studio/components/interfaces/Settings/Logs/LogsQueryPanel.tsx

Co-authored-by: Joshen Lim <joshenlimek@gmail.com>

* fix loading compo

* rm unused import

* use correct alert title

* rm unnecessary div

* fix layout in projectpaused alert

* fix type err, refactor to new method

* rm observer

* rm observer

* refactor warehouse collections update

* use correct get method, comment out so it doesnt err

* use correct method

* fix formatting issues

* ref imports

* ref imports

* clean up imports

* clean imports

* clean imports

* rename props

* destructure props

* cleanup imports

* Update apps/studio/components/interfaces/DataWarehouse/CreateWarehouseCollection.tsx

Co-authored-by: Ziinc <Ziinc@users.noreply.github.com>

* ref scale class

* cleanup divs

* Update apps/studio/components/interfaces/DataWarehouse/TestCollectionDialog.tsx

Co-authored-by: Ziinc <Ziinc@users.noreply.github.com>

* refactor to rhf

* reuse types, cleanup, refactor to react query hooks instead of el events

* refactor table component to handle empty state

* Update TestCollectionDialog to use unique keys for SelectItem components

* Update TestCollectionDialog to use unique keys for SelectItem components

* Update TestCollectionDialog to use unique keys for SelectItem components

* fix empty state ui

* use destructured queries

* rename query types

* Update apps/studio/components/interfaces/DataWarehouse/WarehouseAccessTokens.tsx

Co-authored-by: Ziinc <Ziinc@users.noreply.github.com>

* chore: Refactor WarehouseCollectionDetail component

* rm unused imports

* rename loadolder

* rm typcast

* rhf in create token

* improve err handling

* refactor to rhf, refactor queries, use cn utils, cleanup

* fix loading logselection

* add correct type to warehouse access tokens query

* use correct types in err & clean unused types

* toggle token in curl exampel

* use product empty state component in warehouse

* add client side validation of collection name, temp fix

* add New badge

* update testcolldialog

* cleanup unnecessary div and rm double border

* code style fixes

* handle err

* code style fix

* add correct type

* add correct err type

* fix type error

* fix type err

* fix missing type

---------

Co-authored-by: TzeYiing <ty@tzeyiing.com>
Co-authored-by: Ziinc <Ziinc@users.noreply.github.com>
Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
2024-05-29 10:32:46 +02:00

374 lines
13 KiB
TypeScript

import { useParams } from 'common'
import { useFlag, useIsFeatureEnabled } from 'hooks'
import { Home, User } from 'icons'
import { isUndefined } from 'lodash'
import { Command, FileText, FlaskConical, Search, Settings } from 'lucide-react'
import { useTheme } from 'next-themes'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useState } from 'react'
import {
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
Separator,
Theme,
cn,
themes,
} from 'ui'
import { useCommandMenu } from 'ui-patterns/Cmdk'
import { useIsAPIDocsSidePanelEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { useSignOut } from 'lib/auth'
import { IS_PLATFORM } from 'lib/constants'
import { detectOS } from 'lib/helpers'
import { useProfile } from 'lib/profile'
import { useAppStateSnapshot } from 'state/app-state'
import { useProjectContext } from '../ProjectContext'
import {
generateOtherRoutes,
generateProductRoutes,
generateSettingsRoutes,
generateToolRoutes,
} from './NavigationBar.utils'
import { NavigationIconButton } from './NavigationIconButton'
import NavigationIconLink from './NavigationIconLink'
export const ICON_SIZE = 20
export const ICON_STROKE_WIDTH = 1.5
const NavigationBar = () => {
const os = detectOS()
const router = useRouter()
const { profile } = useProfile()
const { project } = useProjectContext()
const { theme, setTheme } = useTheme()
const { ref: projectRef } = useParams()
const { setIsOpen } = useCommandMenu()
const snap = useAppStateSnapshot()
const signOut = useSignOut()
const navLayoutV2 = useFlag('navigationLayoutV2')
const isNewAPIDocsEnabled = useIsAPIDocsSidePanelEnabled()
const [userDropdownOpen, setUserDropdownOpenState] = useState(false)
const {
projectAuthAll: authEnabled,
projectEdgeFunctionAll: edgeFunctionsEnabled,
projectStorageAll: storageEnabled,
realtimeAll: realtimeEnabled,
} = useIsFeatureEnabled([
'project_auth:all',
'project_edge_function:all',
'project_storage:all',
'realtime:all',
])
const activeRoute = router.pathname.split('/')[3]
const toolRoutes = generateToolRoutes(projectRef, project)
const productRoutes = generateProductRoutes(projectRef, project, {
auth: authEnabled,
edgeFunctions: edgeFunctionsEnabled,
storage: storageEnabled,
realtime: realtimeEnabled,
})
const showWarehouse = useFlag('warehouse')
const otherRoutes = generateOtherRoutes(projectRef, project)
const settingsRoutes = generateSettingsRoutes(projectRef, project)
const onCloseNavigationIconLink = (event: any) => {
snap.setNavigationPanelOpen(
false,
event.target.id === 'icon-link' || ['svg', 'path'].includes(event.target.localName)
)
}
return (
<div className="w-14 h-full flex flex-col">
<nav
data-state={snap.navigationPanelOpen ? 'expanded' : 'collapsed'}
className={cn(
'group py-2 z-10 h-full w-14 data-[state=expanded]:w-[13rem]',
'border-r bg-studio border-default data-[state=expanded]:shadow-xl',
'transition-width duration-200',
'hide-scrollbar flex flex-col justify-between overflow-y-auto'
)}
onMouseEnter={() => snap.setNavigationPanelOpen(true)}
onMouseLeave={() => {
if (!userDropdownOpen) snap.setNavigationPanelOpen(false)
}}
>
<ul className="flex flex-col gap-y-1 justify-start px-2">
{(!navLayoutV2 || !IS_PLATFORM) && (
<Link
href={IS_PLATFORM ? '/projects' : `/project/${projectRef}`}
className="mx-2 flex items-center h-[40px]"
onClick={onCloseNavigationIconLink}
>
<img
alt="Supabase"
src={`${router.basePath}/img/supabase-logo.svg`}
className="absolute h-[40px] w-6 cursor-pointer rounded"
/>
</Link>
)}
<NavigationIconLink
isActive={isUndefined(activeRoute) && !isUndefined(router.query.ref)}
route={{
key: 'HOME',
label: 'Home',
icon: <Home size={ICON_SIZE} strokeWidth={ICON_STROKE_WIDTH} />,
link: `/project/${projectRef}`,
}}
onClick={onCloseNavigationIconLink}
/>
<Separator className="my-1 bg-border-muted" />
{toolRoutes.map((route) => (
<NavigationIconLink
key={route.key}
route={route}
isActive={activeRoute === route.key}
onClick={onCloseNavigationIconLink}
/>
))}
<Separator className="my-1 bg-border-muted" />
{productRoutes.map((route) => (
<NavigationIconLink
key={route.key}
route={route}
isActive={activeRoute === route.key}
onClick={onCloseNavigationIconLink}
/>
))}
<Separator className="my-1 bg-border-muted" />
{otherRoutes.map((route) => {
if (route.key === 'api' && isNewAPIDocsEnabled) {
return (
<NavigationIconButton
key={route.key}
onClick={() => {
snap.setShowProjectApiDocs(true)
snap.setNavigationPanelOpen(false)
}}
icon={<FileText size={ICON_SIZE} strokeWidth={ICON_STROKE_WIDTH} />}
>
Project API
</NavigationIconButton>
)
} else if (route.key === 'logs') {
// TODO: Undo this when warehouse flag is removed
const label = showWarehouse ? 'Logs & Analytics' : route.label
const newRoute = { ...route, label }
return (
<NavigationIconLink
key={newRoute.key}
route={newRoute}
isActive={activeRoute === newRoute.key}
onClick={onCloseNavigationIconLink}
/>
)
} else {
return (
<NavigationIconLink
key={route.key}
route={route}
isActive={activeRoute === route.key}
onClick={onCloseNavigationIconLink}
/>
)
}
})}
</ul>
<ul className="flex flex-col px-2 gap-y-1">
{settingsRoutes.map((route) => (
<NavigationIconLink
key={route.key}
route={route}
isActive={activeRoute === route.key}
onClick={onCloseNavigationIconLink}
/>
))}
{IS_PLATFORM && (
<NavigationIconButton
size="tiny"
onClick={() => {
setIsOpen(true)
snap.setNavigationPanelOpen(false)
}}
type="text"
icon={<Search size={ICON_SIZE} strokeWidth={2} />}
rightText={
<div
className={cn(
'flex items-center gap-1',
'h-6 py-1.5 px-2 leading-none',
'bg-surface-100 text-foreground-lighter',
'border border-default rounded-md',
'shadow-xs shadow-background-surface-100'
)}
>
{os === 'macos' || true ? ( // todo: issue with `os` and hydration fail
<Command size={11.5} strokeWidth={1.5} />
) : (
<p className="text-xs">CTRL</p>
)}
<p className="text-xs">K</p>
</div>
}
>
Search
</NavigationIconButton>
)}
<DropdownMenu
open={userDropdownOpen}
onOpenChange={(open: boolean) => {
setUserDropdownOpenState(open)
if (open === false) snap.setNavigationPanelOpen(false)
}}
>
<DropdownMenuTrigger asChild>
<Button
type="text"
size="tiny"
className={cn(
'mt-3 h-10 [&>span]:relative [&>span]:flex [&>span]:w-full [&>span]:h-full p-0'
)}
block
>
<div className="relative w-full h-full flex items-center justify-center">
<figure className="absolute left-1.5 min-h-6 min-w-6 bg-foreground rounded-full flex items-center justify-center">
<User
size={ICON_SIZE - 2}
strokeWidth={ICON_STROKE_WIDTH}
className="text-background"
/>
</figure>
<span
className={cn(
'w-[8rem] flex flex-col items-start text-sm truncate',
'absolute left-7 group-data-[state=expanded]:left-10',
'group-data-[state=collapsed]:opacity-0 group-data-[state=expanded]:opacity-100',
'transition-all'
)}
>
{profile && IS_PLATFORM && (
<>
<span
title={profile.username}
className="w-full text-left text-foreground truncate"
>
{profile.username}
</span>
{profile.primary_email !== profile.username && (
<span
title={profile.primary_email}
className="w-full text-left text-foreground-light text-xs truncate"
>
{profile.primary_email}
</span>
)}
</>
)}
</span>
</div>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent side="top" align="start">
{IS_PLATFORM && (
<>
<div className="px-2 py-1 flex flex-col gap-0 text-sm">
{profile && (
<>
<span
title={profile.username}
className="w-full text-left text-foreground truncate"
>
{profile.username}
</span>
{profile.primary_email !== profile.username && (
<span
title={profile.primary_email}
className="w-full text-left text-foreground-light text-xs truncate"
>
{profile.primary_email}
</span>
)}
</>
)}
</div>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem className="flex gap-2" asChild>
<Link href="/account/me">
<Settings size={14} strokeWidth={1.5} className="text-foreground-muted" />
Account preferences
</Link>
</DropdownMenuItem>
<DropdownMenuItem
className="flex gap-2"
onClick={() => snap.setShowFeaturePreviewModal(true)}
onSelect={() => snap.setShowFeaturePreviewModal(true)}
>
<FlaskConical size={14} strokeWidth={1.5} className="text-foreground-muted" />
Feature previews
</DropdownMenuItem>
<DropdownMenuSeparator />
</DropdownMenuGroup>
</>
)}
<DropdownMenuGroup>
<DropdownMenuLabel>Theme</DropdownMenuLabel>
<DropdownMenuRadioGroup
value={theme}
onValueChange={(value) => {
setTheme(value)
}}
>
{themes
.filter(
(x) => x.value === 'light' || x.value === 'dark' || x.value === 'system'
)
.map((theme: Theme) => (
<DropdownMenuRadioItem key={theme.value} value={theme.value}>
{theme.name}
</DropdownMenuRadioItem>
))}
</DropdownMenuRadioGroup>
</DropdownMenuGroup>
{IS_PLATFORM && (
<>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem
onSelect={async () => {
await signOut()
await router.push('/sign-in')
}}
>
Log out
</DropdownMenuItem>
</DropdownMenuGroup>
</>
)}
</DropdownMenuContent>
</DropdownMenu>
</ul>
</nav>
</div>
)
}
export default NavigationBar