Files
supabase/apps/studio/components/interfaces/Project/APIKeys.tsx
Alaister Young ca2b50a0a7 chore(ui-patterns): collapse the admonition shim into ui-patterns/Admonition (#48377)
Follow-up to #48344: collapses the two resolution paths for the
Admonition module into one.

`src/admonition.tsx` was a back-compat shim re-exporting
`src/Admonition/`. Two ways to resolve one module is exactly what
produced the macOS self-import bug fixed in #48344, and the local
typecheck errors that #48374 worked around. This removes the shim and
standardizes on the PascalCase subpath, matching every other export in
the package.

**Changed:**

- Codemodded all 246 `ui-patterns/admonition` imports to
`ui-patterns/Admonition` (240 `.tsx`, 5 `.mdx`, 1 `.ts` across studio,
docs, www, design-system, and lite-studio)
- Pointed the 5 internal `'../admonition'` imports back at the
`'../Admonition'` directory

**Removed:**

- `packages/ui-patterns/src/admonition.tsx`, and its `./admonition`
entry in the exports map (regenerated with `pnpm gen:exports`)

## To test

- `grep -r "ui-patterns/admonition" --include='*.ts*'` → no hits
- `pnpm test:case-hazards` → passes
- `pnpm typecheck` → all 15 tasks green
- `pnpm --filter studio run lint:ratchet` → passes
- `pnpm --filter ui-patterns vitest run src/Admonition` → 11 tests pass

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Standardized Admonition component imports across the application and
documentation.
* Improved compatibility with case-sensitive environments by using the
canonical component path.
  * Removed the legacy Admonition import entry point.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
2026-07-29 00:48:56 +08:00

220 lines
9.0 KiB
TypeScript

import { PermissionAction } from '@supabase/shared-types/out/constants'
import { JwtSecretUpdateStatus } from '@supabase/shared-types/out/events'
import { useParams } from 'common'
import { Loader } from 'lucide-react'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from 'ui'
import { Admonition } from 'ui-patterns/Admonition'
import { Input } from 'ui-patterns/DataInputs/Input'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
import { ConnectionIcon } from '@/components/interfaces/Connect/ConnectionIcon'
import { ConnectButton } from '@/components/interfaces/ConnectButton/ConnectButton'
import { AlertError } from '@/components/ui/AlertError'
import { InlineLink } from '@/components/ui/InlineLink'
import { useAPIKeys } from '@/data/api-keys/api-keys-query'
import { useLegacyAPIKeysStatusQuery } from '@/data/api-keys/legacy-api-keys-status-query'
import { useJwtSecretUpdatingStatusQuery } from '@/data/config/jwt-secret-updating-status-query'
import { useProjectSettingsV2Query } from '@/data/config/project-settings-v2-query'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
export const APIKeys = () => {
const { ref: projectRef } = useParams()
const { can: canReadAPIKeys } = useAsyncCheckPermissions(PermissionAction.SECRETS_READ, '*')
const {
data: settings,
error: projectSettingsError,
isError: isProjectSettingsError,
isPending: isProjectSettingsLoading,
} = useProjectSettingsV2Query({ projectRef })
const { data: legacyAPIKeysStatusData, isPending: isLoadingAPIKeysStatus } =
useLegacyAPIKeysStatusQuery({ projectRef }, { enabled: canReadAPIKeys })
const {
data: apiKeysData,
error: errorAPIKeys,
isError: isErrorAPIKeys,
isPending: isLoadingAPIKeys,
} = useAPIKeys({ projectRef }, { enabled: canReadAPIKeys })
const { anonKey, serviceKey, publishableKey, secretKey } = apiKeysData ?? {}
const hasNewAPIKeys = !!publishableKey && !!secretKey
const isLegacyKeysEnabled = legacyAPIKeysStatusData?.enabled ?? false
const isApiKeysEmpty = !hasNewAPIKeys && !anonKey && !serviceKey
const {
data,
error: jwtSecretUpdateError,
isError: isJwtSecretUpdateStatusError,
isPending: isJwtSecretUpdateStatusLoading,
} = useJwtSecretUpdatingStatusQuery(
{ projectRef },
{ enabled: !isProjectSettingsLoading && isApiKeysEmpty }
)
const isLoading = isLoadingAPIKeys || isLoadingAPIKeysStatus || isProjectSettingsLoading
const isError = isErrorAPIKeys || isProjectSettingsError || isJwtSecretUpdateStatusError
// Only show JWT loading state if the query is actually enabled
const showJwtLoading =
isJwtSecretUpdateStatusLoading && !isProjectSettingsLoading && isApiKeysEmpty
const jwtSecretUpdateStatus = data?.jwtSecretUpdateStatus
const isNotUpdatingJwtSecret =
jwtSecretUpdateStatus === undefined || jwtSecretUpdateStatus === JwtSecretUpdateStatus.Updated
const protocol = settings?.app_config?.protocol ?? 'https'
const endpoint = settings?.app_config?.endpoint
const apiUrl = `${protocol}://${endpoint ?? '-'}`
return (
<Card>
<CardHeader>
<CardTitle className="text-sm font-medium">Project API</CardTitle>
<CardDescription>
Your API is secured behind an API gateway which requires an API Key for every request.
<br />
You can use the parameters below to use Supabase client libraries.
</CardDescription>
</CardHeader>
{isLoading ? (
<CardContent>
<GenericSkeletonLoader />
</CardContent>
) : isError ? (
<AlertError
className="rounded-none border-0"
subject={
isErrorAPIKeys
? 'Failed to retrieve API Keys'
: isProjectSettingsError
? 'Failed to retrieve project settings'
: isJwtSecretUpdateStatusError
? 'Failed to update JWT secret'
: ''
}
error={errorAPIKeys ?? projectSettingsError ?? jwtSecretUpdateError}
/>
) : showJwtLoading ? (
<CardContent>
<div className="flex items-center justify-center py-4 space-x-2">
<Loader className="animate-spin" size={16} strokeWidth={1.5} />
<p className="text-sm text-foreground-light">JWT secret is being updated</p>
</div>
</CardContent>
) : !isLegacyKeysEnabled && !hasNewAPIKeys ? (
<Admonition
type="default"
className="border-0 rounded-none"
title="Project has no API keys created yet"
description={
<>
Create a set of API keys from your{' '}
<InlineLink href={`/project/${projectRef}/settings/api-keys`}>
project settings
</InlineLink>{' '}
to connect to your project
</>
}
/>
) : (
<>
<CardContent>
<FormItemLayout
isReactForm={false}
layout="horizontal"
label="Project URL"
description="A RESTful endpoint for querying and managing your database."
>
<Input readOnly copy className="input-mono" value={apiUrl} />
</FormItemLayout>
</CardContent>
<CardContent>
<FormItemLayout
isReactForm={false}
layout="horizontal"
label={
hasNewAPIKeys ? (
'Publishable API Key'
) : (
<div className="space-y-2">
<p className="text-sm">API Key</p>
<div className="flex items-center space-x-1 -ml-1">
<code className="text-code-inline">{anonKey?.name}</code>
<code className="text-code-inline">public</code>
</div>
</div>
)
}
description={
<p>
This key is safe to use in a browser if you have enabled Row Level Security (RLS)
for your tables and configured policies. You may also use the{' '}
{hasNewAPIKeys ? 'secret' : 'service'} key which can be found{' '}
<InlineLink
href={`/project/${projectRef}/settings/api-keys${!hasNewAPIKeys ? '/legacy' : ''}`}
>
here
</InlineLink>{' '}
to bypass RLS.
</p>
}
>
<Input
readOnly
className="input-mono"
copy={canReadAPIKeys && isNotUpdatingJwtSecret}
reveal={anonKey?.name !== 'anon' && canReadAPIKeys && isNotUpdatingJwtSecret}
value={
!canReadAPIKeys
? 'You need additional permissions to view API keys'
: jwtSecretUpdateStatus === JwtSecretUpdateStatus.Failed
? 'JWT secret update failed, new API key may have issues'
: jwtSecretUpdateStatus === JwtSecretUpdateStatus.Updating
? 'Updating JWT secret...'
: (publishableKey?.api_key ?? anonKey?.api_key)
}
/>
</FormItemLayout>
</CardContent>
<CardContent className="relative overflow-hidden">
<div
className="absolute inset-0 rounded-md -mt-px"
style={{
backgroundImage: `
linear-gradient(to top, oklch(from var(--background-surface-100) l c h / 1) 0%, oklch(from var(--background-surface-100) l c h / 1) 30%, oklch(from var(--background-surface-75) l c h / 0) 100%),
linear-gradient(to right, oklch(from var(--border-default) l c h / 0.33) 1px, transparent 1px),
linear-gradient(to bottom, oklch(from var(--border-default) l c h / 0.33) 1px, transparent 1px)
`,
backgroundSize: '100% 100%, 15px 15px, 15px 15px',
backgroundPosition: '0 0, 0 0, 0 0',
}}
/>
<div className="relative mt-6 mb-3">
<div className="flex gap-x-3.5 relative ml-0.5 mb-4 opacity-80">
<ConnectionIcon icon="nextjs" size={26} />
<ConnectionIcon icon="react" size={26} />
<ConnectionIcon icon="svelte" size={22} />
<ConnectionIcon icon="flutter" size={23} />
<ConnectionIcon icon="prisma" size={22} />
</div>
<p className="mb-1">Choose your preferred framework</p>
<p className="text-sm text-foreground-light mb-4 md:mr-20 text-balance">
Connect to your project from a variety of frameworks, ORMs, an MCP server, or even
directly via connection string.
</p>
<ConnectButton />
</div>
</CardContent>
</>
)}
</Card>
)
}