From 57dde08672b1f95405b2f666f2804e64c00755ae Mon Sep 17 00:00:00 2001
From: Danny White <3104761+dnywh@users.noreply.github.com>
Date: Tue, 3 Mar 2026 09:40:35 +1100
Subject: [PATCH 1/6] chore(design-system): better breadcrumbs (#42310)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## What kind of change does this PR introduce?
Minor update
## What is the current behavior?
Our design system’s breadcrumbs:
1. Are not interactive
2. ~~Repeat the page title, which is directly below~~
## What is the new behavior?
These breadcrumbs:
- Are hyperlinked
- Show all parents, if nested
- ~~Don’t repeat the page title~~
| Before | After |
| --- | --- |
| |
|
## Additional context
I wonder if we should either drop the `/docs/` subdirectory or rename
it. The latter would make sense if we split, say, `/product/` from
`/icons/` and/or `/brand`.
[Primer](https://primer.style/octicons/design-guidelines/) does this
well.
---
.../app/(app)/docs/[[...slug]]/page.tsx | 37 ++++++++++++++++---
apps/design-system/components/pager.tsx | 24 ++++++++++++
2 files changed, 55 insertions(+), 6 deletions(-)
diff --git a/apps/design-system/app/(app)/docs/[[...slug]]/page.tsx b/apps/design-system/app/(app)/docs/[[...slug]]/page.tsx
index f4e4c6694d970..f3cdccc24d5c9 100644
--- a/apps/design-system/app/(app)/docs/[[...slug]]/page.tsx
+++ b/apps/design-system/app/(app)/docs/[[...slug]]/page.tsx
@@ -1,5 +1,5 @@
import { Mdx } from '@/components/mdx-components'
-import { DocsPager } from '@/components/pager'
+import { DocsPager, getBreadcrumbSegments } from '@/components/pager'
import { SourcePanel } from '@/components/source-panel'
import { DashboardTableOfContents } from '@/components/toc'
import { siteConfig } from '@/config/site'
@@ -12,6 +12,7 @@ import '@/styles/mdx.css'
import { allDocs } from 'contentlayer/generated'
import { ChevronRight } from 'lucide-react'
import type { Metadata } from 'next'
+import Link from 'next/link'
import { notFound } from 'next/navigation'
import Balancer from 'react-wrap-balancer'
import { ScrollArea, Separator } from 'ui'
@@ -83,15 +84,39 @@ export default async function DocPage(props: DocPageProps) {
}
const toc = await getTableOfContents(doc.body.raw)
+ const breadcrumbSegments = getBreadcrumbSegments(doc)
return (
{item.description}
+ }> + {item.linkLabel ?? 'Read more'} + +For secret keys, see API settings.
diff --git a/apps/studio/components/interfaces/Connect/Connect.tsx b/apps/studio/components/interfaces/Connect/Connect.tsx index e2e91db087405..9ccd9c8d47d48 100644 --- a/apps/studio/components/interfaces/Connect/Connect.tsx +++ b/apps/studio/components/interfaces/Connect/Connect.tsx @@ -5,7 +5,6 @@ import { DatabaseConnectionString } from 'components/interfaces/Connect/Database import { McpTabContent } from 'components/interfaces/Connect/McpTabContent' import Panel from 'components/ui/Panel' import { getKeys, useAPIKeysQuery } from 'data/api-keys/api-keys-query' -import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query' import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { BASE_PATH } from 'lib/constants' @@ -35,6 +34,7 @@ import { CONNECTION_TYPES, ConnectionType, FRAMEWORKS, MOBILES, ORMS } from './C import { getContentFilePath, inferConnectTabFromParentKey } from './Connect.utils' import { ConnectDropdown } from './ConnectDropdown' import { ConnectTabContent } from './ConnectTabContent' +import { useProjectApiUrl } from '@/data/config/project-endpoint-query' export const Connect = () => { const router = useRouter() @@ -97,7 +97,8 @@ export const Connect = () => { ?.children.find((child) => child.key === selectedChild)?.children[0]?.key || '' ) - const { data: settings } = useProjectSettingsV2Query({ projectRef }, { enabled: open }) + const { data: resolvedEndpoint } = useProjectApiUrl({ projectRef }) + const { can: canReadAPIKeys } = useAsyncCheckPermissions( PermissionAction.READ, 'service_api_keys' @@ -216,22 +217,12 @@ export const Connect = () => { : { anonKey: null, publishableKey: null } const projectKeys = useMemo(() => { - const protocol = settings?.app_config?.protocol ?? 'https' - const endpoint = settings?.app_config?.endpoint ?? '' - const apiHost = canReadAPIKeys ? `${protocol}://${endpoint ?? '-'}` : '' - return { - apiUrl: apiHost ?? null, + apiUrl: resolvedEndpoint ?? null, anonKey: anonKey?.api_key ?? null, publishableKey: publishableKey?.api_key ?? null, } - }, [ - settings?.app_config?.protocol, - settings?.app_config?.endpoint, - canReadAPIKeys, - anonKey?.api_key, - publishableKey?.api_key, - ]) + }, [resolvedEndpoint, anonKey?.api_key, publishableKey?.api_key]) const filePath = getContentFilePath({ connectionObject, diff --git a/apps/studio/components/interfaces/ConnectSheet/ConnectSheet.tsx b/apps/studio/components/interfaces/ConnectSheet/ConnectSheet.tsx index 60d3d1f5e941a..02f605fc838ad 100644 --- a/apps/studio/components/interfaces/ConnectSheet/ConnectSheet.tsx +++ b/apps/studio/components/interfaces/ConnectSheet/ConnectSheet.tsx @@ -1,7 +1,6 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { getKeys, useAPIKeysQuery } from 'data/api-keys/api-keys-query' -import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query' import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { parseAsBoolean, parseAsString, useQueryState } from 'nuqs' import { useMemo } from 'react' @@ -11,9 +10,12 @@ import type { ProjectKeys } from './Connect.types' import { ConnectConfigSection, ModeSelector } from './ConnectConfigSection' import { ConnectStepsSection } from './ConnectStepsSection' import { useConnectState } from './useConnectState' +import { useProjectApiUrl } from '@/data/config/project-endpoint-query' import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled' export const ConnectSheet = () => { + const { ref: projectRef } = useParams() + const { projectConnectionShowAppFrameworks: showAppFrameworks, projectConnectionShowMobileFrameworks: showMobileFrameworks, @@ -40,9 +42,8 @@ export const ConnectSheet = () => { const { state, activeFields, resolvedSteps, schema, getFieldOptions, setMode, updateField } = useConnectState() - // Project keys for step components - const { ref: projectRef } = useParams() - const { data: settings } = useProjectSettingsV2Query({ projectRef }, { enabled: showConnect }) + const { data: endpoint = '' } = useProjectApiUrl({ projectRef }, { enabled: showConnect }) + const { can: canReadAPIKeys } = useAsyncCheckPermissions( PermissionAction.READ, 'service_api_keys' @@ -53,22 +54,12 @@ export const ConnectSheet = () => { : { anonKey: null, publishableKey: null } const projectKeys: ProjectKeys = useMemo(() => { - const protocol = settings?.app_config?.protocol ?? 'https' - const endpoint = settings?.app_config?.endpoint ?? '' - const apiHost = canReadAPIKeys ? `${protocol}://${endpoint ?? '-'}` : '' - return { - apiUrl: apiHost ?? null, + apiUrl: endpoint, anonKey: anonKey?.api_key ?? null, publishableKey: publishableKey?.api_key ?? null, } - }, [ - settings?.app_config?.protocol, - settings?.app_config?.endpoint, - canReadAPIKeys, - anonKey?.api_key, - publishableKey?.api_key, - ]) + }, [endpoint, anonKey?.api_key, publishableKey?.api_key]) const availableModeIds = useMemo(() => { const modes: string[] = [] diff --git a/apps/studio/components/interfaces/Docs/ResourceContent.tsx b/apps/studio/components/interfaces/Docs/ResourceContent.tsx index f5f58076ab591..fd4093974e0cb 100644 --- a/apps/studio/components/interfaces/Docs/ResourceContent.tsx +++ b/apps/studio/components/interfaces/Docs/ResourceContent.tsx @@ -7,13 +7,12 @@ import Description from '@/components/interfaces/Docs/Description' import Param from '@/components/interfaces/Docs/Param' import Snippets from '@/components/interfaces/Docs/Snippets' import { InlineLink } from '@/components/ui/InlineLink' -import { useCustomDomainsQuery } from '@/data/custom-domains/custom-domains-query' +import { useProjectApiUrl } from '@/data/config/project-endpoint-query' import { useProjectJsonSchemaQuery } from '@/data/docs/project-json-schema-query' import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled' import { DOCS_URL } from '@/lib/constants' interface ResourceContentProps { - apiEndpoint: string resourceId: string resources: { [key: string]: { id: string; displayName: string; camelCase: string } } selectedLang: 'bash' | 'js' @@ -22,7 +21,6 @@ interface ResourceContentProps { } export const ResourceContent = ({ - apiEndpoint, resourceId, resources, selectedLang, @@ -30,16 +28,12 @@ export const ResourceContent = ({ refreshDocs, }: ResourceContentProps) => { const { ref } = useParams() - const { data: customDomainData } = useCustomDomainsQuery({ projectRef: ref }) const { realtimeAll: realtimeEnabled } = useIsFeatureEnabled(['realtime:all']) const { data: jsonSchema } = useProjectJsonSchemaQuery({ projectRef: ref }) const { paths, definitions } = jsonSchema || {} - const endpoint = - customDomainData?.customDomain?.status === 'active' - ? `https://${customDomainData.customDomain.hostname}` - : apiEndpoint + const { data: endpoint = '' } = useProjectApiUrl({ projectRef: ref }) const keyToShow = !!showApiKey ? showApiKey : 'SUPABASE_KEY' const resourcePaths = paths?.[`/${resourceId}`] diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx b/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx index 40f8fd83fa068..3da5c1a0adaed 100644 --- a/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx +++ b/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx @@ -9,29 +9,30 @@ import { useEffect, useMemo, useState } from 'react' import { SubmitHandler, useForm } from 'react-hook-form' import { toast } from 'sonner' import { + Alert_Shadcn_, AlertDescription_Shadcn_, AlertTitle_Shadcn_, - Alert_Shadcn_, Button, Card, CardContent, CardFooter, + cn, CodeBlock, + copyToClipboard, CriticalIcon, + Form_Shadcn_, FormControl_Shadcn_, FormField_Shadcn_, - Form_Shadcn_, Switch, Tabs_Shadcn_ as Tabs, TabsContent_Shadcn_ as TabsContent, TabsList_Shadcn_ as TabsList, TabsTrigger_Shadcn_ as TabsTrigger, - cn, - copyToClipboard, } from 'ui' import { GenericSkeletonLoader } from 'ui-patterns' import { Input } from 'ui-patterns/DataInputs/Input' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' +import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import { PageContainer } from 'ui-patterns/PageContainer' import { PageSection, @@ -40,7 +41,6 @@ import { PageSectionSummary, PageSectionTitle, } from 'ui-patterns/PageSection' -import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import z from 'zod' import CommandRender from '../CommandRender' @@ -48,8 +48,7 @@ import { INVOCATION_TABS } from './EdgeFunctionDetails.constants' import { generateCLICommands } from './EdgeFunctionDetails.utils' import AlertError from '@/components/ui/AlertError' import { getKeys, useAPIKeysQuery } from '@/data/api-keys/api-keys-query' -import { useProjectSettingsV2Query } from '@/data/config/project-settings-v2-query' -import { useCustomDomainsQuery } from '@/data/custom-domains/custom-domains-query' +import { useProjectApiUrl } from '@/data/config/project-endpoint-query' import { useEdgeFunctionQuery } from '@/data/edge-functions/edge-function-query' import { useEdgeFunctionDeleteMutation } from '@/data/edge-functions/edge-functions-delete-mutation' import { useEdgeFunctionUpdateMutation } from '@/data/edge-functions/edge-functions-update-mutation' @@ -86,24 +85,18 @@ export const EdgeFunctionDetails = () => { const canUpdateEdgeFunction = IS_PLATFORM && canUpdateEdgeFunctionPermission const { can: canReadAPIKeys } = useAsyncCheckPermissions(PermissionAction.SECRETS_READ, '*') - const { data: apiKeys } = useAPIKeysQuery( - { - projectRef, - }, - { enabled: canReadAPIKeys } - ) - const { data: settings } = useProjectSettingsV2Query({ projectRef }) - const { data: customDomainData } = useCustomDomainsQuery({ projectRef }) + const { data: apiKeys } = useAPIKeysQuery({ projectRef }, { enabled: canReadAPIKeys }) + const { data: selectedFunction, error, isPending: isLoading, isError, isSuccess, - } = useEdgeFunctionQuery({ - projectRef, - slug: functionSlug, - }) + } = useEdgeFunctionQuery({ projectRef, slug: functionSlug }) + + const { data: endpoint } = useProjectApiUrl({ projectRef }) + const functionUrl = `${endpoint}/functions/v1/${selectedFunction?.slug}` const { mutate: updateEdgeFunction, isPending: isUpdating } = useEdgeFunctionUpdateMutation() const { mutate: deleteEdgeFunction, isPending: isDeleting } = useEdgeFunctionDeleteMutation({ @@ -121,12 +114,6 @@ export const EdgeFunctionDetails = () => { const { anonKey, publishableKey } = getKeys(apiKeys) const apiKey = publishableKey?.api_key ?? anonKey?.api_key ?? '[YOUR ANON KEY]' - const protocol = settings?.app_config?.protocol ?? 'https' - const endpoint = settings?.app_config?.endpoint ?? '' - const functionUrl = - customDomainData?.customDomain?.status === 'active' - ? `https://${customDomainData.customDomain.hostname}/functions/v1/${selectedFunction?.slug}` - : `${protocol}://${endpoint}/functions/v1/${selectedFunction?.slug}` const hasImportMap = useMemo( () => selectedFunction?.import_map || selectedFunction?.import_map_path, [selectedFunction] diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx b/apps/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx index 359948879a717..910d4f419488b 100644 --- a/apps/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx +++ b/apps/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx @@ -4,11 +4,10 @@ import dayjs from 'dayjs' import { Check, Copy } from 'lucide-react' import { useRouter } from 'next/router' import { useState } from 'react' -import { TableCell, TableRow, copyToClipboard } from 'ui' +import { copyToClipboard, TableCell, TableRow } from 'ui' import { TimestampInfo } from 'ui-patterns' -import { useProjectSettingsV2Query } from '@/data/config/project-settings-v2-query' -import { useCustomDomainsQuery } from '@/data/custom-domains/custom-domains-query' +import { useProjectApiUrl } from '@/data/config/project-endpoint-query' import type { EdgeFunctionsResponse } from '@/data/edge-functions/edge-functions-query' import { createNavigationHandler } from '@/lib/navigation' @@ -21,15 +20,8 @@ export const EdgeFunctionsListItem = ({ function: item }: EdgeFunctionsListItemP const { ref } = useParams() const [isCopied, setIsCopied] = useState(false) - const { data: settings } = useProjectSettingsV2Query({ projectRef: ref }) - const { data: customDomainData } = useCustomDomainsQuery({ projectRef: ref }) - - const protocol = settings?.app_config?.protocol ?? 'https' - const endpoint = settings?.app_config?.endpoint ?? '' - const functionUrl = - customDomainData?.customDomain?.status === 'active' - ? `https://${customDomainData.customDomain.hostname}/functions/v1/${item.slug}` - : `${protocol}://${endpoint}/functions/v1/${item.slug}` + const { data: endpoint } = useProjectApiUrl({ projectRef: ref }) + const functionUrl = `${endpoint}/functions/v1/${item.slug}` const handleNavigation = createNavigationHandler( `/project/${ref}/functions/${item.slug}${IS_PLATFORM ? '' : `/details`}`, diff --git a/apps/studio/components/interfaces/Functions/TerminalInstructions.tsx b/apps/studio/components/interfaces/Functions/TerminalInstructions.tsx index 79e77e112b091..f4d82bb4c029b 100644 --- a/apps/studio/components/interfaces/Functions/TerminalInstructions.tsx +++ b/apps/studio/components/interfaces/Functions/TerminalInstructions.tsx @@ -1,24 +1,23 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' -import { ExternalLink, Maximize2, Minimize2, Terminal } from 'lucide-react' -import { useRouter } from 'next/router' -import { ComponentPropsWithoutRef, ElementRef, forwardRef, useState } from 'react' - import { useParams } from 'common' import CommandRender from 'components/interfaces/Functions/CommandRender' import { DocsButton } from 'components/ui/DocsButton' import { useAccessTokensQuery } from 'data/access-tokens/access-tokens-query' import { getKeys, useAPIKeysQuery } from 'data/api-keys/api-keys-query' -import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query' -import { useCustomDomainsQuery } from 'data/custom-domains/custom-domains-query' import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { DOCS_URL } from 'lib/constants' +import { ExternalLink, Maximize2, Minimize2, Terminal } from 'lucide-react' +import { useRouter } from 'next/router' +import { ComponentPropsWithoutRef, ElementRef, forwardRef, useState } from 'react' import { Button, + Collapsible_Shadcn_, CollapsibleContent_Shadcn_, CollapsibleTrigger_Shadcn_, - Collapsible_Shadcn_, } from 'ui' + import type { Commands } from './Functions.types' +import { useProjectApiUrl } from '@/data/config/project-endpoint-query' interface TerminalInstructionsProps extends ComponentPropsWithoutRef