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(
I’m purchasing as a business
+
+
+
+
+
+ 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.
+
+
)}
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() {
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/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.
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',
})
}
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