From 1363954156fbc98ac06136283713eeb2727f912b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Tue, 9 Sep 2025 13:10:46 +0800 Subject: [PATCH 01/13] ci: use blacksmith (#38543) --- .github/workflows/studio-unit-tests.yml | 3 +-- .github/workflows/typecheck.yml | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/studio-unit-tests.yml b/.github/workflows/studio-unit-tests.yml index f82f29127b5f2..8a49b83ef34c2 100644 --- a/.github/workflows/studio-unit-tests.yml +++ b/.github/workflows/studio-unit-tests.yml @@ -26,8 +26,7 @@ permissions: jobs: test: # Uses larger hosted runner as it significantly decreases build times - runs-on: - group: Default Larger Runners + runs-on: blacksmith-4vcpu-ubuntu-2404 strategy: matrix: test_number: [1] diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index 1ea8e4613028e..826b08a649cbc 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -16,8 +16,7 @@ permissions: jobs: typecheck: # Uses larger hosted runner as it significantly decreases build times - runs-on: - group: Default Larger Runners + runs-on: blacksmith-4vcpu-ubuntu-2404 steps: - name: Checkout From 8b3179ea26b7bbad9e717bd6b14c0bff7699b389 Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Tue, 9 Sep 2025 07:12:16 +0200 Subject: [PATCH 02/13] chore: custom ssl cert url (#38464) * chore: custom ssl cert url * use lodash template * fix lock --- .../Settings/Database/SSLConfiguration.tsx | 16 ++++++++++------ .../hooks/custom-content/CustomContent.types.ts | 2 ++ .../hooks/custom-content/custom-content.json | 4 +++- .../custom-content/custom-content.sample.json | 4 +++- .../custom-content/custom-content.schema.json | 8 +++++++- 5 files changed, 25 insertions(+), 9 deletions(-) diff --git a/apps/studio/components/interfaces/Settings/Database/SSLConfiguration.tsx b/apps/studio/components/interfaces/Settings/Database/SSLConfiguration.tsx index 52990b97e1e39..70d151e18ca7d 100644 --- a/apps/studio/components/interfaces/Settings/Database/SSLConfiguration.tsx +++ b/apps/studio/components/interfaces/Settings/Database/SSLConfiguration.tsx @@ -1,7 +1,8 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' +import { template } from 'lodash' import { Download, Loader2 } from 'lucide-react' import Link from 'next/link' -import { useEffect, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' import { toast } from 'sonner' import { useParams } from 'common' @@ -13,6 +14,7 @@ import { FormSection, FormSectionContent, FormSectionLabel } from 'components/ui import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query' import { useSSLEnforcementQuery } from 'data/ssl-enforcement/ssl-enforcement-query' import { useSSLEnforcementUpdateMutation } from 'data/ssl-enforcement/ssl-enforcement-update-mutation' +import { useCustomContent } from 'hooks/custom-content/useCustomContent' import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { Alert, Button, Switch, Tooltip, TooltipContent, TooltipTrigger } from 'ui' @@ -65,6 +67,12 @@ const SSLConfiguration = () => { const hasSSLCertificate = settings?.inserted_at !== undefined && new Date(settings.inserted_at) >= new Date('2021-04-30') + const { sslCertificateUrl: sslCertificateUrlTemplate } = useCustomContent(['ssl:certificate_url']) + const sslCertificateUrl = useMemo( + () => template(sslCertificateUrlTemplate ?? '')({ env }), + [sslCertificateUrlTemplate, env] + ) + useEffect(() => { if (!isLoading && sslEnforcementConfiguration) { setIsEnforced(initialIsEnforced) @@ -183,11 +191,7 @@ const SSLConfiguration = () => { ) : ( )} diff --git a/apps/studio/hooks/custom-content/CustomContent.types.ts b/apps/studio/hooks/custom-content/CustomContent.types.ts index 28568d054b327..b05e0ee0e7b89 100644 --- a/apps/studio/hooks/custom-content/CustomContent.types.ts +++ b/apps/studio/hooks/custom-content/CustomContent.types.ts @@ -29,4 +29,6 @@ export type CustomContentTypes = { connectFrameworks: (typeof CONNECTION_TYPES)[number] infraCloudProviders: CloudProvider[] + + sslCertificateUrl: string } diff --git a/apps/studio/hooks/custom-content/custom-content.json b/apps/studio/hooks/custom-content/custom-content.json index 57bd6db86b714..3c5c06777833e 100644 --- a/apps/studio/hooks/custom-content/custom-content.json +++ b/apps/studio/hooks/custom-content/custom-content.json @@ -9,5 +9,7 @@ "connect:frameworks": null, - "infra:cloud_providers": ["AWS", "AWS_K8S", "FLY"] + "infra:cloud_providers": ["AWS", "AWS_K8S", "FLY"], + + "ssl:certificate_url": "https://supabase-downloads.s3-ap-southeast-1.amazonaws.com/${env}/ssl/${env}-ca-2021.crt" } diff --git a/apps/studio/hooks/custom-content/custom-content.sample.json b/apps/studio/hooks/custom-content/custom-content.sample.json index da5a880f342ee..18730c60dd3c7 100644 --- a/apps/studio/hooks/custom-content/custom-content.sample.json +++ b/apps/studio/hooks/custom-content/custom-content.sample.json @@ -74,5 +74,7 @@ ] }, - "infra:cloud_providers": ["AWS_NIMBUS"] + "infra:cloud_providers": ["AWS_NIMBUS"], + + "ssl:certificate_url": "https://supabase-downloads.s3-ap-southeast-1.amazonaws.com/${env}/ssl/${env}-ca-2021.crt" } diff --git a/apps/studio/hooks/custom-content/custom-content.schema.json b/apps/studio/hooks/custom-content/custom-content.schema.json index adea1a5f9feec..90f41a819405e 100644 --- a/apps/studio/hooks/custom-content/custom-content.schema.json +++ b/apps/studio/hooks/custom-content/custom-content.schema.json @@ -81,6 +81,11 @@ "type": "string", "enum": ["AWS", "AWS_K8S", "AWS_NIMBUS", "FLY"] } + }, + + "ssl:certificate_url": { + "type": "string", + "description": "The URL of the SSL certificate" } }, "required": [ @@ -88,7 +93,8 @@ "project_homepage:example_projects", "logs:default_query", "connect:frameworks", - "infra:cloud_providers" + "infra:cloud_providers", + "ssl:certificate_url" ], "additionalProperties": false } From ec50337b0ce9da79f38f8f8686055c28fcaf34d0 Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Tue, 9 Sep 2025 07:26:30 +0200 Subject: [PATCH 03/13] fix: usage page select project ref (#38524) * fix: usage page select project ref * remove other instance of all-projects * Fix border issue --------- Co-authored-by: Joshen Lim Co-authored-by: kemal --- .../Organization/Usage/TotalUsage.tsx | 25 ++++++++----------- .../interfaces/Organization/Usage/Usage.tsx | 19 +++++++++----- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/apps/studio/components/interfaces/Organization/Usage/TotalUsage.tsx b/apps/studio/components/interfaces/Organization/Usage/TotalUsage.tsx index 3995d22d4a92d..aa0e359d484d9 100644 --- a/apps/studio/components/interfaces/Organization/Usage/TotalUsage.tsx +++ b/apps/studio/components/interfaces/Organization/Usage/TotalUsage.tsx @@ -1,5 +1,6 @@ import { useMemo } from 'react' +import { useBreakpoint } from 'common' import AlertError from 'components/ui/AlertError' import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { @@ -41,6 +42,7 @@ export const TotalUsage = ({ endDate, currentBillingCycleSelected, }: ComputeProps) => { + const isMobile = useBreakpoint('md') const isUsageBillingEnabled = subscription?.usage_billing_enabled const { billingAll } = useIsFeatureEnabled(['billing:all']) @@ -58,7 +60,7 @@ export const TotalUsage = ({ }) // When the user filters by project ref or selects a custom timeframe, we only display usage+project breakdown, but no costs/limits - const showRelationToSubscription = currentBillingCycleSelected && projectRef === 'all-projects' + const showRelationToSubscription = currentBillingCycleSelected && !projectRef const hasExceededAnyLimits = showRelationToSubscription && @@ -184,19 +186,12 @@ export const TotalUsage = ({ )}

)} -
+
{sortedBillingMetrics.map((metric, i) => { - const isLastBillingMetric = i === sortedBillingMetrics.length - 1 - const isLastInRow = isLastBillingMetric && computeMetrics.length === 0 - return (
{ return (
) })} + + {!isMobile && (sortedBillingMetrics.length + computeMetrics.length) % 2 === 1 && ( +
+ )}
)} diff --git a/apps/studio/components/interfaces/Organization/Usage/Usage.tsx b/apps/studio/components/interfaces/Organization/Usage/Usage.tsx index 728c45750b90f..e1a51f973873f 100644 --- a/apps/studio/components/interfaces/Organization/Usage/Usage.tsx +++ b/apps/studio/components/interfaces/Organization/Usage/Usage.tsx @@ -34,7 +34,14 @@ import { TotalUsage } from './TotalUsage' const Usage = () => { const { slug, projectRef } = useParams() const [dateRange, setDateRange] = useState() - const [selectedProjectRef, setSelectedProjectRef] = useState('all-projects') + const [selectedProjectRefInputValue, setSelectedProjectRefInputValue] = useState< + string | undefined + >('all-projects') + + // [Alaister] 'all-projects' is not a valid project ref, it's just used as an extra + // state for the select input. As such we need to remove it for the selected project ref + const selectedProjectRef = + selectedProjectRefInputValue === 'all-projects' ? undefined : selectedProjectRefInputValue const canReadSubscriptions = useCheckPermissions( PermissionAction.BILLING_READ, @@ -58,7 +65,7 @@ const Usage = () => { useEffect(() => { if (projectRef && isSuccess && orgProjects !== undefined) { if (orgProjects.find((project) => project.ref === projectRef)) { - setSelectedProjectRef(projectRef) + setSelectedProjectRefInputValue(projectRef) } } // [Joshen] Since we're already looking at isSuccess @@ -164,10 +171,10 @@ const Usage = () => { /> { - if (value === 'all-projects') setSelectedProjectRef('all-projects') - else setSelectedProjectRef(value) + if (value === 'all-projects') setSelectedProjectRefInputValue('all-projects') + else setSelectedProjectRefInputValue(value) }} > @@ -227,7 +234,7 @@ const Usage = () => {
- {selectedProjectRef && selectedProjectRef !== 'all-projects' ? ( + {selectedProjectRef ? ( Date: Tue, 9 Sep 2025 14:14:15 +0800 Subject: [PATCH 04/13] chore: use built-in GH runner (#38544) Temp fix until blacksmith is unblocked --- .github/workflows/studio-unit-tests.yml | 2 +- .github/workflows/typecheck.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/studio-unit-tests.yml b/.github/workflows/studio-unit-tests.yml index 8a49b83ef34c2..8ca7828af2ed1 100644 --- a/.github/workflows/studio-unit-tests.yml +++ b/.github/workflows/studio-unit-tests.yml @@ -26,7 +26,7 @@ permissions: jobs: test: # Uses larger hosted runner as it significantly decreases build times - runs-on: blacksmith-4vcpu-ubuntu-2404 + runs-on: ubuntu-latest strategy: matrix: test_number: [1] diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index 826b08a649cbc..d0f24df07f08c 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -16,7 +16,7 @@ permissions: jobs: typecheck: # Uses larger hosted runner as it significantly decreases build times - runs-on: blacksmith-4vcpu-ubuntu-2404 + runs-on: ubuntu-latest steps: - name: Checkout From 2e7a98f9980d9fac8f01c7e41c3dee8ba07bb6b3 Mon Sep 17 00:00:00 2001 From: Chris Chinchilla Date: Tue, 9 Sep 2025 08:27:44 +0200 Subject: [PATCH 05/13] docs: Fix Key changes for functions (#38372) Fix Key changes for functions --- apps/docs/content/guides/functions/secrets.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/docs/content/guides/functions/secrets.mdx b/apps/docs/content/guides/functions/secrets.mdx index 5a7a5ff431ac3..64bd322300740 100644 --- a/apps/docs/content/guides/functions/secrets.mdx +++ b/apps/docs/content/guides/functions/secrets.mdx @@ -10,7 +10,7 @@ subtitle: 'Manage sensitive data securely across environments.' Edge Functions have access to these secrets by default: - `SUPABASE_URL`: The API gateway for your Supabase project -- `SUPABASE_PUBLISHABLE_KEY`: The `publishable` key for your Supabase API. This is safe to use in a browser when you have Row Level Security enabled +- `SUPABASE_ANON_KEY`: The `anon` key for your Supabase API. This is safe to use in a browser when you have Row Level Security enabled - `SUPABASE_SERVICE_ROLE_KEY`: The `service_role` key for your Supabase API. This is safe to use in Edge Functions, but it should NEVER be used in a browser. This key will bypass Row Level Security - `SUPABASE_DB_URL`: The URL for your Postgres database. You can use this to connect directly to your database @@ -32,7 +32,7 @@ import { createClient } from 'npm:@supabase/supabase-js@2' // For user-facing operations (respects RLS) const supabase = createClient( Deno.env.get('SUPABASE_URL')!, - Deno.env.get('SUPABASE_PUBLISHABLE_KEY')! + Deno.env.get('SUPABASE_ANON_KEY')! ) // For admin operations (bypasses RLS) From ce3f7c8e814c98bfaaae937185218e7a5fcaf588 Mon Sep 17 00:00:00 2001 From: Saxon Fletcher Date: Tue, 9 Sep 2025 17:00:59 +1000 Subject: [PATCH 06/13] generic opt in copy (#38117) * generic opt in copy * privacy * revert privacy * remove foundation * update opt in copy * refine copy --- .../GeneralSettings/AIOptInLevelSelector.tsx | 19 ++++++++------ .../GeneralSettings/OptInToOpenAIToggle.tsx | 26 +++++++------------ 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/apps/studio/components/interfaces/Organization/GeneralSettings/AIOptInLevelSelector.tsx b/apps/studio/components/interfaces/Organization/GeneralSettings/AIOptInLevelSelector.tsx index 28e9686ef77e4..fd766e97cf546 100644 --- a/apps/studio/components/interfaces/Organization/GeneralSettings/AIOptInLevelSelector.tsx +++ b/apps/studio/components/interfaces/Organization/GeneralSettings/AIOptInLevelSelector.tsx @@ -39,7 +39,7 @@ export const AIOptInLevelSelector = ({ value: 'disabled', title: 'Disabled', description: - 'You do not consent to sharing any database information with Amazon Bedrock and understand that responses will be generic and not tailored to your database', + 'You do not consent to sharing any database information with third-party AI providers and understand that responses will be generic and not tailored to your database', }, ] : []), @@ -49,7 +49,7 @@ export const AIOptInLevelSelector = ({ value: 'schema', title: 'Schema Only', description: - 'You consent to sharing your database’s schema metadata (such as table and column names, data types, and relationships—but not actual database data) with Amazon Bedrock', + 'You consent to sharing your database’s schema metadata (such as table and column names, data types, and relationships—but not actual database data) with third-party AI providers', }, ] : []), @@ -59,7 +59,7 @@ export const AIOptInLevelSelector = ({ value: 'schema_and_log', title: 'Schema & Logs', description: - 'You consent to sharing your schema and logs (which may contain PII/database data) with Amazon Bedrock for better results', + 'You consent to sharing your schema and logs (which may contain PII/database data) with third-party AI providers for better results', }, ] : []), @@ -69,7 +69,7 @@ export const AIOptInLevelSelector = ({ value: 'schema_and_log_and_data', title: 'Schema, Logs & Database Data', description: - 'You consent to give Amazon Bedrock full access to run database read only queries and analyze results for optimal results', + 'You consent to give third-party AI providers full access to run database read-only queries and analyze results for optimal results', }, ] : []), @@ -83,10 +83,13 @@ export const AIOptInLevelSelector = ({

Supabase AI can provide more relevant answers if you choose to share different levels of - data. This feature is powered by Amazon Bedrock which does not store or log your prompts - and completions, nor does it use them to train AWS models or distribute them to third - parties. This is an organization-wide setting, so please select the level of data you - are comfortable sharing. + data. This feature is powered by third-party AI providers. This is an organization-wide + setting, so please select the level of data you are comfortable sharing. +

+

+ For organizations with HIPAA compliance enabled in their Supabase configuration, any + consented information will only be shared with third-party AI providers with whom + Supabase has established a Business Associate Agreement (BAA).

diff --git a/apps/studio/components/interfaces/Organization/GeneralSettings/OptInToOpenAIToggle.tsx b/apps/studio/components/interfaces/Organization/GeneralSettings/OptInToOpenAIToggle.tsx index 33197145ab31a..0cee6710c139c 100644 --- a/apps/studio/components/interfaces/Organization/GeneralSettings/OptInToOpenAIToggle.tsx +++ b/apps/studio/components/interfaces/Organization/GeneralSettings/OptInToOpenAIToggle.tsx @@ -27,28 +27,22 @@ export const OptInToOpenAIToggle = () => { className="flex flex-col gap-y-4 text-sm text-foreground-light" >

- Supabase AI utilizes Amazon Bedrock ("Bedrock"), a service designed with a strong focus - on data privacy and security. + Supabase AI utilizes third-party AI providers designed with a strong focus on data + privacy and security.

- Amazon Bedrock does not store or log your prompts and completions. This data is not used - to train any AWS models and is not distributed to third parties or model providers. - Model providers do not have access to Amazon Bedrock logs or customer prompts and - completions. + By default, only schema data is shared with third-party AI providers. This is not + retained by them nor used as training data. With your permission, Supabase may also + share customer-generated prompts, database data, and project logs with these providers. + This information is used solely to generate responses to your queries and is not + retained by the providers or used to train their models.

- By default, no information is shared with Bedrock unless you explicitly provide consent. - With your permission, Supabase may share customer-generated prompts, database schema, - database data, and project logs with Bedrock. This information is used solely to - generate responses to your queries and is not retained by Bedrock or used to train their - foundation models. -

- -

- If you are a HIPAA Covered Entity, please note that Bedrock is HIPAA eligible, and - Supabase has a Business Associate Agreement in place covering this use. + For organizations with HIPAA compliance enabled in their Supabase configuration, any + consented information will only be shared with third-party AI providers with whom + Supabase has established a Business Associate Agreement (BAA).

From 774825c1ccc17d1ef43a30683fb6d60b69741756 Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Tue, 9 Sep 2025 09:04:27 +0200 Subject: [PATCH 07/13] chore: customizable dashboard auth (#38516) * chore: customizable dashboard auth * minor fixes --------- Co-authored-by: Joshen Lim --- .../interfaces/SignIn/SignInWithCustom.tsx | 48 +++++++++ .../studio/components/ui/UnknownInterface.tsx | 12 ++- .../custom-content/CustomContent.types.ts | 2 + .../hooks/custom-content/custom-content.json | 2 + .../custom-content/custom-content.sample.json | 2 + .../custom-content/custom-content.schema.json | 5 + apps/studio/pages/sign-in-sso.tsx | 8 ++ apps/studio/pages/sign-in.tsx | 97 +++++++++++++------ apps/studio/pages/sign-up.tsx | 35 +++++-- .../enabled-features/enabled-features.json | 5 + .../enabled-features.schema.json | 21 ++++ 11 files changed, 193 insertions(+), 44 deletions(-) create mode 100644 apps/studio/components/interfaces/SignIn/SignInWithCustom.tsx diff --git a/apps/studio/components/interfaces/SignIn/SignInWithCustom.tsx b/apps/studio/components/interfaces/SignIn/SignInWithCustom.tsx new file mode 100644 index 0000000000000..a51b1a25a0da6 --- /dev/null +++ b/apps/studio/components/interfaces/SignIn/SignInWithCustom.tsx @@ -0,0 +1,48 @@ +import * as Sentry from '@sentry/nextjs' +import { useState } from 'react' +import { toast } from 'sonner' + +import { BASE_PATH } from 'lib/constants' +import { auth, buildPathWithParams } from 'lib/gotrue' +import { Button } from 'ui' + +interface SignInWithCustomProps { + providerName: string +} + +export const SignInWithCustom = ({ providerName }: SignInWithCustomProps) => { + const [loading, setLoading] = useState(false) + + async function handleCustomSignIn() { + setLoading(true) + + try { + // redirects to /sign-in to check if the user has MFA setup (handled in SignInLayout.tsx) + const redirectTo = buildPathWithParams( + `${ + process.env.NEXT_PUBLIC_VERCEL_ENV === 'preview' + ? location.origin + : process.env.NEXT_PUBLIC_SITE_URL + }${BASE_PATH}/sign-in-mfa?method=${providerName.toLowerCase()}` + ) + + const { error } = await auth.signInWithOAuth({ + // @ts-expect-error - providerName is a string + provider: providerName.toLowerCase(), + options: { redirectTo }, + }) + + if (error) throw error + } catch (error: any) { + toast.error(`Failed to sign in via ${providerName}: ${error.message}`) + Sentry.captureMessage('[CRITICAL] Failed to sign in via GH: ' + error.message) + setLoading(false) + } + } + + return ( + + ) +} diff --git a/apps/studio/components/ui/UnknownInterface.tsx b/apps/studio/components/ui/UnknownInterface.tsx index d53ef6c7aaff4..0baf1c5a4015c 100644 --- a/apps/studio/components/ui/UnknownInterface.tsx +++ b/apps/studio/components/ui/UnknownInterface.tsx @@ -1,11 +1,17 @@ import Link from 'next/link' -import { Button } from 'ui' +import { Button, cn } from 'ui' import { Admonition } from 'ui-patterns' -export const UnknownInterface = ({ urlBack }: { urlBack: string }) => { +export const UnknownInterface = ({ + urlBack, + fullHeight = true, +}: { + urlBack: string + fullHeight?: boolean +}) => { return ( -

+
{ + const signInWithSSOEnabled = useIsFeatureEnabled('dashboard_auth:sign_in_with_sso') + + if (!signInWithSSOEnabled) { + return + } + return ( <>
diff --git a/apps/studio/pages/sign-in.tsx b/apps/studio/pages/sign-in.tsx index 62f383d27773e..db421158ce828 100644 --- a/apps/studio/pages/sign-in.tsx +++ b/apps/studio/pages/sign-in.tsx @@ -5,9 +5,12 @@ import { useEffect } from 'react' import { LastSignInWrapper } from 'components/interfaces/SignIn/LastSignInWrapper' import { SignInForm } from 'components/interfaces/SignIn/SignInForm' +import { SignInWithCustom } from 'components/interfaces/SignIn/SignInWithCustom' import { SignInWithGitHub } from 'components/interfaces/SignIn/SignInWithGitHub' import { AuthenticationLayout } from 'components/layouts/AuthenticationLayout' import SignInLayout from 'components/layouts/SignInLayout/SignInLayout' +import { useCustomContent } from 'hooks/custom-content/useCustomContent' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { IS_PLATFORM } from 'lib/constants' import type { NextPageWithLayout } from 'types' import { Button } from 'ui' @@ -15,6 +18,25 @@ import { Button } from 'ui' const SignInPage: NextPageWithLayout = () => { const router = useRouter() + const { + dashboardAuthSignInWithGithub: signInWithGithubEnabled, + dashboardAuthSignInWithSso: signInWithSsoEnabled, + dashboardAuthSignInWithEmail: signInWithEmailEnabled, + dashboardAuthSignUp: signUpEnabled, + } = useIsFeatureEnabled([ + 'dashboard_auth:sign_in_with_github', + 'dashboard_auth:sign_in_with_sso', + 'dashboard_auth:sign_in_with_email', + 'dashboard_auth:sign_up', + ]) + + const { dashboardAuthCustomProvider: customProvider } = useCustomContent([ + 'dashboard_auth:custom_provider', + ]) + + const showOrDivider = + (signInWithGithubEnabled || signInWithSsoEnabled || customProvider) && signInWithEmailEnabled + useEffect(() => { if (!IS_PLATFORM) { // on selfhosted instance just redirect to projects page @@ -25,45 +47,58 @@ const SignInPage: NextPageWithLayout = () => { return ( <>
- - - + + )} + + {showOrDivider && ( +
+
+
+
+
+ or +
+
+ )} + {signInWithEmailEnabled && } +
+ + {signUpEnabled && ( +
+
+ Don't have an account?{' '} - Continue with SSO + Sign Up Now - - - -
-
-
-
-
- or
- -
- -
-
- Don't have an account?{' '} - - Sign Up Now - -
-
+ )} ) } diff --git a/apps/studio/pages/sign-up.tsx b/apps/studio/pages/sign-up.tsx index c60e96d342b5e..5fbaeb116eb81 100644 --- a/apps/studio/pages/sign-up.tsx +++ b/apps/studio/pages/sign-up.tsx @@ -3,22 +3,37 @@ import Link from 'next/link' import { SignInWithGitHub } from 'components/interfaces/SignIn/SignInWithGitHub' import { SignUpForm } from 'components/interfaces/SignIn/SignUpForm' import SignInLayout from 'components/layouts/SignInLayout/SignInLayout' +import { UnknownInterface } from 'components/ui/UnknownInterface' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import type { NextPageWithLayout } from 'types' const SignUpPage: NextPageWithLayout = () => { + const { + dashboardAuthSignUp: signUpEnabled, + dashboardAuthSignInWithGithub: signInWithGithubEnabled, + } = useIsFeatureEnabled(['dashboard_auth:sign_up', 'dashboard_auth:sign_in_with_github']) + + if (!signUpEnabled) { + return + } + return ( <>
- - -
-
-
-
-
- or -
-
+ {signInWithGithubEnabled && ( + <> + + +
+
+
+
+
+ or +
+
+ + )}
diff --git a/packages/common/enabled-features/enabled-features.json b/packages/common/enabled-features/enabled-features.json index 7c7ee99b442e0..0f2a3178fb6e9 100644 --- a/packages/common/enabled-features/enabled-features.json +++ b/packages/common/enabled-features/enabled-features.json @@ -24,6 +24,11 @@ "billing:all": true, + "dashboard_auth:sign_up": true, + "dashboard_auth:sign_in_with_github": true, + "dashboard_auth:sign_in_with_sso": true, + "dashboard_auth:sign_in_with_email": true, + "database:replication": true, "database:roles": true, diff --git a/packages/common/enabled-features/enabled-features.schema.json b/packages/common/enabled-features/enabled-features.schema.json index ae6eeb10c655c..13325bae07f74 100644 --- a/packages/common/enabled-features/enabled-features.schema.json +++ b/packages/common/enabled-features/enabled-features.schema.json @@ -86,6 +86,23 @@ "description": "Enable the billing settings page" }, + "dashboard_auth:sign_up": { + "type": "boolean", + "description": "Enable the sign up page in the dashboard" + }, + "dashboard_auth:sign_in_with_github": { + "type": "boolean", + "description": "Enable the sign in with github provider" + }, + "dashboard_auth:sign_in_with_sso": { + "type": "boolean", + "description": "Enable the sign in with sso provider" + }, + "dashboard_auth:sign_in_with_email": { + "type": "boolean", + "description": "Enable the sign in with email/password provider" + }, + "database:replication": { "type": "boolean", "description": "Enable the database replication page" @@ -235,6 +252,10 @@ "authentication:show_sort_by_phone", "authentication:show_user_type_filter", "billing:all", + "dashboard_auth:sign_up", + "dashboard_auth:sign_in_with_github", + "dashboard_auth:sign_in_with_sso", + "dashboard_auth:sign_in_with_email", "database:replication", "database:roles", "docs:self-hosting", From 58100a012140a1a905dc0cd78f7e7246efaef343 Mon Sep 17 00:00:00 2001 From: Chris Chinchilla Date: Tue, 9 Sep 2025 09:28:49 +0200 Subject: [PATCH 08/13] docs: Change key name for dynamic loading docs (#38011) Change key name for dynamic loading docs --- apps/docs/app/contributing/content.mdx | 4 ++-- .../ProjectConfigVariables/ProjectConfigVariables.tsx | 6 +++--- .../ProjectConfigVariables/ProjectConfigVariables.utils.ts | 4 ++-- .../content/guides/auth/server-side/creating-a-client.mdx | 2 +- apps/docs/content/guides/auth/server-side/nextjs.mdx | 4 ++-- apps/docs/content/guides/auth/server-side/sveltekit.mdx | 2 +- .../content/guides/getting-started/quickstarts/flutter.mdx | 2 +- .../content/guides/getting-started/quickstarts/hono.mdx | 2 +- .../guides/getting-started/quickstarts/ios-swiftui.mdx | 2 +- .../content/guides/getting-started/quickstarts/kotlin.mdx | 2 +- .../content/guides/getting-started/quickstarts/nextjs.mdx | 2 +- .../content/guides/getting-started/quickstarts/nuxtjs.mdx | 2 +- .../content/guides/getting-started/quickstarts/reactjs.mdx | 2 +- .../content/guides/getting-started/quickstarts/refine.mdx | 2 +- .../content/guides/getting-started/quickstarts/solidjs.mdx | 2 +- .../guides/getting-started/quickstarts/sveltekit.mdx | 2 +- .../docs/content/guides/getting-started/quickstarts/vue.mdx | 2 +- apps/docs/content/guides/telemetry/log-drains.mdx | 2 +- 18 files changed, 23 insertions(+), 23 deletions(-) diff --git a/apps/docs/app/contributing/content.mdx b/apps/docs/app/contributing/content.mdx index 2a5086cd2d922..c3632971a2a52 100644 --- a/apps/docs/app/contributing/content.mdx +++ b/apps/docs/app/contributing/content.mdx @@ -354,11 +354,11 @@ Some guides and tutorials will require that users copy their Supabase project UR ```mdx - + ``` - + ### Step Hike diff --git a/apps/docs/components/ProjectConfigVariables/ProjectConfigVariables.tsx b/apps/docs/components/ProjectConfigVariables/ProjectConfigVariables.tsx index 88423df94174e..b34ed122426bb 100644 --- a/apps/docs/components/ProjectConfigVariables/ProjectConfigVariables.tsx +++ b/apps/docs/components/ProjectConfigVariables/ProjectConfigVariables.tsx @@ -274,7 +274,7 @@ function VariableView({ variable, className }: { variable: Variable; className?: const hasBranches = selectedProject?.is_branch_enabled ?? false const ref = hasBranches ? selectedBranch?.project_ref : selectedProject?.ref - const needsApiQuery = variable === 'anonKey' || variable === 'url' + const needsApiQuery = variable === 'publishableKey' || variable === 'url' const needsSupavisorQuery = variable === 'sessionPooler' const { @@ -303,7 +303,7 @@ function VariableView({ variable, className }: { variable: Variable; className?: switch (variable) { case 'url': return !apiData.app_config?.endpoint - case 'anonKey': + case 'publishableKey': return !apiData.service_api_keys?.some((key) => key.tags === 'anon') } } @@ -336,7 +336,7 @@ function VariableView({ variable, className }: { variable: Variable; className?: case 'url': variableValue = `https://${apiData?.app_config?.endpoint}` break - case 'anonKey': + case 'publishableKey': variableValue = apiData?.service_api_keys?.find((key) => key.tags === 'anon')?.api_key || '' break case 'sessionPooler': diff --git a/apps/docs/components/ProjectConfigVariables/ProjectConfigVariables.utils.ts b/apps/docs/components/ProjectConfigVariables/ProjectConfigVariables.utils.ts index 1d1a125e3dd60..2ed28261bedd4 100644 --- a/apps/docs/components/ProjectConfigVariables/ProjectConfigVariables.utils.ts +++ b/apps/docs/components/ProjectConfigVariables/ProjectConfigVariables.utils.ts @@ -6,7 +6,7 @@ export type Org = OrganizationsData[number] export type Project = ProjectsData[number] export type Branch = BranchesData[number] -export type Variable = 'url' | 'anonKey' | 'sessionPooler' +export type Variable = 'url' | 'publishableKey' | 'sessionPooler' function removeDoubleQuotes(str: string) { return str.replaceAll('"', '') @@ -30,7 +30,7 @@ function unescapeDoubleQuotes(str: string) { export const prettyFormatVariable: Record = { url: 'Project URL', - anonKey: 'Anon key', + publishableKey: 'Publishable key', sessionPooler: 'Connection string (pooler session mode)', } 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 08e8649f1d433..bbefe0d40c0c5 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 @@ -42,7 +42,7 @@ pnpm add @supabase/ssr @supabase/supabase-js In your environment variables file, set your Supabase URL and Supabase Anon Key: - + diff --git a/apps/docs/content/guides/auth/server-side/nextjs.mdx b/apps/docs/content/guides/auth/server-side/nextjs.mdx index ca43981674c78..6d437f6caddff 100644 --- a/apps/docs/content/guides/auth/server-side/nextjs.mdx +++ b/apps/docs/content/guides/auth/server-side/nextjs.mdx @@ -39,7 +39,7 @@ Create a `.env.local` file in your project root directory. Fill in your `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY`: - + @@ -560,7 +560,7 @@ Create a `.env.local` file in your project root directory. Fill in your `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY`: - + diff --git a/apps/docs/content/guides/auth/server-side/sveltekit.mdx b/apps/docs/content/guides/auth/server-side/sveltekit.mdx index 81db989fa9ecc..404fb252e2a48 100644 --- a/apps/docs/content/guides/auth/server-side/sveltekit.mdx +++ b/apps/docs/content/guides/auth/server-side/sveltekit.mdx @@ -34,7 +34,7 @@ Create a `.env.local` file in your project root directory. Fill in your `PUBLIC_SUPABASE_URL` and `PUBLIC_SUPABASE_PUBLISHABLE_KEY`: - + diff --git a/apps/docs/content/guides/getting-started/quickstarts/flutter.mdx b/apps/docs/content/guides/getting-started/quickstarts/flutter.mdx index 6420a59f1390e..54142ac73c5b6 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/flutter.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/flutter.mdx @@ -58,7 +58,7 @@ hideToc: true Open `lib/main.dart` and edit the main function to initialize Supabase using your project URL and public API (anon) key: - + diff --git a/apps/docs/content/guides/getting-started/quickstarts/hono.mdx b/apps/docs/content/guides/getting-started/quickstarts/hono.mdx index d85823d199822..983c5086099e5 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/hono.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/hono.mdx @@ -50,7 +50,7 @@ hideToc: true Lastly, [enable anonymous sign-ins](https://supabase.com/dashboard/project/_/settings/auth) in the Auth settings. - + 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 91f73e5616ac3..9424fd47d9f77 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/ios-swiftui.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/ios-swiftui.mdx @@ -42,7 +42,7 @@ hideToc: true Create a new `Supabase.swift` file add a new Supabase instance using your project URL and public API (anon) key: - + diff --git a/apps/docs/content/guides/getting-started/quickstarts/kotlin.mdx b/apps/docs/content/guides/getting-started/quickstarts/kotlin.mdx index 38c857552c379..59f7b6cf283bc 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/kotlin.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/kotlin.mdx @@ -79,7 +79,7 @@ hideToc: true Replace the `supabaseUrl` and `supabaseKey` with your own: - + diff --git a/apps/docs/content/guides/getting-started/quickstarts/nextjs.mdx b/apps/docs/content/guides/getting-started/quickstarts/nextjs.mdx index 70277ee447ef3..6db673183c3d2 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/nextjs.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/nextjs.mdx @@ -40,7 +40,7 @@ hideToc: true Rename `.env.example` to `.env.local` and populate with your Supabase connection variables: - + diff --git a/apps/docs/content/guides/getting-started/quickstarts/nuxtjs.mdx b/apps/docs/content/guides/getting-started/quickstarts/nuxtjs.mdx index cad739e7e8624..5524606a613ea 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/nuxtjs.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/nuxtjs.mdx @@ -56,7 +56,7 @@ hideToc: true Create a `.env` file and populate with your Supabase connection variables: - + diff --git a/apps/docs/content/guides/getting-started/quickstarts/reactjs.mdx b/apps/docs/content/guides/getting-started/quickstarts/reactjs.mdx index 1e15500c1518c..1bf67565651f1 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/reactjs.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/reactjs.mdx @@ -56,7 +56,7 @@ hideToc: true Create a `.env.local` file and populate with your Supabase connection variables: - + diff --git a/apps/docs/content/guides/getting-started/quickstarts/refine.mdx b/apps/docs/content/guides/getting-started/quickstarts/refine.mdx index da4656c9f4276..a6cb4ff632ed0 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/refine.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/refine.mdx @@ -78,7 +78,7 @@ hideToc: true You now have to update the `supabaseClient` with the `SUPABASE_URL` and `SUPABASE_KEY` of your Supabase API. The `supabaseClient` is used in auth provider and data provider methods that allow the refine app to connect to your Supabase backend. - + diff --git a/apps/docs/content/guides/getting-started/quickstarts/solidjs.mdx b/apps/docs/content/guides/getting-started/quickstarts/solidjs.mdx index 0646a93b22ad8..10aa736884a14 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/solidjs.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/solidjs.mdx @@ -56,7 +56,7 @@ hideToc: true Create a `.env.local` file and populate with your Supabase connection variables: - + diff --git a/apps/docs/content/guides/getting-started/quickstarts/sveltekit.mdx b/apps/docs/content/guides/getting-started/quickstarts/sveltekit.mdx index f71f5bda2b84b..c5b20f469cb02 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/sveltekit.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/sveltekit.mdx @@ -57,7 +57,7 @@ hideToc: true Create a `.env` file at the root of your project and populate with your Supabase connection variables: - + diff --git a/apps/docs/content/guides/getting-started/quickstarts/vue.mdx b/apps/docs/content/guides/getting-started/quickstarts/vue.mdx index f98c7b3556252..2db179b77f4c4 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/vue.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/vue.mdx @@ -56,7 +56,7 @@ hideToc: true Create a `.env.local` file and populate with your Supabase connection variables: - + diff --git a/apps/docs/content/guides/telemetry/log-drains.mdx b/apps/docs/content/guides/telemetry/log-drains.mdx index 7b1920a19557c..be5a1cd18cd9e 100644 --- a/apps/docs/content/guides/telemetry/log-drains.mdx +++ b/apps/docs/content/guides/telemetry/log-drains.mdx @@ -85,7 +85,7 @@ Create a HTTP drain under the [Project Settings > Log Drains](https://supabase.c - Under URL, set it to your edge function URL `https://[PROJECT REF].supabase.co/functions/v1/hello-world` - Under Headers, set the `Authorization: Bearer [ANON KEY]` - + From c0041e0c28216c633ac53cef66f790e3a0ad9f43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Tue, 9 Sep 2025 15:40:14 +0800 Subject: [PATCH 09/13] feat: support additional tax ids (#38542) --- .../BillingCustomerData/TaxID.constants.ts | 205 ++++++++++++------ 1 file changed, 138 insertions(+), 67 deletions(-) diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/BillingCustomerData/TaxID.constants.ts b/apps/studio/components/interfaces/Organization/BillingSettings/BillingCustomerData/TaxID.constants.ts index 290af4077ed1f..b34098174b306 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/BillingCustomerData/TaxID.constants.ts +++ b/apps/studio/components/interfaces/Organization/BillingSettings/BillingCustomerData/TaxID.constants.ts @@ -10,13 +10,13 @@ export interface TaxId { // Commented out countries are not currently supported by Orb API export const TAX_IDS: TaxId[] = [ - /*{ + { name: 'AL TIN', type: 'al_tin', country: 'Albania', placeholder: 'J12345678N', countryIso2: 'AL', - },*/ + }, { name: 'AE TRN', type: 'ae_trn', @@ -39,13 +39,13 @@ export const TAX_IDS: TaxId[] = [ placeholder: 'A-123456-Z', countryIso2: 'AD', }, - /*{ + { name: 'AO TIN', type: 'ao_tin', country: 'Angola', placeholder: '5123456789', countryIso2: 'AO', - },*/ + }, { name: 'AR CUIT', type: 'ar_cuit', @@ -53,7 +53,7 @@ export const TAX_IDS: TaxId[] = [ placeholder: '12-3456789-01', countryIso2: 'AR', }, - /*{ + { name: 'AM TIN', type: 'am_tin', country: 'Armenia', @@ -66,7 +66,7 @@ export const TAX_IDS: TaxId[] = [ country: 'Aruba', placeholder: '12345678', countryIso2: 'AW', - },*/ + }, { name: 'AU ABN', type: 'au_abn', @@ -81,7 +81,7 @@ export const TAX_IDS: TaxId[] = [ placeholder: '123456789123', countryIso2: 'AU', }, - /*{ + { name: 'AZ TIN', type: 'az_tin', country: 'Azerbaijan', @@ -94,7 +94,7 @@ export const TAX_IDS: TaxId[] = [ country: 'Bahamas', placeholder: '123.456.789', countryIso2: 'BS', - },*/ + }, { name: 'BH VAT', type: 'bh_vat', @@ -102,7 +102,7 @@ export const TAX_IDS: TaxId[] = [ placeholder: '123456789012345', countryIso2: 'BH', }, - /*{ + { name: 'BD BIN', type: 'bd_bin', country: 'Bangladesh', @@ -116,13 +116,6 @@ export const TAX_IDS: TaxId[] = [ placeholder: '1123456789012', countryIso2: 'BB', }, - { - name: 'BY TIN', - type: 'by_tin', - country: 'Belarus', - placeholder: '123456789', - countryIso2: 'BY', - },*/ { name: 'BE VAT', type: 'eu_vat', @@ -131,13 +124,13 @@ export const TAX_IDS: TaxId[] = [ vatPrefix: 'BE', countryIso2: 'BE', }, - /*{ + { name: 'BJ IFU', type: 'bj_ifu', country: 'Benin', placeholder: '1234567890123', countryIso2: 'BJ', - },*/ + }, { name: 'BO TIN', type: 'bo_tin', @@ -145,13 +138,13 @@ export const TAX_IDS: TaxId[] = [ placeholder: '123456789', countryIso2: 'BO', }, - /*{ + { name: 'BA TIN', type: 'ba_tin', country: 'Bosnia & Herzegovina', placeholder: '123456789012', countryIso2: 'BA', - },*/ + }, { name: 'BG VAT', type: 'eu_vat', @@ -160,6 +153,13 @@ export const TAX_IDS: TaxId[] = [ vatPrefix: 'BG', countryIso2: 'BG', }, + { + name: 'BG UIC', + type: 'bg_uic', + country: 'Bulgaria', + placeholder: '123456789', + countryIso2: 'BG', + }, { name: 'BR CNPJ', type: 'br_cnpj', @@ -174,7 +174,7 @@ export const TAX_IDS: TaxId[] = [ placeholder: '123.456.789-87', countryIso2: 'BR', }, - /*{ + { name: 'BF IFU', type: 'bf_ifu', country: 'Burkina Faso', @@ -194,7 +194,7 @@ export const TAX_IDS: TaxId[] = [ country: 'Cameroon', placeholder: 'M123456789000L', countryIso2: 'CM', - },*/ + }, { name: 'CA BN', type: 'ca_bn', @@ -225,7 +225,7 @@ export const TAX_IDS: TaxId[] = [ }, { name: 'CA PST-SK', - type: 'ca_pst_mb', + type: 'ca_pst_sk', country: 'Canada', placeholder: '1234567', countryIso2: 'CA', @@ -237,13 +237,13 @@ export const TAX_IDS: TaxId[] = [ placeholder: '1234567890TQ1234', countryIso2: 'CA', }, - /*{ + { name: 'CV NIF', type: 'cv_nif', country: 'Cape Verde', placeholder: '213456789', countryIso2: 'CV', - },*/ + }, { name: 'CH VAT', type: 'ch_vat', @@ -252,6 +252,13 @@ export const TAX_IDS: TaxId[] = [ vatPrefix: 'CHE', countryIso2: 'CH', }, + { + name: 'CH UID', + type: 'ch_uid', + country: 'Switzerland', + placeholder: 'CHE-123.456.789 HR ', + countryIso2: 'CH', + }, { name: 'CL TIN', type: 'cl_tin', @@ -273,13 +280,13 @@ export const TAX_IDS: TaxId[] = [ placeholder: '123.456.789-0', countryIso2: 'CO', }, - /*{ + { name: 'CD NIF', type: 'cd_nif', country: 'Congo (DRC)', placeholder: 'A0123456M', countryIso2: 'CD', - },*/ + }, { name: 'CR TIN', type: 'cr_tin', @@ -311,6 +318,13 @@ export const TAX_IDS: TaxId[] = [ vatPrefix: 'DE', countryIso2: 'DE', }, + { + name: 'DE STN', + type: 'de_stn', + country: 'Germany', + placeholder: '1234567890', + countryIso2: 'DE', + }, { name: 'DK VAT', type: 'eu_vat', @@ -347,13 +361,13 @@ export const TAX_IDS: TaxId[] = [ placeholder: '1234-567890-123-4', countryIso2: 'SV', }, - /*{ + { name: 'ET TIN', type: 'et_tin', country: 'Ethiopia', placeholder: '1234567890', countryIso2: 'ET', - },*/ + }, { name: 'EE VAT', type: 'eu_vat', @@ -377,13 +391,13 @@ export const TAX_IDS: TaxId[] = [ vatPrefix: 'ES', countryIso2: 'ES', }, - /*{ + { name: 'SR FIN', type: 'sr_fin', country: 'Suriname', placeholder: '1234567890', countryIso2: 'SR', - },*/ + }, { name: 'FI VAT', type: 'eu_vat', @@ -408,6 +422,14 @@ export const TAX_IDS: TaxId[] = [ vatPrefix: 'GB', countryIso2: 'GB', }, + { + name: 'UK VAT', + type: 'gb_vat', + country: 'United Kingdom', + placeholder: 'GB123456789', + vatPrefix: 'GB', + countryIso2: 'GB', + }, { name: 'GE VAT', type: 'ge_vat', @@ -423,13 +445,13 @@ export const TAX_IDS: TaxId[] = [ placeholder: 'EL123456789', vatPrefix: 'EL', }, - /*{ + { name: 'GN NIF', type: 'gn_nif', country: 'Guinea', placeholder: '123456789', countryIso2: 'GN', - },*/ + }, { name: 'HK BR', type: 'hk_br', @@ -445,6 +467,13 @@ export const TAX_IDS: TaxId[] = [ vatPrefix: 'HR', countryIso2: 'HR', }, + { + name: 'HR OIB', + type: 'hr_oib', + country: 'Croatia', + placeholder: '12345678901', + countryIso2: 'HR', + }, { name: 'HU VAT', type: 'eu_vat', @@ -453,6 +482,13 @@ export const TAX_IDS: TaxId[] = [ vatPrefix: 'HU', countryIso2: 'HU', }, + { + name: 'HU TIN', + type: 'hu_tin', + country: 'Hungary', + placeholder: '12345678-1-23', + countryIso2: 'HU', + }, { name: 'ID NPWP', type: 'id_npwp', @@ -498,11 +534,11 @@ export const TAX_IDS: TaxId[] = [ countryIso2: 'IT', }, { - name: 'JP CN', - type: 'jp_cn', - country: 'Japan', - placeholder: '1234567891234', - countryIso2: 'JP', + name: 'KE PIN', + type: 'ke_pin', + country: 'Kenya', + placeholder: 'P000111111A', + countryIso2: 'KE', }, { name: 'KZ BIN', @@ -512,19 +548,19 @@ export const TAX_IDS: TaxId[] = [ countryIso2: 'KZ', }, { - name: 'KE PIN', - type: 'ke_pin', - country: 'Kenya', - placeholder: 'P000111111A', - countryIso2: 'KE', - }, - /*{ name: 'KG TIN', type: 'kg_tin', country: 'Kyrgyzstan', placeholder: '12345678901234', countryIso2: 'KG', - },*/ + }, + { + name: 'JP CN', + type: 'jp_cn', + country: 'Japan', + placeholder: '1234567891234', + countryIso2: 'JP', + }, { name: 'JP RN', type: 'jp_rn', @@ -532,6 +568,13 @@ export const TAX_IDS: TaxId[] = [ placeholder: '12345', countryIso2: 'JP', }, + { + name: 'JP TRN', + type: 'jp_trn', + country: 'Japan', + placeholder: 'T1234567891234', + countryIso2: 'JP', + }, { name: 'KR BRN', type: 'kr_brn', @@ -546,6 +589,14 @@ export const TAX_IDS: TaxId[] = [ placeholder: 'CHE123456789', countryIso2: 'LI', }, + { + name: 'LI VAT', + type: 'li_vat', + country: 'Liechtenstein', + placeholder: '12345', + vatPrefix: 'LI', + countryIso2: 'LI', + }, { name: 'LT VAT', type: 'eu_vat', @@ -562,13 +613,13 @@ export const TAX_IDS: TaxId[] = [ vatPrefix: 'LU', countryIso2: 'LU', }, - /*{ + { name: 'LA TIN', type: 'la_tin', country: 'Laos', placeholder: '123456789-000', countryIso2: 'LA', - },*/ + }, { name: 'LV VAT', type: 'eu_vat', @@ -585,13 +636,13 @@ export const TAX_IDS: TaxId[] = [ vatPrefix: 'MT', countryIso2: 'MT', }, - /*{ + { name: 'MR NIF', type: 'mr_nif', country: 'Mauritania', placeholder: '12345678', countryIso2: 'MR', - },*/ + }, { name: 'MX RFC', type: 'mx_rfc', @@ -599,7 +650,7 @@ export const TAX_IDS: TaxId[] = [ placeholder: 'ABC010203AB9', countryIso2: 'MX', }, - /* { + { name: 'MD VAT', type: 'md_vat', country: 'Moldova', @@ -619,7 +670,7 @@ export const TAX_IDS: TaxId[] = [ country: 'Morocco', placeholder: '12345678', countryIso2: 'MA', - },*/ + }, { name: 'MY FRP', type: 'my_frp', @@ -641,13 +692,13 @@ export const TAX_IDS: TaxId[] = [ placeholder: 'A12-3456-78912345', countryIso2: 'MY', }, - /*{ + { name: 'NP PAN', type: 'np_pan', country: 'Nepal', placeholder: '123456789', countryIso2: 'NP', - },*/ + }, { name: 'NL VAT', type: 'eu_vat', @@ -663,13 +714,13 @@ export const TAX_IDS: TaxId[] = [ placeholder: '12345678-0001', countryIso2: 'NG', }, - /*{ + { name: 'MK VAT', type: 'mk_vat', country: 'North Macedonia', placeholder: 'MK1234567890123', countryIso2: 'MK', - },*/ + }, { name: 'nz_gst', type: 'nz_gst', @@ -684,6 +735,13 @@ export const TAX_IDS: TaxId[] = [ placeholder: '123456789MVA', countryIso2: 'NO', }, + { + name: 'NO VOEC', + type: 'no_voec', + country: 'Norway', + placeholder: '1234567 ', + countryIso2: 'NO', + }, { name: 'OM VAT', type: 'om_vat', @@ -736,6 +794,13 @@ export const TAX_IDS: TaxId[] = [ vatPrefix: 'RO', countryIso2: 'RO', }, + { + name: 'RO TIN', + type: 'ro_tin', + country: 'Romania', + placeholder: '1234567890123', + countryIso2: 'RO', + }, { name: 'RU INN', type: 'ru_inn', @@ -757,13 +822,13 @@ export const TAX_IDS: TaxId[] = [ placeholder: '123456789012345', countryIso2: 'SA', }, - /*{ + { name: 'SN NINEA', type: 'sn_ninea', country: 'Senegal', placeholder: '12345672A2', countryIso2: 'SN', - },*/ + }, { name: 'RS PIB', type: 'rs_pib', @@ -801,6 +866,13 @@ export const TAX_IDS: TaxId[] = [ vatPrefix: 'SI', countryIso2: 'SI', }, + { + name: 'SI TIN', + type: 'si_tin', + country: 'Slovenia', + placeholder: '12345678', + countryIso2: 'SI', + }, { name: 'SK VAT', type: 'eu_vat', @@ -830,7 +902,7 @@ export const TAX_IDS: TaxId[] = [ placeholder: '12345678', countryIso2: 'TW', }, - /* { + { name: 'TJ TIN', type: 'tj_tin', country: 'Tajikistan', @@ -843,14 +915,14 @@ export const TAX_IDS: TaxId[] = [ country: 'Tanzania', placeholder: '12345678A', countryIso2: 'TZ', - },*/ - /*{ + }, + { name: 'UG TIN', type: 'ug_tin', country: 'Uganda', placeholder: '0123456789', countryIso2: 'UG', - },*/ + }, { name: 'UA VAT', type: 'ua_vat', @@ -887,20 +959,20 @@ export const TAX_IDS: TaxId[] = [ placeholder: '123456789012', countryIso2: 'UY', }, - /* { + { name: 'UZ TIN', type: 'uz_tin', country: 'Uzbekistan', placeholder: '123456789', countryIso2: 'UZ', }, - { + { name: 'UZ VAT', type: 'uz_vat', country: 'Uzbekistan', placeholder: '123456789012', countryIso2: 'UZ', - },*/ + }, { name: 'VE RIF', type: 've_rif', @@ -915,7 +987,6 @@ export const TAX_IDS: TaxId[] = [ placeholder: '1234567890', countryIso2: 'VN', }, - /* { name: 'ZM TIN', type: 'zm_tin', @@ -929,5 +1000,5 @@ export const TAX_IDS: TaxId[] = [ country: 'Zimbabwe', placeholder: '1234567890', countryIso2: 'ZW', - },*/ + }, ] From b27ad019ce383bef35bb6f310f602bf4aeb10357 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 9 Sep 2025 15:52:45 +0800 Subject: [PATCH 10/13] (Clean up) Remove secrets:pull command (#38541) Remove secrets:pull command --- apps/studio/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/studio/package.json b/apps/studio/package.json index 33def8d179e1d..4320f6d587b29 100644 --- a/apps/studio/package.json +++ b/apps/studio/package.json @@ -5,7 +5,6 @@ "scripts": { "preinstall": "npx only-allow pnpm", "dev": "next dev --turbopack -p 8082", - "dev:secrets:pull": "AWS_PROFILE=supa-dev node ../../scripts/getSecrets.js -n local/studio", "build": "next build && ./../../scripts/upload-static-assets.sh", "start": "next start", "lint": "next lint", From 336788b1208fd52395f5d927316f2bf9f78e5811 Mon Sep 17 00:00:00 2001 From: Chris Chinchilla Date: Tue, 9 Sep 2025 10:06:33 +0200 Subject: [PATCH 11/13] docs: Correct key usage in edge function docs (#38545) Correct keys --- apps/docs/content/guides/functions/auth.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/content/guides/functions/auth.mdx b/apps/docs/content/guides/functions/auth.mdx index 7e8959a6368e6..ca91575b35ca0 100644 --- a/apps/docs/content/guides/functions/auth.mdx +++ b/apps/docs/content/guides/functions/auth.mdx @@ -25,7 +25,7 @@ import { createClient } from 'npm:@supabase/supabase-js@2' Deno.serve(async (req: Request) => { const supabaseClient = createClient( Deno.env.get('SUPABASE_URL') ?? '', - Deno.env.get('SUPABASE_PUBLISHABLE_KEY') ?? '', + Deno.env.get('SUPABASE_ANON_KEY') ?? '', // Create client with Auth context of the user that called the function. // This way your row-level-security (RLS) policies are applied. { From 1a78aecb053574e6f3a31e36fd1ab13be171440d Mon Sep 17 00:00:00 2001 From: Han Qiao Date: Tue, 9 Sep 2025 17:00:12 +0800 Subject: [PATCH 12/13] fix: estimate data branch cost before creation (#38511) * fix: estimate data branch cost before creation * chore: comments to clarify conditionals * Use addons endpoint to check for compute size instead * Revert branchComputeSize changes * Fix * chore: update compute size only on data branch * chore: replace slow query with disk size estimate * Add loading and error states for disk attr, and update copy in create branch modal * Update copy to improve transparency on billing * Tiny fix * Nit --------- Co-authored-by: Joshen Lim --- .../BranchManagement.utils.ts | 66 ++++ .../BranchManagement/CreateBranchModal.tsx | 324 ++++++++++++------ .../data/branches/branch-create-mutation.ts | 10 +- 3 files changed, 286 insertions(+), 114 deletions(-) create mode 100644 apps/studio/components/interfaces/BranchManagement/BranchManagement.utils.ts diff --git a/apps/studio/components/interfaces/BranchManagement/BranchManagement.utils.ts b/apps/studio/components/interfaces/BranchManagement/BranchManagement.utils.ts new file mode 100644 index 0000000000000..e1f23edabf531 --- /dev/null +++ b/apps/studio/components/interfaces/BranchManagement/BranchManagement.utils.ts @@ -0,0 +1,66 @@ +import { DiskAttributesData } from 'data/config/disk-attributes-query' +import { DesiredInstanceSize, instanceSizeSpecs } from 'data/projects/new-project.constants' +import { + DISK_LIMITS, + DISK_PRICING, + DiskType, + PLAN_DETAILS, +} from '../DiskManagement/ui/DiskManagement.constants' + +// Ref: https://supabase.com/docs/guides/platform/compute-and-disk +const maxDiskForCompute = new Map([ + [10, instanceSizeSpecs.micro], + [50, instanceSizeSpecs.small], + [100, instanceSizeSpecs.medium], + [200, instanceSizeSpecs.large], + [500, instanceSizeSpecs.xlarge], + [1_000, instanceSizeSpecs['2xlarge']], + [2_000, instanceSizeSpecs['4xlarge']], + [4_000, instanceSizeSpecs['8xlarge']], + [6_000, instanceSizeSpecs['12xlarge']], + [10_000, instanceSizeSpecs['16xlarge']], +]) + +export const estimateComputeSize = ( + projectDiskSize: number, + branchComputeSize?: DesiredInstanceSize +) => { + if (branchComputeSize) { + return instanceSizeSpecs[branchComputeSize] + } + // Fallback to estimating based on volume size + for (const [disk, compute] of maxDiskForCompute) { + if (projectDiskSize <= disk) { + return compute + } + } + return instanceSizeSpecs['24xlarge'] +} + +export const estimateDiskCost = (disk: DiskAttributesData['attributes']) => { + const diskType = disk.type as DiskType + + const pricing = DISK_PRICING[diskType] + const includedGB = PLAN_DETAILS['pro'].includedDiskGB[diskType] + const priceSize = Math.max(disk.size_gb - includedGB, 0) * pricing.storage + const includedIOPS = DISK_LIMITS[diskType].includedIops + const priceIOPS = Math.max(disk.iops - includedIOPS, 0) * pricing.iops + + const priceThroughput = + diskType === DiskType.GP3 && 'throughput_mbps' in disk + ? Math.max(disk.throughput_mbps - DISK_LIMITS[DiskType.GP3].includedThroughput, 0) * + DISK_PRICING[DiskType.GP3].throughput + : 0 + + return { + total: priceSize + priceIOPS + priceThroughput, + size: priceSize, + iops: priceIOPS, + throughput: priceThroughput, + } +} + +export const estimateRestoreTime = (disk: DiskAttributesData['attributes']) => { + // This is interpolated from real restore time + return (720 / 21000) * disk.size_gb + 3 +} diff --git a/apps/studio/components/interfaces/BranchManagement/CreateBranchModal.tsx b/apps/studio/components/interfaces/BranchManagement/CreateBranchModal.tsx index 9fb2103792bf8..e6c183d0bf073 100644 --- a/apps/studio/components/interfaces/BranchManagement/CreateBranchModal.tsx +++ b/apps/studio/components/interfaces/BranchManagement/CreateBranchModal.tsx @@ -1,4 +1,5 @@ import { zodResolver } from '@hookform/resolvers/zod' +import { PermissionAction } from '@supabase/shared-types/out/constants' import { useQueryClient } from '@tanstack/react-query' import { DatabaseZap, DollarSign, GitMerge, Github, Loader2 } from 'lucide-react' import Image from 'next/image' @@ -9,20 +10,21 @@ import { useForm } from 'react-hook-form' import { toast } from 'sonner' import * as z from 'zod' -import { PermissionAction } from '@supabase/shared-types/out/constants' import { useFlag, useParams } from 'common' import { useIsBranching2Enabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext' import { BranchingPITRNotice } from 'components/layouts/AppLayout/EnableBranchingButton/BranchingPITRNotice' import AlertError from 'components/ui/AlertError' import { ButtonTooltip } from 'components/ui/ButtonTooltip' +import { InlineLink, InlineLinkClassName } from 'components/ui/InlineLink' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import UpgradeToPro from 'components/ui/UpgradeToPro' import { useBranchCreateMutation } from 'data/branches/branch-create-mutation' import { useBranchesQuery } from 'data/branches/branches-query' +import { useDiskAttributesQuery } from 'data/config/disk-attributes-query' import { useCheckGithubBranchValidity } from 'data/integrations/github-branch-check-query' import { useGitHubConnectionsQuery } from 'data/integrations/github-connections-query' -import { useCloneBackupsQuery } from 'data/projects/clone-query' import { projectKeys } from 'data/projects/keys' +import { DesiredInstanceSize, instanceSizeSpecs } from 'data/projects/new-project.constants' import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' @@ -46,9 +48,18 @@ import { Input_Shadcn_, Label_Shadcn_ as Label, Switch, + Tooltip, + TooltipContent, + TooltipTrigger, cn, } from 'ui' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' +import ShimmeringLoader from 'ui-patterns/ShimmeringLoader' +import { + estimateComputeSize, + estimateDiskCost, + estimateRestoreTime, +} from './BranchManagement.utils' export const CreateBranchModal = () => { const { ref } = useParams() @@ -60,90 +71,19 @@ export const CreateBranchModal = () => { const gitlessBranching = useIsBranching2Enabled() const allowDataBranching = useFlag('allowDataBranching') - // [Joshen] This is meant to be short lived while we're figuring out how to control - // requests to this endpoint. Kill switch in case we need to stop the requests - const disableBackupsCheck = useFlag('disableBackupsCheckInCreatebranchmodal') - - const isProPlanAndUp = selectedOrg?.plan?.id !== 'free' - const promptProPlanUpgrade = IS_PLATFORM && !isProPlanAndUp - - const isBranch = projectDetails?.parent_project_ref !== undefined - const projectRef = - projectDetails !== undefined ? (isBranch ? projectDetails.parent_project_ref : ref) : undefined - - const { - data: connections, - error: connectionsError, - isLoading: isLoadingConnections, - isSuccess: isSuccessConnections, - isError: isErrorConnections, - } = useGitHubConnectionsQuery({ - organizationId: selectedOrg?.id, - }) - - const { data: branches } = useBranchesQuery({ projectRef }) - const { data: addons } = useProjectAddonsQuery({ projectRef }) - const hasPitrEnabled = - (addons?.selected_addons ?? []).find((addon) => addon.type === 'pitr') !== undefined - const { mutateAsync: checkGithubBranchValidity, isLoading: isChecking } = - useCheckGithubBranchValidity({ - onError: () => {}, - }) - const { - data: cloneBackups, - error: cloneBackupsError, - isLoading: isLoadingCloneBackups, - } = useCloneBackupsQuery( - { projectRef }, - { - // [Joshen] Only trigger this request when the modal is opened - enabled: showCreateBranchModal && !disableBackupsCheck, - } - ) - const targetVolumeSizeGb = cloneBackups?.target_volume_size_gb ?? 0 - const noPhysicalBackups = cloneBackupsError?.message.startsWith( - 'Physical backups need to be enabled' - ) - - const { mutate: sendEvent } = useSendEventMutation() - - const { mutate: createBranch, isLoading: isCreating } = useBranchCreateMutation({ - onSuccess: async (data) => { - toast.success(`Successfully created preview branch "${data.name}"`) - if (projectRef) { - await Promise.all([queryClient.invalidateQueries(projectKeys.detail(projectRef))]) - } - sendEvent({ - action: 'branch_create_button_clicked', - properties: { - branchType: data.persistent ? 'persistent' : 'preview', - gitlessBranching, - }, - groups: { - project: ref ?? 'Unknown', - organization: selectedOrg?.slug ?? 'Unknown', - }, - }) - - setShowCreateBranchModal(false) - router.push(`/project/${data.project_ref}`) - }, - onError: (error) => { - toast.error(`Failed to create branch: ${error.message}`) - }, - }) const { can: canCreateBranch } = useAsyncCheckProjectPermissions( PermissionAction.CREATE, 'preview_branches' ) - const githubConnection = connections?.find((connection) => connection.project.ref === projectRef) - - // Fetch production/default branch to inspect git_branch linkage - const prodBranch = branches?.find((branch) => branch.is_default) + const isProPlanAndUp = selectedOrg?.plan?.id !== 'free' + const promptProPlanUpgrade = IS_PLATFORM && !isProPlanAndUp - const [repoOwner, repoName] = githubConnection?.repository.name.split('/') ?? [] + const isBranch = projectDetails?.parent_project_ref !== undefined + const projectRef = + projectDetails !== undefined ? (isBranch ? projectDetails.parent_project_ref : ref) : undefined + const noPhysicalBackups = !projectDetails?.is_physical_backups_enabled const formId = 'create-branch-form' const FormSchema = z @@ -193,15 +133,95 @@ export const CreateBranchModal = () => { }) const withData = form.watch('withData') - const canSubmit = !isCreating && !isChecking + const { + data: connections, + error: connectionsError, + isLoading: isLoadingConnections, + isSuccess: isSuccessConnections, + isError: isErrorConnections, + } = useGitHubConnectionsQuery( + { organizationId: selectedOrg?.id }, + { enabled: showCreateBranchModal } + ) + + const { data: branches } = useBranchesQuery({ projectRef }) + const { data: addons, isSuccess: isSuccessAddons } = useProjectAddonsQuery( + { projectRef }, + { enabled: showCreateBranchModal } + ) + const computeAddon = addons?.selected_addons.find((addon) => addon.type === 'compute_instance') + const computeSize = !!computeAddon + ? (computeAddon.variant.identifier.split('ci_')[1] as DesiredInstanceSize) + : undefined + const hasPitrEnabled = + (addons?.selected_addons ?? []).find((addon) => addon.type === 'pitr') !== undefined + + const { + data: disk, + isLoading: isLoadingDiskAttr, + isError: isErrorDiskAttr, + } = useDiskAttributesQuery({ projectRef }, { enabled: showCreateBranchModal && withData }) + const projectDiskAttributes = disk?.attributes ?? { + type: 'gp3', + size_gb: 0, + iops: 0, + throughput_mbps: 0, + } + // Branch disk is oversized to include backup files, it should be scaled back eventually. + const branchDiskAttributes = { + ...projectDiskAttributes, + // [Joshen] JFYI for Qiao - this multiplier may eventually be dropped + size_gb: Math.round(projectDiskAttributes.size_gb * 1.5), + } + const branchComputeSize = estimateComputeSize(projectDiskAttributes.size_gb, computeSize) + const estimatedDiskCost = estimateDiskCost(branchDiskAttributes) + + const { mutate: sendEvent } = useSendEventMutation() + + const { mutateAsync: checkGithubBranchValidity, isLoading: isCheckingGHBranchValidity } = + useCheckGithubBranchValidity({ + onError: () => {}, + }) + + const { mutate: createBranch, isLoading: isCreatingBranch } = useBranchCreateMutation({ + onSuccess: async (data) => { + toast.success(`Successfully created preview branch "${data.name}"`) + if (projectRef) { + await Promise.all([queryClient.invalidateQueries(projectKeys.detail(projectRef))]) + } + sendEvent({ + action: 'branch_create_button_clicked', + properties: { + branchType: data.persistent ? 'persistent' : 'preview', + gitlessBranching, + }, + groups: { + project: ref ?? 'Unknown', + organization: selectedOrg?.slug ?? 'Unknown', + }, + }) + + setShowCreateBranchModal(false) + router.push(`/project/${data.project_ref}`) + }, + onError: (error) => { + toast.error(`Failed to create branch: ${error.message}`) + }, + }) + + // Fetch production/default branch to inspect git_branch linkage + const githubConnection = connections?.find((connection) => connection.project.ref === projectRef) + const prodBranch = branches?.find((branch) => branch.is_default) + const [repoOwner, repoName] = githubConnection?.repository.name.split('/') ?? [] + const isDisabled = + !canCreateBranch || + !isSuccessAddons || !isSuccessConnections || - isCreating || - !canSubmit || - isChecking || - (!gitlessBranching && !githubConnection) || promptProPlanUpgrade || - !canCreateBranch + (!gitlessBranching && !githubConnection) || + isCreatingBranch || + isCheckingGHBranchValidity const onSubmit = (data: z.infer) => { if (!projectRef) return console.error('Project ref is required') @@ -209,6 +229,7 @@ export const CreateBranchModal = () => { projectRef, branchName: data.branchName, is_default: false, + ...(data.withData ? { desired_instance_size: computeSize } : {}), ...(data.gitBranchName ? { gitBranch: data.gitBranchName } : {}), ...(allowDataBranching ? { withData: data.withData } : {}), }) @@ -231,9 +252,7 @@ export const CreateBranchModal = () => { size="large" hideClose onOpenAutoFocus={(e) => { - if (promptProPlanUpgrade) { - e.preventDefault() - } + if (promptProPlanUpgrade) e.preventDefault() }} > @@ -315,7 +334,9 @@ export const CreateBranchModal = () => { />
- {isChecking && } + {isCheckingGHBranchValidity && ( + + )}
@@ -363,7 +384,7 @@ export const CreateBranchModal = () => { label={ <> - {!disableBackupsCheck && (isLoadingCloneBackups || noPhysicalBackups) && ( + {noPhysicalBackups && ( Requires PITR @@ -376,9 +397,7 @@ export const CreateBranchModal = () => { > @@ -406,18 +425,91 @@ export const CreateBranchModal = () => {
-

- Data branch takes longer time to create -

-

- Since your target database volume size is{' '} - {targetVolumeSizeGb} GB, creating a - data branch is estimated to take around{' '} - - {Math.round((720 / 21000) * targetVolumeSizeGb) + 3} minutes - - . -

+ {isLoadingDiskAttr ? ( + <> + + + + ) : ( + <> + {isErrorDiskAttr ? ( + <> +

+ Branch disk size will incur additional cost per month +

+

+ The additional cost and time taken to create a data branch is relative + to the size of your database. We are unable to provide an estimate as + we were unable to retrieve your project's disk configuration +

+ + ) : ( + <> +

+ Branch disk size is billed at ${estimatedDiskCost.total.toFixed(2)}{' '} + per month +

+

+ Creating a data branch will take about{' '} + + {estimateRestoreTime(branchDiskAttributes).toFixed()} minutes + {' '} + and costs{' '} + + ${estimatedDiskCost.total.toFixed(2)} + {' '} + per month based on your current target database volume size of{' '} + {branchDiskAttributes.size_gb} GB and your{' '} + + + + project's disk configuration + + + +

+

Disk type:

+

+ {branchDiskAttributes.type.toUpperCase()} +

+
+
+

Targer disk size:

+

{branchDiskAttributes.size_gb} GB

+

(${estimatedDiskCost.size.toFixed(2)})

+
+
+

IOPs:

+

{branchDiskAttributes.iops} IOPS

+

(${estimatedDiskCost.iops.toFixed(2)})

+
+ {'throughput_mbps' in branchDiskAttributes && ( +
+

Throughput:

+

+ {branchDiskAttributes.throughput_mbps} MB/s +

+

(${estimatedDiskCost.throughput.toFixed(2)})

+
+ )} +

+ More info in{' '} + setShowCreateBranchModal(false)} + className="pointer-events-auto" + href={`/project/${ref}/settings/compute-and-disk`} + > + Compute and Disk + +

+ + + . +

+ + )} + + )}
)} @@ -439,7 +531,7 @@ export const CreateBranchModal = () => { {prodBranch?.git_branch ? ( <> When this branch is merged to{' '} - {prodBranch.git_branch}, + {prodBranch.git_branch}, migrations will be deployed to production. Otherwise, migrations only run on preview branches. @@ -462,9 +554,21 @@ export const CreateBranchModal = () => {
-

Branches are billed $0.01344 per hour

+

+ Branch compute is billed at $ + {withData ? branchComputeSize.priceHourly : instanceSizeSpecs.micro.priceHourly}{' '} + per hour +

- This cost will continue for as long as the branch has not been removed. + {withData ? ( + <> + {branchComputeSize.label} compute + size is automatically selected to match your production branch. You may + downgrade after creation or pause the branch when not in use to save cost. + + ) : ( + <>This cost will continue for as long as the branch has not been removed. + )}

@@ -474,8 +578,8 @@ export const CreateBranchModal = () => {