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. + + )} 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