From 83681d036a15bde42b9cac5ada22481de826cb6d Mon Sep 17 00:00:00 2001 From: Ignacio Dobronich Date: Fri, 5 Jun 2026 14:14:59 -0300 Subject: [PATCH 1/5] feat: purchasing as business tooltip (#46659) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds an info tooltip next to the "I'm purchasing as a business" checkbox explaining that it's for tax-registered businesses and requires a tax ID. ## Summary by CodeRabbit * **New Features** * Added a help icon with tooltip next to the "I’m purchasing as a business" checkbox. The tooltip clarifies when to select the business option and notes that tax ID entry is required only for tax‑registered businesses, reducing confusion and helping users provide correct billing information. --- .../PaymentMethods/NewPaymentMethodElement.tsx | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/apps/studio/components/interfaces/Billing/Payment/PaymentMethods/NewPaymentMethodElement.tsx b/apps/studio/components/interfaces/Billing/Payment/PaymentMethods/NewPaymentMethodElement.tsx index 6c6abcc3d92b7..183684a6d5a3e 100644 --- a/apps/studio/components/interfaces/Billing/Payment/PaymentMethods/NewPaymentMethodElement.tsx +++ b/apps/studio/components/interfaces/Billing/Payment/PaymentMethods/NewPaymentMethodElement.tsx @@ -12,7 +12,7 @@ import { type SetupIntent, } from '@stripe/stripe-js' import { Form } from '@ui/components/shadcn/ui/form' -import { Check, ChevronsUpDown } from 'lucide-react' +import { Check, ChevronsUpDown, HelpCircle } from 'lucide-react' import { forwardRef, useEffect, useId, useImperativeHandle, useMemo, useRef, useState } from 'react' import { useForm } from 'react-hook-form' import { toast } from 'sonner' @@ -34,6 +34,9 @@ import { Popover, PopoverContent, PopoverTrigger, + Tooltip, + TooltipContent, + TooltipTrigger, } from 'ui' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import { z } from 'zod' @@ -345,6 +348,19 @@ export const NewPaymentMethodElement = forwardRef( + + + + + + Select this only if your business is tax-registered. You’ll be asked for a tax ID + (e.g. US EIN, VAT, GST), which is required to issue a compliant business invoice. If + you’re not tax-registered, leave this unchecked. You’ll still receive a receipt. + + )} From c0e0501c010dd5e32ab17d0b3c6ee01d13c0630c Mon Sep 17 00:00:00 2001 From: Alex Hall Date: Fri, 5 Jun 2026 14:02:23 -0400 Subject: [PATCH 2/5] feat(studio): support custom installation logic for marketplace integrations (#46660) - Fetch additional project data needed to determine marketplace integration installation status - Consolidate logic to ensure consistency between left-hand nav and overview tab - Add special-case handling of new integrations to give us more granular control of their behavior --- .../InstallOAuthIntegrationButton.tsx | 63 +------ .../Landing/Integrations.constants.tsx | 1 + .../Integrations/Landing/Landing.utils.ts | 163 +++++++++++++++--- .../Landing/useAvailableIntegrations.tsx | 2 + .../Landing/useInstalledIntegrations.tsx | 89 ++-------- packages/common/marketplace.types.ts | 2 +- 6 files changed, 166 insertions(+), 154 deletions(-) diff --git a/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTabV2/InstallIntegrationSheet/InstallOAuthIntegrationButton.tsx b/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTabV2/InstallIntegrationSheet/InstallOAuthIntegrationButton.tsx index 362aa65617ac2..06f4a50cc84d1 100644 --- a/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTabV2/InstallIntegrationSheet/InstallOAuthIntegrationButton.tsx +++ b/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTabV2/InstallIntegrationSheet/InstallOAuthIntegrationButton.tsx @@ -3,11 +3,9 @@ import { useMemo } from 'react' import { toast } from 'sonner' import { Button } from 'ui' +import { isOAuthInstalled, useProjectOAuthIntegrationData } from '../../../Landing/Landing.utils' import type { IntegrationDefinition } from '@/components/interfaces/Integrations/Landing/Integrations.constants' -import { useAPIKeysQuery } from '@/data/api-keys/api-keys-query' import { useInstallOAuthIntegrationMutation } from '@/data/marketplace/install-oauth-integration-mutation' -import { usePartnerIntegrationsQuery } from '@/data/partners/integration-status-query' -import { useSecretsQuery } from '@/data/secrets/secrets-query' interface InstallOAuthIntegrationButtonProps { integration: IntegrationDefinition @@ -16,28 +14,7 @@ interface InstallOAuthIntegrationButtonProps { export function InstallOAuthIntegrationButton({ integration }: InstallOAuthIntegrationButtonProps) { const { ref: projectRef } = useParams() - const requiresApiKeysCheck = - integration.installIdentificationMethod === 'secret_key_prefix' && !!integration.secretKeyPrefix - - const requiresEdgeFunctionSecretsCheck = - integration.installIdentificationMethod === 'edge_function_secret_name' && - !!integration.edgeFunctionSecretName - - const requiresPartnerIntegrationsCheck = - integration.installIdentificationMethod === 'integration_status' - - const { data: apiKeys, isLoading: isApiKeysLoading } = useAPIKeysQuery( - { projectRef, reveal: false }, - { enabled: requiresApiKeysCheck } - ) - - const { data: edgeFunctionSecrets, isPending: isEdgeFunctionSecretsLoading } = useSecretsQuery( - { projectRef }, - { enabled: requiresEdgeFunctionSecretsCheck } - ) - - const { data: partnerIntegrations, isPending: isPartnerIntegrationsLoading } = - usePartnerIntegrationsQuery({ projectRef }, { enabled: requiresPartnerIntegrationsCheck }) + const { data, isLoading } = useProjectOAuthIntegrationData(projectRef) const { mutate: installOAuthIntegration, isPending: isInstalling } = useInstallOAuthIntegrationMutation({ @@ -54,43 +31,11 @@ export function InstallOAuthIntegrationButton({ integration }: InstallOAuthInteg }, }) - const isLoading = - (requiresApiKeysCheck && isApiKeysLoading) || - (requiresEdgeFunctionSecretsCheck && isEdgeFunctionSecretsLoading) || - (requiresPartnerIntegrationsCheck && isPartnerIntegrationsLoading) - const isIntegrationInstalled = useMemo(() => { if (!integration) return false - if (integration.installIdentificationMethod === 'secret_key_prefix') { - const prefix = integration.secretKeyPrefix - if (!prefix || isApiKeysLoading || !apiKeys) return false - return apiKeys.some((k) => k.type === 'secret' && k.name.startsWith(prefix)) - } - - if (integration.installIdentificationMethod === 'edge_function_secret_name') { - const secretName = integration.edgeFunctionSecretName - if (!secretName || isEdgeFunctionSecretsLoading || !edgeFunctionSecrets) return false - return edgeFunctionSecrets.some((secret) => secret.name === secretName) - } - - if (integration.installIdentificationMethod === 'integration_status') { - if (isPartnerIntegrationsLoading || !partnerIntegrations) return false - return partnerIntegrations.some( - (i) => i.listing_slug === integration.id && i.status === 'ready' - ) - } - - return false - }, [ - apiKeys, - edgeFunctionSecrets, - partnerIntegrations, - integration, - isApiKeysLoading, - isEdgeFunctionSecretsLoading, - isPartnerIntegrationsLoading, - ]) + return isOAuthInstalled({ integration, projectData: data }) + }, [data, integration]) const handleInstallClick = async () => { if (!integration || !projectRef) return diff --git a/apps/studio/components/interfaces/Integrations/Landing/Integrations.constants.tsx b/apps/studio/components/interfaces/Integrations/Landing/Integrations.constants.tsx index c650c29d021d5..c8e59f07d480c 100644 --- a/apps/studio/components/interfaces/Integrations/Landing/Integrations.constants.tsx +++ b/apps/studio/components/interfaces/Integrations/Landing/Integrations.constants.tsx @@ -118,6 +118,7 @@ export type IntegrationDefinition = { secretKeyPrefix?: string edgeFunctionSecretName?: string listingId?: string + oauthAppId?: string } & ( | { type: 'wrapper'; meta: WrapperMeta } | { type: 'postgres_extension' | 'custom' | 'oauth' | 'template' } diff --git a/apps/studio/components/interfaces/Integrations/Landing/Landing.utils.ts b/apps/studio/components/interfaces/Integrations/Landing/Landing.utils.ts index 50024118dbd6b..16edbd37d11cc 100644 --- a/apps/studio/components/interfaces/Integrations/Landing/Landing.utils.ts +++ b/apps/studio/components/interfaces/Integrations/Landing/Landing.utils.ts @@ -1,3 +1,4 @@ +import { useMemo } from 'react' import { parseSchemaComment } from 'stripe-experiment-sync/supabase' import { type WrapperMeta } from '../Wrappers/Wrappers.types' @@ -7,12 +8,19 @@ import { isInstalled as checkIsInstalled, findStripeSchema, } from '@/components/interfaces/Integrations/templates/StripeSyncEngine/stripe-sync-status' -import { type APIKey } from '@/data/api-keys/api-keys-query' +import { useAPIKeysQuery, type APIKey } from '@/data/api-keys/api-keys-query' +import { ProjectAuthConfigData, useAuthConfigQuery } from '@/data/auth/auth-config-query' import { type DatabaseExtension } from '@/data/database-extensions/database-extensions-query' import { type Schema } from '@/data/database/schemas-query' import { type FDW } from '@/data/fdw/fdws-query' -import { IntegrationStatus } from '@/data/partners/integration-status-query' -import { type ProjectSecret } from '@/data/secrets/secrets-query' +import { AuthorizedApp, useAuthorizedAppsQuery } from '@/data/oauth/authorized-apps-query' +import { + IntegrationStatus, + usePartnerIntegrationsQuery, +} from '@/data/partners/integration-status-query' +import { useSecretsQuery, type ProjectSecret } from '@/data/secrets/secrets-query' +import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' +import { ResponseError } from '@/types' export const isStripeSyncEngineInstalled = (schemas: Schema[]) => { const stripeSchema = findStripeSchema(schemas) @@ -20,35 +28,150 @@ export const isStripeSyncEngineInstalled = (schemas: Schema[]) => { return checkIsInstalled(parsedSchema.status) } +type ProjectOAuthIntegrationData = { + apiKeys: APIKey[] + edgeFunctionSecrets: ProjectSecret[] + authConfig: ProjectAuthConfigData | undefined + partnerIntegrations: IntegrationStatus[] + oauthApps: AuthorizedApp[] +} + +/** + * Gathers all the information needed to determine if an arbitrary OAuth integration is installed. + */ +export const useProjectOAuthIntegrationData = ( + projectRef: string | undefined, + { enabled = true } = {} +): { + data: ProjectOAuthIntegrationData + error: ResponseError | null + isError: boolean + isLoading: boolean + isPending: boolean + isSuccess: boolean +} => { + const { data: org } = useSelectedOrganizationQuery({ enabled }) + const queries = { + apiKeys: useAPIKeysQuery({ projectRef, reveal: false }, { enabled }), + edgeFunctionSecrets: useSecretsQuery({ projectRef }, { enabled }), + authConfig: useAuthConfigQuery({ projectRef }, { enabled }), + partnerIntegrations: usePartnerIntegrationsQuery({ projectRef }, { enabled }), + oauthApps: useAuthorizedAppsQuery({ slug: org?.slug }, { enabled: !!org }), + } + + // memoize to prevent object creation from triggering a re-render when the result data is used + // as a dependency. + const data = useMemo(() => { + return { + apiKeys: queries.apiKeys.data ?? [], + edgeFunctionSecrets: queries.edgeFunctionSecrets.data ?? [], + authConfig: queries.authConfig.data, + partnerIntegrations: queries.partnerIntegrations.data ?? [], + oauthApps: queries.oauthApps.data ?? [], + } + }, [ + queries.apiKeys.data, + queries.edgeFunctionSecrets.data, + queries.authConfig.data, + queries.partnerIntegrations.data, + queries.oauthApps.data, + ]) + + return { + data, + error: + Object.values(queries) + .map((q) => q.error) + .find((e) => !!e) || null, + isError: Object.values(queries).some((x) => x.isError), + isLoading: Object.values(queries).some((x) => x.isLoading), + isPending: Object.values(queries).some((x) => x.isPending), + isSuccess: Object.values(queries).every((x) => x.isSuccess), + } +} + +const isPartnerIntegrationReady = ( + projectData: ProjectOAuthIntegrationData, + integration: IntegrationDefinition +) => { + return projectData.partnerIntegrations.some( + (i) => i.listing_slug === integration.id && i.status === 'ready' + ) +} + +const isOAuthAppAuthorized = ( + projectData: ProjectOAuthIntegrationData, + integration: IntegrationDefinition +) => { + return ( + !!integration.oauthAppId && + projectData.oauthApps.some((app) => app.app_id === integration.oauthAppId) + ) +} + +const isSecretKeyPrefixPresent = (projectData: ProjectOAuthIntegrationData, prefix?: string) => { + if (!prefix) return false + return projectData.apiKeys.some((key) => key.type === 'secret' && key.name.startsWith(prefix)) +} + +const isEdgeFunctionSecretPresent = ( + projectData: ProjectOAuthIntegrationData, + secretName?: string +) => { + if (!secretName) return false + return projectData.edgeFunctionSecrets.some((secret) => secret.name === secretName) +} + export const isOAuthInstalled = ({ integration, - apiKeys, - secrets, - partnerIntegrations: partnerIntegrations, + projectData, }: { integration: IntegrationDefinition - apiKeys: APIKey[] - secrets: ProjectSecret[] - partnerIntegrations: IntegrationStatus[] + projectData: ProjectOAuthIntegrationData }) => { - if (integration.installIdentificationMethod === 'integration_status') { - return partnerIntegrations.some( - (i) => i.listing_slug === integration.id && i.status === 'ready' + // Special-case logic for in-development integrations + if (integration.id === 'resend') { + return ( + projectData.authConfig?.SMTP_HOST === 'smtp.resend.com' && + // Keying off of OAuth App instead of integration status lets us show the integration as + // installed for partner-initiated connections, without degrading the experience for + // marketplace-initiated installations. + isOAuthAppAuthorized(projectData, integration) ) } - if (integration.installIdentificationMethod === 'secret_key_prefix') { - const prefix = integration.secretKeyPrefix - if (!prefix) return false + if (integration.id === 'grafana') { + // Grafana is not yet sending integration status, so just use presence of API key. + return isSecretKeyPrefixPresent(projectData, 'grafana_cloud_integration_') + } - return apiKeys.some((key) => key.type === 'secret' && key.name.startsWith(prefix)) + if (integration.id === 'aikido') { + return isOAuthAppAuthorized(projectData, integration) } - if (integration.installIdentificationMethod === 'edge_function_secret_name') { - const secretName = integration.edgeFunctionSecretName - if (!secretName) return false + if (integration.id === 'doppler') { + return ( + isEdgeFunctionSecretPresent(projectData, 'DOPPLER_CONFIG') && + isPartnerIntegrationReady(projectData, integration) + ) + } - return secrets.some((secret) => secret.name === secretName) + // Fallback logic for generic OAuth integrations. + if (integration.installIdentificationMethod === 'integration_status') { + return isPartnerIntegrationReady(projectData, integration) + } + + if (integration.installIdentificationMethod === 'oauth_authorization') { + return isOAuthAppAuthorized(projectData, integration) + } + + // Special-case logic that is still encoded as database fields, consider removing. + if (integration.installIdentificationMethod === 'secret_key_prefix') { + return isSecretKeyPrefixPresent(projectData, integration.secretKeyPrefix) + } + + if (integration.installIdentificationMethod === 'edge_function_secret_name') { + return isEdgeFunctionSecretPresent(projectData, integration.edgeFunctionSecretName) } return false diff --git a/apps/studio/components/interfaces/Integrations/Landing/useAvailableIntegrations.tsx b/apps/studio/components/interfaces/Integrations/Landing/useAvailableIntegrations.tsx index a65e1167846cc..2dd5d116ce4cf 100644 --- a/apps/studio/components/interfaces/Integrations/Landing/useAvailableIntegrations.tsx +++ b/apps/studio/components/interfaces/Integrations/Landing/useAvailableIntegrations.tsx @@ -83,6 +83,7 @@ export const useAvailableIntegrations = () => { content, built_by: authorName, listing_logo: listingLogo, + oauth_app_id: oauthAppId, } = integration const status = undefined @@ -109,6 +110,7 @@ export const useAvailableIntegrations = () => { installIdentificationMethod: installMethod ?? undefined, secretKeyPrefix: secretKeyPrefix ?? undefined, edgeFunctionSecretName: edgeFunctionSecretName ?? undefined, + oauthAppId: oauthAppId ?? undefined, listingId: listingId ?? undefined, author, requiredExtensions: [], diff --git a/apps/studio/components/interfaces/Integrations/Landing/useInstalledIntegrations.tsx b/apps/studio/components/interfaces/Integrations/Landing/useInstalledIntegrations.tsx index cc0510b12a6a7..e17ce5e617cca 100644 --- a/apps/studio/components/interfaces/Integrations/Landing/useInstalledIntegrations.tsx +++ b/apps/studio/components/interfaces/Integrations/Landing/useInstalledIntegrations.tsx @@ -5,14 +5,12 @@ import { hasRequiredExtensions, isOAuthInstalled, isStripeSyncEngineInstalled, + useProjectOAuthIntegrationData, } from './Landing.utils' import { useAvailableIntegrations } from './useAvailableIntegrations' -import { useAPIKeysQuery } from '@/data/api-keys/api-keys-query' import { useDatabaseExtensionsQuery } from '@/data/database-extensions/database-extensions-query' import { useSchemasQuery } from '@/data/database/schemas-query' import { useFDWsQuery } from '@/data/fdw/fdws-query' -import { usePartnerIntegrationsQuery } from '@/data/partners/integration-status-query' -import { useSecretsQuery } from '@/data/secrets/secrets-query' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { EMPTY_ARR } from '@/lib/void' @@ -27,64 +25,17 @@ export const useInstalledIntegrations = () => { isError: isErrorAvailableIntegrations, } = useAvailableIntegrations() - const hasSecretKeyPrefixIntegration = useMemo(() => { - return allIntegrations.some( - (integration) => - integration.type === 'oauth' && - integration.installIdentificationMethod === 'secret_key_prefix' && - !!integration.secretKeyPrefix - ) + const hasOAuthIntegration = useMemo(() => { + return allIntegrations.some((integration) => integration.type === 'oauth') }, [allIntegrations]) - const hasEdgeFunctionSecretNameIntegration = useMemo(() => { - return allIntegrations.some( - (integration) => - integration.type === 'oauth' && - integration.installIdentificationMethod === 'edge_function_secret_name' && - !!integration.edgeFunctionSecretName - ) - }, [allIntegrations]) - - const hasCallbackStatusIntegration = useMemo(() => { - return allIntegrations.some( - (integration) => - integration.type === 'oauth' && - integration.installIdentificationMethod === 'integration_status' - ) - }, [allIntegrations]) - - const { - data: apiKeys = EMPTY_ARR, - error: apiKeysError, - isError: isErrorApiKeys, - isLoading: isApiKeysLoading, - isSuccess: isSuccessApiKeys, - } = useAPIKeysQuery( - { projectRef: project?.ref, reveal: false }, - { enabled: hasSecretKeyPrefixIntegration } - ) - - const { - data: edgeFunctionSecrets = EMPTY_ARR, - error: edgeFunctionSecretsError, - isError: isErrorEdgeFunctionSecrets, - isLoading: isEdgeFunctionSecretsLoading, - isSuccess: isSuccessEdgeFunctionSecrets, - } = useSecretsQuery( - { projectRef: project?.ref }, - { enabled: hasEdgeFunctionSecretNameIntegration } - ) - const { - data: partnerIntegrations = EMPTY_ARR, - error: partnerIntegrationsError, - isError: isErrorPartnerIntegrations, - isLoading: isPartnerIntegrationsLoading, - isSuccess: isSuccessPartnerIntegrations, - } = usePartnerIntegrationsQuery( - { projectRef: project?.ref }, - { enabled: hasCallbackStatusIntegration } - ) + data: oauthData, + error: oauthDataError, + isError: isErrorOAuthData, + isLoading: isOAuthDataLoading, + isSuccess: isSuccessOAuthData, + } = useProjectOAuthIntegrationData(project?.ref, { enabled: hasOAuthIntegration }) const { data: wrappers = EMPTY_ARR, @@ -137,48 +88,38 @@ export const useInstalledIntegrations = () => { if (integration.type === 'oauth') { return isOAuthInstalled({ integration, - apiKeys, - partnerIntegrations, - secrets: edgeFunctionSecrets, + projectData: oauthData, }) } return false }) .sort((a, b) => a.name.localeCompare(b.name)) - }, [allIntegrations, wrappers, extensions, schemas, isHooksEnabled, apiKeys, edgeFunctionSecrets]) + }, [allIntegrations, wrappers, extensions, schemas, isHooksEnabled, oauthData]) const error = fdwError || extensionsError || schemasError || availableIntegrationsError || - (hasSecretKeyPrefixIntegration ? apiKeysError : null) || - (hasEdgeFunctionSecretNameIntegration ? edgeFunctionSecretsError : null) || - (hasCallbackStatusIntegration ? partnerIntegrationsError : null) + (hasOAuthIntegration ? oauthDataError : null) const isLoading = isSchemasLoading || isFDWLoading || isExtensionsLoading || isAvailableIntegrationsLoading || - (hasSecretKeyPrefixIntegration && isApiKeysLoading) || - (hasEdgeFunctionSecretNameIntegration && isEdgeFunctionSecretsLoading) || - (hasCallbackStatusIntegration && isPartnerIntegrationsLoading) + (hasOAuthIntegration && isOAuthDataLoading) const isError = isErrorFDWs || isErrorExtensions || isErrorSchemas || isErrorAvailableIntegrations || - (hasSecretKeyPrefixIntegration && isErrorApiKeys) || - (hasEdgeFunctionSecretNameIntegration && isErrorEdgeFunctionSecrets) || - (hasCallbackStatusIntegration && isErrorPartnerIntegrations) + (hasOAuthIntegration && isErrorOAuthData) const isSuccess = isSuccessFDWs && isSuccessExtensions && isSuccessSchemas && isSuccessAvailableIntegrations && - (!hasSecretKeyPrefixIntegration || isSuccessApiKeys) && - (!hasEdgeFunctionSecretNameIntegration || isSuccessEdgeFunctionSecrets) && - (!hasCallbackStatusIntegration || isSuccessPartnerIntegrations) + (!hasOAuthIntegration || isSuccessOAuthData) return { // show all integrations at once instead of showing partial results diff --git a/packages/common/marketplace.types.ts b/packages/common/marketplace.types.ts index b2712607278dc..225f7370b9c37 100644 --- a/packages/common/marketplace.types.ts +++ b/packages/common/marketplace.types.ts @@ -50,7 +50,7 @@ export type Database = { listing_logo: string | null listing_tsv: unknown marketplace_url: string | null - oauth_client_id: string | null + oauth_app_id: string | null partner_id: string | null partner_logo: string | null partner_name: string | null From 4eaef098fa4f053d90baa92b145f968836fca3f8 Mon Sep 17 00:00:00 2001 From: Charis <26616127+charislam@users.noreply.github.com> Date: Fri, 5 Jun 2026 14:44:11 -0400 Subject: [PATCH 3/5] fix: auth config updates (#46683) ## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES/NO ## What kind of change does this PR introduce? Bug fix, feature, docs update, ... ## What is the current behavior? Please link any relevant issues here. ## What is the new behavior? Feel free to include screenshots if it includes visual changes. ## Additional context Add any other context or screenshots. ## Summary by CodeRabbit * **Bug Fixes** * Authentication configuration updates now trigger the app's default refresh behavior, ensuring changes propagate automatically and remain synchronized across the interface. --- apps/studio/data/auth/auth-config-update-mutation.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/studio/data/auth/auth-config-update-mutation.ts b/apps/studio/data/auth/auth-config-update-mutation.ts index 7d1fb426221b1..51f6ddc7577bb 100644 --- a/apps/studio/data/auth/auth-config-update-mutation.ts +++ b/apps/studio/data/auth/auth-config-update-mutation.ts @@ -1,7 +1,6 @@ import { useMutation, useQueryClient } from '@tanstack/react-query' import { toast } from 'sonner' -import type { ProjectAuthConfigData } from './auth-config-query' import { authKeys } from './keys' import type { components } from '@/data/api' import { handleError, patch } from '@/data/fetchers' @@ -46,10 +45,8 @@ export const useAuthConfigUpdateMutation = ({ const { projectRef, skipInvalidation = false } = variables if (!skipInvalidation) { - queryClient.setQueryData(authKeys.authConfig(projectRef), data) await queryClient.invalidateQueries({ queryKey: authKeys.authConfig(projectRef), - refetchType: 'none', }) } From b9f95c9aa9708061a5410807520671685b2c74cb Mon Sep 17 00:00:00 2001 From: Miranda Limonczenko <23711156+czenko@users.noreply.github.com> Date: Fri, 5 Jun 2026 15:00:32 -0700 Subject: [PATCH 4/5] fix(docs) Resolve React Router auth setup errors and confusion (#46684) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes DOCS-651 ## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? This adds a non-null assertion to a Supabase method that expects non-null. Additionally, it updates a label from 'Remix' to 'React Router'. ## What is the current behavior? Two issues: - Following the Auth client steps with React Router creates a `deprecation` error and downstream Typescript errors. - 'Remix' is renamed to 'React Router'. > Remix and React Router are the same thing, made by the same people. Remix was simply renamed React Router Framework Mode starting in version 7 of React Router. - [Blog Source](https://reacttraining.com/blog/remix-vs-react-router-framework) Screenshot 2026-06-04 at 4 00 45 PM Screenshot 2026-06-05 at 11 21 54 AM ## What is the new behavior? - Adding a non-null assertion clears up all error. Running the application does not produce errors. - Changing the label from "Remix" to "React Router" updates the dropdown name to match the rebrand. Now, it does not look outdated and matches the docs. Screenshot 2026-06-05 at 11 22 48 AM ## Additional context The task was to clarify our documentation on this page: [Create a Client](https://supabase.com/docs/guides/auth/server-side/creating-a-client?queryGroups=framework&framework=react-router&queryGroups=environment&environment=react-router-loader#create-a-client) However, the code sample in the docs is correct; the documentation in **Dashboard** produced the errors. ## Future improvements - To make this more robust, the code could have a single source of truth. ## Summary by CodeRabbit * **Bug Fixes** * Clarified generated Supabase server client template text to improve type/reference safety in the Remix integration guide. * **UI** * Renamed framework label from "Remix" to "React Router" across the Connect interfaces for clearer framework identification. --------- Co-authored-by: Miranda Limonczenko --- apps/studio/components/interfaces/Connect/Connect.constants.ts | 2 +- .../interfaces/Connect/content/remix/supabasejs/content.tsx | 2 +- .../components/interfaces/ConnectSheet/Connect.constants.ts | 2 +- .../ConnectSheet/content/remix/supabasejs/content.tsx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/studio/components/interfaces/Connect/Connect.constants.ts b/apps/studio/components/interfaces/Connect/Connect.constants.ts index 07854fdfb18b6..6b71f1e82294e 100644 --- a/apps/studio/components/interfaces/Connect/Connect.constants.ts +++ b/apps/studio/components/interfaces/Connect/Connect.constants.ts @@ -109,7 +109,7 @@ export const FRAMEWORKS: ConnectionType[] = [ }, { key: 'remix', - label: 'Remix', + label: 'React Router', icon: 'remix', guideLink: `${DOCS_URL}/guides/auth/server-side/creating-a-client?framework=remix&environment=remix-loader`, children: [ diff --git a/apps/studio/components/interfaces/Connect/content/remix/supabasejs/content.tsx b/apps/studio/components/interfaces/Connect/content/remix/supabasejs/content.tsx index 58ab3a1a9079e..8f65ce355d261 100644 --- a/apps/studio/components/interfaces/Connect/content/remix/supabasejs/content.tsx +++ b/apps/studio/components/interfaces/Connect/content/remix/supabasejs/content.tsx @@ -44,7 +44,7 @@ export function createClient(request: Request) { const supabase = createServerClient( process.env.VITE_SUPABASE_URL!, - process.env.VITE_${projectKeys.publishableKey ? 'SUPABASE_PUBLISHABLE_KEY' : 'SUPABASE_ANON_KEY'};, + process.env.VITE_${projectKeys.publishableKey ? 'SUPABASE_PUBLISHABLE_KEY' : 'SUPABASE_ANON_KEY'}!, { cookies: { getAll() { diff --git a/apps/studio/components/interfaces/ConnectSheet/Connect.constants.ts b/apps/studio/components/interfaces/ConnectSheet/Connect.constants.ts index b1f9f1a6d5022..2d7673555a07b 100644 --- a/apps/studio/components/interfaces/ConnectSheet/Connect.constants.ts +++ b/apps/studio/components/interfaces/ConnectSheet/Connect.constants.ts @@ -109,7 +109,7 @@ export const FRAMEWORKS: ConnectionType[] = [ }, { key: 'remix', - label: 'Remix', + label: 'React Router', icon: 'remix', guideLink: `${DOCS_URL}/guides/auth/server-side/creating-a-client?framework=remix&environment=remix-loader`, children: [ diff --git a/apps/studio/components/interfaces/ConnectSheet/content/remix/supabasejs/content.tsx b/apps/studio/components/interfaces/ConnectSheet/content/remix/supabasejs/content.tsx index 3b3786fae1323..d2eada2353ecc 100644 --- a/apps/studio/components/interfaces/ConnectSheet/content/remix/supabasejs/content.tsx +++ b/apps/studio/components/interfaces/ConnectSheet/content/remix/supabasejs/content.tsx @@ -30,7 +30,7 @@ export function createClient(request: Request) { const supabase = createServerClient( process.env.VITE_SUPABASE_URL!, - process.env.VITE_${projectKeys.publishableKey ? 'SUPABASE_PUBLISHABLE_KEY' : 'SUPABASE_ANON_KEY'}, + process.env.VITE_${projectKeys.publishableKey ? 'SUPABASE_PUBLISHABLE_KEY' : 'SUPABASE_ANON_KEY'}!, { cookies: { getAll() { From 3e3496ef6c395b60bf90d0266e77751919443682 Mon Sep 17 00:00:00 2001 From: Alex Hall Date: Fri, 5 Jun 2026 18:10:49 -0400 Subject: [PATCH 5/5] fix(studio): copy updates to revoke oauth modal (#46687) --- .../Organization/OAuthApps/RevokeAppModal.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/studio/components/interfaces/Organization/OAuthApps/RevokeAppModal.tsx b/apps/studio/components/interfaces/Organization/OAuthApps/RevokeAppModal.tsx index f46e0e4d03756..bdf097b0d95cc 100644 --- a/apps/studio/components/interfaces/Organization/OAuthApps/RevokeAppModal.tsx +++ b/apps/studio/components/interfaces/Organization/OAuthApps/RevokeAppModal.tsx @@ -40,13 +40,13 @@ export const RevokeAppModal = ({ selectedApp, onClose }: RevokeAppModalProps) => - {`Confirm to revoke ${selectedApp?.name}`} + {`Revoke access for ${selectedApp?.name}?`}
    @@ -56,8 +56,15 @@ export const RevokeAppModal = ({ selectedApp, onClose }: RevokeAppModalProps) => Before you remove this app, consider:
    • - No users are currently using this application. The application will no - longer have access to your organization after being revoked. + The application will no longer have access to your organization after being + revoked. +
    • +
    • + This will remove the application for all members in your organization. +
    • +
    • + Restoring access will require an organization administrator to re-authorize + the application.