useAvailableIntegrations.tsx166 lines · main
1// @ts-nocheck
2import { useQuery } from '@tanstack/react-query'
3import { FeatureFlagContext, IS_PLATFORM } from 'common'
4import { fullImageUrl } from 'common/marketplace-client'
5import { Boxes } from 'lucide-react'
6import dynamic from 'next/dynamic'
7import Image from 'next/image'
8import { useContext, useMemo } from 'react'
9import { cn } from 'ui'
10
11import { INTEGRATIONS, Loading, type IntegrationDefinition } from './Integrations.constants'
12import { useIsMarketplaceEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
13import { useMarketplaceIntegrationsQuery } from '@/data/marketplace/integrations-query'
14import { useCLIReleaseVersionQuery } from '@/data/misc/cli-release-version-query'
15import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
16
17/**
18 * [Joshen] Returns a combination of
19 * - Marketplace integrations retrieved remotely (Only if feature flag enabled)
20 * - Existing integrations that are defined within studio
21 */
22export const useAvailableIntegrations = () => {
23 const { hasLoaded } = useContext(FeatureFlagContext)
24 const isMarketplaceEnabled = useIsMarketplaceEnabled()
25 const { integrationsWrappers } = useIsFeatureEnabled(['integrations:wrappers'])
26
27 const { data: cliData } = useCLIReleaseVersionQuery()
28 const isCLI = !!cliData?.current
29
30 const { data, error } = useQuery({
31 ...useMarketplaceIntegrationsQuery(),
32 enabled: isMarketplaceEnabled,
33 })
34 const isPending = IS_PLATFORM && (!hasLoaded || (isMarketplaceEnabled && !data && !error))
35 const isSuccess = !IS_PLATFORM || (hasLoaded && (!isMarketplaceEnabled || (!!data && !error)))
36 const isError = IS_PLATFORM && isMarketplaceEnabled && !!error
37
38 // [Joshen] Format marketplace integrations into existing ones for now
39 // Likely that we might need to change, but can look into separately
40 const marketplaceIntegrations: IntegrationDefinition[] = useMemo(
41 () =>
42 (data ?? [])?.map((integration) => {
43 const {
44 id: listingId,
45 slug,
46 categories,
47 featured,
48 title,
49 description,
50 documentation_url: docsUrl,
51 website_url: siteUrl,
52 installation_url: installUrl,
53 installation_url_type: installUrlType,
54 installation_identification_method: installMethod,
55 secret_key_prefix: secretKeyPrefix,
56 edge_function_secret_name: edgeFunctionSecretName,
57 images,
58 content,
59 partner_name: authorName,
60 listing_logo: listingLogo,
61 } = integration
62
63 const status = undefined
64 const author = { name: authorName ?? '', websiteUrl: '' }
65
66 return {
67 id: slug ?? '',
68 name: title ?? '',
69 status,
70 featured: !!featured,
71 type: 'oauth' as const, // Currently marketplace only supports oauth apps
72 source: 'Partner' as const,
73 categories: Array.isArray(categories)
74 ? (categories as Array<{ slug: string }>).map((x) => x.slug)
75 : [],
76 content,
77 files: images?.map((image) => fullImageUrl(image)),
78 description,
79 docsUrl,
80 siteUrl,
81 installUrl,
82 installUrlType: installUrlType ?? undefined,
83 installIdentificationMethod: installMethod ?? undefined,
84 secretKeyPrefix: secretKeyPrefix ?? undefined,
85 edgeFunctionSecretName: edgeFunctionSecretName ?? undefined,
86 listingId: listingId ?? undefined,
87 author,
88 requiredExtensions: [],
89 icon: ({ className, ...props } = {}) => (
90 <div className="relative w-full h-full">
91 {listingLogo ? (
92 <Image
93 fill
94 src={fullImageUrl(listingLogo)}
95 alt=""
96 className={cn('p-2', className)}
97 {...props}
98 />
99 ) : (
100 <Boxes
101 className={cn('inset-0 p-2 text-black w-full h-full', className)}
102 {...props}
103 />
104 )}
105 </div>
106 ),
107 navigation: [
108 {
109 route: 'overview',
110 label: 'Overview',
111 },
112 ],
113 navigate: ({ pageId = 'overview' }) => {
114 switch (pageId) {
115 case 'overview':
116 return dynamic(
117 () =>
118 import('@/components/interfaces/Integrations/Integration/IntegrationOverviewTabV2/index').then(
119 (mod) => mod.IntegrationOverviewTabV2
120 ),
121 {
122 loading: Loading,
123 }
124 )
125 }
126 return null
127 },
128 }
129 }),
130 [data]
131 )
132
133 // [Joshen] Existing integrations that are defined within studio
134 // Available integrations are all integrations that can be installed. If an integration can't be installed (needed
135 // extensions are not available on this DB image), the UI will provide a tooltip explaining why.
136 const allIntegrations = useMemo(() => {
137 return INTEGRATIONS.filter((integration) => {
138 if (
139 !integrationsWrappers &&
140 (integration.type === 'wrapper' || integration.id.endsWith('_wrapper'))
141 ) {
142 return false
143 }
144
145 if (integration.id === 'stripe_sync_engine' && isCLI) {
146 return false
147 }
148
149 return true
150 })
151 }, [integrationsWrappers, isCLI])
152
153 const dataWithMarketplace = useMemo(() => {
154 return [...marketplaceIntegrations, ...allIntegrations].sort((a, b) =>
155 a.name.localeCompare(b.name)
156 )
157 }, [marketplaceIntegrations, allIntegrations])
158
159 return {
160 data: dataWithMarketplace,
161 error,
162 isPending,
163 isSuccess,
164 isError,
165 }
166}