ProjectIntegrationsLayout.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. // @ts-nocheck
  2. import { useQuery } from '@tanstack/react-query'
  3. import { IS_PLATFORM, useFeatureFlags, useParams } from 'common'
  4. import { useRouter } from 'next/router'
  5. import { PropsWithChildren } from 'react'
  6. import { Menu, Separator } from 'ui'
  7. import { GenericSkeletonLoader } from 'ui-patterns'
  8. import { useIsMarketplaceEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
  9. import { useInstalledIntegrations } from '@/components/interfaces/Integrations/Landing/useInstalledIntegrations'
  10. import { ProjectLayout } from '@/components/layouts/ProjectLayout'
  11. import AlertError from '@/components/ui/AlertError'
  12. import { ProductMenu } from '@/components/ui/ProductMenu'
  13. import { useMarketplaceCategoriesQuery } from '@/data/marketplace/integration-categories-query'
  14. import { useMarketplaceIntegrationsQuery } from '@/data/marketplace/integrations-query'
  15. import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
  16. import { withAuth } from '@/hooks/misc/withAuth'
  17. /**
  18. * Layout component for the Integrations section
  19. * Provides sidebar navigation for integrations
  20. */
  21. export const ProjectIntegrationsLayout = withAuth(({ children }: PropsWithChildren) => {
  22. const router = useRouter()
  23. const segments = router.asPath.split('/')
  24. // construct the page url to be used to determine the active state for the sidebar
  25. const page = `${segments[3]}${segments[4] ? `/${segments[4]}` : ''}`
  26. return (
  27. <ProjectLayout
  28. product="Integrations"
  29. browserTitle={{ section: 'Integrations' }}
  30. isBlocking={false}
  31. productMenu={
  32. <>
  33. <IntegrationCategoriesMenu page={page} />
  34. <Separator />
  35. <InstalledIntegrationsMenu page={page} />
  36. </>
  37. }
  38. >
  39. {children}
  40. </ProjectLayout>
  41. )
  42. })
  43. const IntegrationCategoriesMenu = ({ page }: { page: string }) => {
  44. const router = useRouter()
  45. const { ref } = useParams()
  46. const { hasLoaded: flagsLoaded } = useFeatureFlags()
  47. const isMarketplaceEnabled = useIsMarketplaceEnabled()
  48. const urlParams = new URLSearchParams(router.asPath.split('?')[1] || '')
  49. const categoryParam = urlParams.get('category')
  50. const pageKey = categoryParam || page
  51. const { integrationsWrappers: showWrappers } = useIsFeatureEnabled(['integrations:wrappers'])
  52. const { data: categories = [], isPending: isPendingCategories } = useQuery(
  53. useMarketplaceCategoriesQuery({ enabled: isMarketplaceEnabled })
  54. )
  55. const { data: listings = [], isPending: isPendingListings } = useQuery(
  56. useMarketplaceIntegrationsQuery({ enabled: isMarketplaceEnabled })
  57. )
  58. const populatedCategoryIds = new Set(
  59. listings.flatMap((listing) =>
  60. Array.isArray(listing.categories)
  61. ? (listing.categories as Array<{ id: string }>).map((c) => c.id)
  62. : []
  63. )
  64. )
  65. const nonEmptyCategories = categories.filter(
  66. (category) => category.id && populatedCategoryIds.has(category.id)
  67. )
  68. const isLoading = IS_PLATFORM
  69. ? !flagsLoaded || (isMarketplaceEnabled && (isPendingCategories || isPendingListings))
  70. : false
  71. const allCategories = [
  72. {
  73. name: 'All',
  74. key: 'integrations',
  75. url: `/project/${ref}/integrations`,
  76. pages: ['integrations'],
  77. items: [],
  78. },
  79. ...(showWrappers
  80. ? [
  81. {
  82. name: 'Wrappers',
  83. key: 'wrapper',
  84. url: `/project/${ref}/integrations?category=wrapper`,
  85. items: [],
  86. },
  87. ]
  88. : []),
  89. {
  90. name: 'Postgres Modules',
  91. key: 'postgres_extension',
  92. url: `/project/${ref}/integrations?category=postgres_extension`,
  93. items: [],
  94. },
  95. ...nonEmptyCategories.map((category) => ({
  96. name: category.name ?? '',
  97. key: category.slug ?? '',
  98. url: `/project/${ref}/integrations?category=${category.slug}`,
  99. items: [],
  100. })),
  101. ]
  102. return (
  103. <>
  104. {isLoading ? (
  105. <div className="px-4 py-6 md:px-6">
  106. <Menu type="pills">
  107. <Menu.Group title={<span className="uppercase font-mono">Explore</span>} />
  108. </Menu>
  109. <GenericSkeletonLoader />
  110. </div>
  111. ) : (
  112. <ProductMenu
  113. page={pageKey}
  114. menu={[{ key: 'explore', title: 'Explore', items: allCategories }]}
  115. />
  116. )}
  117. </>
  118. )
  119. }
  120. const InstalledIntegrationsMenu = ({ page }: { page: string }) => {
  121. const { ref } = useParams()
  122. const {
  123. installedIntegrations: integrations,
  124. error,
  125. isLoading,
  126. isSuccess,
  127. isError,
  128. } = useInstalledIntegrations()
  129. const installedIntegrationItems = integrations.map((integration) => ({
  130. name: integration.name,
  131. label: integration.status,
  132. key: `integrations/${integration.id}`,
  133. url: `/project/${ref}/integrations/${integration.id}/overview`,
  134. icon: (
  135. <div className="relative w-6 h-6 bg-white border rounded-sm flex items-center justify-center">
  136. {integration.icon({ className: 'p-1' })}
  137. </div>
  138. ),
  139. items: [],
  140. }))
  141. return (
  142. <>
  143. {(isLoading || isError) && (
  144. <div className="px-4 py-6 md:px-6">
  145. <Menu type="pills">
  146. <Menu.Group title={<span className="uppercase font-mono">Installed</span>} />
  147. </Menu>
  148. {isLoading && <GenericSkeletonLoader />}
  149. {isError && (
  150. <AlertError
  151. showIcon={false}
  152. error={error}
  153. subject="Failed to retrieve installed integrations"
  154. />
  155. )}
  156. </div>
  157. )}
  158. {isSuccess && (
  159. <ProductMenu
  160. page={page}
  161. menu={[{ key: 'installed', title: 'Installed', items: installedIntegrationItems }]}
  162. />
  163. )}
  164. </>
  165. )
  166. }