chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
'use client'
|
||||
|
||||
import { type ErrorBoundaryProps, ErrorState } from '@/app/workspace/[workspaceId]/components'
|
||||
|
||||
export default function SettingsSectionError({ error, reset }: ErrorBoundaryProps) {
|
||||
return (
|
||||
<ErrorState
|
||||
error={error}
|
||||
reset={reset}
|
||||
title='Something went wrong'
|
||||
description='An unexpected error occurred. Please try again or refresh the page.'
|
||||
loggerName='SettingsSectionError'
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import {
|
||||
SettingsHeaderProvider,
|
||||
SettingsHeaderShell,
|
||||
} from '@/app/workspace/[workspaceId]/settings/components/settings-header/settings-header'
|
||||
|
||||
/**
|
||||
* Persistent chrome for the settings panel pages. The header bar, title,
|
||||
* description, scroll region, and centered column live in the shell and stay
|
||||
* mounted across section navigation — only the body swaps. Scoped to `[section]`
|
||||
* so detail routes (e.g. `secrets/[credentialId]`) keep their own chrome.
|
||||
*/
|
||||
export default function SettingsSectionLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<SettingsHeaderProvider>
|
||||
<SettingsHeaderShell>{children}</SettingsHeaderShell>
|
||||
</SettingsHeaderProvider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import { Suspense } from 'react'
|
||||
import { dehydrate, HydrationBoundary } from '@tanstack/react-query'
|
||||
import type { Metadata } from 'next'
|
||||
import { redirect } from 'next/navigation'
|
||||
import { isBillingEnabled } from '@/lib/core/config/env-flags'
|
||||
import { getQueryClient } from '@/app/_shell/providers/get-query-client'
|
||||
import type { SettingsSection } from '@/app/workspace/[workspaceId]/settings/navigation'
|
||||
import { prefetchGeneralSettings, prefetchSubscriptionData, prefetchUserProfile } from './prefetch'
|
||||
import { SettingsPage } from './settings'
|
||||
|
||||
/**
|
||||
* Legacy settings sections that moved to top-level workspace routes.
|
||||
* Old bookmarks and emails deep-link here; without the redirect the
|
||||
* section renderer has no matching branch and shows an empty panel.
|
||||
*/
|
||||
const SETTINGS_REDIRECTS: Record<string, (workspaceId: string) => string> = {
|
||||
integrations: (id) => `/workspace/${id}/integrations`,
|
||||
skills: (id) => `/workspace/${id}/skills`,
|
||||
}
|
||||
|
||||
const SECTION_TITLES: Record<string, string> = {
|
||||
general: 'General',
|
||||
secrets: 'Secrets',
|
||||
'access-control': 'Access Control',
|
||||
'audit-logs': 'Audit Logs',
|
||||
apikeys: 'Sim API Keys',
|
||||
byok: 'BYOK',
|
||||
subscription: 'Subscription',
|
||||
billing: 'Billing',
|
||||
teammates: 'Teammates',
|
||||
team: 'Team',
|
||||
sso: 'Single Sign-On',
|
||||
whitelabeling: 'Whitelabeling',
|
||||
copilot: 'Chat Keys',
|
||||
forks: 'Workspace Forks',
|
||||
mcp: 'MCP Tools',
|
||||
'custom-tools': 'Custom Tools',
|
||||
'workflow-mcp-servers': 'MCP Servers',
|
||||
'data-retention': 'Data Retention',
|
||||
'recently-deleted': 'Recently Deleted',
|
||||
debug: 'Debug',
|
||||
} as const
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ section: string }>
|
||||
}): Promise<Metadata> {
|
||||
const { section } = await params
|
||||
return { title: SECTION_TITLES[section] ?? 'Settings' }
|
||||
}
|
||||
|
||||
export default async function SettingsSectionPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ workspaceId: string; section: string }>
|
||||
}) {
|
||||
const { workspaceId, section } = await params
|
||||
|
||||
const redirectTo = SETTINGS_REDIRECTS[section]
|
||||
if (redirectTo) redirect(redirectTo(workspaceId))
|
||||
|
||||
const queryClient = getQueryClient()
|
||||
|
||||
void prefetchGeneralSettings(queryClient)
|
||||
void prefetchUserProfile(queryClient)
|
||||
if (isBillingEnabled) void prefetchSubscriptionData(queryClient)
|
||||
|
||||
return (
|
||||
<HydrationBoundary state={dehydrate(queryClient)}>
|
||||
<Suspense fallback={null}>
|
||||
<SettingsPage section={section as SettingsSection} />
|
||||
</Suspense>
|
||||
</HydrationBoundary>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import type { QueryClient } from '@tanstack/react-query'
|
||||
import { headers } from 'next/headers'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { getInternalApiBaseUrl } from '@/lib/core/utils/urls'
|
||||
import { getUserProfile, getUserSettings } from '@/lib/users/queries'
|
||||
import {
|
||||
GENERAL_SETTINGS_STALE_TIME,
|
||||
generalSettingsKeys,
|
||||
mapGeneralSettingsResponse,
|
||||
} from '@/hooks/queries/general-settings'
|
||||
import { SUBSCRIPTION_DATA_STALE_TIME, subscriptionKeys } from '@/hooks/queries/subscription'
|
||||
import {
|
||||
mapUserProfileResponse,
|
||||
USER_PROFILE_STALE_TIME,
|
||||
userProfileKeys,
|
||||
} from '@/hooks/queries/user-profile'
|
||||
|
||||
/**
|
||||
* Prefetch general settings server-side via the shared data layer.
|
||||
* Uses the same query keys as the client `useGeneralSettings` hook
|
||||
* so data is shared via HydrationBoundary.
|
||||
*/
|
||||
export function prefetchGeneralSettings(queryClient: QueryClient) {
|
||||
return queryClient.prefetchQuery({
|
||||
queryKey: generalSettingsKeys.settings(),
|
||||
queryFn: async () => {
|
||||
const session = await getSession()
|
||||
const data = await getUserSettings(session?.user?.id ?? null)
|
||||
return mapGeneralSettingsResponse(data)
|
||||
},
|
||||
staleTime: GENERAL_SETTINGS_STALE_TIME,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefetch subscription data server-side. Unlike the other prefetches this goes
|
||||
* through the internal billing API rather than calling the data layer directly:
|
||||
* the billing summary contains `Date` fields (and an untyped `metadata` blob) that
|
||||
* `NextResponse.json` serializes to the string wire shape the client caches. Going
|
||||
* through the route yields that exact shape, avoiding a Date-vs-string mismatch
|
||||
* between server-hydrated and client-fetched data. Uses the same query key as the
|
||||
* client `useSubscriptionData` hook (with includeOrg=false) so data is shared via
|
||||
* HydrationBoundary.
|
||||
*/
|
||||
export function prefetchSubscriptionData(queryClient: QueryClient) {
|
||||
return queryClient.prefetchQuery({
|
||||
queryKey: subscriptionKeys.user(false),
|
||||
queryFn: async () => {
|
||||
const h = await headers()
|
||||
const cookie = h.get('cookie')
|
||||
const response = await fetch(`${getInternalApiBaseUrl()}/api/billing?context=user`, {
|
||||
headers: cookie ? { cookie } : {},
|
||||
})
|
||||
if (!response.ok) throw new Error(`Subscription prefetch failed: ${response.status}`)
|
||||
return response.json()
|
||||
},
|
||||
staleTime: SUBSCRIPTION_DATA_STALE_TIME,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefetch user profile server-side via the shared data layer.
|
||||
* Uses the same query keys as the client `useUserProfile` hook
|
||||
* so data is shared via HydrationBoundary.
|
||||
*/
|
||||
export function prefetchUserProfile(queryClient: QueryClient) {
|
||||
return queryClient.prefetchQuery({
|
||||
queryKey: userProfileKeys.profile(),
|
||||
queryFn: async () => {
|
||||
const session = await getSession()
|
||||
if (!session?.user?.id) throw new Error('Unauthorized')
|
||||
const user = await getUserProfile(session.user.id)
|
||||
if (!user) throw new Error('User not found')
|
||||
return mapUserProfileResponse(user)
|
||||
},
|
||||
staleTime: USER_PROFILE_STALE_TIME,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { parseAsString, parseAsStringLiteral } from 'nuqs/server'
|
||||
|
||||
/**
|
||||
* Co-located, typed URL query-param definitions for the settings section pages.
|
||||
* The client hook consumes this typed param definition as the single source of
|
||||
* truth.
|
||||
*
|
||||
* `mcpServerId` deep-links the MCP settings tab to a specific server so the row
|
||||
* can be focused/opened from a shared link.
|
||||
*/
|
||||
export const mcpServerIdParam = {
|
||||
key: 'mcpServerId',
|
||||
parser: parseAsString,
|
||||
} as const
|
||||
|
||||
/** Opening a server is a destination → push to history; clear on close. */
|
||||
export const mcpServerIdUrlKeys = {
|
||||
history: 'push',
|
||||
clearOnDefault: true,
|
||||
} as const
|
||||
|
||||
/**
|
||||
* `fork-id` deep-links the Forks settings tab to a specific fork's detail
|
||||
* sub-view (mirrors `mcpServerId` on the MCP tab).
|
||||
*/
|
||||
export const forkIdParam = {
|
||||
key: 'fork-id',
|
||||
parser: parseAsString,
|
||||
} as const
|
||||
|
||||
/** Opening a fork's detail is a destination → push to history; clear on close. */
|
||||
export const forkIdUrlKeys = {
|
||||
history: 'push',
|
||||
clearOnDefault: true,
|
||||
} as const
|
||||
|
||||
/**
|
||||
* `fork-view` deep-links the Forks settings tab to its workspace-scoped Activity
|
||||
* view (opened from the page header's "See activity" action).
|
||||
*/
|
||||
export const forkViewParam = {
|
||||
key: 'fork-view',
|
||||
parser: parseAsStringLiteral(['activity'] as const),
|
||||
} as const
|
||||
|
||||
/** Opening the activity view is a destination → push to history; clear on close. */
|
||||
export const forkViewUrlKeys = {
|
||||
history: 'push',
|
||||
clearOnDefault: true,
|
||||
} as const
|
||||
|
||||
/**
|
||||
* `fork-direction` is the sync direction (push/pull) on the parent fork's detail
|
||||
* page — shareable view state, so a copied link opens the same side of the sync.
|
||||
*/
|
||||
export const forkSyncDirectionParam = {
|
||||
key: 'fork-direction',
|
||||
parser: parseAsStringLiteral(['push', 'pull'] as const).withDefault('push'),
|
||||
} as const
|
||||
|
||||
/** Toggling direction is in-place view state → replace history; clear at the push default. */
|
||||
export const forkSyncDirectionUrlKeys = {
|
||||
history: 'replace',
|
||||
clearOnDefault: true,
|
||||
} as const
|
||||
@@ -0,0 +1,151 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect } from 'react'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { usePostHog } from 'posthog-js/react'
|
||||
import { useSession } from '@/lib/auth/auth-client'
|
||||
import { captureEvent } from '@/lib/posthog/client'
|
||||
import { General } from '@/app/workspace/[workspaceId]/settings/components/general/general'
|
||||
import { SettingsSectionProvider } from '@/app/workspace/[workspaceId]/settings/components/settings-panel'
|
||||
import { useSettingsBeforeUnload } from '@/app/workspace/[workspaceId]/settings/hooks/use-settings-before-unload'
|
||||
import type { SettingsSection } from '@/app/workspace/[workspaceId]/settings/navigation'
|
||||
import { isBillingEnabled } from '@/app/workspace/[workspaceId]/settings/navigation'
|
||||
|
||||
const Admin = dynamic(() =>
|
||||
import('@/app/workspace/[workspaceId]/settings/components/admin/admin').then((m) => m.Admin)
|
||||
)
|
||||
const ApiKeys = dynamic(() =>
|
||||
import('@/app/workspace/[workspaceId]/settings/components/api-keys/api-keys').then(
|
||||
(m) => m.ApiKeys
|
||||
)
|
||||
)
|
||||
const BYOK = dynamic(() =>
|
||||
import('@/app/workspace/[workspaceId]/settings/components/byok/byok').then((m) => m.BYOK)
|
||||
)
|
||||
const Copilot = dynamic(() =>
|
||||
import('@/app/workspace/[workspaceId]/settings/components/copilot/copilot').then((m) => m.Copilot)
|
||||
)
|
||||
const Forks = dynamic(() => import('@/ee/workspace-forking/components/forks').then((m) => m.Forks))
|
||||
const Secrets = dynamic(() =>
|
||||
import('@/app/workspace/[workspaceId]/settings/components/secrets/secrets').then((m) => m.Secrets)
|
||||
)
|
||||
const CustomTools = dynamic(() =>
|
||||
import('@/app/workspace/[workspaceId]/settings/components/custom-tools/custom-tools').then(
|
||||
(m) => m.CustomTools
|
||||
)
|
||||
)
|
||||
const Inbox = dynamic(() =>
|
||||
import('@/app/workspace/[workspaceId]/settings/components/inbox/inbox').then((m) => m.Inbox)
|
||||
)
|
||||
const MCP = dynamic(() =>
|
||||
import('@/app/workspace/[workspaceId]/settings/components/mcp/mcp').then((m) => m.MCP)
|
||||
)
|
||||
const Mothership = dynamic(() =>
|
||||
import('@/app/workspace/[workspaceId]/settings/components/mothership/mothership').then(
|
||||
(m) => m.Mothership
|
||||
)
|
||||
)
|
||||
const RecentlyDeleted = dynamic(() =>
|
||||
import(
|
||||
'@/app/workspace/[workspaceId]/settings/components/recently-deleted/recently-deleted'
|
||||
).then((m) => m.RecentlyDeleted)
|
||||
)
|
||||
const Billing = dynamic(() =>
|
||||
import('@/app/workspace/[workspaceId]/settings/components/billing/billing').then((m) => m.Billing)
|
||||
)
|
||||
const Teammates = dynamic(() =>
|
||||
import('@/app/workspace/[workspaceId]/settings/components/teammates/teammates').then(
|
||||
(m) => m.Teammates
|
||||
)
|
||||
)
|
||||
const TeamManagement = dynamic(() =>
|
||||
import('@/app/workspace/[workspaceId]/settings/components/team-management/team-management').then(
|
||||
(m) => m.TeamManagement
|
||||
)
|
||||
)
|
||||
const WorkflowMcpServers = dynamic(() =>
|
||||
import(
|
||||
'@/app/workspace/[workspaceId]/settings/components/workflow-mcp-servers/workflow-mcp-servers'
|
||||
).then((m) => m.WorkflowMcpServers)
|
||||
)
|
||||
const AccessControl = dynamic(() =>
|
||||
import('@/ee/access-control/components/access-control').then((m) => m.AccessControl)
|
||||
)
|
||||
const CustomBlocks = dynamic(() =>
|
||||
import('@/ee/custom-blocks/components/custom-blocks').then((m) => m.CustomBlocks)
|
||||
)
|
||||
const AuditLogs = dynamic(() =>
|
||||
import('@/ee/audit-logs/components/audit-logs').then((m) => m.AuditLogs)
|
||||
)
|
||||
const SSO = dynamic(() => import('@/ee/sso/components/sso-settings').then((m) => m.SSO))
|
||||
const DataRetentionSettings = dynamic(() =>
|
||||
import('@/ee/data-retention/components/data-retention-settings').then(
|
||||
(m) => m.DataRetentionSettings
|
||||
)
|
||||
)
|
||||
const DataDrainsSettings = dynamic(() =>
|
||||
import('@/ee/data-drains/components/data-drains-settings').then((m) => m.DataDrainsSettings)
|
||||
)
|
||||
const WhitelabelingSettings = dynamic(
|
||||
() =>
|
||||
import('@/ee/whitelabeling/components/whitelabeling-settings').then(
|
||||
(m) => m.WhitelabelingSettings
|
||||
),
|
||||
{ ssr: false }
|
||||
)
|
||||
|
||||
interface SettingsPageProps {
|
||||
section: SettingsSection
|
||||
}
|
||||
|
||||
export function SettingsPage({ section }: SettingsPageProps) {
|
||||
const { data: session, isPending: sessionLoading } = useSession()
|
||||
const posthog = usePostHog()
|
||||
|
||||
useSettingsBeforeUnload()
|
||||
|
||||
const isAdminRole = session?.user?.role === 'admin'
|
||||
const normalizedSection: SettingsSection =
|
||||
(section as string) === 'subscription' ? 'billing' : section
|
||||
const effectiveSection =
|
||||
!isBillingEnabled && (normalizedSection === 'billing' || normalizedSection === 'organization')
|
||||
? 'general'
|
||||
: normalizedSection === 'admin' && !sessionLoading && !isAdminRole
|
||||
? 'general'
|
||||
: normalizedSection === 'mothership' && !sessionLoading && !isAdminRole
|
||||
? 'general'
|
||||
: normalizedSection
|
||||
|
||||
useEffect(() => {
|
||||
if (sessionLoading) return
|
||||
captureEvent(posthog, 'settings_tab_viewed', { section: effectiveSection })
|
||||
}, [effectiveSection, sessionLoading, posthog])
|
||||
|
||||
return (
|
||||
<SettingsSectionProvider section={effectiveSection}>
|
||||
{effectiveSection === 'general' && <General />}
|
||||
{effectiveSection === 'secrets' && <Secrets />}
|
||||
{effectiveSection === 'access-control' && <AccessControl />}
|
||||
{effectiveSection === 'custom-blocks' && <CustomBlocks />}
|
||||
{effectiveSection === 'audit-logs' && <AuditLogs />}
|
||||
{effectiveSection === 'apikeys' && <ApiKeys />}
|
||||
{isBillingEnabled && effectiveSection === 'billing' && <Billing />}
|
||||
{effectiveSection === 'teammates' && <Teammates />}
|
||||
{isBillingEnabled && effectiveSection === 'organization' && <TeamManagement />}
|
||||
{effectiveSection === 'sso' && <SSO />}
|
||||
{effectiveSection === 'data-retention' && <DataRetentionSettings />}
|
||||
{effectiveSection === 'data-drains' && <DataDrainsSettings />}
|
||||
{effectiveSection === 'whitelabeling' && <WhitelabelingSettings />}
|
||||
{effectiveSection === 'byok' && <BYOK />}
|
||||
{effectiveSection === 'copilot' && <Copilot />}
|
||||
{effectiveSection === 'mcp' && <MCP />}
|
||||
{effectiveSection === 'forks' && <Forks />}
|
||||
{effectiveSection === 'custom-tools' && <CustomTools />}
|
||||
{effectiveSection === 'workflow-mcp-servers' && <WorkflowMcpServers />}
|
||||
{effectiveSection === 'inbox' && <Inbox />}
|
||||
{effectiveSection === 'recently-deleted' && <RecentlyDeleted />}
|
||||
{effectiveSection === 'admin' && <Admin />}
|
||||
{effectiveSection === 'mothership' && <Mothership />}
|
||||
</SettingsSectionProvider>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user