Files
supabase/apps/studio/components/layouts/ProjectIntegrationsLayout.tsx
Raminder Singh d691073f02 feat: override wrappers in dashboard with those from marketplace db (#46472)
This PR overrides title, description, content, logo, images, docs url,
and site url from marketplace db for wrappers. If marketplace doesn't
yet publish a wrapper listing, the page falls back to the hardcoded
content we show today.

It also improves the marketplace listings and categories queries by
returning typed results, making the code more type safe.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Studio integrations now surface updated marketplace metadata (name,
description, icon, docs, site, author, files) when available.
* Marketplace wrapper integrations are consolidated and shown alongside
studio integrations.

* **Refactor**
* Marketplace category and integration fetching rewritten for more
reliable loading, cancellation support, and improved menu/category
population.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46472?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-01 13:06:37 +05:30

175 lines
5.4 KiB
TypeScript

import { IS_PLATFORM, useFeatureFlags, useParams } from 'common'
import { useRouter } from 'next/router'
import { PropsWithChildren } from 'react'
import { Menu, Separator } from 'ui'
import { GenericSkeletonLoader } from 'ui-patterns'
import { useIsMarketplaceEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { useInstalledIntegrations } from '@/components/interfaces/Integrations/Landing/useInstalledIntegrations'
import { ProjectLayout } from '@/components/layouts/ProjectLayout'
import AlertError from '@/components/ui/AlertError'
import { ProductMenu } from '@/components/ui/ProductMenu'
import { useMarketplaceCategoriesQuery } from '@/data/marketplace/integration-categories-query'
import { useMarketplaceIntegrationsQuery } from '@/data/marketplace/integrations-query'
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
import { withAuth } from '@/hooks/misc/withAuth'
/**
* Layout component for the Integrations section
* Provides sidebar navigation for integrations
*/
export const ProjectIntegrationsLayout = withAuth(({ children }: PropsWithChildren) => {
const router = useRouter()
const segments = router.asPath.split('/')
// construct the page url to be used to determine the active state for the sidebar
const page = `${segments[3]}${segments[4] ? `/${segments[4]}` : ''}`
return (
<ProjectLayout
product="Integrations"
browserTitle={{ section: 'Integrations' }}
isBlocking={false}
productMenu={
<>
<IntegrationCategoriesMenu page={page} />
<Separator />
<InstalledIntegrationsMenu page={page} />
</>
}
>
{children}
</ProjectLayout>
)
})
const IntegrationCategoriesMenu = ({ page }: { page: string }) => {
const router = useRouter()
const { ref } = useParams()
const { hasLoaded: flagsLoaded } = useFeatureFlags()
const isMarketplaceEnabled = useIsMarketplaceEnabled()
const urlParams = new URLSearchParams(router.asPath.split('?')[1] || '')
const categoryParam = urlParams.get('category')
const pageKey = categoryParam || page
const { integrationsWrappers: showWrappers } = useIsFeatureEnabled(['integrations:wrappers'])
const { data: categories = [], isPending: isPendingCategories } = useMarketplaceCategoriesQuery({
enabled: isMarketplaceEnabled,
})
const { data: listings = [], isPending: isPendingListings } = useMarketplaceIntegrationsQuery({
enabled: isMarketplaceEnabled,
})
const populatedCategoryIds = new Set(
listings.flatMap((listing) => listing.categories.map((c) => c.id))
)
const nonEmptyCategories = categories.filter(
(category) => category.id && populatedCategoryIds.has(category.id)
)
const isLoading = IS_PLATFORM
? !flagsLoaded || (isMarketplaceEnabled && (isPendingCategories || isPendingListings))
: false
const allCategories = [
{
name: 'All',
key: 'integrations',
url: `/project/${ref}/integrations`,
pages: ['integrations'],
items: [],
},
...(showWrappers
? [
{
name: 'Wrappers',
key: 'wrapper',
url: `/project/${ref}/integrations?category=wrapper`,
items: [],
},
]
: []),
{
name: 'Postgres Modules',
key: 'postgres_extension',
url: `/project/${ref}/integrations?category=postgres_extension`,
items: [],
},
...nonEmptyCategories.map((category) => ({
name: category.name ?? '',
key: category.slug ?? '',
url: `/project/${ref}/integrations?category=${category.slug}`,
items: [],
})),
]
return (
<>
{isLoading ? (
<div className="px-4 py-6 md:px-6">
<Menu type="pills">
<Menu.Group title={<span className="uppercase font-mono">Explore</span>} />
</Menu>
<GenericSkeletonLoader />
</div>
) : (
<ProductMenu
page={pageKey}
menu={[{ key: 'explore', title: 'Explore', items: allCategories }]}
/>
)}
</>
)
}
const InstalledIntegrationsMenu = ({ page }: { page: string }) => {
const { ref } = useParams()
const {
installedIntegrations: integrations,
error,
isLoading,
isSuccess,
isError,
} = useInstalledIntegrations()
const installedIntegrationItems = integrations.map((integration) => ({
name: integration.name,
label: integration.status,
key: `integrations/${integration.id}`,
url: `/project/${ref}/integrations/${integration.id}/overview`,
icon: (
<div className="relative w-6 h-6 bg-white border rounded-sm flex items-center justify-center">
{integration.icon({ className: 'p-1' })}
</div>
),
items: [],
}))
return (
<>
{(isLoading || isError) && (
<div className="px-4 py-6 md:px-6">
<Menu type="pills">
<Menu.Group title={<span className="uppercase font-mono">Installed</span>} />
</Menu>
{isLoading && <GenericSkeletonLoader />}
{isError && (
<AlertError
showIcon={false}
error={error}
subject="Failed to retrieve installed integrations"
/>
)}
</div>
)}
{isSuccess && (
<ProductMenu
page={page}
menu={[{ key: 'installed', title: 'Installed', items: installedIntegrationItems }]}
/>
)}
</>
)
}