Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ export default function SandboxesHeaderInjectablePage({
}: SandboxesHeaderInjectablePageProps) {
return (
<LiveSandboxCounterServer
className="top-1/2 -translate-y-1/2 absolute right-10 max-md:hidden"
params={params}
className="top-5 absolute right-17 max-md:hidden"
/>
)
}
23 changes: 23 additions & 0 deletions src/app/dashboard/[teamIdOrSlug]/members/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { MemberCard } from '@/features/dashboard/members/member-card'
import Frame from '@/ui/frame'

interface MembersPageProps {
params: Promise<{
teamIdOrSlug: string
}>
}

export default async function MembersPage({ params }: MembersPageProps) {
return (
<Frame
classNames={{
wrapper: 'w-full max-md:p-0',
frame: 'max-md:border-none max-md:p-0',
}}
>
<section className="col-span-full">
<MemberCard params={params} className="" />
</section>
</Frame>
)
}
4 changes: 2 additions & 2 deletions src/app/dashboard/[teamIdOrSlug]/sandboxes/@list/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ async function PageContent({ teamIdOrSlug }: PageContentProps) {
'Unknown error',
} satisfies Error
}
description={'Could not load sandboxes'}
description="Could not load sandboxes"
/>
)
}
Expand All @@ -73,7 +73,7 @@ async function PageContent({ teamIdOrSlug }: PageContentProps) {
]

