InstallOAuthIntegrationButton.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import { useParams } from 'common'
  2. import { useMemo } from 'react'
  3. import { toast } from 'sonner'
  4. import { Button } from 'ui'
  5. import type { IntegrationDefinition } from '@/components/interfaces/Integrations/Landing/Integrations.constants'
  6. import { useAPIKeysQuery } from '@/data/api-keys/api-keys-query'
  7. import { useInstallOAuthIntegrationMutation } from '@/data/marketplace/install-oauth-integration-mutation'
  8. import { useSecretsQuery } from '@/data/secrets/secrets-query'
  9. import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
  10. interface InstallOAuthIntegrationButtonProps {
  11. integration: IntegrationDefinition
  12. }
  13. export function InstallOAuthIntegrationButton({ integration }: InstallOAuthIntegrationButtonProps) {
  14. const { ref: projectRef } = useParams()
  15. const { data: selectedOrg } = useSelectedOrganizationQuery()
  16. const requiresApiKeysCheck =
  17. integration.installIdentificationMethod === 'secret_key_prefix' && !!integration.secretKeyPrefix
  18. const requiresEdgeFunctionSecretsCheck =
  19. integration.installIdentificationMethod === 'edge_function_secret_name' &&
  20. !!integration.edgeFunctionSecretName
  21. const { data: apiKeys, isLoading: isApiKeysLoading } = useAPIKeysQuery(
  22. { projectRef, reveal: false },
  23. { enabled: requiresApiKeysCheck }
  24. )
  25. const { data: edgeFunctionSecrets, isPending: isEdgeFunctionSecretsLoading } = useSecretsQuery(
  26. { projectRef },
  27. { enabled: requiresEdgeFunctionSecretsCheck }
  28. )
  29. const { mutate: installOAuthIntegration, isPending: isInstalling } =
  30. useInstallOAuthIntegrationMutation({
  31. onSuccess: (data) => {
  32. if ('redirectUrl' in data) {
  33. if (!data.redirectUrl) {
  34. toast.error('Failed to redirect because redirect URL is invalid')
  35. return
  36. }
  37. window.open(data.redirectUrl, '_blank', 'noreferrer')
  38. } else {
  39. toast.error('Failed to start integration installation')
  40. }
  41. },
  42. })
  43. const isLoading =
  44. (requiresApiKeysCheck && isApiKeysLoading) ||
  45. (requiresEdgeFunctionSecretsCheck && isEdgeFunctionSecretsLoading)
  46. const isIntegrationInstalled = useMemo(() => {
  47. if (!integration) return false
  48. if (integration.installIdentificationMethod === 'secret_key_prefix') {
  49. const prefix = integration.secretKeyPrefix
  50. if (!prefix || isApiKeysLoading || !apiKeys) return false
  51. return apiKeys.some((k) => k.type === 'secret' && k.name.startsWith(prefix))
  52. }
  53. if (integration.installIdentificationMethod === 'edge_function_secret_name') {
  54. const secretName = integration.edgeFunctionSecretName
  55. if (!secretName || isEdgeFunctionSecretsLoading || !edgeFunctionSecrets) return false
  56. return edgeFunctionSecrets.some((secret) => secret.name === secretName)
  57. }
  58. return false
  59. }, [apiKeys, edgeFunctionSecrets, integration, isApiKeysLoading, isEdgeFunctionSecretsLoading])
  60. const handleInstallClick = async () => {
  61. if (!integration || !projectRef) return
  62. if (integration.installUrlType === 'post') {
  63. if (!integration.id) return toast.error('Listing ID is required')
  64. installOAuthIntegration({ projectRef, listingSlug: integration.id })
  65. } else {
  66. let redirectUrl = '/'
  67. if (integration.installUrl) {
  68. const url = new URL(integration.installUrl)
  69. url.searchParams.append('organization_slug', selectedOrg?.slug ?? '')
  70. url.searchParams.append('project_id', projectRef)
  71. redirectUrl = url.href
  72. }
  73. window.open(redirectUrl, '_blank', 'noreferrer')
  74. }
  75. }
  76. return (
  77. <>
  78. {isIntegrationInstalled ? (
  79. <Button disabled type="outline" className="shrink-0">
  80. Installed
  81. </Button>
  82. ) : (
  83. <Button
  84. type="primary"
  85. className="shrink-0"
  86. loading={isInstalling || isLoading}
  87. disabled={isLoading}
  88. onClick={handleInstallClick}
  89. >
  90. Install integration
  91. </Button>
  92. )}
  93. </>
  94. )
  95. }