From bfe3ea06df7af0f145388ae8b885deaea43f7fce Mon Sep 17 00:00:00 2001 From: "kemal.earth" <606977+kemaldotearth@users.noreply.github.com> Date: Mon, 15 Dec 2025 10:44:08 +0000 Subject: [PATCH 01/11] feat(design-system): add initial copy writing guide (#41307) * feat: create entry points for copy writing docs * feat: basic page generated using the supabase writing style guide * chore: editing page * feat: more components * feat: remaining example components * chore: remove duplicate registry entry * chore: add aria label to example * chore: run prettier * fix: alphabetise getting started section * chore: rename copy writing to copywriting --- apps/design-system/__registry__/index.tsx | 88 +++++++ .../components/component-preview.tsx | 79 +++--- apps/design-system/config/docs.ts | 29 ++- .../content/docs/copywriting.mdx | 240 ++++++++++++++++++ apps/design-system/registry/copy-writing.ts | 68 +++++ .../default/example/copy-button-verbs.tsx | 22 ++ .../default/example/copy-confirmations.tsx | 54 ++++ .../default/example/copy-empty-states.tsx | 34 +++ .../default/example/copy-error-messages.tsx | 24 ++ .../default/example/copy-form-labels.tsx | 29 +++ .../default/example/copy-loading-states.tsx | 38 +++ .../default/example/copy-success-messages.tsx | 24 ++ .../default/example/copy-tooltips.tsx | 51 ++++ apps/design-system/registry/registry.ts | 3 +- 14 files changed, 735 insertions(+), 48 deletions(-) create mode 100644 apps/design-system/content/docs/copywriting.mdx create mode 100644 apps/design-system/registry/copy-writing.ts create mode 100644 apps/design-system/registry/default/example/copy-button-verbs.tsx create mode 100644 apps/design-system/registry/default/example/copy-confirmations.tsx create mode 100644 apps/design-system/registry/default/example/copy-empty-states.tsx create mode 100644 apps/design-system/registry/default/example/copy-error-messages.tsx create mode 100644 apps/design-system/registry/default/example/copy-form-labels.tsx create mode 100644 apps/design-system/registry/default/example/copy-loading-states.tsx create mode 100644 apps/design-system/registry/default/example/copy-success-messages.tsx create mode 100644 apps/design-system/registry/default/example/copy-tooltips.tsx diff --git a/apps/design-system/__registry__/index.tsx b/apps/design-system/__registry__/index.tsx index b668b042da043..1a6766deeab47 100644 --- a/apps/design-system/__registry__/index.tsx +++ b/apps/design-system/__registry__/index.tsx @@ -2975,5 +2975,93 @@ export const Index: Record = { subcategory: "Composed", chunks: [] }, + "copy-button-verbs": { + name: "copy-button-verbs", + type: "components:example", + registryDependencies: ["button"], + component: React.lazy(() => import("@/registry/default/example/copy-button-verbs")), + source: "", + files: ["registry/default/example/copy-button-verbs.tsx"], + category: "Getting Started", + subcategory: "Copywriting", + chunks: [] + }, + "copy-form-labels": { + name: "copy-form-labels", + type: "components:example", + registryDependencies: ["form"], + component: React.lazy(() => import("@/registry/default/example/copy-form-labels")), + source: "", + files: ["registry/default/example/copy-form-labels.tsx"], + category: "Getting Started", + subcategory: "Copywriting", + chunks: [] + }, + "copy-error-messages": { + name: "copy-error-messages", + type: "components:example", + registryDependencies: ["form"], + component: React.lazy(() => import("@/registry/default/example/copy-error-messages")), + source: "", + files: ["registry/default/example/copy-error-messages.tsx"], + category: "Getting Started", + subcategory: "Copywriting", + chunks: [] + }, + "copy-success-messages": { + name: "copy-success-messages", + type: "components:example", + registryDependencies: ["form"], + component: React.lazy(() => import("@/registry/default/example/copy-success-messages")), + source: "", + files: ["registry/default/example/copy-success-messages.tsx"], + category: "Getting Started", + subcategory: "Copywriting", + chunks: [] + }, + "copy-tooltips": { + name: "copy-tooltips", + type: "components:example", + registryDependencies: ["tooltip"], + component: React.lazy(() => import("@/registry/default/example/copy-tooltips")), + source: "", + files: ["registry/default/example/copy-tooltips.tsx"], + category: "Getting Started", + subcategory: "Copywriting", + chunks: [] + }, + "copy-loading-states": { + name: "copy-loading-states", + type: "components:example", + registryDependencies: ["loading-state"], + component: React.lazy(() => import("@/registry/default/example/copy-loading-states")), + source: "", + files: ["registry/default/example/copy-loading-states.tsx"], + category: "Getting Started", + subcategory: "Copywriting", + chunks: [] + }, + "copy-empty-states": { + name: "copy-empty-states", + type: "components:example", + registryDependencies: ["empty-state"], + component: React.lazy(() => import("@/registry/default/example/copy-empty-states")), + source: "", + files: ["registry/default/example/copy-empty-states.tsx"], + category: "Getting Started", + subcategory: "Copywriting", + chunks: [] + }, + "copy-confirmations": { + name: "copy-confirmations", + type: "components:example", + registryDependencies: ["confirmation"], + component: React.lazy(() => import("@/registry/default/example/copy-confirmations")), + source: "", + files: ["registry/default/example/copy-confirmations.tsx"], + category: "Getting Started", + subcategory: "Copywriting", + chunks: [] + }, }, } diff --git a/apps/design-system/components/component-preview.tsx b/apps/design-system/components/component-preview.tsx index 278dddbcfd0ad..4dcfe8c56c0da 100644 --- a/apps/design-system/components/component-preview.tsx +++ b/apps/design-system/components/component-preview.tsx @@ -23,6 +23,7 @@ interface ComponentPreviewProps extends React.HTMLAttributes { showGrid?: boolean showDottedGrid?: boolean wide?: boolean + hideCode?: boolean } export function ComponentPreview({ @@ -36,6 +37,7 @@ export function ComponentPreview({ showGrid = false, showDottedGrid = true, wide = false, + hideCode = false, ...props }: ComponentPreviewProps) { const [config] = useConfig() @@ -136,7 +138,10 @@ export function ComponentPreview({ return (
{showGrid && (
@@ -146,42 +151,44 @@ export function ComponentPreview({ )}
{ComponentPreview}
- - - - View code - - -
+ - {Code} -
-
-
+ + View code + + +
+ {Code} +
+
+ + )}
) } diff --git a/apps/design-system/config/docs.ts b/apps/design-system/config/docs.ts index 8979ca8bfc151..a877949c4928f 100644 --- a/apps/design-system/config/docs.ts +++ b/apps/design-system/config/docs.ts @@ -9,41 +9,48 @@ export const docsConfig: DocsConfig = { sidebarNav: [ { title: 'Getting Started', - sortOrder: 'manual', + sortOrder: 'alphabetical', items: [ { title: 'Introduction', href: '/docs', + priority: true, items: [], }, { - title: 'Tailwind Classes', - href: '/docs/tailwind-classes', + title: 'Accessibility', + href: '/docs/accessibility', items: [], }, + { title: 'Color Usage', href: '/docs/color-usage', items: [], }, { - title: 'Typography', - href: '/docs/typography', + title: 'Copywriting', + href: '/docs/copywriting', items: [], }, { - title: 'Theming', - href: '/docs/theming', + title: 'Icons', + href: '/docs/icons', items: [], }, { - title: 'Icons', - href: '/docs/icons', + title: 'Tailwind Classes', + href: '/docs/tailwind-classes', items: [], }, { - title: 'Accessibility', - href: '/docs/accessibility', + title: 'Theming', + href: '/docs/theming', + items: [], + }, + { + title: 'Typography', + href: '/docs/typography', items: [], }, ], diff --git a/apps/design-system/content/docs/copywriting.mdx b/apps/design-system/content/docs/copywriting.mdx new file mode 100644 index 0000000000000..751cf4e04975f --- /dev/null +++ b/apps/design-system/content/docs/copywriting.mdx @@ -0,0 +1,240 @@ +--- +title: Copywriting +description: A concise guide for writing UI copy in Supabase. +--- + +Write UI copy that helps developers complete tasks quickly. Be direct, action-oriented, and respectful of developer time. + +## Voice and tone + +Supabase UI copy is: + +- **Direct**: Say what something does, not what it "enables" you to do. +- **Action-oriented**: Focus on what happens, not what we built. +- **Technical without jargon**: Use precise terms but explain when necessary. +- **Pragmatic**: Acknowledge tradeoffs and limitations when relevant. + +## Buttons and actions + +### Use verbs, not nouns + + + +### Be specific about outcomes + +| Bad | Good | +| ----------- | ---------------- | +| "Remove" | "Delete project" | +| "Change" | "Revoke access" | +| "Configure" | "Enable RLS" | + +### Match button text to the action + +| Action | Bad | Good | +| ----------------- | --------- | -------------- | +| Primary action: | "Submit" | "Create table" | +| Secondary action: | "Go back" | "Cancel" | + +## Form labels and descriptions + +### Labels describe the field, not the feature + + + +| Action | Bad | Good | +| ------------ | ------------------------------------------------------------------------------------------------ | ---------------------------------------- | +| Label: | "Name your table" | "Table name" | +| Description: | "This field allows you to specify a name for your table using letters, numbers, and underscores" | "Letters, numbers, and underscores only" | + +### Descriptions explain constraints, not concepts + +| Bad | Good | +| --------------------------------------------------------------- | ---------------------------------- | +| "This ensures your table name is unique" | "Must be unique within the schema" | +| "You can enter up to 255 characters here" | "Maximum 255 characters" | +| "This field is required when using Row Level Security policies" | "Required for RLS policies" | + +### Use present tense + +| Bad | Good | +| -------------------------------------- | --------------------------------- | +| "Will store connection pool settings" | "Stores connection pool settings" | +| "This will limit query execution time" | "Limits query execution time" | + +## Error messages + +### State what went wrong, then how to fix it + + + +| Bad | Good | +| ----------------------------------------- | ----------------------------------------------------- | +| "An error occurred" | "Table name already exists. Choose a different name." | +| "Something went wrong. Please try again." | "Invalid API key. Check your project settings." | + +### Be specific about the problem + +| Bad | Good | +| ------------------ | --------------------------------------------- | +| "Invalid input" | "Password must be at least 8 characters" | +| "Connection error" | "Connection failed: timeout after 30 seconds" | + +### Avoid blame or apology + +| Bad | Good | +| ---------------------------- | ------------------------------------------------ | +| "Sorry, we couldn't connect" | "Unable to connect to database" | +| "Oops! Something went wrong" | "Table creation failed: column name is reserved" | + +## Success messages + +### Confirm what happened + + + +| Bad | Good | +| --------------------- | ---------------------------- | +| "Success!" | "Table created successfully" | +| "Done" | "API key revoked" | +| "Operation completed" | "Changes saved" | + +### Keep it brief + +| Bad | Good | +| ------------------------------------------------------------- | ------------------- | +| "Your backup has been successfully restored to your database" | "Backup restored" | +| "The migration has been applied successfully to your project" | "Migration applied" | + +## Tooltips and help text + +### Explain why, not what + + + +| Bad | Good | +| ------------------------- | ------------------------------------------------ | +| "This is a toggle switch" | "Enables real-time subscriptions for this table" | +| "Click to delete" | "Prevents accidental deletions" | + +### One sentence maximum + +| Bad | Good | +| ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------ | +| "Row Level Security restricts access based on user policies. When enabled, users can only access rows that match their policy conditions." | "Restricts access based on user policies" | +| "This setting controls the maximum number of concurrent connections that can be established to your database at any given time." | "Maximum number of concurrent connections" | + +## Navigation and headings + +### Use sentence case + +| Bad | Good | +| ----------------------- | ----------------------- | +| "Set Up Authentication" | "Set up authentication" | +| "Database Settings" | "Database settings" | +| "Create New Project" | "Create new project" | + +### Headings describe the page, not the feature + +| Bad | Good | +| ------------------------------ | -------------------- | +| "Manage your API keys" | "API keys" | +| "Configure connection pooling" | "Connection pooling" | +| "Edit your tables" | "Table editor" | + +## Empty states + +### Explain what's missing, then how to add it + + + +| Bad | Good | +| --------------------------------- | ---------------------------------------------------------- | +| "You don't have any tables" | "No tables yet. Create your first table to get started." | +| "There are no API keys available" | "No API keys. Generate a key to connect your application." | + +### Include the action + +| Bad | Good | +| ------------------------ | ------------------------------------------------- | +| "No buckets found" | "No buckets yet. [Create bucket] button" | +| "No functions available" | "No functions deployed. [Deploy function] button" | + +## Loading states + +### Describe what's happening + + + +| Bad | Good | +| ---------------- | ----------------------- | +| "Please wait..." | "Creating table..." | +| "Loading..." | "Loading schema..." | +| "Processing..." | "Applying migration..." | + +### Match the action verb + +| Action | Bad | Good | +| --------------------------- | ---------------- | --------------------- | +| "Delete project" → Loading: | "Processing..." | "Deleting project..." | +| "Save changes" → Loading: | "Please wait..." | "Saving changes..." | + +## Confirmations and dialogs + +### State consequences clearly + + + +| Bad | Good | +| ------------------------------------- | ----------------------------------------------------------------------------------------- | +| "Are you sure?" | "Delete this project? This action cannot be undone and will permanently delete all data." | +| "This action is permanent. Continue?" | "Revoke this API key? Applications using this key will stop working immediately." | + +### Use active voice + +| Bad | Good | +| ------------------------------------------------------------ | --------------------------------------------------- | +| "All data will be removed if this project is deleted" | "Deleting this project will remove all data" | +| "Existing connections will be broken if this key is revoked" | "Revoking this key will break existing connections" | + +## Words to avoid + +### Marketing language + +| Bad | Good | +| ---------------------------- | -------------------- | +| "Easily create tables" | "Create tables" | +| "Simply configure settings" | "Configure settings" | +| "Powerful database features" | "Database features" | + +### Vague verbs + +| Bad | Good | +| ---------------- | -------------------------------- | +| "Manage tables" | "Create, edit, or delete tables" | +| "Handle errors" | "View and resolve errors" | +| "Work with data" | "Query and update data" | + +## Capitalization + +- **Sentence case** for all UI text (buttons, labels, headings) +- **Product names:** Database, Auth, Storage, Edge Functions, Realtime, Vector +- **Postgres**, not PostgreSQL +- **Supabase** (capitalize except in code) + +## Formatting + +- **Bold for emphasis** only when necessary +- **Inline code** for technical terms: `RLS`, `API key`, `supabase init` +- **No italics** for emphasis +- **No exclamation marks** unless critical (e.g., destructive actions) + +## Quick checklist + +Before publishing UI copy, ask: + +- Does it use an action verb? +- Is it specific about what happens? +- Can a developer complete the task without reading more? +- Does it avoid marketing language? +- Is it in sentence case? +- Is it one sentence or less (for labels, buttons, tooltips)? diff --git a/apps/design-system/registry/copy-writing.ts b/apps/design-system/registry/copy-writing.ts new file mode 100644 index 0000000000000..33afc906d8089 --- /dev/null +++ b/apps/design-system/registry/copy-writing.ts @@ -0,0 +1,68 @@ +import { Registry } from './schema' + +export const copyWriting: Registry = [ + { + name: 'copy-button-verbs', + type: 'components:example', + files: ['example/copy-button-verbs.tsx'], + registryDependencies: ['button'], + category: 'Getting Started', + subcategory: 'Copywriting', + }, + { + name: 'copy-form-labels', + type: 'components:example', + files: ['example/copy-form-labels.tsx'], + registryDependencies: ['form'], + category: 'Getting Started', + subcategory: 'Copywriting', + }, + { + name: 'copy-error-messages', + type: 'components:example', + files: ['example/copy-error-messages.tsx'], + registryDependencies: ['form'], + category: 'Getting Started', + subcategory: 'Copywriting', + }, + { + name: 'copy-success-messages', + type: 'components:example', + files: ['example/copy-success-messages.tsx'], + registryDependencies: ['form'], + category: 'Getting Started', + subcategory: 'Copywriting', + }, + { + name: 'copy-tooltips', + type: 'components:example', + files: ['example/copy-tooltips.tsx'], + registryDependencies: ['tooltip'], + category: 'Getting Started', + subcategory: 'Copywriting', + }, + { + name: 'copy-loading-states', + type: 'components:example', + files: ['example/copy-loading-states.tsx'], + registryDependencies: ['loading-state'], + category: 'Getting Started', + subcategory: 'Copywriting', + }, + { + name: 'copy-empty-states', + type: 'components:example', + files: ['example/copy-empty-states.tsx'], + registryDependencies: ['empty-state'], + category: 'Getting Started', + subcategory: 'Copywriting', + }, + { + name: 'copy-confirmations', + type: 'components:example', + files: ['example/copy-confirmations.tsx'], + registryDependencies: ['confirmation'], + category: 'Getting Started', + subcategory: 'Copywriting', + }, +] diff --git a/apps/design-system/registry/default/example/copy-button-verbs.tsx b/apps/design-system/registry/default/example/copy-button-verbs.tsx new file mode 100644 index 0000000000000..3cf3c0715319c --- /dev/null +++ b/apps/design-system/registry/default/example/copy-button-verbs.tsx @@ -0,0 +1,22 @@ +'use client' + +import { Button } from 'ui' + +export default function CopyButtonVerbs() { + return ( +
+
+ Bad Example + + + +
+
+ Good Example + + + +
+
+ ) +} diff --git a/apps/design-system/registry/default/example/copy-confirmations.tsx b/apps/design-system/registry/default/example/copy-confirmations.tsx new file mode 100644 index 0000000000000..ce9406cbfc950 --- /dev/null +++ b/apps/design-system/registry/default/example/copy-confirmations.tsx @@ -0,0 +1,54 @@ +'use client' + +import { Button } from 'ui' +import { AlertTriangle } from 'lucide-react' + +export default function CopyConfirmations() { + return ( +
+
+ Bad Example +
+
+
+

Are you sure?

+

+ All data will be removed if this project is deleted +

+
+
+
+ + +
+
+
+
+ Good Example +
+
+
+

Delete this project?

+

+ This action cannot be undone and will permanently delete all data. Deleting this + project will remove all data. +

+
+
+
+ + +
+
+
+
+ ) +} diff --git a/apps/design-system/registry/default/example/copy-empty-states.tsx b/apps/design-system/registry/default/example/copy-empty-states.tsx new file mode 100644 index 0000000000000..1315f8e42b644 --- /dev/null +++ b/apps/design-system/registry/default/example/copy-empty-states.tsx @@ -0,0 +1,34 @@ +'use client' + +import { Button } from 'ui' +import { EmptyStatePresentational } from 'ui-patterns' +import { Key } from 'lucide-react' + +export default function CopyEmptyStates() { + return ( +
+
+ Bad Example +
+ +
+
+
+ Good Example +
+ + + +
+
+
+ ) +} diff --git a/apps/design-system/registry/default/example/copy-error-messages.tsx b/apps/design-system/registry/default/example/copy-error-messages.tsx new file mode 100644 index 0000000000000..7a19c25ca3134 --- /dev/null +++ b/apps/design-system/registry/default/example/copy-error-messages.tsx @@ -0,0 +1,24 @@ +'use client' + +import { CircleAlert } from 'lucide-react' + +export default function CopyErrorMessages() { + return ( +
+
+ Bad Example +
+ +

Something went wrong. Please try again.

+
+
+
+ Good Example +
+ +

Invalid API key. Check your project settings.

+
+
+
+ ) +} diff --git a/apps/design-system/registry/default/example/copy-form-labels.tsx b/apps/design-system/registry/default/example/copy-form-labels.tsx new file mode 100644 index 0000000000000..099afe5b906ed --- /dev/null +++ b/apps/design-system/registry/default/example/copy-form-labels.tsx @@ -0,0 +1,29 @@ +'use client' + +import { Input_Shadcn_, Label_Shadcn_ } from 'ui' + +export default function CopyFormLabels() { + return ( +
+
+ Bad Example +
+ Name your table + +

+ This field allows you to specify a name for your table using letters, numbers, and + underscores +

+
+
+
+ Good Example +
+ Table name + +

Letters, numbers, and underscores only

+
+
+
+ ) +} diff --git a/apps/design-system/registry/default/example/copy-loading-states.tsx b/apps/design-system/registry/default/example/copy-loading-states.tsx new file mode 100644 index 0000000000000..db1259dc19611 --- /dev/null +++ b/apps/design-system/registry/default/example/copy-loading-states.tsx @@ -0,0 +1,38 @@ +'use client' + +import { Button } from 'ui' + +export default function CopyLoadingStates() { + return ( +
+
+ Bad Example +
+ + + +
+
+
+ Good Example +
+ + + +
+
+
+ ) +} diff --git a/apps/design-system/registry/default/example/copy-success-messages.tsx b/apps/design-system/registry/default/example/copy-success-messages.tsx new file mode 100644 index 0000000000000..d5589fede236f --- /dev/null +++ b/apps/design-system/registry/default/example/copy-success-messages.tsx @@ -0,0 +1,24 @@ +'use client' + +import { CheckCircle } from 'lucide-react' + +export default function CopySuccessMessages() { + return ( +
+
+ Bad Example +
+ +

Success!

+
+
+
+ Good Example +
+ +

Table created successfully

+
+
+
+ ) +} diff --git a/apps/design-system/registry/default/example/copy-tooltips.tsx b/apps/design-system/registry/default/example/copy-tooltips.tsx new file mode 100644 index 0000000000000..594c562253388 --- /dev/null +++ b/apps/design-system/registry/default/example/copy-tooltips.tsx @@ -0,0 +1,51 @@ +'use client' + +import { Button, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from 'ui' +import { Info } from 'lucide-react' + +export default function CopyTooltips() { + return ( +
+
+ Bad Example +
+ + + +
+
+
+ Good Example +
+ + + +
+
+
+ ) +} diff --git a/apps/design-system/registry/registry.ts b/apps/design-system/registry/registry.ts index e03e39fe31bba..27f60ea8459da 100644 --- a/apps/design-system/registry/registry.ts +++ b/apps/design-system/registry/registry.ts @@ -2,5 +2,6 @@ import { Registry } from '@/registry/schema' import { examples } from '@/registry//examples' import { fragments } from '@/registry/fragments' import { charts } from '@/registry/charts' +import { copyWriting } from '@/registry/copy-writing' -export const registry: Registry = [...fragments, ...examples, ...charts] +export const registry: Registry = [...fragments, ...examples, ...charts, ...copyWriting] From 84e5df97ea28f0a58220d92a42e469f98d7c891c Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Mon, 15 Dec 2025 21:13:36 +0800 Subject: [PATCH 02/11] Fix log drains error handling (#41343) --- .../components/interfaces/LogDrains/LogDrains.tsx | 8 ++++---- .../NavigationBar/NavigationBar.utils.tsx | 15 ++++----------- apps/studio/data/log-drains/log-drains-query.ts | 8 ++++++++ 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/apps/studio/components/interfaces/LogDrains/LogDrains.tsx b/apps/studio/components/interfaces/LogDrains/LogDrains.tsx index ae111a5634978..6bc48cfaa3507 100644 --- a/apps/studio/components/interfaces/LogDrains/LogDrains.tsx +++ b/apps/studio/components/interfaces/LogDrains/LogDrains.tsx @@ -82,6 +82,10 @@ export function LogDrains({ return } + if (isError) { + return + } + if (!isLoading && !hasLogDrains) { return ( <> @@ -104,10 +108,6 @@ export function LogDrains({ ) } - if (isError) { - return - } - return ( <> diff --git a/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.utils.tsx b/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.utils.tsx index 9ab064fbb451a..af5a7161460f8 100644 --- a/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.utils.tsx +++ b/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.utils.tsx @@ -7,16 +7,7 @@ import { generateSettingsMenu } from 'components/layouts/ProjectSettingsLayout/S import type { Route } from 'components/ui/ui.types' import { EditorIndexPageLink } from 'data/prefetchers/project.$ref.editor' import type { Project } from 'data/projects/project-detail-query' -import { - Auth, - Database, - EdgeFunctions, - Realtime, - Reports, - SqlEditor, - Storage, - TableEditor, -} from 'icons' +import { Auth, Database, EdgeFunctions, Realtime, SqlEditor, Storage, TableEditor } from 'icons' import { IS_PLATFORM, PROJECT_STATUS } from 'lib/constants' export const generateToolRoutes = (ref?: string, project?: Project, features?: {}): Route[] => { @@ -193,7 +184,9 @@ export const generateSettingsRoutes = (ref?: string, project?: Project): Route[] key: 'settings', label: 'Project Settings', icon: , - link: ref && `/project/${ref}/settings/general`, + link: + ref && + (IS_PLATFORM ? `/project/${ref}/settings/general` : `/project/${ref}/settings/log-drains`), items: settingsMenu, }, ] diff --git a/apps/studio/data/log-drains/log-drains-query.ts b/apps/studio/data/log-drains/log-drains-query.ts index a5d616e5abf4b..d5567c47fe54b 100644 --- a/apps/studio/data/log-drains/log-drains-query.ts +++ b/apps/studio/data/log-drains/log-drains-query.ts @@ -1,5 +1,6 @@ import { useQuery } from '@tanstack/react-query' import { get, handleError } from 'data/fetchers' +import { MAX_RETRY_FAILURE_COUNT } from 'data/query-client' import type { ResponseError, UseCustomQueryOptions } from 'types' import { logDrainsKeys } from './keys' @@ -37,5 +38,12 @@ export const useLogDrainsQuery = ( queryFn: ({ signal }) => getLogDrains({ ref }, signal), enabled: enabled && !!ref, refetchOnMount: false, + retry: (failureCount, error) => { + if (error.code === 500 || error.message.includes('API error happened')) return false + if (failureCount < MAX_RETRY_FAILURE_COUNT) { + return true + } + return false + }, ...options, }) From 8d3c3cd7e98c30f23a56895b232d1a61f263dc64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Mon, 15 Dec 2025 22:10:37 +0800 Subject: [PATCH 03/11] chore: use platform SSO entitlement (#41158) * chore: use platform SSO entitlement * Update platform.d.ts * Update platform.d.ts * Smol fix * Fix --------- Co-authored-by: Joshen Lim --- .../interfaces/Organization/SSO/SSOConfig.tsx | 183 ++-- .../studio/hooks/misc/useCheckEntitlements.ts | 8 +- packages/api-types/types/platform.d.ts | 875 +----------------- 3 files changed, 131 insertions(+), 935 deletions(-) diff --git a/apps/studio/components/interfaces/Organization/SSO/SSOConfig.tsx b/apps/studio/components/interfaces/Organization/SSO/SSOConfig.tsx index da606e0cd4f8e..bfd5c0456fdc2 100644 --- a/apps/studio/components/interfaces/Organization/SSO/SSOConfig.tsx +++ b/apps/studio/components/interfaces/Organization/SSO/SSOConfig.tsx @@ -11,6 +11,7 @@ import { UpgradeToPro } from 'components/ui/UpgradeToPro' import { useSSOConfigCreateMutation } from 'data/sso/sso-config-create-mutation' import { useOrgSSOConfigQuery } from 'data/sso/sso-config-query' import { useSSOConfigUpdateMutation } from 'data/sso/sso-config-update-mutation' +import { useCheckEntitlements } from 'hooks/misc/useCheckEntitlements' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { DOCS_URL } from 'lib/constants' import { @@ -64,8 +65,8 @@ export const SSOConfig = () => { const FORM_ID = 'sso-config-form' const { data: organization } = useSelectedOrganizationQuery() - const plan = organization?.plan.id - const canSetupSSOConfig = ['team', 'enterprise', 'platform'].includes(plan ?? '') + const { hasAccess: hasAccessToSso, isLoading: isLoadingEntitlement } = + useCheckEntitlements('auth.platform.sso') const { data: ssoConfig, @@ -154,7 +155,15 @@ export const SSOConfig = () => { return ( - {!!plan && !canSetupSSOConfig ? ( + {isLoadingEntitlement || (hasAccessToSso && isLoadingSSOConfig) ? ( + + + + + + ) : isError && !isSSOProviderNotFound ? ( + + ) : !hasAccessToSso ? ( { secondaryText="SSO as a login option provides additional acccount security for your team by enforcing the use of an identity provider when logging into Supabase. Upgrade to Team or above to set up SSO for your organization." featureProposition="enable Single Sign-on (SSO)" /> - ) : ( - <> - {isLoadingSSOConfig && ( + ) : isSuccess || isSSOProviderNotFound ? ( + +
- + ( + + Enable and configure SSO for your organization. Learn more about SSO{' '} + + here + + . + + } + > + + + + + )} + /> - - )} - - {isError && !isSSOProviderNotFound && ( - - )} - {(isSuccess || isSSOProviderNotFound) && ( - - - + {(isSSOEnabled || ssoConfig) && ( + <> - ( - - Enable and configure SSO for your organization. Learn more about SSO{' '} - - here - - . - - } - > - - - - - )} - /> + - {(isSSOEnabled || ssoConfig) && ( - <> - - - - - - - + + + - - - + + + - - - - - )} + + + + + )} - - {form.formState.isDirty && ( - - )} - - - - - - )} - - )} + + {form.formState.isDirty && ( + + )} + + +
+ + + ) : null} ) diff --git a/apps/studio/hooks/misc/useCheckEntitlements.ts b/apps/studio/hooks/misc/useCheckEntitlements.ts index ab06352ae3473..97f6105657897 100644 --- a/apps/studio/hooks/misc/useCheckEntitlements.ts +++ b/apps/studio/hooks/misc/useCheckEntitlements.ts @@ -96,15 +96,17 @@ export function useCheckEntitlements( } }, [entitlementsData, featureKey, finalOrgSlug]) - const isLoading = shouldGetSelectedOrg ? isLoadingSelectedOrg : isLoadingEntitlements + const isLoading = shouldGetSelectedOrg + ? isLoadingSelectedOrg || isLoadingEntitlements + : isLoadingEntitlements const isSuccess = shouldGetSelectedOrg ? isSuccessSelectedOrg && isSuccessEntitlements : isSuccessEntitlements return { hasAccess: IS_PLATFORM ? entitlement?.hasAccess ?? false : true, - isLoading, - isSuccess, + isLoading: IS_PLATFORM ? isLoading : false, + isSuccess: IS_PLATFORM ? isSuccess : true, getEntitlementNumericValue: () => getEntitlementNumericValue(entitlement), isEntitlementUnlimited: () => isEntitlementUnlimited(entitlement), getEntitlementSetValues: () => getEntitlementSetValues(entitlement), diff --git a/packages/api-types/types/platform.d.ts b/packages/api-types/types/platform.d.ts index 7ab05a36245f3..5b1dfd39b30bc 100644 --- a/packages/api-types/types/platform.d.ts +++ b/packages/api-types/types/platform.d.ts @@ -415,23 +415,6 @@ export interface paths { patch?: never trace?: never } - '/platform/database/{ref}/hook-logs': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** Gets hook logs with the given ID */ - get: operations['HooksController_getHookLogs'] - put?: never - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } '/platform/feedback/conversations/{conversation_id}/custom-fields': { parameters: { query?: never @@ -576,40 +559,6 @@ export interface paths { patch?: never trace?: never } - '/platform/integrations/github/branches/{connection_id}': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** List GitHub connection branches */ - get: operations['GitHubBranchesController_listConnectionBranches'] - put?: never - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/platform/integrations/github/branches/{connection_id}/{branch_name}': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** Get GitHub connection branch */ - get: operations['GitHubBranchesController_getConnectionBranch'] - put?: never - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } '/platform/integrations/github/connections': { parameters: { query?: never @@ -739,23 +688,6 @@ export interface paths { patch?: never trace?: never } - '/platform/integrations/vercel/{organization_integration_id}': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - get?: never - put?: never - post?: never - /** Removes Vercel organization integration with the given id */ - delete: operations['VercelIntegrationController_removeVercelIntegration'] - options?: never - head?: never - patch?: never - trace?: never - } '/platform/integrations/vercel/connections': { parameters: { query?: never @@ -808,23 +740,6 @@ export interface paths { patch?: never trace?: never } - '/platform/integrations/vercel/connections/{organization_integration_id}': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** Gets installed vercel project connections for the given organization integration */ - get: operations['VercelConnectionsController_getVercelConnections'] - put?: never - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } '/platform/integrations/vercel/connections/project/{ref}': { parameters: { query?: never @@ -1992,8 +1907,7 @@ export interface paths { put?: never /** Creates user's profile */ post: operations['ProfileController_createProfile'] - /** Deletes user's profile */ - delete: operations['ProfileController_deleteProfile'] + delete?: never options?: never head?: never /** Updates user's profile */ @@ -2658,24 +2572,6 @@ export interface paths { patch?: never trace?: never } - '/platform/projects/{ref}/config/postgres': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** Gets project's Postgres config */ - get: operations['PostgresConfigController_getConfig'] - /** Updates project's Postgres config */ - put: operations['PostgresConfigController_updateConfig'] - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } '/platform/projects/{ref}/config/postgrest': { parameters: { query?: never @@ -2778,8 +2674,7 @@ export interface paths { delete?: never options?: never head?: never - /** Updates project's supavisor config */ - patch: operations['SupavisorConfigController_updateSupavisorConfig'] + patch?: never trace?: never } '/platform/projects/{ref}/content': { @@ -2793,8 +2688,7 @@ export interface paths { get: operations['ContentController_getContent'] /** Updates project's content */ put: operations['ContentController_updateWholeContent'] - /** Creates project's content */ - post: operations['ContentController_createContent'] + post?: never /** Deletes project's contents */ delete: operations['ContentController_deleteContents'] options?: never @@ -3011,23 +2905,6 @@ export interface paths { patch?: never trace?: never } - '/platform/projects/{ref}/live': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** Gets project health check */ - get: operations['HealthCheckController_projectHealthCheck'] - put?: never - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } '/platform/projects/{ref}/load-balancers': { parameters: { query?: never @@ -3257,23 +3134,6 @@ export interface paths { patch?: never trace?: never } - '/platform/projects/{ref}/restore/cancel': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - get?: never - put?: never - /** Cancels a failed restoration */ - post: operations['UnpauseController_cancelProjectRestoration'] - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } '/platform/projects/{ref}/restore/versions': { parameters: { query?: never @@ -3973,10 +3833,10 @@ export interface paths { cookie?: never } /** Gets list of namespaces from a bucket */ - get: operations['StorageAnalyticsBucketNamespacesController_getBuckets'] + get: operations['StorageAnalyticsBucketNamespacesController_getNamespaces'] put?: never /** Create a namespace within a bucket */ - post: operations['StorageAnalyticsBucketNamespacesController_createBucket'] + post: operations['StorageAnalyticsBucketNamespacesController_createNamespace'] delete?: never options?: never head?: never @@ -3994,7 +3854,7 @@ export interface paths { put?: never post?: never /** Drop a namespace within an analytics bucket */ - delete: operations['StorageAnalyticsBucketNamespaceController_deleteBucket'] + delete: operations['StorageAnalyticsBucketNamespaceController_deleteNamespace'] options?: never head?: never patch?: never @@ -4008,7 +3868,7 @@ export interface paths { cookie?: never } /** Gets list of tables from a namespace */ - get: operations['StorageAnalyticsBucketNamespaceTablesController_getBuckets'] + get: operations['StorageAnalyticsBucketNamespaceTablesController_getTables'] put?: never /** Create a table within a namespace */ post: operations['StorageAnalyticsBucketNamespaceTablesController_createTable'] @@ -4600,8 +4460,7 @@ export interface paths { post?: never delete?: never options?: never - /** Count the number of workflow runs for the given branch */ - head: operations['WorkflowRunController_countWorkflowRuns'] + head?: never patch?: never trace?: never } @@ -4945,23 +4804,6 @@ export interface components { name: string retention_days: number } - CreateContentBody: { - content?: { - [key: string]: unknown - } - description?: string - /** @default false */ - favorite?: boolean - /** Format: uuid */ - folder_id?: (null | (string | null)) | null - id?: string - name: string - owner_id?: number - /** @enum {string} */ - type: 'sql' | 'report' | 'log_sql' - /** @enum {string} */ - visibility: 'user' | 'project' | 'org' | 'public' - } CreateContentFolderBody: { name: string /** Format: uuid */ @@ -5631,8 +5473,8 @@ export interface components { | 'edge_functions_write' | 'edge_functions_secrets_read' | 'edge_functions_secrets_write' - | 'infra_add-ons_read' - | 'infra_add-ons_write' + | 'infra_add_ons_read' + | 'infra_add_ons_write' | 'infra_read_replicas_read' | 'infra_read_replicas_write' | 'project_snippets_read' @@ -6676,17 +6518,6 @@ export interface components { } updated_at: string } - GetVercelConnectionsResponse: { - foreign_project_id: string - id: string - inserted_at: string - metadata: { - [key: string]: unknown - } - organization_integration_id: string - supabase_project_ref: string - updated_at: string - }[] GetVercelProjectsResponse: { pagination: { count: number @@ -7125,6 +6956,7 @@ export interface components { | 'instances.compute_update_available_sizes' | 'storage.max_file_size' | 'security.audit_logs_days' + | 'security.questionnaire' | 'log.retention_days' | 'custom_domain' | 'vanity_subdomain' @@ -7792,37 +7624,6 @@ export interface components { relation_name: string relation_schema: string } - PostgresConfigResponse: { - /** @description Default unit: s */ - checkpoint_timeout?: string - effective_cache_size?: string - hot_standby_feedback?: boolean - logical_decoding_work_mem?: string - maintenance_work_mem?: string - max_connections?: number - max_locks_per_transaction?: number - max_parallel_maintenance_workers?: number - max_parallel_workers?: number - max_parallel_workers_per_gather?: number - max_replication_slots?: number - max_slot_wal_keep_size?: string - max_standby_archive_delay?: string - max_standby_streaming_delay?: string - max_wal_senders?: number - max_wal_size?: string - max_worker_processes?: number - /** @enum {string} */ - session_replication_role?: 'origin' | 'replica' | 'local' - shared_buffers?: string - /** @description Default unit: ms */ - statement_timeout?: string - track_activity_query_size?: string - track_commit_timestamp?: boolean - wal_keep_size?: string - /** @description Default unit: ms */ - wal_sender_timeout?: string - work_mem?: string - } PostgresExtension: { comment: string | null default_version: string @@ -9003,7 +8804,6 @@ export interface components { source_notification_id?: string } } - RestoreCancellation: Record RestoreLogicalBackupBody: { id: number } @@ -10109,38 +9909,6 @@ export interface components { server_idle_timeout?: number server_lifetime?: number } - UpdatePostgresConfigBody: { - /** @description Default unit: s */ - checkpoint_timeout?: string - effective_cache_size?: string - hot_standby_feedback?: boolean - logical_decoding_work_mem?: string - maintenance_work_mem?: string - max_connections?: number - max_locks_per_transaction?: number - max_parallel_maintenance_workers?: number - max_parallel_workers?: number - max_parallel_workers_per_gather?: number - max_replication_slots?: number - max_slot_wal_keep_size?: string - max_standby_archive_delay?: string - max_standby_streaming_delay?: string - max_wal_senders?: number - max_wal_size?: string - max_worker_processes?: number - restart_database?: boolean - /** @enum {string} */ - session_replication_role?: 'origin' | 'replica' | 'local' - shared_buffers?: string - /** @description Default unit: ms */ - statement_timeout?: string - track_activity_query_size?: string - track_commit_timestamp?: boolean - wal_keep_size?: string - /** @description Default unit: ms */ - wal_sender_timeout?: string - work_mem?: string - } UpdatePostgrestConfigBody: { db_extra_search_path?: string db_pool?: number @@ -10547,18 +10315,6 @@ export interface components { UpdateSubscriptionResponse: { pending_payment_intent_secret: string | null } - UpdateSupavisorConfigBody: { - default_pool_size?: number | null - /** - * @description Dedicated pooler mode for the project - * @enum {string} - */ - pool_mode?: 'transaction' | 'session' - } - UpdateSupavisorConfigResponse: { - default_pool_size: number | null - pool_mode: string - } UpdateUserBody: { ban_duration?: string } @@ -10614,11 +10370,14 @@ export interface components { [key: string]: unknown } description?: string - /** @description A missing `favorite` value means that the value will remained unchanged for updates. Defaults to `false` for inserts. */ + /** + * @description A missing `favorite` value means that the value will remained unchanged for updates. Defaults to `false` for inserts. + * @default false + */ favorite?: boolean /** Format: uuid */ folder_id?: (null | (string | null)) | null - id: string + id?: string name: string owner_id?: number project_id?: number @@ -10674,25 +10433,6 @@ export interface components { role?: string updated_at?: string } - UserContentObject: { - content: { - [key: string]: unknown - } - description?: string - favorite: boolean - folder_id?: string | null - id: string - inserted_at: string - last_updated_by?: number - name: string - owner_id: number - project_id: number - /** @enum {string} */ - type: 'sql' | 'report' | 'log_sql' - updated_at: string - /** @enum {string} */ - visibility: 'user' | 'project' | 'org' | 'public' - } ValidateSpamBody: { content: string subject: string @@ -12044,60 +11784,6 @@ export interface operations { } } } - HooksController_getHookLogs: { - parameters: { - query: { - id: number - limit?: number - offset?: number - } - header: { - 'x-connection-encrypted': string - } - path: { - /** @description Project ref */ - ref: string - } - cookie?: never - } - requestBody?: never - responses: { - 200: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Unauthorized */ - 401: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Forbidden action */ - 403: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Rate limit exceeded */ - 429: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Failed to get hook logs with the given ID */ - 500: { - headers: { - [name: string]: unknown - } - content?: never - } - } - } LinkConversationController_updateConversationCustomFields: { parameters: { query?: never @@ -12410,16 +12096,13 @@ export interface operations { } } } - GitHubBranchesController_listConnectionBranches: { + GitHubConnectionsController_listOrganizationGitHubConnections: { parameters: { - query?: { - page?: number - per_page?: number + query: { + organization_id: number } header?: never - path: { - connection_id: number - } + path?: never cookie?: never } requestBody?: never @@ -12429,10 +12112,10 @@ export interface operations { [name: string]: unknown } content: { - 'application/json': components['schemas']['GitHubBranchResponse'][] + 'application/json': components['schemas']['ListGitHubConnectionsResponse'] } } - /** @description Failed to list GitHub connection branches */ + /** @description Failed to list organization GitHub connections */ 500: { headers: { [name: string]: unknown @@ -12441,27 +12124,28 @@ export interface operations { } } } - GitHubBranchesController_getConnectionBranch: { + GitHubConnectionsController_createGitHubConnection: { parameters: { query?: never header?: never - path: { - branch_name: string - connection_id: number - } + path?: never cookie?: never } - requestBody?: never + requestBody: { + content: { + 'application/json': components['schemas']['CreateGitHubConnectionBody'] + } + } responses: { - 200: { + 201: { headers: { [name: string]: unknown } content: { - 'application/json': components['schemas']['GitHubBranchResponse'] + 'application/json': components['schemas']['CreateGitHubConnectionResponse'] } } - /** @description Failed to get GitHub connection branch */ + /** @description Failed to create project connections */ 500: { headers: { [name: string]: unknown @@ -12470,67 +12154,9 @@ export interface operations { } } } - GitHubConnectionsController_listOrganizationGitHubConnections: { + GitHubConnectionsController_deleteGitHubConnection: { parameters: { - query: { - organization_id: number - } - header?: never - path?: never - cookie?: never - } - requestBody?: never - responses: { - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['ListGitHubConnectionsResponse'] - } - } - /** @description Failed to list organization GitHub connections */ - 500: { - headers: { - [name: string]: unknown - } - content?: never - } - } - } - GitHubConnectionsController_createGitHubConnection: { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - requestBody: { - content: { - 'application/json': components['schemas']['CreateGitHubConnectionBody'] - } - } - responses: { - 201: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['CreateGitHubConnectionResponse'] - } - } - /** @description Failed to create project connections */ - 500: { - headers: { - [name: string]: unknown - } - content?: never - } - } - } - GitHubConnectionsController_deleteGitHubConnection: { - parameters: { - query?: never + query?: never header?: never path: { connection_id: string @@ -12797,32 +12423,6 @@ export interface operations { } } } - VercelIntegrationController_removeVercelIntegration: { - parameters: { - query?: never - header?: never - path: { - organization_integration_id: string - } - cookie?: never - } - requestBody?: never - responses: { - 200: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Failed to remove Vercel organization integration with the given id */ - 500: { - headers: { - [name: string]: unknown - } - content?: never - } - } - } VercelConnectionsController_createVercelConnection: { parameters: { query?: never @@ -12937,34 +12537,6 @@ export interface operations { } } } - VercelConnectionsController_getVercelConnections: { - parameters: { - query?: never - header?: never - path: { - organization_integration_id: string - } - cookie?: never - } - requestBody?: never - responses: { - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['GetVercelConnectionsResponse'] - } - } - /** @description Failed to get installed vercel connections for the given organization integration */ - 500: { - headers: { - [name: string]: unknown - } - content?: never - } - } - } VercelConnectionsController_getProjectVercelConnections: { parameters: { query?: never @@ -17097,36 +16669,6 @@ export interface operations { } } } - ProfileController_deleteProfile: { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - requestBody?: never - responses: { - 200: { - headers: { - [name: string]: unknown - } - content?: never - } - 403: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Failed to delete user's profile */ - 500: { - headers: { - [name: string]: unknown - } - content?: never - } - } - } ProfileController_updateProfile: { parameters: { query?: never @@ -19622,110 +19164,6 @@ export interface operations { } } } - PostgresConfigController_getConfig: { - parameters: { - query?: never - header?: never - path: { - /** @description Project ref */ - ref: string - } - cookie?: never - } - requestBody?: never - responses: { - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['PostgresConfigResponse'] - } - } - /** @description Unauthorized */ - 401: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Forbidden action */ - 403: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Rate limit exceeded */ - 429: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Failed to retrieve project's Postgres config */ - 500: { - headers: { - [name: string]: unknown - } - content?: never - } - } - } - PostgresConfigController_updateConfig: { - parameters: { - query?: never - header?: never - path: { - /** @description Project ref */ - ref: string - } - cookie?: never - } - requestBody: { - content: { - 'application/json': components['schemas']['UpdatePostgresConfigBody'] - } - } - responses: { - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['PostgresConfigResponse'] - } - } - /** @description Unauthorized */ - 401: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Forbidden action */ - 403: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Rate limit exceeded */ - 429: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Failed to update project's Postgres config */ - 500: { - headers: { - [name: string]: unknown - } - content?: never - } - } - } PostgrestConfigController_getPostgRESTConfig: { parameters: { query?: never @@ -20177,60 +19615,6 @@ export interface operations { } } } - SupavisorConfigController_updateSupavisorConfig: { - parameters: { - query?: never - header?: never - path: { - /** @description Project ref */ - ref: string - } - cookie?: never - } - requestBody: { - content: { - 'application/json': components['schemas']['UpdateSupavisorConfigBody'] - } - } - responses: { - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['UpdateSupavisorConfigResponse'] - } - } - /** @description Unauthorized */ - 401: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Forbidden action */ - 403: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Rate limit exceeded */ - 429: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Failed to update project's supavisor config */ - 500: { - headers: { - [name: string]: unknown - } - content?: never - } - } - } ContentController_getContent: { parameters: { query?: { @@ -20343,60 +19727,6 @@ export interface operations { } } } - ContentController_createContent: { - parameters: { - query?: never - header?: never - path: { - /** @description Project ref */ - ref: string - } - cookie?: never - } - requestBody: { - content: { - 'application/json': components['schemas']['CreateContentBody'] - } - } - responses: { - 201: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['UserContentObject'] - } - } - /** @description Unauthorized */ - 401: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Forbidden action */ - 403: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Rate limit exceeded */ - 429: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Failed to create project's content */ - 500: { - headers: { - [name: string]: unknown - } - content?: never - } - } - } ContentController_deleteContents: { parameters: { query: { @@ -21463,54 +20793,6 @@ export interface operations { } } } - HealthCheckController_projectHealthCheck: { - parameters: { - query?: never - header?: never - path: { - /** @description Project ref */ - ref: string - } - cookie?: never - } - requestBody?: never - responses: { - 200: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Unauthorized */ - 401: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Forbidden action */ - 403: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Rate limit exceeded */ - 429: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Failed to get project health check */ - 500: { - headers: { - [name: string]: unknown - } - content?: never - } - } - } LoadBalancersController_getLoadBalancers: { parameters: { query?: never @@ -22272,56 +21554,6 @@ export interface operations { } } } - UnpauseController_cancelProjectRestoration: { - parameters: { - query?: never - header?: never - path: { - /** @description Project ref */ - ref: string - } - cookie?: never - } - requestBody?: never - responses: { - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['RestoreCancellation'] - } - } - /** @description Unauthorized */ - 401: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Forbidden action */ - 403: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Rate limit exceeded */ - 429: { - headers: { - [name: string]: unknown - } - content?: never - } - /** @description Failed to cancel project restoration */ - 500: { - headers: { - [name: string]: unknown - } - content?: never - } - } - } UnpauseController_getAvailableImageVersions: { parameters: { query?: never @@ -24671,7 +23903,7 @@ export interface operations { } } } - StorageAnalyticsBucketNamespacesController_getBuckets: { + StorageAnalyticsBucketNamespacesController_getNamespaces: { parameters: { query?: never header?: never @@ -24723,7 +23955,7 @@ export interface operations { } } } - StorageAnalyticsBucketNamespacesController_createBucket: { + StorageAnalyticsBucketNamespacesController_createNamespace: { parameters: { query?: never header?: never @@ -24777,7 +24009,7 @@ export interface operations { } } } - StorageAnalyticsBucketNamespaceController_deleteBucket: { + StorageAnalyticsBucketNamespaceController_deleteNamespace: { parameters: { query?: never header?: never @@ -24828,7 +24060,7 @@ export interface operations { } } } - StorageAnalyticsBucketNamespaceTablesController_getBuckets: { + StorageAnalyticsBucketNamespaceTablesController_getTables: { parameters: { query?: never header?: never @@ -26762,37 +25994,6 @@ export interface operations { } } } - WorkflowRunController_countWorkflowRuns: { - parameters: { - query?: { - /** @description Branch ID */ - branch_id?: string - /** @description Project ref */ - project_ref?: string - } - header?: never - path?: never - cookie?: never - } - requestBody?: never - responses: { - 200: { - headers: { - /** @description total count value */ - 'X-Total-Count'?: number - [name: string]: unknown - } - content?: never - } - /** @description Failed to count workflow runs */ - 500: { - headers: { - [name: string]: unknown - } - content?: never - } - } - } WorkflowRunController_getWorkflowRunLogs: { parameters: { query?: never From 9ddeb3af03ee9e914d8d1cbc7e57926d34e37a73 Mon Sep 17 00:00:00 2001 From: Ignacio Dobronich Date: Mon, 15 Dec 2025 11:20:15 -0300 Subject: [PATCH 04/11] chore: auth hooks entitlement (#41036) --- .../interfaces/Auth/Hooks/AddHookDropdown.tsx | 82 +++++++++---------- .../interfaces/Auth/Hooks/hooks.constants.ts | 14 ++-- 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/apps/studio/components/interfaces/Auth/Hooks/AddHookDropdown.tsx b/apps/studio/components/interfaces/Auth/Hooks/AddHookDropdown.tsx index eed802a8f73d5..aafc243be6b1a 100644 --- a/apps/studio/components/interfaces/Auth/Hooks/AddHookDropdown.tsx +++ b/apps/studio/components/interfaces/Auth/Hooks/AddHookDropdown.tsx @@ -1,6 +1,5 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { ChevronDown } from 'lucide-react' - import { useParams } from 'common' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { InlineLink } from 'components/ui/InlineLink' @@ -18,6 +17,8 @@ import { } from 'ui' import { HOOKS_DEFINITIONS, HOOK_DEFINITION_TITLE, Hook } from './hooks.constants' import { extractMethod, isValidHook } from './hooks.utils' +import { useCheckEntitlements } from 'hooks/misc/useCheckEntitlements' +import { useMemo } from 'react' interface AddHookDropdownProps { buttonText?: string @@ -37,25 +38,29 @@ export const AddHookDropdown = ({ const { data: authConfig } = useAuthConfigQuery({ projectRef }) const { can: canUpdateAuthHook } = useAsyncCheckPermissions(PermissionAction.AUTH_EXECUTE, '*') + const { getEntitlementSetValues: getEntitledHookSet } = useCheckEntitlements('auth.hooks') + const entitledHookSet = getEntitledHookSet() - const hooks: Hook[] = HOOKS_DEFINITIONS.map((definition) => { - return { + const { availableHooks, nonAvailableHooks } = useMemo(() => { + const allHooks: Hook[] = HOOKS_DEFINITIONS.map((definition) => ({ ...definition, enabled: authConfig?.[definition.enabledKey] || false, method: extractMethod( authConfig?.[definition.uriKey] || '', authConfig?.[definition.secretsKey] || '' ), - } - }) + })) - const nonEnterpriseHookOptions = hooks.filter((h) => !isValidHook(h) && !h.enterprise) - const enterpriseHookOptions = hooks.filter((h) => !isValidHook(h) && h.enterprise) + const availableHooks: Hook[] = allHooks.filter( + (h) => !isValidHook(h) && entitledHookSet.includes(h.entitlementKey) + ) - const hasAccessToAdvancedHooks = - organization?.plan.id === 'team' || - organization?.plan.id === 'enterprise' || - organization?.plan.id === 'platform' + const nonAvailableHooks: Hook[] = allHooks.filter( + (h) => !isValidHook(h) && !entitledHookSet.includes(h.entitlementKey) + ) + + return { availableHooks, nonAvailableHooks } + }, [entitledHookSet, authConfig]) if (!canUpdateAuthHook) { return ( @@ -80,43 +85,38 @@ export const AddHookDropdown = ({
- {nonEnterpriseHookOptions.map((h) => ( + {availableHooks.length === 0 && ( + + All available hooks have been added + + )} + {availableHooks.map((h) => ( onSelectHook(h.title)}> {h.title} ))}
+ {nonAvailableHooks.length > 0 && ( + <> + {availableHooks.length > 0 && } - + +

Team or Enterprise Plan required

+

+ The following hooks are not available on{' '} + + your plan + + . +

+
- {!hasAccessToAdvancedHooks && ( - -

Team or Enterprise Plan required

-

- The following hooks are not available on{' '} - your plan. -

-
- )} - {enterpriseHookOptions.map((h) => - hasAccessToAdvancedHooks ? ( - onSelectHook(h.title)} - > - {h.title} - - ) : ( - onSelectHook(h.title)} - > - {h.title} - - ) + {nonAvailableHooks.map((h) => ( + + {h.title} + + ))} + )}
diff --git a/apps/studio/components/interfaces/Auth/Hooks/hooks.constants.ts b/apps/studio/components/interfaces/Auth/Hooks/hooks.constants.ts index eb5234874aa8a..28037f8adb5a2 100644 --- a/apps/studio/components/interfaces/Auth/Hooks/hooks.constants.ts +++ b/apps/studio/components/interfaces/Auth/Hooks/hooks.constants.ts @@ -3,20 +3,20 @@ export const HOOKS_DEFINITIONS = [ id: 'send-sms', title: 'Send SMS hook', subtitle: 'Will be called by Supabase Auth each time an SMS message needs to be sent.', + entitlementKey: 'HOOK_SEND_SMS', enabledKey: 'HOOK_SEND_SMS_ENABLED', uriKey: 'HOOK_SEND_SMS_URI', secretsKey: 'HOOK_SEND_SMS_SECRETS', - enterprise: false, docSlug: 'send-sms-hook', }, { id: 'send-email', title: 'Send Email hook', subtitle: 'Will be called by Supabase Auth each time an email message needs to be sent.', + entitlementKey: 'HOOK_SEND_EMAIL', enabledKey: 'HOOK_SEND_EMAIL_ENABLED', uriKey: 'HOOK_SEND_EMAIL_URI', secretsKey: 'HOOK_SEND_EMAIL_SECRETS', - enterprise: false, docSlug: 'send-email-hook', }, { @@ -24,10 +24,10 @@ export const HOOKS_DEFINITIONS = [ title: 'Customize Access Token (JWT) Claims hook', subtitle: 'Will be called by Supabase Auth each time a new JWT is created. It should return the claims you wish to be present in the JWT.', + entitlementKey: 'HOOK_CUSTOM_ACCESS_TOKEN', enabledKey: 'HOOK_CUSTOM_ACCESS_TOKEN_ENABLED', uriKey: 'HOOK_CUSTOM_ACCESS_TOKEN_URI', secretsKey: 'HOOK_CUSTOM_ACCESS_TOKEN_SECRETS', - enterprise: false, docSlug: 'custom-access-token-hook', }, { @@ -35,10 +35,10 @@ export const HOOKS_DEFINITIONS = [ title: 'MFA Verification Attempt hook', subtitle: 'Will be called by Supabase Auth each time a user tries to verify an MFA factor. Return a decision on whether to reject the attempt and future ones, or to allow the user to keep trying.', + entitlementKey: 'HOOK_MFA_VERIFICATION_ATTEMPT', enabledKey: 'HOOK_MFA_VERIFICATION_ATTEMPT_ENABLED', uriKey: 'HOOK_MFA_VERIFICATION_ATTEMPT_URI', secretsKey: 'HOOK_MFA_VERIFICATION_ATTEMPT_SECRETS', - enterprise: true, docSlug: 'mfa-verification-hook', }, { @@ -46,10 +46,10 @@ export const HOOKS_DEFINITIONS = [ title: 'Password Verification Attempt hook', subtitle: 'Will be called by Supabase Auth each time a user tries to sign in with a password. Return a decision whether to allow the user to reject the attempt, or to allow the user to keep trying.', + entitlementKey: 'HOOK_PASSWORD_VERIFICATION_ATTEMPT', enabledKey: 'HOOK_PASSWORD_VERIFICATION_ATTEMPT_ENABLED', uriKey: 'HOOK_PASSWORD_VERIFICATION_ATTEMPT_URI', secretsKey: 'HOOK_PASSWORD_VERIFICATION_ATTEMPT_SECRETS', - enterprise: true, docSlug: 'password-verification-hook', }, { @@ -57,10 +57,10 @@ export const HOOKS_DEFINITIONS = [ title: 'Before User Created hook', subtitle: 'Will be called by Supabase Auth before creating a new user. Returning an error will prevent the user from being created.', + entitlementKey: 'HOOK_BEFORE_USER_CREATED', enabledKey: 'HOOK_BEFORE_USER_CREATED_ENABLED', uriKey: 'HOOK_BEFORE_USER_CREATED_URI', secretsKey: 'HOOK_BEFORE_USER_CREATED_SECRETS', - enterprise: false, docSlug: 'before-user-created-hook', }, ] as const @@ -71,6 +71,7 @@ export interface Hook { id: string title: HOOK_DEFINITION_TITLE subtitle: string + entitlementKey: string enabled: boolean enabledKey: string uriKey: string @@ -79,5 +80,4 @@ export interface Hook { method: | { type: 'postgres'; schema: string; functionName: string } | { type: 'https'; url: string; secret: string } - enterprise: boolean } From 3a4c160a1fcda7174a89a76dc3664c291c5cbf98 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Mon, 15 Dec 2025 22:38:35 +0800 Subject: [PATCH 05/11] Fix replica panel closing when clicking new replica CTA from database selector (#41341) * Fix replica panel closing when clicking new replica CTA from database selector * Add comment * Fix --- .../Connect/DatabaseConnectionString.tsx | 2 +- .../interfaces/Reports/ReportFilterBar.tsx | 7 ++--- .../interfaces/Reports/ReportHeader.tsx | 2 +- .../components/interfaces/Reports/Reports.tsx | 2 +- .../SQLEditor/UtilityPanel/UtilityActions.tsx | 2 +- .../interfaces/Settings/API/ServiceList.tsx | 4 +-- .../Infrastructure/InfrastructureActivity.tsx | 8 ++--- .../Settings/Logs/PreviewFilterPanel.tsx | 2 +- .../Logs/PreviewFilterPanelWithUniversal.tsx | 31 ++----------------- .../studio/components/ui/DatabaseSelector.tsx | 20 +++++++----- .../[ref]/observability/query-performance.tsx | 4 +-- 11 files changed, 28 insertions(+), 56 deletions(-) diff --git a/apps/studio/components/interfaces/Connect/DatabaseConnectionString.tsx b/apps/studio/components/interfaces/Connect/DatabaseConnectionString.tsx index 2a51cfb11a713..608de62205c63 100644 --- a/apps/studio/components/interfaces/Connect/DatabaseConnectionString.tsx +++ b/apps/studio/components/interfaces/Connect/DatabaseConnectionString.tsx @@ -5,7 +5,7 @@ import { HTMLAttributes, ReactNode, useEffect, useMemo, useState } from 'react' import { useParams } from 'common' import { getAddons } from 'components/interfaces/Billing/Subscription/Subscription.utils' import AlertError from 'components/ui/AlertError' -import DatabaseSelector from 'components/ui/DatabaseSelector' +import { DatabaseSelector } from 'components/ui/DatabaseSelector' import { InlineLink } from 'components/ui/InlineLink' import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { usePgbouncerConfigQuery } from 'data/database/pgbouncer-config-query' diff --git a/apps/studio/components/interfaces/Reports/ReportFilterBar.tsx b/apps/studio/components/interfaces/Reports/ReportFilterBar.tsx index 2068758d2f547..babafd843c7ef 100644 --- a/apps/studio/components/interfaces/Reports/ReportFilterBar.tsx +++ b/apps/studio/components/interfaces/Reports/ReportFilterBar.tsx @@ -1,10 +1,11 @@ -import { ChevronDown, Database, Plus, RefreshCw, X } from 'lucide-react' +import { ChevronDown, Database, Network, Plus, RefreshCw, X } from 'lucide-react' import { ComponentProps, useEffect, useState } from 'react' import SVG from 'react-inlinesvg' +import { Popover, PopoverContent, PopoverTrigger } from '@ui/components/shadcn/ui/popover' import { useParams } from 'common' import { ButtonTooltip } from 'components/ui/ButtonTooltip' -import DatabaseSelector from 'components/ui/DatabaseSelector' +import { DatabaseSelector } from 'components/ui/DatabaseSelector' import { useLoadBalancersQuery } from 'data/read-replicas/load-balancers-query' import { Auth, Realtime, Storage } from 'icons' import { BASE_PATH } from 'lib/constants' @@ -22,8 +23,6 @@ import { import { DatePickerValue, LogsDatePicker } from '../Settings/Logs/Logs.DatePickers' import { REPORTS_DATEPICKER_HELPERS } from './Reports.constants' import type { ReportFilterItem } from './Reports.types' -import { Popover, PopoverContent, PopoverTrigger } from '@ui/components/shadcn/ui/popover' -import { Network } from 'lucide-react' interface ReportFilterBarProps { filters: ReportFilterItem[] diff --git a/apps/studio/components/interfaces/Reports/ReportHeader.tsx b/apps/studio/components/interfaces/Reports/ReportHeader.tsx index 394f37762ed61..39369c0625220 100644 --- a/apps/studio/components/interfaces/Reports/ReportHeader.tsx +++ b/apps/studio/components/interfaces/Reports/ReportHeader.tsx @@ -1,7 +1,7 @@ import { useRouter } from 'next/router' import { useParams } from 'common' -import DatabaseSelector from 'components/ui/DatabaseSelector' +import { DatabaseSelector } from 'components/ui/DatabaseSelector' interface ReportHeaderProps { title: string diff --git a/apps/studio/components/interfaces/Reports/Reports.tsx b/apps/studio/components/interfaces/Reports/Reports.tsx index e70be026ae3c9..84407d9684d67 100644 --- a/apps/studio/components/interfaces/Reports/Reports.tsx +++ b/apps/studio/components/interfaces/Reports/Reports.tsx @@ -9,7 +9,7 @@ import { toast } from 'sonner' import { useParams } from 'common' import { ButtonTooltip } from 'components/ui/ButtonTooltip' -import DatabaseSelector from 'components/ui/DatabaseSelector' +import { DatabaseSelector } from 'components/ui/DatabaseSelector' import { DateRangePicker } from 'components/ui/DateRangePicker' import NoPermission from 'components/ui/NoPermission' import { DEFAULT_CHART_CONFIG } from 'components/ui/QueryBlock/QueryBlock' diff --git a/apps/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityActions.tsx b/apps/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityActions.tsx index aaa9cdcd4c186..93147e806cab5 100644 --- a/apps/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityActions.tsx +++ b/apps/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityActions.tsx @@ -3,7 +3,7 @@ import { toast } from 'sonner' import { LOCAL_STORAGE_KEYS, useParams } from 'common' import { RoleImpersonationPopover } from 'components/interfaces/RoleImpersonationSelector/RoleImpersonationPopover' -import DatabaseSelector from 'components/ui/DatabaseSelector' +import { DatabaseSelector } from 'components/ui/DatabaseSelector' import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage' import { IS_PLATFORM } from 'lib/constants' import { useSqlEditorV2StateSnapshot } from 'state/sql-editor-v2' diff --git a/apps/studio/components/interfaces/Settings/API/ServiceList.tsx b/apps/studio/components/interfaces/Settings/API/ServiceList.tsx index e64d6f907773f..453eec2fff636 100644 --- a/apps/studio/components/interfaces/Settings/API/ServiceList.tsx +++ b/apps/studio/components/interfaces/Settings/API/ServiceList.tsx @@ -4,7 +4,7 @@ import { useEffect } from 'react' import { useParams } from 'common' import { ScaffoldSection } from 'components/layouts/Scaffold' -import DatabaseSelector from 'components/ui/DatabaseSelector' +import { DatabaseSelector } from 'components/ui/DatabaseSelector' import { useCustomDomainsQuery } from 'data/custom-domains/custom-domains-query' import { useLoadBalancersQuery } from 'data/read-replicas/load-balancers-query' import { useReadReplicasQuery } from 'data/read-replicas/replicas-query' @@ -14,7 +14,7 @@ import { useDatabaseSelectorStateSnapshot } from 'state/database-selector' import { Alert_Shadcn_, AlertTitle_Shadcn_, Badge, Card, CardContent, CardHeader } from 'ui' import { Input } from 'ui-patterns/DataInputs/Input' import { FormLayout } from 'ui-patterns/form/Layout/FormLayout' -import ShimmeringLoader from 'ui-patterns/ShimmeringLoader' +import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' import { PostgrestConfig } from './PostgrestConfig' export const ServiceList = () => { diff --git a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureActivity.tsx b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureActivity.tsx index 20e4cff7c8385..5370be4b0fbd9 100644 --- a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureActivity.tsx +++ b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureActivity.tsx @@ -17,7 +17,7 @@ import { ScaffoldSectionContent, ScaffoldSectionDetail, } from 'components/layouts/Scaffold' -import DatabaseSelector from 'components/ui/DatabaseSelector' +import { DatabaseSelector } from 'components/ui/DatabaseSelector' import { DateRangePicker } from 'components/ui/DateRangePicker' import { DocsButton } from 'components/ui/DocsButton' import Panel from 'components/ui/Panel' @@ -212,11 +212,7 @@ export const InfrastructureActivity = () => {
- { - setShowNewReplicaPanel(true) - }} - /> + {!isLoadingSubscription && ( <> void } -function useDebounce void>(callback: T, delay: number) { - const timeoutRef = useRef() - - useEffect(() => { - return () => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current) - } - } - }, []) - - return useCallback( - (...args: Parameters) => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current) - } - - timeoutRef.current = setTimeout(() => { - callback(...args) - }, delay) - }, - [callback, delay] - ) as T -} - export const PreviewFilterPanelWithUniversal = ({ isLoading, newCount, @@ -133,8 +108,6 @@ export const PreviewFilterPanelWithUniversal = ({ const router = useRouter() const { ref } = useParams() - const logName = router.pathname.split('/').pop() - const { data: loadBalancers } = useLoadBalancersQuery({ projectRef: ref }) const showDatabaseSelector = diff --git a/apps/studio/components/ui/DatabaseSelector.tsx b/apps/studio/components/ui/DatabaseSelector.tsx index e5831043cf156..686a9bd2a188e 100644 --- a/apps/studio/components/ui/DatabaseSelector.tsx +++ b/apps/studio/components/ui/DatabaseSelector.tsx @@ -8,10 +8,12 @@ import { useEffect, useState } from 'react' import { useParams } from 'common' import { Markdown } from 'components/interfaces/Markdown' import { REPLICA_STATUS } from 'components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.constants' +import { useShowNewReplicaPanel } from 'components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/use-show-new-replica' import { useReadReplicasQuery } from 'data/read-replicas/replicas-query' import { formatDatabaseID, formatDatabaseRegion } from 'data/read-replicas/replicas.utils' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { IS_PLATFORM } from 'lib/constants' +import { timeout } from 'lib/helpers' import { useDatabaseSelectorStateSnapshot } from 'state/database-selector' import { Button, @@ -36,18 +38,16 @@ interface DatabaseSelectorProps { additionalOptions?: { id: string; name: string }[] buttonProps?: ButtonProps onSelectId?: (id: string) => void // Optional callback - onCreateReplicaClick?: () => void portal?: boolean className?: string } -const DatabaseSelector = ({ +export const DatabaseSelector = ({ selectedDatabaseId: _selectedDatabaseId, variant = 'regular', additionalOptions = [], onSelectId = noop, buttonProps, - onCreateReplicaClick = noop, portal = true, className, }: DatabaseSelectorProps) => { @@ -55,6 +55,7 @@ const DatabaseSelector = ({ const { ref: projectRef } = useParams() const [open, setOpen] = useState(false) const [, setShowConnect] = useQueryState('showConnect', parseAsBoolean.withDefault(false)) + const { setShowNewReplicaPanel } = useShowNewReplicaPanel() const { infrastructureReadReplicas } = useIsFeatureEnabled(['infrastructure:read_replicas']) @@ -220,11 +221,16 @@ const DatabaseSelector = ({ > { + onClick={async () => { setOpen(false) // [Joshen] This is used in the Connect UI which is available across all pages - setShowConnect(null) - onCreateReplicaClick?.() + setShowConnect(false) + + // [Joshen] Adding a short timeout to compensate for the shift in focus + // the replica panel from a "portal" based component (e.g dialog, sheet, dropdown, etc) + // Although I'd prefer if there's a better way to resolve this + await timeout(50) + setShowNewReplicaPanel(true) }} className="w-full flex items-center gap-2" > @@ -240,5 +246,3 @@ const DatabaseSelector = ({ ) } - -export default DatabaseSelector diff --git a/apps/studio/pages/project/[ref]/observability/query-performance.tsx b/apps/studio/pages/project/[ref]/observability/query-performance.tsx index 4ccabcd5b7c2d..feaa527f55cea 100644 --- a/apps/studio/pages/project/[ref]/observability/query-performance.tsx +++ b/apps/studio/pages/project/[ref]/observability/query-performance.tsx @@ -12,9 +12,9 @@ import { useQueryPerformanceQuery } from 'components/interfaces/Reports/Reports. import { Presets } from 'components/interfaces/Reports/Reports.types' import { queriesFactory } from 'components/interfaces/Reports/Reports.utils' import { LogsDatePicker } from 'components/interfaces/Settings/Logs/Logs.DatePickers' -import DefaultLayout from 'components/layouts/DefaultLayout' +import { DefaultLayout } from 'components/layouts/DefaultLayout' import ObservabilityLayout from 'components/layouts/ObservabilityLayout/ObservabilityLayout' -import DatabaseSelector from 'components/ui/DatabaseSelector' +import { DatabaseSelector } from 'components/ui/DatabaseSelector' import { DocsButton } from 'components/ui/DocsButton' import { useReportDateRange } from 'hooks/misc/useReportDateRange' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' From 3dfb7ace25bb8e128b3cb3ba930f1ed748f417df Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Mon, 15 Dec 2025 22:41:29 +0800 Subject: [PATCH 06/11] Fix advisor panel header not scrollable (#41340) * Fix advisor panel header not scrollable * Smol --- .../ui/AIAssistantPanel/AIAssistantHeader.tsx | 2 +- .../components/ui/AdvisorPanel/AdvisorFilters.tsx | 10 ++++------ .../studio/components/ui/AdvisorPanel/AdvisorPanel.tsx | 1 - apps/studio/components/ui/EditorPanel/EditorPanel.tsx | 2 +- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/apps/studio/components/ui/AIAssistantPanel/AIAssistantHeader.tsx b/apps/studio/components/ui/AIAssistantPanel/AIAssistantHeader.tsx index 7e5d312da2e06..d3db906c65882 100644 --- a/apps/studio/components/ui/AIAssistantPanel/AIAssistantHeader.tsx +++ b/apps/studio/components/ui/AIAssistantPanel/AIAssistantHeader.tsx @@ -29,7 +29,7 @@ export const AIAssistantHeader = ({ const [isOptInModalOpen, setIsOptInModalOpen] = useState(false) return (
-
+
diff --git a/apps/studio/components/ui/AdvisorPanel/AdvisorFilters.tsx b/apps/studio/components/ui/AdvisorPanel/AdvisorFilters.tsx index 6fc70c1930cbd..7f57aed8ec562 100644 --- a/apps/studio/components/ui/AdvisorPanel/AdvisorFilters.tsx +++ b/apps/studio/components/ui/AdvisorPanel/AdvisorFilters.tsx @@ -23,7 +23,6 @@ interface AdvisorFiltersProps { onSeverityFiltersChange: (filters: AdvisorSeverity[]) => void statusFilters: string[] onStatusFiltersChange: (filters: string[]) => void - hasProjectRef?: boolean onClose: () => void isPlatform?: boolean } @@ -35,14 +34,13 @@ export const AdvisorFilters = ({ onSeverityFiltersChange, statusFilters, onStatusFiltersChange, - hasProjectRef = true, onClose, isPlatform = false, }: AdvisorFiltersProps) => { return ( -
-
- +
+
+ All @@ -63,7 +61,7 @@ export const AdvisorFilters = ({ )} -
+
{isPlatform && ( { .filter((status) => !notificationFilterStatuses.includes(status)) .forEach((status) => setNotificationFilters(status, 'status')) }} - hasProjectRef={hasProjectRef} onClose={handleClose} isPlatform={IS_PLATFORM} /> diff --git a/apps/studio/components/ui/EditorPanel/EditorPanel.tsx b/apps/studio/components/ui/EditorPanel/EditorPanel.tsx index c4dce58a3cc8b..10d7a6059e383 100644 --- a/apps/studio/components/ui/EditorPanel/EditorPanel.tsx +++ b/apps/studio/components/ui/EditorPanel/EditorPanel.tsx @@ -150,7 +150,7 @@ export const EditorPanel = () => { return (
-
+
{label}
{templates.length > 0 && ( From 34b5e0bf07ef6f193ffae1796b28f9a7a8d11888 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Mon, 15 Dec 2025 22:42:08 +0800 Subject: [PATCH 07/11] Fix deleting a user shows User not found toast error (#41339) --- apps/studio/components/interfaces/Auth/Users/UsersV2.tsx | 2 +- apps/studio/data/auth/user-delete-mutation.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/studio/components/interfaces/Auth/Users/UsersV2.tsx b/apps/studio/components/interfaces/Auth/Users/UsersV2.tsx index 1b6d7acc4b4d0..d0c910b52d98d 100644 --- a/apps/studio/components/interfaces/Auth/Users/UsersV2.tsx +++ b/apps/studio/components/interfaces/Auth/Users/UsersV2.tsx @@ -236,8 +236,8 @@ export const UsersV2 = () => { const { setValue: setSelectedUser, value: selectedUser } = useQueryStateWithSelect({ urlKey: 'show', + enabled: users.length > 0 && isSuccess, select: (id: string) => (id ? users?.find((u) => u.id === id)?.id : undefined), - enabled: !!users && !isLoading, onError: () => toast.error(`User not found`), }) diff --git a/apps/studio/data/auth/user-delete-mutation.ts b/apps/studio/data/auth/user-delete-mutation.ts index 17e0d9bd482a7..c898da043ea54 100644 --- a/apps/studio/data/auth/user-delete-mutation.ts +++ b/apps/studio/data/auth/user-delete-mutation.ts @@ -36,13 +36,13 @@ export const useUserDeleteMutation = ({ async onSuccess(data, variables, context) { const { projectRef, skipInvalidation = false } = variables + await onSuccess?.(data, variables, context) + if (!skipInvalidation) { await Promise.all([ queryClient.invalidateQueries({ queryKey: authKeys.usersInfinite(projectRef) }), ]) } - - await onSuccess?.(data, variables, context) }, async onError(data, variables, context) { if (onError === undefined) { From 98915473b6b0645d5d8fa4c012cac9a57361636e Mon Sep 17 00:00:00 2001 From: Chris Chinchilla Date: Mon, 15 Dec 2025 15:52:57 +0100 Subject: [PATCH 08/11] Docs: Update Development section to Development & Branching (#41342) * Text changes * Update icon --- .../NavigationMenu/MenuIconPicker.tsx | 3 +++ .../Navigation/NavigationMenu/MenuIcons.tsx | 22 +++++++++++++++++++ .../NavigationMenu.constants.ts | 8 +++---- apps/docs/content/guides/deployment.mdx | 6 ++--- 4 files changed, 32 insertions(+), 7 deletions(-) diff --git a/apps/docs/components/Navigation/NavigationMenu/MenuIconPicker.tsx b/apps/docs/components/Navigation/NavigationMenu/MenuIconPicker.tsx index 2a17390edf195..d9ed3451f402a 100644 --- a/apps/docs/components/Navigation/NavigationMenu/MenuIconPicker.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/MenuIconPicker.tsx @@ -3,6 +3,7 @@ import { Clock, Heart, Server, SquareStack, Telescope } from 'lucide-react' import { IconBranching, IconGitHub, + IconGitBranch, IconMenuApi, IconMenuAuth, IconMenuCli, @@ -85,6 +86,8 @@ function getMenuIcon(menuKey: string, width: number = 16, height: number = 16, c return case 'status': return + case 'git-branch': + return case 'github': return case 'support': diff --git a/apps/docs/components/Navigation/NavigationMenu/MenuIcons.tsx b/apps/docs/components/Navigation/NavigationMenu/MenuIcons.tsx index dd0667b168479..8d42222fdb9e5 100644 --- a/apps/docs/components/Navigation/NavigationMenu/MenuIcons.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/MenuIcons.tsx @@ -556,6 +556,28 @@ export function IconBranching({ width = 16, height = 16, className }: HomeMenuIc ) } +export function IconGitBranch({ width = 16, height = 16, className }: HomeMenuIcon) { + return ( + + + + ) +} + export function IconGitHub({ width = 16, height = 16, className }: HomeMenuIcon) { return ( -See the [self-hosting guides](/docs/guides/self-hosting) for instructions on hosting your own Supabase stack. +Read the [self-hosting guides](/docs/guides/self-hosting) for instructions on hosting your own Supabase stack. From 52e62a69f1a52e9df99f4d2471c507ef709a5f17 Mon Sep 17 00:00:00 2001 From: Chris Chinchilla Date: Mon, 15 Dec 2025 16:23:37 +0100 Subject: [PATCH 09/11] blog: Add warning to pricing blog (#41346) Add warning to pricing blog --- apps/www/_blog/2021-03-29-pricing.mdx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/www/_blog/2021-03-29-pricing.mdx b/apps/www/_blog/2021-03-29-pricing.mdx index 6d775169c0c60..051d5e3e8c76e 100644 --- a/apps/www/_blog/2021-03-29-pricing.mdx +++ b/apps/www/_blog/2021-03-29-pricing.mdx @@ -11,6 +11,12 @@ tags: date: '03-29-2021' --- + + +Pricing is now generally available. See [www.supabase.com/pricing](https://www.supabase.com/pricing). + + + ## Pricing is hard Many developers have been waiting for [pricing](/pricing) before building on Supabase. It has taken this long to announce pricing for one simple reason: pricing is hard. We've spent countless hours discussing and testing different models. We consulted users, OSS veterans, and SaaS Gurus. We explored and ruled out a number of options including: no Free Plan, pure usage based, pay-per-seat, pay-per-row, and "bolt-on" feature based pricing. From a3b7c8f3a72792d9e4cda2ef5cbdc6070ca6e0a4 Mon Sep 17 00:00:00 2001 From: Chris Chinchilla Date: Mon, 15 Dec 2025 16:45:21 +0100 Subject: [PATCH 10/11] docs: Check and clarify API keys (#41200) * Update parial * Add partial to quickstarts * Auth section * More * Prettier * Realtime * Add soft links to frameworks * Add tab * Fix typo * More changes * Updates * Prettier --- apps/docs/content/_partials/api_settings.mdx | 6 +++-- .../content/_partials/api_settings_steps.mdx | 6 ++++- .../_partials/kotlin_project_setup.mdx | 3 ++- .../docs/content/_partials/metrics_access.mdx | 4 +-- apps/docs/content/_partials/project_setup.mdx | 3 ++- apps/docs/content/guides/api/api-keys.mdx | 15 +++++++++++ .../content/guides/api/creating-routes.mdx | 26 ++++++++++++------- .../guides/auth/quickstarts/nextjs.mdx | 8 ++++-- .../guides/auth/quickstarts/react-native.mdx | 5 +++- .../content/guides/auth/quickstarts/react.mdx | 8 ++++-- .../with-expo-react-native-social-auth.mdx | 4 +-- .../auth/server-side/creating-a-client.mdx | 2 ++ .../getting-started/quickstarts/flutter.mdx | 2 ++ .../getting-started/quickstarts/hono.mdx | 3 +++ .../quickstarts/ios-swiftui.mdx | 2 ++ .../getting-started/quickstarts/kotlin.mdx | 2 ++ .../getting-started/quickstarts/nextjs.mdx | 2 ++ .../getting-started/quickstarts/nuxtjs.mdx | 1 + .../getting-started/quickstarts/reactjs.mdx | 2 ++ .../getting-started/quickstarts/refine.mdx | 1 + .../getting-started/quickstarts/solidjs.mdx | 2 ++ .../getting-started/quickstarts/sveltekit.mdx | 1 + .../getting-started/quickstarts/vue.mdx | 1 + .../tutorials/with-angular.mdx | 2 +- .../tutorials/with-expo-react-native.mdx | 2 +- .../tutorials/with-flutter.mdx | 2 +- .../tutorials/with-ionic-angular.mdx | 2 +- .../tutorials/with-ionic-react.mdx | 2 +- .../tutorials/with-ionic-vue.mdx | 2 +- .../getting-started/tutorials/with-kotlin.mdx | 2 +- .../getting-started/tutorials/with-nextjs.mdx | 2 +- .../getting-started/tutorials/with-nuxt-3.mdx | 2 +- .../getting-started/tutorials/with-react.mdx | 2 +- .../tutorials/with-redwoodjs.mdx | 2 +- .../getting-started/tutorials/with-refine.mdx | 2 +- .../tutorials/with-solidjs.mdx | 2 +- .../getting-started/tutorials/with-svelte.mdx | 6 ++--- .../tutorials/with-sveltekit.mdx | 2 +- .../getting-started/tutorials/with-swift.mdx | 2 +- .../getting-started/tutorials/with-vue-3.mdx | 2 +- .../content/guides/realtime/broadcast.mdx | 15 ++++++++++- .../docs/content/guides/realtime/presence.mdx | 15 ++++++++++- 42 files changed, 132 insertions(+), 45 deletions(-) diff --git a/apps/docs/content/_partials/api_settings.mdx b/apps/docs/content/_partials/api_settings.mdx index 090ae9ba089f0..7f19874cef807 100644 --- a/apps/docs/content/_partials/api_settings.mdx +++ b/apps/docs/content/_partials/api_settings.mdx @@ -2,15 +2,17 @@ Now that you've created some database tables, you are ready to insert data using the auto-generated API. -To do this, you need to get the Project URL and key. Get the URL from [the API settings section](/dashboard/project/_/settings/api) of a project and the key from the [the API Keys section of a project's Settings page](/dashboard/project/_/settings/api-keys/). +To do this, you need to get the Project URL and key from [the project **Connect** dialog](/dashboard/project/\_?showConnect=true&connectTab={{ .tab }}&framework={{ .framework }}). Supabase is changing the way keys work to improve project security and developer experience. You can [read the full announcement](https://github.com/orgs/supabase/discussions/29260), but in the transition period, you can use both the current `anon` and `service_role` keys and the new publishable key with the form `sb_publishable_xxx` which will replace the older keys. -To get the key values, open [the API Keys section of a project's Settings page](/dashboard/project/_/settings/api-keys/) and do the following: +In most cases, you can get the correct key from [the Project's **Connect** dialog](/dashboard/project/\_?showConnect=true&connectTab={{ .tab }}&framework={{ .framework }}), but if you want a specific key, you can find all keys in [the API Keys section of a Project's Settings page](/dashboard/project/_/settings/api-keys/): - **For legacy keys**, copy the `anon` key for client-side operations and the `service_role` key for server-side operations from the **Legacy API Keys** tab. - **For new keys**, open the **API Keys** tab, if you don't have a publishable key already, click **Create new API Keys**, and copy the value from the **Publishable key** section. + +[Read the API keys docs](/docs/guides/api/api-keys) for a full explanation of all key types and their uses. diff --git a/apps/docs/content/_partials/api_settings_steps.mdx b/apps/docs/content/_partials/api_settings_steps.mdx index dbd3d3cd7e6be..e3f21307de0de 100644 --- a/apps/docs/content/_partials/api_settings_steps.mdx +++ b/apps/docs/content/_partials/api_settings_steps.mdx @@ -1,12 +1,16 @@ {/* TODO: How to completely consolidate partials? */} +You can also get the Project URL and key from [the project's **Connect** dialog](/dashboard/project/\_?showConnect=true&connectTab={{ .tab }}&framework={{ .framework }}). + Supabase is changing the way keys work to improve project security and developer experience. You can [read the full announcement](https://github.com/orgs/supabase/discussions/29260), but in the transition period, you can use both the current `anon` and `service_role` keys and the new publishable key with the form `sb_publishable_xxx` which will replace the older keys. -To get the key values, open [the API Keys section of a project's Settings page](/dashboard/project/_/settings/api-keys/) and do the following: +In most cases, you can get the correct key from [the Project's **Connect** dialog](/dashboard/project/\_?showConnect=true&connectTab={{ .tab }}&framework={{ .framework }}), but if you want a specific key, you can find all keys in [the API Keys section of a Project's Settings page](/dashboard/project/_/settings/api-keys/): - **For legacy keys**, copy the `anon` key for client-side operations and the `service_role` key for server-side operations from the **Legacy API Keys** tab. - **For new keys**, open the **API Keys** tab, if you don't have a publishable key already, click **Create new API Keys**, and copy the value from the **Publishable key** section. + +[Read the API keys docs](/docs/guides/api/api-keys) for a full explanation of all key types and their uses. diff --git a/apps/docs/content/_partials/kotlin_project_setup.mdx b/apps/docs/content/_partials/kotlin_project_setup.mdx index 751559eaff0ce..1f32521eefd97 100644 --- a/apps/docs/content/_partials/kotlin_project_setup.mdx +++ b/apps/docs/content/_partials/kotlin_project_setup.mdx @@ -33,7 +33,8 @@ Now we are going to set up the database schema. You can just copy/paste the SQL -<$Partial path="api_settings.mdx" /> +<$Partial path="api_settings.mdx" variables={{ "framework": "{{ .framework }}", "tab": "{{ .tab }}" }} +/> ### Set up Google authentication diff --git a/apps/docs/content/_partials/metrics_access.mdx b/apps/docs/content/_partials/metrics_access.mdx index bd16c90b3487b..89cc6cf2cb061 100644 --- a/apps/docs/content/_partials/metrics_access.mdx +++ b/apps/docs/content/_partials/metrics_access.mdx @@ -23,7 +23,7 @@ className="rounded-lg border border-foreground/10 bg-surface-100 text-foreground 3. Authenticate with HTTP Basic Auth: - **Username**: `service_role` - - **Password**: a service role secret (JWT) from [**Project Settings > JWT** (opens in a new tab)](/dashboard/project/_/settings/jwt) or any other Secret API key from [**Project Settings > API keys** (opens in a new tab)](/dashboard/project/_/settings/api-keys) + - **Password**: a service role secret (JWT) from [**Project Settings > JWT**](/dashboard/project/_/settings/jwt) or any other Secret API key from [**Project Settings > API keys** (opens in a new tab)](/dashboard/project/_/settings/api-keys) Testing locally is as simple as running `curl` with your service role secret: @@ -34,7 +34,7 @@ className="rounded-lg border border-foreground/10 bg-surface-100 text-foreground You can provision long-lived automation tokens in two ways: - - Create an account access token once at [**Account Settings > Access Tokens (opens in a new tab)**](/dashboard/account/tokens) and reuse it wherever you configure observability tooling. + - Create an account access token once at [**Account Settings > Access Tokens**](/dashboard/account/tokens) and reuse it wherever you configure observability tooling. - **Optional**: programmatically exchange an access token for project API keys via the [Management API ](/docs/reference/api/management-projects-api-keys-retrieve'). ```bash diff --git a/apps/docs/content/_partials/project_setup.mdx b/apps/docs/content/_partials/project_setup.mdx index 3548980bcea2c..f1601388316c0 100644 --- a/apps/docs/content/_partials/project_setup.mdx +++ b/apps/docs/content/_partials/project_setup.mdx @@ -55,4 +55,5 @@ supabase migration new user_management_starter -<$Partial path="api_settings.mdx" /> +<$Partial path="api_settings.mdx" variables={{ "framework": "{{ .framework }}", "tab": "{{ .tab }}" }} +/> diff --git a/apps/docs/content/guides/api/api-keys.mdx b/apps/docs/content/guides/api/api-keys.mdx index 6b32efd7ac19c..90ae6e7a75713 100644 --- a/apps/docs/content/guides/api/api-keys.mdx +++ b/apps/docs/content/guides/api/api-keys.mdx @@ -32,6 +32,21 @@ There are 4 types of API keys that can be used with Supabase: + + +Supabase is changing the way keys work to improve project security and developer experience. You can [read the full announcement](https://github.com/orgs/supabase/discussions/29260), but in the transition period, you can use both the current `anon` and `service_role` keys and the new publishable key with the form `sb_publishable_xxx` which will replace the older keys. + + + +## Where to find keys + +You can find API keys in a couple of different places. + +In most cases, you can get the correct key from [the Project's **Connect** dialog](/dashboard/project/_?showConnect=true), but if you want a specific key, you can find all keys in [the API Keys section of a Project's Settings page](/dashboard/project/_/settings/api-keys/): + +- **For legacy keys**, copy the `anon` key for client-side operations and the `service_role` key for server-side operations from the **Legacy API Keys** tab. +- **For new keys**, open the **API Keys** tab, if you don't have a publishable key already, click **Create new API Keys**, and copy the value from the **Publishable key** section. + ## `anon` and publishable keys The `anon` and publishable keys secure the public components of your application. Public components run in environments where it is impossible to secure any secrets. These include: diff --git a/apps/docs/content/guides/api/creating-routes.mdx b/apps/docs/content/guides/api/creating-routes.mdx index 2e987ce107418..7c84e6cc82e22 100644 --- a/apps/docs/content/guides/api/creating-routes.mdx +++ b/apps/docs/content/guides/api/creating-routes.mdx @@ -52,20 +52,26 @@ create table Every Supabase project has a unique API URL. Your API is secured behind an API gateway which requires an API Key for every request. -1. Go to the [Settings](/dashboard/project/_/settings/general) page in the Dashboard. -2. Click **API** in the sidebar. -3. Find your API `URL`, `anon`, and `service_role` keys on this page. +{/* TODO: Further consolidate partial */} - +To do this, you need to get the Project URL and key from [the project's **Connect** dialog](/dashboard/project/_?showConnect=true). + + + +Supabase is changing the way keys work to improve project security and developer experience. You can [read the full announcement](https://github.com/orgs/supabase/discussions/29260), but in the transition period, you can use both the current `anon` and `service_role` keys and the new publishable key with the form `sb_publishable_xxx` which will replace the older keys. + +In most cases, you can get the correct key from [the Project's **Connect** dialog](/dashboard/project/_?showConnect=true), but if you want a specific key, you can find all keys in [the API Keys section of a Project's Settings page](/dashboard/project/_/settings/api-keys/): + +- **For legacy keys**, copy the `anon` key for client-side operations and the `service_role` key for server-side operations from the **Legacy API Keys** tab. +- **For new keys**, open the **API Keys** tab, if you don't have a publishable key already, click **Create new API Keys**, and copy the value from the **Publishable key** section. + + + +[Read the API keys docs](/docs/guides/api/api-keys) for a full explanation of all key types and their uses. The REST API is accessible through the URL `https://.supabase.co/rest/v1` -Both of these routes require the `anon` key to be passed through an `apikey` header. +Both of these routes require the key to be passed through an `apikey` header. ## Using the API diff --git a/apps/docs/content/guides/auth/quickstarts/nextjs.mdx b/apps/docs/content/guides/auth/quickstarts/nextjs.mdx index a65eb1adcafe1..db5d05ee51817 100644 --- a/apps/docs/content/guides/auth/quickstarts/nextjs.mdx +++ b/apps/docs/content/guides/auth/quickstarts/nextjs.mdx @@ -52,9 +52,11 @@ hideToc: true - Rename `.env.example` to `.env.local` and populate with [your project's URL and Key](/dashboard/project/_/settings/api). + Rename `.env.example` to `.env.local` and populate with your Supabase connection variables: - <$Partial path="api_settings_steps.mdx" /> + + + @@ -65,6 +67,8 @@ hideToc: true NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=sb_publishable_... or anon key ``` + <$Partial path="api_settings_steps.mdx" /> + diff --git a/apps/docs/content/guides/auth/quickstarts/react-native.mdx b/apps/docs/content/guides/auth/quickstarts/react-native.mdx index 8e2eccc75b5d0..adf3ad3b5f978 100644 --- a/apps/docs/content/guides/auth/quickstarts/react-native.mdx +++ b/apps/docs/content/guides/auth/quickstarts/react-native.mdx @@ -66,7 +66,9 @@ hideToc: true Create a helper file `lib/supabase.ts` that exports a Supabase client using your Project URL and key. - <$Partial path="api_settings_steps.mdx" /> + + + @@ -106,6 +108,7 @@ hideToc: true }) } ``` + <$Partial path="api_settings_steps.mdx" /> diff --git a/apps/docs/content/guides/auth/quickstarts/react.mdx b/apps/docs/content/guides/auth/quickstarts/react.mdx index dc82dd8626b53..51c35b328faa1 100644 --- a/apps/docs/content/guides/auth/quickstarts/react.mdx +++ b/apps/docs/content/guides/auth/quickstarts/react.mdx @@ -63,9 +63,12 @@ hideToc: true - Create `.env.local` and populate with your project's URL and Key. + Rename `.env.example` to `.env.local` and populate with your Supabase connection variables: + + + + - <$Partial path="api_settings_steps.mdx" /> @@ -75,6 +78,7 @@ hideToc: true VITE_SUPABASE_URL=your-project-url VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY=sb_publishable_... or anon key ``` + <$Partial path="api_settings_steps.mdx" /> diff --git a/apps/docs/content/guides/auth/quickstarts/with-expo-react-native-social-auth.mdx b/apps/docs/content/guides/auth/quickstarts/with-expo-react-native-social-auth.mdx index be42dd87c98d8..2f6e0a9206fc3 100644 --- a/apps/docs/content/guides/auth/quickstarts/with-expo-react-native-social-auth.mdx +++ b/apps/docs/content/guides/auth/quickstarts/with-expo-react-native-social-auth.mdx @@ -16,7 +16,7 @@ If you get stuck while working through this guide, refer to the [full example on -<$Partial path="project_setup.mdx" /> +<$Partial path="project_setup.mdx" variables={{ "framework": "exporeactnative", "tab": "mobiles" }} /> ## Building the app @@ -832,7 +832,7 @@ For more information, follow the [Supabase Login with Apple](/docs/guides/auth/s - + #### Prerequisites Before proceeding, as per the mobile options you need an Apple Service ID. To obtain it you can follow the [Invertase Initial Setup Guide](https://github.com/invertase/react-native-apple-authentication/blob/main/docs/INITIAL_SETUP.md) and the [Invertase Android Setup Guide](https://github.com/invertase/react-native-apple-authentication/blob/main/docs/ANDROID_EXTRA.md) mentioned in the Invertase tab. diff --git a/apps/docs/content/guides/auth/server-side/creating-a-client.mdx b/apps/docs/content/guides/auth/server-side/creating-a-client.mdx index e91b5d24c7797..876c643677036 100644 --- a/apps/docs/content/guides/auth/server-side/creating-a-client.mdx +++ b/apps/docs/content/guides/auth/server-side/creating-a-client.mdx @@ -45,6 +45,8 @@ Create a `.env.local` file in the project root directory. In the file, set the p +<$Partial path="api_settings_steps.mdx" /> + diff --git a/apps/docs/content/guides/getting-started/quickstarts/flutter.mdx b/apps/docs/content/guides/getting-started/quickstarts/flutter.mdx index b36cbd8f6c992..c984a73595ee6 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/flutter.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/flutter.mdx @@ -79,6 +79,8 @@ hideToc: true } ``` + <$Partial path="api_settings_steps.mdx" variables={{ "framework": "flutter", "tab": "mobiles" }} /> + diff --git a/apps/docs/content/guides/getting-started/quickstarts/hono.mdx b/apps/docs/content/guides/getting-started/quickstarts/hono.mdx index 7efe526cbd8fa..6dde6d2013f5b 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/hono.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/hono.mdx @@ -61,6 +61,9 @@ hideToc: true cp .env.example .env ``` +{/* TODO: Not ideal for frameworks that have no entry in Connect */} +<$Partial path="api_settings_steps.mdx" variables={{ "framework": "", "tab": "frameworks" }} /> + diff --git a/apps/docs/content/guides/getting-started/quickstarts/ios-swiftui.mdx b/apps/docs/content/guides/getting-started/quickstarts/ios-swiftui.mdx index 2cda6ab608902..23114cd4437be 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/ios-swiftui.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/ios-swiftui.mdx @@ -58,6 +58,8 @@ hideToc: true ) ``` + <$Partial path="api_settings_steps.mdx" variables={{ "framework": "swift", "tab": "mobiles" }} /> + diff --git a/apps/docs/content/guides/getting-started/quickstarts/kotlin.mdx b/apps/docs/content/guides/getting-started/quickstarts/kotlin.mdx index 12711786238cf..bb919a5b8c904 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/kotlin.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/kotlin.mdx @@ -98,6 +98,8 @@ hideToc: true ... ``` + <$Partial path="api_settings_steps.mdx" variables={{ "framework": "androidkotlin", "tab": "mobiles" }} /> + diff --git a/apps/docs/content/guides/getting-started/quickstarts/nextjs.mdx b/apps/docs/content/guides/getting-started/quickstarts/nextjs.mdx index a3b42a6c9b67f..50442f48e5cb1 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/nextjs.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/nextjs.mdx @@ -58,6 +58,8 @@ hideToc: true + <$Partial path="api_settings_steps.mdx" variables={{ "framework": "nextjs", "tab": "frameworks" }} /> + diff --git a/apps/docs/content/guides/getting-started/quickstarts/nuxtjs.mdx b/apps/docs/content/guides/getting-started/quickstarts/nuxtjs.mdx index 4a0c47a1e2bdc..9e47c024e2e4a 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/nuxtjs.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/nuxtjs.mdx @@ -84,6 +84,7 @@ hideToc: true ``` + <$Partial path="api_settings_steps.mdx" variables={{ "framework": "nuxt", "tab": "frameworks" }} /> diff --git a/apps/docs/content/guides/getting-started/quickstarts/reactjs.mdx b/apps/docs/content/guides/getting-started/quickstarts/reactjs.mdx index ffafeb5391d37..a61018b11eff7 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/reactjs.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/reactjs.mdx @@ -74,6 +74,8 @@ hideToc: true + <$Partial path="api_settings_steps.mdx" variables={{ "framework": "react", "tab": "frameworks" }} /> + diff --git a/apps/docs/content/guides/getting-started/quickstarts/refine.mdx b/apps/docs/content/guides/getting-started/quickstarts/refine.mdx index 9c0f9783a8a16..18ca950ee585d 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/refine.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/refine.mdx @@ -100,6 +100,7 @@ hideToc: true }, }); ``` + <$Partial path="api_settings_steps.mdx" variables={{ "framework": "refine", "tab": "frameworks" }} /> diff --git a/apps/docs/content/guides/getting-started/quickstarts/solidjs.mdx b/apps/docs/content/guides/getting-started/quickstarts/solidjs.mdx index a0d82752f2e8e..6043d57f98580 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/solidjs.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/solidjs.mdx @@ -72,6 +72,8 @@ hideToc: true + <$Partial path="api_settings_steps.mdx" variables={{ "framework": "solidjs", "tab": "frameworks" }} /> + diff --git a/apps/docs/content/guides/getting-started/quickstarts/sveltekit.mdx b/apps/docs/content/guides/getting-started/quickstarts/sveltekit.mdx index 071118d0d8063..e0e064ec2b56f 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/sveltekit.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/sveltekit.mdx @@ -72,6 +72,7 @@ hideToc: true ``` + <$Partial path="api_settings_steps.mdx" variables={{ "framework": "sveltekit", "tab": "frameworks" }} /> diff --git a/apps/docs/content/guides/getting-started/quickstarts/vue.mdx b/apps/docs/content/guides/getting-started/quickstarts/vue.mdx index 0c73a9f5f6504..4d4e311d39740 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/vue.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/vue.mdx @@ -73,6 +73,7 @@ hideToc: true ``` + <$Partial path="api_settings_steps.mdx" variables={{ "framework": "vue", "tab": "frameworks" }} /> diff --git a/apps/docs/content/guides/getting-started/tutorials/with-angular.mdx b/apps/docs/content/guides/getting-started/tutorials/with-angular.mdx index 3aa93feb37594..fa99b87b179b5 100644 --- a/apps/docs/content/guides/getting-started/tutorials/with-angular.mdx +++ b/apps/docs/content/guides/getting-started/tutorials/with-angular.mdx @@ -13,7 +13,7 @@ If you get stuck while working through this guide, refer to the [full example on -<$Partial path="project_setup.mdx" /> +<$Partial path="project_setup.mdx" variables={{ "framework": "ionicangular", "tab": "mobiles" }} /> ## Building the app diff --git a/apps/docs/content/guides/getting-started/tutorials/with-expo-react-native.mdx b/apps/docs/content/guides/getting-started/tutorials/with-expo-react-native.mdx index 6521e47307dc7..05fe900af0f6a 100644 --- a/apps/docs/content/guides/getting-started/tutorials/with-expo-react-native.mdx +++ b/apps/docs/content/guides/getting-started/tutorials/with-expo-react-native.mdx @@ -14,7 +14,7 @@ If you get stuck while working through this guide, refer to the [full example on -<$Partial path="project_setup.mdx" /> +<$Partial path="project_setup.mdx" variables={{ "framework": "exporeactnative", "tab": "mobiles" }} /> ## Building the app diff --git a/apps/docs/content/guides/getting-started/tutorials/with-flutter.mdx b/apps/docs/content/guides/getting-started/tutorials/with-flutter.mdx index da29ecd8b2ff7..0ce422ecf8a15 100644 --- a/apps/docs/content/guides/getting-started/tutorials/with-flutter.mdx +++ b/apps/docs/content/guides/getting-started/tutorials/with-flutter.mdx @@ -14,7 +14,7 @@ If you get stuck while working through this guide, refer to the [full example on -<$Partial path="project_setup.mdx" /> +<$Partial path="project_setup.mdx" variables={{ "framework": "flutter", "tab": "mobiles" }} /> ## Building the app diff --git a/apps/docs/content/guides/getting-started/tutorials/with-ionic-angular.mdx b/apps/docs/content/guides/getting-started/tutorials/with-ionic-angular.mdx index 90aa0eec33f15..3ee88c2e33ffd 100644 --- a/apps/docs/content/guides/getting-started/tutorials/with-ionic-angular.mdx +++ b/apps/docs/content/guides/getting-started/tutorials/with-ionic-angular.mdx @@ -13,7 +13,7 @@ If you get stuck while working through this guide, refer to the [full example on -<$Partial path="project_setup.mdx" /> +<$Partial path="project_setup.mdx" variables={{ "framework": "ionicangular", "tab": "mobiles" }} /> ## Building the app diff --git a/apps/docs/content/guides/getting-started/tutorials/with-ionic-react.mdx b/apps/docs/content/guides/getting-started/tutorials/with-ionic-react.mdx index 0885c96f44cc8..56ad0ee998f2d 100644 --- a/apps/docs/content/guides/getting-started/tutorials/with-ionic-react.mdx +++ b/apps/docs/content/guides/getting-started/tutorials/with-ionic-react.mdx @@ -13,7 +13,7 @@ If you get stuck while working through this guide, refer to the [full example on -<$Partial path="project_setup.mdx" /> +<$Partial path="project_setup.mdx" variables={{ "framework": "ionicreact", "tab": "mobiles" }} /> ## Building the app diff --git a/apps/docs/content/guides/getting-started/tutorials/with-ionic-vue.mdx b/apps/docs/content/guides/getting-started/tutorials/with-ionic-vue.mdx index 4136c3ed5c25e..ed80d02077410 100644 --- a/apps/docs/content/guides/getting-started/tutorials/with-ionic-vue.mdx +++ b/apps/docs/content/guides/getting-started/tutorials/with-ionic-vue.mdx @@ -13,7 +13,7 @@ If you get stuck while working through this guide, refer to the [full example on -<$Partial path="project_setup.mdx" /> +<$Partial path="project_setup.mdx" variables={{ "framework": "vuejs", "tab": "frameworks" }} /> ## Building the app diff --git a/apps/docs/content/guides/getting-started/tutorials/with-kotlin.mdx b/apps/docs/content/guides/getting-started/tutorials/with-kotlin.mdx index 25dc9824ffd1f..e04e38c96b622 100644 --- a/apps/docs/content/guides/getting-started/tutorials/with-kotlin.mdx +++ b/apps/docs/content/guides/getting-started/tutorials/with-kotlin.mdx @@ -17,7 +17,7 @@ If you get stuck while working through this guide, refer to the [full example on -<$Partial path="kotlin_project_setup.mdx" /> +<$Partial path="kotlin_project_setup.mdx" variables={{ "framework": "androidkotlin", "tab": "mobiles" }} /> ## Building the app diff --git a/apps/docs/content/guides/getting-started/tutorials/with-nextjs.mdx b/apps/docs/content/guides/getting-started/tutorials/with-nextjs.mdx index ef89210d43ba6..e34045aac65e9 100644 --- a/apps/docs/content/guides/getting-started/tutorials/with-nextjs.mdx +++ b/apps/docs/content/guides/getting-started/tutorials/with-nextjs.mdx @@ -14,7 +14,7 @@ If you get stuck while working through this guide, refer to the [full example on -<$Partial path="project_setup.mdx" /> +<$Partial path="project_setup.mdx" variables={{ "framework": "nextjs", "tab": "frameworks" }} /> ## Building the app diff --git a/apps/docs/content/guides/getting-started/tutorials/with-nuxt-3.mdx b/apps/docs/content/guides/getting-started/tutorials/with-nuxt-3.mdx index d4d1c79344488..88868969d7214 100644 --- a/apps/docs/content/guides/getting-started/tutorials/with-nuxt-3.mdx +++ b/apps/docs/content/guides/getting-started/tutorials/with-nuxt-3.mdx @@ -14,7 +14,7 @@ If you get stuck while working through this guide, refer to the [full example on -<$Partial path="project_setup.mdx" /> +<$Partial path="project_setup.mdx" variables={{ "framework": "nuxt", "tab": "frameworks" }} /> ## Building the app diff --git a/apps/docs/content/guides/getting-started/tutorials/with-react.mdx b/apps/docs/content/guides/getting-started/tutorials/with-react.mdx index a877837bfa8a4..abb433c92e78d 100644 --- a/apps/docs/content/guides/getting-started/tutorials/with-react.mdx +++ b/apps/docs/content/guides/getting-started/tutorials/with-react.mdx @@ -14,7 +14,7 @@ If you get stuck while working through this guide, refer to the [full example on -<$Partial path="project_setup.mdx" variables={{ "framework": "React" }} /> +<$Partial path="project_setup.mdx" variables={{ "framework": "react", "tab": "frameworks" }} /> ## Building the app diff --git a/apps/docs/content/guides/getting-started/tutorials/with-redwoodjs.mdx b/apps/docs/content/guides/getting-started/tutorials/with-redwoodjs.mdx index 8518040eac8cf..a071af17097a7 100644 --- a/apps/docs/content/guides/getting-started/tutorials/with-redwoodjs.mdx +++ b/apps/docs/content/guides/getting-started/tutorials/with-redwoodjs.mdx @@ -39,7 +39,7 @@ Instead, we'll rely on the Supabase client to do some of the work on the **`web` That means you will want to refrain from running any `yarn rw prisma migrate` commands and also double check your build commands on deployment to ensure Prisma won't reset your database. Prisma currently doesn't support cross-schema foreign keys, so introspecting the schema fails due to how your Supabase `public` schema references the `auth.users`. -<$Partial path="project_setup.mdx" /> +<$Partial path="project_setup.mdx" variables={{ "framework": "", "tab": "frameworks" }} /> ## Building the app diff --git a/apps/docs/content/guides/getting-started/tutorials/with-refine.mdx b/apps/docs/content/guides/getting-started/tutorials/with-refine.mdx index ea5e70609014a..2f7b929dd28b2 100644 --- a/apps/docs/content/guides/getting-started/tutorials/with-refine.mdx +++ b/apps/docs/content/guides/getting-started/tutorials/with-refine.mdx @@ -25,7 +25,7 @@ It is possible to customize the `authProvider` for Supabase and as we'll see bel -<$Partial path="project_setup.mdx" /> +<$Partial path="project_setup.mdx" variables={{ "framework": "refine", "tab": "frameworks" }} /> ## Building the app diff --git a/apps/docs/content/guides/getting-started/tutorials/with-solidjs.mdx b/apps/docs/content/guides/getting-started/tutorials/with-solidjs.mdx index 9324596329435..6eef0ff067b0b 100644 --- a/apps/docs/content/guides/getting-started/tutorials/with-solidjs.mdx +++ b/apps/docs/content/guides/getting-started/tutorials/with-solidjs.mdx @@ -13,7 +13,7 @@ If you get stuck while working through this guide, refer to the [full example on -<$Partial path="project_setup.mdx" /> +<$Partial path="project_setup.mdx" variables={{ "framework": "solidjs", "tab": "frameworks" }} /> ## Building the app diff --git a/apps/docs/content/guides/getting-started/tutorials/with-svelte.mdx b/apps/docs/content/guides/getting-started/tutorials/with-svelte.mdx index 026815a95ac47..15c69b1c2fe0a 100644 --- a/apps/docs/content/guides/getting-started/tutorials/with-svelte.mdx +++ b/apps/docs/content/guides/getting-started/tutorials/with-svelte.mdx @@ -13,7 +13,7 @@ If you get stuck while working through this guide, refer to the [full example on -<$Partial path="project_setup.mdx" /> +<$Partial path="project_setup.mdx" variables={{ "framework": "sveltekit", "tab": "frameworks" }} /> ## Building the app @@ -73,7 +73,7 @@ meta="name=src/lib/Auth.svelte" After a user is signed in, allow them to edit their profile details and manage their account. Create a new component for that called `Account.svelte`. -<$CodeSample +<$CodeSample path="/user-management/svelte-user-management/src/lib/Account.svelte" lines={[[1, 4], [7, 11], [14, 33], [34,53], [55,73], [75,-1]]} meta="src/lib/Account.svelte" @@ -121,7 +121,7 @@ meta="name=src/lib/Avatar.svelte" And then you can add the widget to the Account page: -<$CodeSample +<$CodeSample path="/user-management/svelte-user-management/src/lib/Account.svelte" lines={[[1,1], [5,5], [71,73], [74,74], [92,-1]]} meta="src/lib/Account.svelte" diff --git a/apps/docs/content/guides/getting-started/tutorials/with-sveltekit.mdx b/apps/docs/content/guides/getting-started/tutorials/with-sveltekit.mdx index ccdc1bcdf5121..f83a941e77756 100644 --- a/apps/docs/content/guides/getting-started/tutorials/with-sveltekit.mdx +++ b/apps/docs/content/guides/getting-started/tutorials/with-sveltekit.mdx @@ -13,7 +13,7 @@ If you get stuck while working through this guide, refer to the [full example on -<$Partial path="project_setup.mdx" /> +<$Partial path="project_setup.mdx" variables={{ "framework": "sveltekit", "tab": "frameworks" }} /> ## Building the app diff --git a/apps/docs/content/guides/getting-started/tutorials/with-swift.mdx b/apps/docs/content/guides/getting-started/tutorials/with-swift.mdx index da2508ad2b024..89177e8ea8bf9 100644 --- a/apps/docs/content/guides/getting-started/tutorials/with-swift.mdx +++ b/apps/docs/content/guides/getting-started/tutorials/with-swift.mdx @@ -13,7 +13,7 @@ If you get stuck while working through this guide, refer to the [full example on -<$Partial path="project_setup.mdx" /> +<$Partial path="project_setup.mdx" variables={{ "framework": "swift", "tab": "mobiles" }} /> ## Building the app diff --git a/apps/docs/content/guides/getting-started/tutorials/with-vue-3.mdx b/apps/docs/content/guides/getting-started/tutorials/with-vue-3.mdx index 1d5185fd9310c..12e15b09d994f 100644 --- a/apps/docs/content/guides/getting-started/tutorials/with-vue-3.mdx +++ b/apps/docs/content/guides/getting-started/tutorials/with-vue-3.mdx @@ -14,7 +14,7 @@ If you get stuck while working through this guide, refer to the [full example on -<$Partial path="project_setup.mdx" /> +<$Partial path="project_setup.mdx" variables={{ "framework": "vuejs", "tab": "frameworks" }} /> ## Building the app diff --git a/apps/docs/content/guides/realtime/broadcast.mdx b/apps/docs/content/guides/realtime/broadcast.mdx index 1623beb441b96..34f35fee7d939 100644 --- a/apps/docs/content/guides/realtime/broadcast.mdx +++ b/apps/docs/content/guides/realtime/broadcast.mdx @@ -33,7 +33,20 @@ You can use the Supabase client libraries to receive Broadcast messages. ### Initialize the client -Go to your Supabase project's [API Settings](/dashboard/project/_/settings/api) and grab the `URL` and `anon` public API key. +{/* TODO: Further consolidate partial */} + +Get the Project URL and key from [the project's **Connect** dialog](/dashboard/project/_?showConnect=true). + + + +Supabase is changing the way keys work to improve project security and developer experience. You can [read the full announcement](https://github.com/orgs/supabase/discussions/29260), but in the transition period, you can use both the current `anon` and `service_role` keys and the new publishable key with the form `sb_publishable_xxx` which will replace the older keys. + +In most cases, you can get the correct key from [the Project's **Connect** dialog](/dashboard/project/_?showConnect=true), but if you want a specific key, you can find all keys in [the API Keys section of a Project's Settings page](/dashboard/project/_/settings/api-keys/): + +- **For legacy keys**, copy the `anon` key for client-side operations and the `service_role` key for server-side operations from the **Legacy API Keys** tab. +- **For new keys**, open the **API Keys** tab, if you don't have a publishable key already, click **Create new API Keys**, and copy the value from the **Publishable key** section. + + + +Supabase is changing the way keys work to improve project security and developer experience. You can [read the full announcement](https://github.com/orgs/supabase/discussions/29260), but in the transition period, you can use both the current `anon` and `service_role` keys and the new publishable key with the form `sb_publishable_xxx` which will replace the older keys. + +In most cases, you can get the correct key from [the Project's **Connect** dialog](/dashboard/project/_?showConnect=true), but if you want a specific key, you can find all keys in [the API Keys section of a Project's Settings page](/dashboard/project/_/settings/api-keys/): + +- **For legacy keys**, copy the `anon` key for client-side operations and the `service_role` key for server-side operations from the **Legacy API Keys** tab. +- **For new keys**, open the **API Keys** tab, if you don't have a publishable key already, click **Create new API Keys**, and copy the value from the **Publishable key** section. + + Date: Mon, 15 Dec 2025 15:53:18 +0000 Subject: [PATCH 11/11] fix(studio): update page layout for remaining account views (#41349) fix: page layouts for other account areas --- apps/studio/pages/account/audit.tsx | 32 +++++++++++++++--------- apps/studio/pages/account/security.tsx | 34 ++++++++++++++++---------- apps/studio/pages/account/tokens.tsx | 32 +++++++++++++++--------- 3 files changed, 61 insertions(+), 37 deletions(-) diff --git a/apps/studio/pages/account/audit.tsx b/apps/studio/pages/account/audit.tsx index 88b8f6b65ff66..d7cec7e762124 100644 --- a/apps/studio/pages/account/audit.tsx +++ b/apps/studio/pages/account/audit.tsx @@ -3,24 +3,32 @@ import AccountLayout from 'components/layouts/AccountLayout/AccountLayout' import AppLayout from 'components/layouts/AppLayout/AppLayout' import DefaultLayout from 'components/layouts/DefaultLayout' import OrganizationLayout from 'components/layouts/OrganizationLayout' -import { ScaffoldContainer } from 'components/layouts/Scaffold' -import { FormHeader } from 'components/ui/Forms/FormHeader' import type { NextPageWithLayout } from 'types' -import { cn } from 'ui' +import { PageContainer } from 'ui-patterns/PageContainer' +import { + PageHeader, + PageHeaderDescription, + PageHeaderMeta, + PageHeaderSummary, + PageHeaderTitle, +} from 'ui-patterns/PageHeader' const Audit: NextPageWithLayout = () => { return ( <> - - - - - div]:mt-8')} bottomPadding> + + + + Audit Logs + + View a detailed history of account activities and security events. + + + + + - + ) } diff --git a/apps/studio/pages/account/security.tsx b/apps/studio/pages/account/security.tsx index 69e15f6c8aabf..2388a568970ef 100644 --- a/apps/studio/pages/account/security.tsx +++ b/apps/studio/pages/account/security.tsx @@ -5,11 +5,6 @@ import AccountLayout from 'components/layouts/AccountLayout/AccountLayout' import AppLayout from 'components/layouts/AppLayout/AppLayout' import DefaultLayout from 'components/layouts/DefaultLayout' import OrganizationLayout from 'components/layouts/OrganizationLayout' -import { - ScaffoldContainer, - ScaffoldHeader, - ScaffoldSectionTitle, -} from 'components/layouts/Scaffold' import { UnknownInterface } from 'components/ui/UnknownInterface' import { useMfaListFactorsQuery } from 'data/profile/mfa-list-factors-query' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' @@ -21,6 +16,14 @@ import { CollapsibleContent_Shadcn_, CollapsibleTrigger_Shadcn_, } from 'ui' +import { PageContainer } from 'ui-patterns/PageContainer' +import { + PageHeader, + PageHeaderDescription, + PageHeaderMeta, + PageHeaderSummary, + PageHeaderTitle, +} from 'ui-patterns/PageHeader' const collapsibleClasses = [ 'bg-surface-100', @@ -46,13 +49,18 @@ const Security: NextPageWithLayout = () => { return ( <> - - - Security - - - - + + + + Security + + Manage your account security settings and authentication methods. + + + + + + - + ) } diff --git a/apps/studio/pages/account/tokens.tsx b/apps/studio/pages/account/tokens.tsx index 58863bc5b9c10..33ed3334fab86 100644 --- a/apps/studio/pages/account/tokens.tsx +++ b/apps/studio/pages/account/tokens.tsx @@ -8,16 +8,19 @@ import AccountLayout from 'components/layouts/AccountLayout/AccountLayout' import AppLayout from 'components/layouts/AppLayout/AppLayout' import DefaultLayout from 'components/layouts/DefaultLayout' import OrganizationLayout from 'components/layouts/OrganizationLayout' -import { - ScaffoldContainer, - ScaffoldHeader, - ScaffoldSectionTitle, -} from 'components/layouts/Scaffold' import { NewAccessToken } from 'data/access-tokens/access-tokens-create-mutation' import { DOCS_URL } from 'lib/constants' import type { NextPageWithLayout } from 'types' import { Button } from 'ui' import { Input } from 'ui-patterns/DataInputs/Input' +import { PageContainer } from 'ui-patterns/PageContainer' +import { + PageHeader, + PageHeaderDescription, + PageHeaderMeta, + PageHeaderSummary, + PageHeaderTitle, +} from 'ui-patterns/PageHeader' const UserAccessTokens: NextPageWithLayout = () => { const [newToken, setNewToken] = useState() @@ -25,12 +28,17 @@ const UserAccessTokens: NextPageWithLayout = () => { return ( <> - - - Access Tokens - - - + + + + Access Tokens + + Create and manage personal access tokens for API authentication. + + + + +
{newToken && setNewToken(undefined)} />}
@@ -65,7 +73,7 @@ const UserAccessTokens: NextPageWithLayout = () => { }} />
- + ) }