return (
<div className="flex flex-1 flex-col md:overflow-hidden">
<div className="flex flex-col h-full relative min-h-0 md:overflow-hidden">
<SandboxesTable
sandboxes={sandboxes}
templates={templates}
Expand Down
6 changes: 6 additions & 0 deletions src/app/dashboard/[teamIdOrSlug]/sandboxes/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// This page is required for Next.js parallel routes to work properly
// The actual content is rendered through the parallel route slots (@monitoring, @list)
// which are passed to the layout and then to the tabs component
export default function SandboxesPage() {
return null
}
99 changes: 26 additions & 73 deletions src/app/dashboard/[teamIdOrSlug]/sandboxes/tabs.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,11 @@
'use client'

import { PROTECTED_URLS } from '@/configs/urls'
import { cn } from '@/lib/utils'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/ui/primitives/tabs'
import { ActivityIcon, LayoutListIcon, LucideIcon } from 'lucide-react'
import { DashboardTab, DashboardTabs } from '@/ui/dashboard-tabs'
import { ListIcon, TrendIcon } from '@/ui/primitives/icons'
import micromatch from 'micromatch'
import Link from 'next/link'
import { useParams, usePathname, useSearchParams } from 'next/navigation'
import { usePathname } from 'next/navigation'
import { ReactNode } from 'react'

type TabValue = 'monitoring' | 'list'

interface TabConfig {
value: TabValue
icon: LucideIcon
label: string
url: (teamIdOrSlug: string) => string
}

const TABS: TabConfig[] = [
{
value: 'monitoring',
icon: ActivityIcon,
label: 'Monitoring',
url: (teamIdOrSlug: string) =>
PROTECTED_URLS.SANDBOXES(teamIdOrSlug, 'monitoring'),
},
{
value: 'list',
icon: LayoutListIcon,
label: 'List',
url: (teamIdOrSlug: string) =>
PROTECTED_URLS.SANDBOXES(teamIdOrSlug, 'list'),
},
]

interface SandboxesTabsProps {
monitoringContent: ReactNode
listContent: ReactNode
Expand All @@ -46,55 +17,37 @@ export default function SandboxesTabs({
listContent,
inspectContent,
}: SandboxesTabsProps) {
const searchParams = useSearchParams()
const urlTab = searchParams.get('tab') || TABS[0]?.value

const pathname = usePathname()
const { teamIdOrSlug } = useParams<{ teamIdOrSlug: string }>()

const activeTab = TABS.find((tab) => tab.value === urlTab)

const isInspectRoute = micromatch.isMatch(pathname, '*/**/sandboxes/**/*')
const isInspectRoute = micromatch.isMatch(
pathname,
'/dashboard/*/sandboxes/**/*'
)

if (isInspectRoute) {
return inspectContent
}

const tabContentMap: Record<TabValue, ReactNode> = {
monitoring: monitoringContent,
list: listContent,
}

return (
<Tabs
value={activeTab?.value}
className="min-h-0 w-full flex-1 pt-3.5 h-full"
<DashboardTabs
type="query"
layoutKey="tabs-indicator-sandboxes"
className="mt-2 md:mt-3"
>
<TabsList className="bg-bg z-30 w-full justify-start pl-6">
{TABS.map((tab) => (
<TabsTrigger
key={tab.value}
layoutkey="tabs-indicator-sandboxes"
value={tab.value}
className="w-fit flex-none"
asChild
>
<Link href={tab.url(teamIdOrSlug)} prefetch>
<tab.icon className="size-3.5" />
{tab.label}
</Link>
</TabsTrigger>
))}
</TabsList>
{TABS.map((tab) => (
<TabsContent
key={tab.value}
value={tab.value}
className={cn('flex flex-1 flex-col overflow-hidden')}
>
{tabContentMap[tab.value]}
</TabsContent>
))}
</Tabs>
<DashboardTab
id="monitoring"
label="Monitoring"
icon={<TrendIcon className="size-4" />}
>
{monitoringContent}
</DashboardTab>
<DashboardTab
id="list"
label="List"
icon={<ListIcon className="size-4" />}
>
{listContent}
</DashboardTab>
</DashboardTabs>
)
}
29 changes: 29 additions & 0 deletions src/app/dashboard/[teamIdOrSlug]/settings/@general/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { InfoCard } from '@/features/dashboard/settings/general/info-card'
import { NameCard } from '@/features/dashboard/settings/general/name-card'
import { ProfilePictureCard } from '@/features/dashboard/settings/general/profile-picture-card'
import Frame from '@/ui/frame'

interface GeneralPageProps {
params: Promise<{
teamIdOrSlug: string
}>
}

export default async function GeneralPage({ params }: GeneralPageProps) {
return (
<Frame
classNames={{
wrapper: 'w-full max-md:p-0',
frame: 'max-md:border-none',
}}
>
<section className="col-span-full flex-col">
<div className="flex gap-2 border-b md:gap-3">
<ProfilePictureCard className="size-32" />
<NameCard />
</div>
<InfoCard className="flex flex-col justify-between" />
</section>
</Frame>
)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import CreateApiKeyDialog from '@/features/dashboard/keys/create-api-key-dialog'
import ApiKeysTable from '@/features/dashboard/keys/table'
import { resolveTeamIdInServerComponent } from '@/lib/utils/server'
import CreateApiKeyDialog from '@/features/dashboard/settings/keys/create-api-key-dialog'
import ApiKeysTable from '@/features/dashboard/settings/keys/table'
import Frame from '@/ui/frame'
import { Button } from '@/ui/primitives/button'
import {
Expand All @@ -11,7 +10,6 @@ import {
CardTitle,
} from '@/ui/primitives/card'
import { Plus } from 'lucide-react'
import { Suspense } from 'react'

interface KeysPageClientProps {
params: Promise<{
Expand All @@ -20,9 +18,6 @@ interface KeysPageClientProps {
}

export default async function KeysPage({ params }: KeysPageClientProps) {
const { teamIdOrSlug } = await params
const teamId = await resolveTeamIdInServerComponent(teamIdOrSlug)

return (
<Frame
classNames={{
Expand All @@ -34,26 +29,24 @@ export default async function KeysPage({ params }: KeysPageClientProps) {
<CardHeader>
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between sm:gap-6">
<div className="flex flex-col gap-2">
<CardTitle>Manage Team Keys</CardTitle>
<CardTitle>Manage API Keys</CardTitle>
<CardDescription className="max-w-[400px]">
Organization keys are used to authenticate API requests from
your organization's applications.
API Keys are used to authenticate API requests from your teams
applications.
</CardDescription>
</div>

<Suspense fallback={null}>
<CreateApiKeyDialog teamId={teamId}>
<Button className="w-full sm:w-auto sm:self-start">
<Plus className="size-4" /> CREATE KEY
</Button>
</CreateApiKeyDialog>
</Suspense>
<CreateApiKeyDialog params={params}>
<Button className="w-full sm:w-auto sm:self-start">
<Plus className="size-4" /> CREATE KEY
</Button>
</CreateApiKeyDialog>
</div>
</CardHeader>

<CardContent>
<div className="w-full overflow-x-auto">
<ApiKeysTable teamId={teamId} className="min-w-[800px]" />
<ApiKeysTable params={params} className="min-w-[800px]" />
</div>
</CardContent>
</Card>
Expand Down
43 changes: 43 additions & 0 deletions src/app/dashboard/[teamIdOrSlug]/settings/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { DashboardTab, DashboardTabs } from '@/ui/dashboard-tabs'
import { KeyIcon, SettingsIcon } from '@/ui/primitives/icons'

interface SettingsLayoutProps {
general: React.ReactNode
keys: React.ReactNode
}

export default async function SettingsLayout({
general,
keys,
}: SettingsLayoutProps) {
return (
<DashboardTabs
type="query"
layoutKey="tabs-indicator-settings"
className="mt-2 md:mt-3"
>
<DashboardTab
id="general"
label="General"
icon={<SettingsIcon className="size-5" />}
>
<div className="flex-1 overflow-y-auto h-full min-h-0">
<div className="container mx-auto p-0 md:p-8 2xl:p-24 h-min w-full">
{general}
</div>
</div>
</DashboardTab>
<DashboardTab
id="keys"
label="Keys"
icon={<KeyIcon className="size-5" />}
>
<div className="flex-1 overflow-y-auto h-full min-h-0">
<div className="container mx-auto p-0 md:p-8 2xl:p-24 h-min w-full">
{keys}
</div>
</div>
</DashboardTab>
</DashboardTabs>
)
}
6 changes: 6 additions & 0 deletions src/app/dashboard/[teamIdOrSlug]/settings/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// This page is required for Next.js parallel routes to work properly
// The actual content is rendered through the parallel route slots (@general, @keys)
// which are passed to the layout and then to the tabs component
export default function SettingsPage() {
return null
}
47 changes: 0 additions & 47 deletions src/app/dashboard/[teamIdOrSlug]/team/page.tsx

This file was deleted.

Loading
Loading