diff --git a/apps/studio/.github/eslint-rule-baselines.json b/apps/studio/.github/eslint-rule-baselines.json
index c95bbd984d6..77b60c9fefa 100644
--- a/apps/studio/.github/eslint-rule-baselines.json
+++ b/apps/studio/.github/eslint-rule-baselines.json
@@ -639,7 +639,6 @@
"components/interfaces/Functions/EdgeFunctionSecrets/EdgeFunctionSecret.tsx": 1,
"components/interfaces/Integrations/CronJobs/CronJobsEmptyState.tsx": 1,
"components/interfaces/Integrations/CronJobs/CronSyntaxChart.tsx": 1,
- "components/interfaces/Integrations/Integration/LegacyIntegrationPage.tsx": 1,
"components/interfaces/Integrations/Vercel/OrganizationPicker.tsx": 1,
"components/interfaces/Integrations/Wrappers/InputField.tsx": 1,
"components/interfaces/Integrations/Wrappers/WrapperTableEditor.tsx": 1,
diff --git a/apps/studio/TANSTACK_MIGRATION.md b/apps/studio/TANSTACK_MIGRATION.md
index 387e808c2ce..19a7141e828 100644
--- a/apps/studio/TANSTACK_MIGRATION.md
+++ b/apps/studio/TANSTACK_MIGRATION.md
@@ -96,7 +96,7 @@ These are the layout-only TanStack files. Most hold a single product layout comp
- [x] `routes/project/$ref/advisors.tsx` — AdvisorsLayout (reads `advisorsLayoutTitle` from leaf staticData). Honours `skipAdvisorsLayout: true` opt-out for the rules sub-shell, which provides its own AdvisorsLayout-less-DefaultLayout wrap. Scans whole match chain (same pattern as functions.tsx).
- [x] `routes/project/$ref/advisors/rules.tsx` — sub-shell that inlines the inner body of `AdvisorRulesLayout` (AdvisorsLayout + PageLayout with title/tabs/feature-preview badge), minus the outer DefaultLayout (already provided by the parent project shell). Sets `skipAdvisorsLayout: true` on its own staticData. **Delta vs plan:** the existing `AdvisorRulesLayout` component wraps in DefaultLayout + AdvisorsLayout internally, so reusing it as-is would double-wrap both. Inlined the inner part; the Next-side component is untouched.
- [x] `routes/project/$ref/settings.tsx` — SettingsLayout (reads `settingsLayoutTitle` from leaf staticData). Honours `skipSettingsLayout: true` for `settings/api` (redirect-only page). Adds a sub-shell at `routes/project/$ref/settings/api-keys.tsx` providing `ApiKeysLayout` for both api-keys leaves; `jwt/index` wraps in `JWTKeysLayout` inline since `jwt/legacy` doesn't share it.
-- [x] `routes/project/$ref/integrations.tsx` — ProjectIntegrationsLayout (no staticData; all 4 leaves share identical layout). Layout is `withAuth(({ children }) => {children} )`, so the shell just wraps ` ` once.
+- [x] `routes/project/$ref/integrations.tsx` — ProjectMarketplaceLayout (no staticData; all 4 leaves share identical layout). Layout is `withAuth(({ children }) => {children} )`, so the shell just wraps ` ` once.
- [x] `routes/project/$ref/sql.tsx` — EditorBaseLayout + SQLEditorLayout. Twin of editor.tsx; all four leaves share identical layout props so the shell hardcodes them (no `staticData` overrides). EditorBaseLayout wraps in ProjectLayoutWithAuth; SQLEditorLayout adds its own `withAuth` HOC but no extra ProjectLayout — same shape as the table editor (auth check runs twice but no double render).
- [x] `routes/project/$ref/editor.tsx` — EditorBaseLayout + TableEditorLayout. All three leaves share identical layout props so the shell hardcodes them (no `staticData` overrides). EditorBaseLayout wraps in `ProjectLayoutWithAuth` internally; TableEditorLayout's happy path is just a fragment + side-effect (banner) and only wraps in `ProjectLayoutWithAuth` on its no-permission branch — same as Next, no double-wrap in normal use.
diff --git a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx
index ce32634141e..fcce4010dde 100644
--- a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx
+++ b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx
@@ -134,12 +134,6 @@ export const useIsSqlEditorManualSaveEnabled = () => {
return sqlEditorManualSaveEnabled && flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_SQL_EDITOR_MANUAL_SAVE]
}
-export const useIsMarketplaceEnabled = () => {
- const { flags } = useFeaturePreviewContext()
- const isMarketplaceEnabled = useFlag('marketplaceIntegrations')
- return isMarketplaceEnabled && flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_MARKETPLACE]
-}
-
export const useFeaturePreviewModal = () => {
const featurePreviews = useFeaturePreviews()
const [featurePreviewModal, setFeaturePreviewModal] = useQueryState('featurePreviewModal')
diff --git a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewModal.tsx b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewModal.tsx
index 5453e536903..9cd78597ffc 100644
--- a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewModal.tsx
+++ b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewModal.tsx
@@ -30,7 +30,6 @@ import {
import { AdvisorRulesPreview } from './AdvisorRulesPreview'
import { CLSPreview } from './CLSPreview'
import { useFeaturePreviewContext, useFeaturePreviewModal } from './FeaturePreviewContext'
-import { IntegrationsLayoutPreview } from './IntegrationsLayoutPreview'
import { JitDbAccessPreview } from './JitDbAccessPreview'
import { PgDeltaDiffPreview } from './PgDeltaDiffPreview'
import { PlatformWebhooksPreview } from './PlatformWebhooksPreview'
@@ -50,7 +49,6 @@ const FEATURE_PREVIEW_KEY_TO_CONTENT: {
[LOCAL_STORAGE_KEYS.UI_PREVIEW_PLATFORM_WEBHOOKS]: ,
[LOCAL_STORAGE_KEYS.UI_PREVIEW_JIT_DB_ACCESS]: ,
[LOCAL_STORAGE_KEYS.UI_PREVIEW_SQL_EDITOR_MANUAL_SAVE]: ,
- [LOCAL_STORAGE_KEYS.UI_PREVIEW_MARKETPLACE]: ,
}
export const FeaturePreviewModal = () => {
diff --git a/apps/studio/components/interfaces/App/FeaturePreview/IntegrationsLayoutPreview.tsx b/apps/studio/components/interfaces/App/FeaturePreview/IntegrationsLayoutPreview.tsx
deleted file mode 100644
index 28c8e453fd1..00000000000
--- a/apps/studio/components/interfaces/App/FeaturePreview/IntegrationsLayoutPreview.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import Image from 'next/image'
-import { BASE_PATH } from 'ui-patterns/CommandMenu/prepackaged/shared/constants'
-
-export const IntegrationsLayoutPreview = () => (
-
-
- Install Dashboard Integrations in a single click and try the new layout.
-
-
-
-
-)
diff --git a/apps/studio/components/interfaces/App/FeaturePreview/useFeaturePreviews.ts b/apps/studio/components/interfaces/App/FeaturePreview/useFeaturePreviews.ts
index 65be8e9df45..c14b1c185e5 100644
--- a/apps/studio/components/interfaces/App/FeaturePreview/useFeaturePreviews.ts
+++ b/apps/studio/components/interfaces/App/FeaturePreview/useFeaturePreviews.ts
@@ -22,7 +22,6 @@ export type FeaturePreview = {
export const useFeaturePreviews = (): FeaturePreview[] => {
const platformWebhooksEnabled = useFlag('platformWebhooks')
const jitDbAccessEnabled = useFlag('jitDbAccess')
- const isMarketplaceEnabled = useFlag('marketplaceIntegrations')
const sqlEditorManualSaveEnabled = useFlag('sqlEditorManualSave')
const unifiedLogsDefaultOptIn = useFlag('unifiedLogsDefaultOptIn')
@@ -90,16 +89,6 @@ export const useFeaturePreviews = (): FeaturePreview[] => {
isDefaultOptIn: false,
getRoute: (ref?: string) => `/project/${ref}/database/column-privileges`,
},
- {
- key: LOCAL_STORAGE_KEYS.UI_PREVIEW_MARKETPLACE,
- name: 'One-Click Integrations',
- discussionsUrl: undefined,
- enabled: isMarketplaceEnabled,
- isNew: true,
- isPlatformOnly: false,
- isDefaultOptIn: true,
- getRoute: (ref?: string) => `/project/${ref}/integrations`,
- },
{
key: LOCAL_STORAGE_KEYS.UI_PREVIEW_SQL_EDITOR_MANUAL_SAVE,
name: 'Disable snippet auto-saving',
@@ -110,12 +99,6 @@ export const useFeaturePreviews = (): FeaturePreview[] => {
enabled: sqlEditorManualSaveEnabled,
},
].sort((a, b) => Number(b.isNew) - Number(a.isNew)),
- [
- unifiedLogsDefaultOptIn,
- platformWebhooksEnabled,
- jitDbAccessEnabled,
- isMarketplaceEnabled,
- sqlEditorManualSaveEnabled,
- ]
+ [unifiedLogsDefaultOptIn, platformWebhooksEnabled, jitDbAccessEnabled, sqlEditorManualSaveEnabled]
)
}
diff --git a/apps/studio/components/interfaces/Integrations/ConstrainedIntegrationTabScaffold.tsx b/apps/studio/components/interfaces/Integrations/ConstrainedIntegrationTabScaffold.tsx
index bcdacecb969..b5ee7aa8e11 100644
--- a/apps/studio/components/interfaces/Integrations/ConstrainedIntegrationTabScaffold.tsx
+++ b/apps/studio/components/interfaces/Integrations/ConstrainedIntegrationTabScaffold.tsx
@@ -1,8 +1,6 @@
import { PropsWithChildren } from 'react'
import { cn } from 'ui'
-import { useIsMarketplaceEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
-
interface ConstrainedIntegrationTabScaffoldProps extends PropsWithChildren {
className?: string
}
@@ -10,11 +8,6 @@ interface ConstrainedIntegrationTabScaffoldProps extends PropsWithChildren {
export const ConstrainedIntegrationTabScaffold = ({
children,
className,
-}: ConstrainedIntegrationTabScaffoldProps) => {
- const isMarketplace = useIsMarketplaceEnabled()
- return (
-
- {children}
-
- )
-}
+}: ConstrainedIntegrationTabScaffoldProps) => (
+ {children}
+)
diff --git a/apps/studio/components/interfaces/Integrations/CronJobs/OverviewTab.tsx b/apps/studio/components/interfaces/Integrations/CronJobs/OverviewTab.tsx
index 491455185b1..f7240c4dec2 100644
--- a/apps/studio/components/interfaces/Integrations/CronJobs/OverviewTab.tsx
+++ b/apps/studio/components/interfaces/Integrations/CronJobs/OverviewTab.tsx
@@ -1,10 +1,3 @@
-import { IntegrationOverviewTab } from '../Integration/IntegrationOverviewTab'
import { RequiredExtensionsSection } from '../Integration/RequiredExtensionsSection'
-import { useIsMarketplaceEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
-export const CronOverviewTab = () => {
- const isMarketplaceEnabled = useIsMarketplaceEnabled()
-
- if (isMarketplaceEnabled) return
- return
-}
+export const CronOverviewTab = () =>
diff --git a/apps/studio/components/interfaces/Integrations/DataApi/OverviewTab.tsx b/apps/studio/components/interfaces/Integrations/DataApi/OverviewTab.tsx
index 54e702b3748..ced9aad1302 100644
--- a/apps/studio/components/interfaces/Integrations/DataApi/OverviewTab.tsx
+++ b/apps/studio/components/interfaces/Integrations/DataApi/OverviewTab.tsx
@@ -1,17 +1,3 @@
-import { IntegrationOverviewTab } from '../Integration/IntegrationOverviewTab'
import { DataApiURLSettings } from './DataApiURLSettings'
-import { useIsMarketplaceEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
-export const DataApiOverviewTab = () => {
- const isMarketplaceEnabled = useIsMarketplaceEnabled()
-
- if (isMarketplaceEnabled) return
-
- return (
-
-
-
-
-
- )
-}
+export const DataApiOverviewTab = () =>
diff --git a/apps/studio/components/interfaces/Integrations/GraphQL/OverviewTab.tsx b/apps/studio/components/interfaces/Integrations/GraphQL/OverviewTab.tsx
index 573b2b59e21..37e6d91ad06 100644
--- a/apps/studio/components/interfaces/Integrations/GraphQL/OverviewTab.tsx
+++ b/apps/studio/components/interfaces/Integrations/GraphQL/OverviewTab.tsx
@@ -1,10 +1,3 @@
-import { IntegrationOverviewTab } from '../Integration/IntegrationOverviewTab'
import { RequiredExtensionsSection } from '../Integration/RequiredExtensionsSection'
-import { useIsMarketplaceEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
-export const GraphQLOverviewTab = () => {
- const isMarketplaceEnabled = useIsMarketplaceEnabled()
-
- if (isMarketplaceEnabled) return
- return
-}
+export const GraphQLOverviewTab = () =>
diff --git a/apps/studio/components/interfaces/Integrations/Integration/BuildBySection.tsx b/apps/studio/components/interfaces/Integrations/Integration/BuildBySection.tsx
deleted file mode 100644
index 0fe6dc6dfad..00000000000
--- a/apps/studio/components/interfaces/Integrations/Integration/BuildBySection.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import { Book } from 'lucide-react'
-import Link from 'next/link'
-import { ComponentPropsWithoutRef, ElementRef, forwardRef, ReactNode } from 'react'
-import { cn } from 'ui'
-
-import { IntegrationDefinition } from '../Landing/Integrations.constants'
-
-interface BuiltBySectionProps extends ComponentPropsWithoutRef<'div'> {
- integration: IntegrationDefinition
- status?: string | ReactNode
-}
-
-export const BuiltBySection = forwardRef, BuiltBySectionProps>(
- ({ integration, status, className, ...props }, ref) => {
- const { docsUrl } = integration
- const { name, websiteUrl } = integration?.author ?? {}
-
- if (!name && !docsUrl && !websiteUrl) return null
-
- return (
-
- {status && (
-
- )}
- {name && (
-
- )}
- {docsUrl && (
-
-
DOCS
-
-
- {docsUrl.includes('supabase.com/docs')
- ? 'Supabase Docs'
- : docsUrl.includes('github.com')
- ? 'GitHub Docs'
- : 'Documentation'}
-
-
- )}
- {websiteUrl && (
-
-
WEBSITE
-
- {websiteUrl.replace('https://', '')}
-
-
- )}
-
- )
- }
-)
-
-BuiltBySection.displayName = 'BuiltBySection'
diff --git a/apps/studio/components/interfaces/Integrations/Integration/IntegrationDetailTabShortcuts.tsx b/apps/studio/components/interfaces/Integrations/Integration/IntegrationDetailTabShortcuts.tsx
index a97a049d316..4a1f7b395cc 100644
--- a/apps/studio/components/interfaces/Integrations/Integration/IntegrationDetailTabShortcuts.tsx
+++ b/apps/studio/components/interfaces/Integrations/Integration/IntegrationDetailTabShortcuts.tsx
@@ -8,9 +8,7 @@ const TAB_DIGIT_KEYS = ['1', '2', '3', '4', '5', '6', '7', '8', '9'] as const
/**
* Binds digit keys 1-9 to whichever tabs the active integration declares in
- * its `navigation` array. Render this once per integration detail page —
- * shared between `LegacyIntegrationPage` and the marketplace `MarketplaceDetail`
- * since both surfaces drive off the same `tabs` shape from `useIntegrationDetail`.
+ * its `navigation` array. Render this once per integration detail page.
*
* The dynamic shortcut registry handles per-tab label overrides ("Go to Jobs",
* "Go to Wrappers", etc.) so the reference sheet and Cmd+K pick up the actual
diff --git a/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTab.test.tsx b/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTab.test.tsx
deleted file mode 100644
index 68dd6a4a79f..00000000000
--- a/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTab.test.tsx
+++ /dev/null
@@ -1,98 +0,0 @@
-import { screen } from '@testing-library/dom'
-import { mockAnimationsApi } from 'jsdom-testing-mocks'
-import { beforeEach, describe, expect, it, vi } from 'vitest'
-
-import { IntegrationOverviewTab } from './IntegrationOverviewTab'
-import { customRender } from '@/tests/lib/custom-render'
-import { routerMock } from '@/tests/lib/route-mock'
-
-mockAnimationsApi()
-
-vi.mock('../Landing/Integrations.constants', () => ({
- INTEGRATIONS: [
- {
- id: 'test-integration',
- name: 'Test Integration',
- requiredExtensions: ['pg_net'],
- },
- ],
-}))
-
-vi.mock('framer-motion', async (importOriginal) => {
- const actual = (await importOriginal()) as any
- return {
- ...actual,
- motion: {
- ...actual.motion,
- div: ({ children, ...props }: any) => {children}
,
- },
- }
-})
-
-const mockExtensions = vi.fn()
-
-vi.mock('@/data/database-extensions/database-extensions-query', () => ({
- useDatabaseExtensionsQuery: () => ({ data: mockExtensions() }),
-}))
-
-vi.mock('@/hooks/misc/useSelectedProject', () => ({
- useSelectedProjectQuery: () => ({
- data: { ref: 'default', connectionString: 'postgres://localhost' },
- }),
- useIsOrioleDb: () => false,
-}))
-
-vi.mock('common', async (importOriginal) => {
- const actual = (await importOriginal()) as any
- return {
- ...actual,
- useParams: () => ({ id: 'test-integration', ref: 'default' }),
- }
-})
-
-vi.mock('./MarkdownContent', () => ({
- MarkdownContent: () => null,
-}))
-
-vi.mock('../Landing/useIntegrationDetail', () => ({
- useIntegrationDetail: () => ({
- integration: {
- id: 'test-integration',
- name: 'Test Integration',
- requiredExtensions: ['pg_net'],
- missingExtensionsAlert: null,
- },
- }),
-}))
-
-describe('IntegrationOverviewTab', () => {
- beforeEach(() => {
- routerMock.setCurrentUrl('/project/default/integrations/test-integration/overview')
- mockExtensions.mockReturnValue([
- { name: 'pg_net', installed_version: null, default_version: '0.6.0' },
- ])
- })
-
- it('does not disable actions when hideRequiredExtensionsSection is true and extensions are uninstalled', () => {
- customRender(
- Enable webhooks}
- />
- )
-
- const actionsArea = screen.getByText('Enable webhooks').closest('[aria-disabled]')
- expect(actionsArea).toHaveAttribute('aria-disabled', 'false')
- expect(actionsArea).not.toHaveClass('opacity-25')
- })
-
- it('disables actions when extensions are uninstalled and hideRequiredExtensionsSection is false', () => {
- customRender(
- Enable integration} />
- )
-
- const actionsArea = screen.getByText('Enable integration').closest('[aria-disabled]')
- expect(actionsArea).toHaveAttribute('aria-disabled', 'true')
- expect(actionsArea).toHaveClass('opacity-25')
- })
-})
diff --git a/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTab.tsx b/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTab.tsx
deleted file mode 100644
index b86a488db39..00000000000
--- a/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTab.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-import { useParams } from 'common'
-import { PropsWithChildren, ReactNode } from 'react'
-import { cn } from 'ui'
-
-import { IntegrationDefinition, INTEGRATIONS } from '../Landing/Integrations.constants'
-import { BuiltBySection } from './BuildBySection'
-import { MarkdownContent } from './MarkdownContent'
-import { RequiredExtensionsSection } from './RequiredExtensionsSection'
-import { useDatabaseExtensionsQuery } from '@/data/database-extensions/database-extensions-query'
-import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
-
-export interface IntegrationOverviewTabProps {
- actions?: ReactNode
- status?: string | ReactNode
- alert?: ReactNode
- hideRequiredExtensionsSection?: boolean
-}
-
-export const OverviewTabSharedContent = ({
- integration,
- hideRequiredExtensionsSection = false,
- actions,
- alert,
- children,
-}: PropsWithChildren<{
- integration: IntegrationDefinition
- hideRequiredExtensionsSection?: boolean
- actions?: ReactNode
- alert?: ReactNode
-}>) => {
- const { data: project } = useSelectedProjectQuery()
-
- const { data: extensions } = useDatabaseExtensionsQuery({
- projectRef: project?.ref,
- connectionString: project?.connectionString,
- })
-
- if (!integration) {
- return Unsupported integration type
- }
-
- const installableExtensions = (extensions ?? []).filter((ext) =>
- (integration.requiredExtensions ?? []).includes(ext.name)
- )
- const hasToInstallExtensions = installableExtensions.some((x) => !x.installed_version)
-
- return (
- <>
- {!!alert && {alert}
}
-
-
-
-
-
- {!!actions && (
-
- {actions}
-
- )}
- {children}
- >
- )
-}
-
-export const IntegrationOverviewTab = ({
- actions,
- alert,
- status,
- children,
- hideRequiredExtensionsSection = false,
-}: PropsWithChildren) => {
- const { id } = useParams()
- const integration = INTEGRATIONS.find((i) => i.id === id)
-
- if (!integration) {
- return Unsupported integration type
- }
-
- return (
-
-
-
- {children}
-
-
- )
-}
diff --git a/apps/studio/components/interfaces/Integrations/Integration/LegacyIntegrationPage.tsx b/apps/studio/components/interfaces/Integrations/Integration/LegacyIntegrationPage.tsx
deleted file mode 100644
index 5f201f69ebe..00000000000
--- a/apps/studio/components/interfaces/Integrations/Integration/LegacyIntegrationPage.tsx
+++ /dev/null
@@ -1,148 +0,0 @@
-import Link from 'next/link'
-import { useMemo } from 'react'
-import {
- BreadcrumbItem,
- BreadcrumbLink,
- BreadcrumbList,
- BreadcrumbPage,
- BreadcrumbSeparator,
- NavMenu,
- NavMenuItem,
-} from 'ui'
-import { Admonition } from 'ui-patterns/admonition'
-import { PageContainer } from 'ui-patterns/PageContainer'
-import {
- PageHeader,
- PageHeaderBreadcrumb,
- PageHeaderDescription,
- PageHeaderIcon,
- PageHeaderMeta,
- PageHeaderNavigationTabs,
- PageHeaderSummary,
- PageHeaderTitle,
-} from 'ui-patterns/PageHeader'
-import { PageSection, PageSectionContent } from 'ui-patterns/PageSection'
-import { GenericSkeletonLoader, ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
-
-import { IntegrationLogo } from '@/components/interfaces/Integrations/Integration/IntegrationLogo'
-import { InstallOAuthIntegrationButton } from '@/components/interfaces/Integrations/Integration/IntegrationOverviewTabV2/InstallIntegrationSheet/InstallOAuthIntegrationButton'
-import { useIntegrationDetail } from '@/components/interfaces/Integrations/Landing/useIntegrationDetail'
-import { UnknownInterface } from '@/components/ui/UnknownInterface'
-
-const LegacyIntegrationPage = () => {
- const {
- ref,
- id,
- isReady,
- isWrapperBlocked,
- pageTitle,
- pageSubTitle,
- integration,
- isAvailableLoading,
- isInstalledLoading,
- isIntegrationStatusLoading,
- oauthIntegrationData,
- tabs,
- Component,
- } = useIntegrationDetail()
-
- const content = useMemo(() => {
- if (!isReady || isInstalledLoading || isAvailableLoading) {
- return (
-
-
-
-
-
-
-
- )
- } else if (!Component || !id || !integration) {
- return (
-
-
-
-
- Please try again later or contact support if the problem persists.
-
-
-
-
- )
- } else {
- return
- }
- }, [isReady, isInstalledLoading, isAvailableLoading, id, integration, Component])
-
- if (!isReady) return null
- if (isWrapperBlocked) return
-
- return (
- <>
-
-
-
-
-
- Integrations
-
-
-
-
- {integration?.name || 'Integration not found'}
-
-
-
-
- {isAvailableLoading ? (
-
-
-
-
-
-
-
-
-
-
- ) : (
-
- {integration && (
-
-
-
- )}
-
- {pageTitle}
- {pageSubTitle}
-
-
- {integration?.type === 'oauth' && (
-
- )}
-
- )}
-
- {tabs.length > 0 && (
-
-
- {tabs.map((tab) => (
-
- {tab.label}
-
- ))}
-
-
- )}
-
-
- {content}
- >
- )
-}
-
-export default LegacyIntegrationPage
diff --git a/apps/studio/components/interfaces/Integrations/Integration/MarketplaceIntegrationOverviewTab.tsx b/apps/studio/components/interfaces/Integrations/Integration/MarketplaceIntegrationOverviewTab.tsx
deleted file mode 100644
index b6cc6f0aed3..00000000000
--- a/apps/studio/components/interfaces/Integrations/Integration/MarketplaceIntegrationOverviewTab.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import type { PropsWithChildren } from 'react'
-
-import { IntegrationOverviewTab } from './IntegrationOverviewTab'
-import { useIsMarketplaceEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
-
-export const MarketplaceIntegrationOverviewTab = (props: PropsWithChildren) => {
- const isMarketplaceEnabled = useIsMarketplaceEnabled()
-
- if (isMarketplaceEnabled) return null
- return {props.children}
-}
diff --git a/apps/studio/components/interfaces/Integrations/Integration/MissingExtensionAlert.tsx b/apps/studio/components/interfaces/Integrations/Integration/MissingExtensionAlert.tsx
deleted file mode 100644
index 29bad660b75..00000000000
--- a/apps/studio/components/interfaces/Integrations/Integration/MissingExtensionAlert.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import { useState } from 'react'
-import { Button } from 'ui'
-
-import { EnableExtensionModal } from '@/components/interfaces/Database/Extensions/EnableExtensionModal'
-import { DatabaseExtension } from '@/data/database-extensions/database-extensions-query'
-
-export const MissingExtensionAlert = ({ extension }: { extension: DatabaseExtension }) => {
- const [showEnableExtensionModal, setShowEnableExtensionModal] = useState(false)
-
- const extensionInstalled = !!extension?.installed_version
- if (!extensionInstalled) {
- return (
- <>
- setShowEnableExtensionModal(true)}
- >
- Enable {extension.name}
-
-
- setShowEnableExtensionModal(false)}
- />
- >
- )
- }
- return null
-}
diff --git a/apps/studio/components/interfaces/Integrations/Integration/RequiredExtensionsSection.tsx b/apps/studio/components/interfaces/Integrations/Integration/RequiredExtensionsSection.tsx
index 070985b792c..16a352e8312 100644
--- a/apps/studio/components/interfaces/Integrations/Integration/RequiredExtensionsSection.tsx
+++ b/apps/studio/components/interfaces/Integrations/Integration/RequiredExtensionsSection.tsx
@@ -1,7 +1,5 @@
import { Badge, Card, CardContent, cn, Separator } from 'ui'
-import { useIsMarketplaceEnabled } from '../../App/FeaturePreview/FeaturePreviewContext'
-import { MissingExtensionAlert } from './MissingExtensionAlert'
import { useIntegrationDetail } from '@/components/interfaces/Integrations/Landing/useIntegrationDetail'
import { useDatabaseExtensionsQuery } from '@/data/database-extensions/database-extensions-query'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
@@ -21,7 +19,6 @@ export const RequiredExtensionsSection = ({
projectRef: project?.ref,
connectionString: project?.connectionString,
})
- const isMarketplaceEnabled = useIsMarketplaceEnabled()
const requiredExtensions = integration?.requiredExtensions ?? []
@@ -59,10 +56,8 @@ export const RequiredExtensionsSection = ({
{extension ? (
isInstalled ? (
Installed
- ) : isMarketplaceEnabled ? (
- Required
) : (
-
+ Required
)
) : (
Unavailable
diff --git a/apps/studio/components/interfaces/Integrations/Landing/IntegrationCard.tsx b/apps/studio/components/interfaces/Integrations/Landing/IntegrationCard.tsx
index 26f807d0765..b8b942660dc 100644
--- a/apps/studio/components/interfaces/Integrations/Landing/IntegrationCard.tsx
+++ b/apps/studio/components/interfaces/Integrations/Landing/IntegrationCard.tsx
@@ -1,18 +1,6 @@
-import { BadgeCheck } from 'lucide-react'
-import Image from 'next/image'
-import Link from 'next/link'
-import { Badge, Card, CardContent, cn } from 'ui'
+import { cn } from 'ui'
import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
-import { IntegrationDefinition } from './Integrations.constants'
-import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
-
-type IntegrationCardProps = IntegrationDefinition & {
- isInstalled?: boolean
- featured?: boolean
- image?: string
-}
-
const INTEGRATION_CARD_STYLE = cn(
'w-full h-full bg-surface-100 hover:bg-surface-200 hover:border-strong',
'border border-border rounded-md ease-out duration-200 transition-all'
@@ -33,81 +21,3 @@ export const IntegrationLoadingCard = () => {
)
}
-
-export const IntegrationCard = ({
- id,
- listingId,
- status,
- name,
- icon,
- description,
- isInstalled,
- featured = false,
- image,
-}: IntegrationCardProps) => {
- const { data: project } = useSelectedProjectQuery()
- const shouldShowOfficialBadge = !listingId
-
- if (featured) {
- return (
-
-
- {/* Full-width image/icon at the top */}
-
- {image ? (
-
- ) : (
-
- {icon({ className: 'w-full h-full text-foreground' })}
-
- )}
-
-
-
-
{name}
-
{description}
-
- {status && {status} }
- {shouldShowOfficialBadge && Official }
-
-
-
-
-
- )
- }
-
- return (
-
-
-
-
-
- {icon()}
-
- {isInstalled && (
-
-
- Installed
-
- )}
-
-
-
{name}
-
-
{description}
-
- {status && {status} }
- {shouldShowOfficialBadge && Official }
-
-
-
-
-
- )
-}
diff --git a/apps/studio/components/interfaces/Integrations/Landing/useAvailableIntegrations.tsx b/apps/studio/components/interfaces/Integrations/Landing/useAvailableIntegrations.tsx
index c0817a4b86c..749bb8ac916 100644
--- a/apps/studio/components/interfaces/Integrations/Landing/useAvailableIntegrations.tsx
+++ b/apps/studio/components/interfaces/Integrations/Landing/useAvailableIntegrations.tsx
@@ -13,7 +13,6 @@ import { useContext, useMemo } from 'react'
import { cn } from 'ui'
import { INTEGRATIONS, Loading, type IntegrationDefinition } from './Integrations.constants'
-import { useIsMarketplaceEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import {
useMarketplaceIntegrationsQuery,
type MarketplaceIntegration,
@@ -55,16 +54,11 @@ const isPreviewEnabled = (featureFlags: FeatureFlagContextType, listingSlug: str
const useMarketplaceListings = () => {
const { hasLoaded } = useContext(FeatureFlagContext)
- const isMarketplaceEnabled = useIsMarketplaceEnabled()
- const { data: marketplaceData, error } = useMarketplaceIntegrationsQuery({
- enabled: isMarketplaceEnabled,
- })
- const isPending =
- IS_PLATFORM && (!hasLoaded || (isMarketplaceEnabled && !marketplaceData && !error))
- const isSuccess =
- !IS_PLATFORM || (hasLoaded && (!isMarketplaceEnabled || (!!marketplaceData && !error)))
- const isError = IS_PLATFORM && isMarketplaceEnabled && !!error
+ const { data: marketplaceData, error } = useMarketplaceIntegrationsQuery()
+ const isPending = IS_PLATFORM && (!hasLoaded || (!marketplaceData && !error))
+ const isSuccess = !IS_PLATFORM || (hasLoaded && !!marketplaceData && !error)
+ const isError = IS_PLATFORM && !!error
// This flag can globally enable preview listings for all partners for a given user (i.e. for Supabase users)
const previewAllListingsEnabled = useFlag('previewMarketplaceListingsEnabled')
@@ -177,15 +171,7 @@ export const useAvailableIntegrations = () => {
navigate: ({ pageId = 'overview' }) => {
switch (pageId) {
case 'overview':
- return dynamic(
- () =>
- import('@/components/interfaces/Integrations/Integration/MarketplaceIntegrationOverviewTab').then(
- (mod) => mod.MarketplaceIntegrationOverviewTab
- ),
- {
- loading: Loading,
- }
- )
+ return null
case 'settings':
return dynamic(
() =>
diff --git a/apps/studio/components/interfaces/Integrations/Landing/useIntegrationDetail.ts b/apps/studio/components/interfaces/Integrations/Landing/useIntegrationDetail.ts
index 2e5b3da8f54..4c7b7e65b24 100644
--- a/apps/studio/components/interfaces/Integrations/Landing/useIntegrationDetail.ts
+++ b/apps/studio/components/interfaces/Integrations/Landing/useIntegrationDetail.ts
@@ -5,14 +5,7 @@ import { useEffect, useMemo } from 'react'
import { useProjectOAuthIntegrationData } from './Landing.utils'
import { useAvailableIntegrations } from './useAvailableIntegrations'
import { useInstalledIntegrations } from './useInstalledIntegrations'
-import {
- areRequiredExtensionsInstalledFor,
- getFilteredNavItems,
- getInstallActionType,
- type InstallActionType,
-} from './useIntegrationDetail.utils'
-import { useIsMarketplaceEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
-import { useDatabaseExtensionsQuery } from '@/data/database-extensions/database-extensions-query'
+import { getInstallActionType, type InstallActionType } from './useIntegrationDetail.utils'
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
@@ -27,13 +20,8 @@ export const useIntegrationDetail = () => {
const { ref, id, pageId, childId } = useParams()
const { integrationsWrappers } = useIsFeatureEnabled(['integrations:wrappers'])
- const isMarketplaceEnabled = useIsMarketplaceEnabled()
const { data: project } = useSelectedProjectQuery()
- const { data: extensions } = useDatabaseExtensionsQuery({
- projectRef: project?.ref,
- connectionString: project?.connectionString,
- })
const { data: allIntegrations, isPending: isAvailableLoading } = useAvailableIntegrations()
const { installedIntegrations, isLoading: isInstalledLoading } = useInstalledIntegrations()
@@ -55,21 +43,7 @@ export const useIntegrationDetail = () => {
const isInstalled = !!integration && !!installation
- const areRequiredExtensionsInstalled = useMemo(
- () => areRequiredExtensionsInstalledFor(integration, extensions),
- [integration, extensions]
- )
-
- const navItems = useMemo(
- () =>
- getFilteredNavItems({
- integration,
- isInstalled,
- isMarketplaceEnabled,
- areRequiredExtensionsInstalled,
- }),
- [integration, isInstalled, isMarketplaceEnabled, areRequiredExtensionsInstalled]
- )
+ const navItems = useMemo(() => integration?.navigation ?? [], [integration])
const activeRoute = pageId ?? 'overview'
@@ -102,14 +76,8 @@ export const useIntegrationDetail = () => {
)
const installActionType: InstallActionType = useMemo(
- () =>
- getInstallActionType({
- integration,
- isMarketplaceEnabled,
- areRequiredExtensionsInstalled,
- isInstalled,
- }),
- [integration, isMarketplaceEnabled, areRequiredExtensionsInstalled, isInstalled]
+ () => getInstallActionType({ integration, isInstalled }),
+ [integration, isInstalled]
)
const isReady = !!router?.isReady
@@ -151,7 +119,6 @@ export const useIntegrationDetail = () => {
installation,
integrationStatus,
isInstalled,
- areRequiredExtensionsInstalled,
installActionType,
wrappersTabHref,
isAvailableLoading,
diff --git a/apps/studio/components/interfaces/Integrations/Landing/useIntegrationDetail.utils.ts b/apps/studio/components/interfaces/Integrations/Landing/useIntegrationDetail.utils.ts
index a21552f9ad6..fc9246916d4 100644
--- a/apps/studio/components/interfaces/Integrations/Landing/useIntegrationDetail.utils.ts
+++ b/apps/studio/components/interfaces/Integrations/Landing/useIntegrationDetail.utils.ts
@@ -1,65 +1,20 @@
-import type { IntegrationDefinition, Navigation } from './Integrations.constants'
+import type { IntegrationDefinition } from './Integrations.constants'
export type InstallActionType = 'oauth' | 'add-wrapper' | 'installed' | 'install-sheet' | null
-export type DatabaseExtension = {
- name: string
- installed_version: string | null
-}
-
-/**
- * Returns whether all required extensions for an integration are enabled in the database.
- */
-export function areRequiredExtensionsInstalledFor(
- integration: IntegrationDefinition | undefined,
- extensions: DatabaseExtension[] | undefined
-): boolean {
- if (!integration?.requiredExtensions?.length || !extensions) return false
- return integration.requiredExtensions.every(
- (name) => !!extensions.find((ext) => ext.name === name)?.installed_version
- )
-}
-
-/**
- * Returns the filtered navigation items for an integration based on install / feature-flag state.
- * If the marketplace flag is off, only expose the Wrappers tab once extensions are installed
- */
-export function getFilteredNavItems({
- integration,
- isInstalled,
- isMarketplaceEnabled,
- areRequiredExtensionsInstalled,
-}: {
- integration: IntegrationDefinition | undefined
- isInstalled: boolean
- isMarketplaceEnabled: boolean
- areRequiredExtensionsInstalled: boolean
-}): Navigation[] {
- if (!integration?.navigation) return []
- if (isInstalled || integration.type !== 'wrapper') return integration.navigation
- if (isMarketplaceEnabled || areRequiredExtensionsInstalled) return integration.navigation
- return integration.navigation.filter((nav) => nav.route !== 'wrappers')
-}
-
/**
* Returns which install action type to render for an integration.
*/
export function getInstallActionType({
integration,
- isMarketplaceEnabled,
- areRequiredExtensionsInstalled,
isInstalled,
}: {
integration: IntegrationDefinition | undefined
- isMarketplaceEnabled: boolean
- areRequiredExtensionsInstalled: boolean
isInstalled: boolean
}): InstallActionType {
if (!integration) return null
if (integration.type === 'oauth') return 'oauth'
- if (integration.type === 'wrapper' && (isMarketplaceEnabled || areRequiredExtensionsInstalled)) {
- return 'add-wrapper'
- }
+ if (integration.type === 'wrapper') return 'add-wrapper'
if (isInstalled) return 'installed'
return 'install-sheet'
}
diff --git a/apps/studio/components/interfaces/Integrations/Queues/OverviewTab.tsx b/apps/studio/components/interfaces/Integrations/Queues/OverviewTab.tsx
index 26fc8374fc8..8d1405ac3e2 100644
--- a/apps/studio/components/interfaces/Integrations/Queues/OverviewTab.tsx
+++ b/apps/studio/components/interfaces/Integrations/Queues/OverviewTab.tsx
@@ -1,52 +1,3 @@
-import { useParams } from 'common'
-import Link from 'next/link'
-import { Button } from 'ui'
-import { Admonition } from 'ui-patterns/admonition'
-
-import { IntegrationOverviewTab } from '../Integration/IntegrationOverviewTab'
import { RequiredExtensionsSection } from '../Integration/RequiredExtensionsSection'
-import { useIsMarketplaceEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
-import { useDatabaseExtensionsQuery } from '@/data/database-extensions/database-extensions-query'
-import { useQueuesExposePostgrestStatusQuery } from '@/data/database-queues/database-queues-expose-postgrest-status-query'
-import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
-const QueuesContent = () => {
- const { ref } = useParams()
- const { data: project } = useSelectedProjectQuery()
- const { data: isExposed } = useQueuesExposePostgrestStatusQuery({
- projectRef: project?.ref,
- connectionString: project?.connectionString,
- })
-
- const { data: extensions = [] } = useDatabaseExtensionsQuery({
- projectRef: project?.ref,
- connectionString: project?.connectionString,
- })
- const isQueuesInstalled = !!extensions.find((x) => x.name === 'pgmq')?.installed_version
-
- if (isExposed) return null
-
- return (
-
-
- You may choose to toggle the exposure of Queues through Data APIs via the queues settings
-
-
- {isQueuesInstalled && (
-
- Manage queues settings
-
- )}
-
- )
-}
-
-export const QueuesOverviewTab = () => {
- const isMarketplaceEnabled = useIsMarketplaceEnabled()
-
- if (isMarketplaceEnabled) return
- return } />
-}
+export const QueuesOverviewTab = () =>
diff --git a/apps/studio/components/interfaces/Integrations/Vault/OverviewTab.tsx b/apps/studio/components/interfaces/Integrations/Vault/OverviewTab.tsx
index 120e6f7c16b..f5795d6b457 100644
--- a/apps/studio/components/interfaces/Integrations/Vault/OverviewTab.tsx
+++ b/apps/studio/components/interfaces/Integrations/Vault/OverviewTab.tsx
@@ -1,10 +1,3 @@
-import { IntegrationOverviewTab } from '../Integration/IntegrationOverviewTab'
import { RequiredExtensionsSection } from '../Integration/RequiredExtensionsSection'
-import { useIsMarketplaceEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
-export const VaultOverviewTab = () => {
- const isMarketplaceEnabled = useIsMarketplaceEnabled()
-
- if (isMarketplaceEnabled) return
- return
-}
+export const VaultOverviewTab = () =>
diff --git a/apps/studio/components/interfaces/Integrations/Webhooks/OverviewTab.tsx b/apps/studio/components/interfaces/Integrations/Webhooks/OverviewTab.tsx
index 454acf34276..b32ee5d909c 100644
--- a/apps/studio/components/interfaces/Integrations/Webhooks/OverviewTab.tsx
+++ b/apps/studio/components/interfaces/Integrations/Webhooks/OverviewTab.tsx
@@ -1,99 +1,3 @@
-import { PermissionAction } from '@supabase/shared-types/out/constants'
-import { useParams } from 'common'
-import { toast } from 'sonner'
-import { Admonition } from 'ui-patterns/admonition'
-import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
-
-import { IntegrationOverviewTab } from '../Integration/IntegrationOverviewTab'
import { RequiredExtensionsSection } from '../Integration/RequiredExtensionsSection'
-import { useIsMarketplaceEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
-import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
-import { NoPermission } from '@/components/ui/NoPermission'
-import { useHooksEnableMutation } from '@/data/database/hooks-enable-mutation'
-import { useSchemasQuery } from '@/data/database/schemas-query'
-import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
-import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
-const WebhooksContent = () => {
- const { ref: projectRef } = useParams()
- const { data: project } = useSelectedProjectQuery()
-
- const {
- data: schemas,
- isSuccess: isSchemasLoaded,
- refetch,
- } = useSchemasQuery({
- projectRef: project?.ref,
- connectionString: project?.connectionString,
- })
-
- const isHooksEnabled = schemas?.some((schema) => schema.name === 'supabase_functions')
- const { can: canReadWebhooks, isLoading: isLoadingPermissions } = useAsyncCheckPermissions(
- PermissionAction.TENANT_SQL_ADMIN_READ,
- 'triggers'
- )
-
- const { mutate: enableHooks, isPending: isEnablingHooks } = useHooksEnableMutation({
- onSuccess: async () => {
- await refetch()
- toast.success('Successfully enabled webhooks')
- },
- })
-
- const enableHooksForProject = async () => {
- if (!projectRef) return console.error('Project ref is required')
- enableHooks({ ref: projectRef })
- }
-
- if (!isSchemasLoaded || isLoadingPermissions) {
- return (
-
-
-
- )
- }
-
- if (!canReadWebhooks) {
- return (
-
-
-
- )
- }
-
- if (isSchemasLoaded && isHooksEnabled) return null
-
- return (
-
-
- Database Webhooks can be used to trigger serverless functions or send requests to an HTTP
- endpoint
-
- enableHooksForProject()}
- disabled={isEnablingHooks}
- tooltip={{
- content: {
- side: 'bottom',
- text: !canReadWebhooks
- ? 'You need additional permissions to enable webhooks'
- : undefined,
- },
- }}
- >
- Enable webhooks
-
-
- )
-}
-
-export const WebhooksOverviewTab = () => {
- const isMarketplaceEnabled = useIsMarketplaceEnabled()
-
- if (isMarketplaceEnabled) {
- return
- }
-
- return } />
-}
+export const WebhooksOverviewTab = () =>
diff --git a/apps/studio/components/interfaces/Integrations/Wrappers/CreateWrapperSheet.tsx b/apps/studio/components/interfaces/Integrations/Wrappers/CreateWrapperSheet.tsx
index d231545c009..2c990424a0c 100644
--- a/apps/studio/components/interfaces/Integrations/Wrappers/CreateWrapperSheet.tsx
+++ b/apps/studio/components/interfaces/Integrations/Wrappers/CreateWrapperSheet.tsx
@@ -32,7 +32,6 @@ import {
NewTable,
} from './Wrappers.utils'
import WrapperTableEditor from './WrapperTableEditor'
-import { useIsMarketplaceEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { getExtensionDefaultSchema } from '@/components/interfaces/Integrations/Integration/IntegrationOverviewTabV2/IntegrationOverviewTabV2.utils'
import { RequiredExtensionsSection } from '@/components/interfaces/Integrations/Integration/RequiredExtensionsSection'
import { useIntegrationDetail } from '@/components/interfaces/Integrations/Landing/useIntegrationDetail'
@@ -66,7 +65,6 @@ export const CreateWrapperSheet = ({
onCloseWithConfirmation,
}: CreateWrapperSheetProps) => {
const queryClient = useQueryClient()
- const isMarketplaceEnabled = useIsMarketplaceEnabled()
const { integration } = useIntegrationDetail()
const { data: project } = useSelectedProjectQuery()
@@ -85,8 +83,8 @@ export const CreateWrapperSheet = ({
const wrappersExtension = extensions?.find((ext) => ext.name === 'wrappers')
const hasRequiredVersionForeignSchema = hasForeignSchemaSupport(wrappersExtension)
- const needsExtensions = isMarketplaceEnabled && (requiredExtensionsToInstall?.length ?? 0) > 0
- const isExtensionDataLoading = isMarketplaceEnabled && requiredExtensionsToInstall === null
+ const needsExtensions = (requiredExtensionsToInstall?.length ?? 0) > 0
+ const isExtensionDataLoading = requiredExtensionsToInstall === null
const { data: schemas } = useSchemasQuery({
projectRef: project?.ref!,
@@ -261,18 +259,16 @@ export const CreateWrapperSheet = ({
Create a {wrapperMeta.label} wrapper
- {isMarketplaceEnabled && (
-
- {needsExtensions && (
-
- )}
-
-
- )}
+
+ {needsExtensions && (
+
+ )}
+
+
Wrapper Configuration}>
{
- const { id } = useParams()
- const wrapperMeta = WRAPPERS.find((w) => w.name === id)
-
- const [isDirty, setIsDirty] = useState(false)
- const [createWrapperShown, setCreateWrapperShown] = useQueryState(
- 'new',
- parseAsBoolean.withDefault(false).withOptions({ history: 'push', clearOnDefault: true })
- )
-
- const { confirmOnClose, handleOpenChange, modalProps } = useConfirmOnClose({
- checkIsDirty: () => isDirty,
- onClose: () => {
- setCreateWrapperShown(false)
- setIsDirty(false)
- },
- })
-
- // [Joshen] Opting to declare custom wrapper sheets here instead of within Wrappers.constants.ts
- // as we'll easily run into circular dependencies doing so unfortunately
- const CreateWrapperSheetComponent = !wrapperMeta
- ? null
- : wrapperMeta.customComponent
- ? wrapperMeta.name === 'iceberg_wrapper'
- ? CreateIcebergWrapperSheet
- : null
- : CreateWrapperSheet
-
- return (
- <>
-
-
- {!!CreateWrapperSheetComponent && !!wrapperMeta && (
-
-
- setCreateWrapperShown(false)}
- onCloseWithConfirmation={confirmOnClose}
- />
-
-
- )}
-
-
- >
- )
-}
-
-const AddNewWrapperCTA = () => {
- const { id } = useParams()
- const { data: project } = useSelectedProjectQuery()
- const [, setCreateWrapperShown] = useQueryState(
- 'new',
- parseAsBoolean.withDefault(false).withOptions({ history: 'push', clearOnDefault: true })
- )
-
- const { can: canCreateWrapper } = useAsyncCheckPermissions(
- PermissionAction.TENANT_SQL_ADMIN_WRITE,
- 'wrappers'
- )
-
- const { data } = useDatabaseExtensionsQuery({
- projectRef: project?.ref,
- connectionString: project?.connectionString,
- })
-
- const wrapperMeta = WRAPPERS.find((w) => w.name === id)
- const wrappersExtension = data?.find((ext) => ext.name === 'wrappers')
- const isWrappersExtensionInstalled = !!wrappersExtension?.installed_version
- const hasRequiredVersion =
- (wrappersExtension?.installed_version ?? '') >= (wrapperMeta?.minimumExtensionVersion ?? '')
- // [Joshen] Default version is what's on the DB, so if the installed version is already the default version
- // but still doesnt meet the minimum extension version, then DB upgrade is required
- const databaseNeedsUpgrading =
- wrappersExtension?.installed_version === wrappersExtension?.default_version
-
- if (!!wrapperMeta && isWrappersExtensionInstalled && !hasRequiredVersion) {
- return (
-
-
-
- The {wrapperMeta.label} wrapper requires a minimum extension version of{' '}
- {wrapperMeta.minimumExtensionVersion}. You have version{' '}
- {wrappersExtension?.installed_version} installed. Please{' '}
- {databaseNeedsUpgrading && 'upgrade your database then '}update the extension by
- disabling and enabling the wrappers extension
- to create this wrapper.
-
-
- Warning: Before reinstalling the wrapper extension, you must first remove all existing
- wrappers. Afterward, you can recreate the wrappers.
-
-
-
-
- {databaseNeedsUpgrading ? 'Upgrade database' : 'View wrappers extension'}
-
-
-
- )
- }
-
- return (
-
- setCreateWrapperShown(true)}
- disabled={!canCreateWrapper}
- tooltip={{
- content: {
- text: !canCreateWrapper
- ? 'You need additional permissions to create a foreign data wrapper'
- : undefined,
- },
- }}
- >
- Add new wrapper
-
-
- )
-}
-
-export const WrapperContent = () => {
- const { id } = useParams()
- const { data: project } = useSelectedProjectQuery()
-
- const { data: integrations = [] } = useAvailableIntegrations()
- const integration = integrations.find((i) => i.id === id)
- const wrapperMeta = WRAPPERS.find((w) => w.name === id)
-
- const { data: extensions } = useDatabaseExtensionsQuery({
- projectRef: project?.ref,
- connectionString: project?.connectionString,
- })
- const installableExtensions = (extensions ?? []).filter((ext) =>
- (integration?.requiredExtensions ?? []).includes(ext.name)
- )
- const isInstalled = installableExtensions.every((x) => x.installed_version)
-
- if (!wrapperMeta) {
- return (
-
-
- Unsupported integration type
-
-
- )
- }
-
- return (
- <>
-
-
- {isInstalled && }
- >
- )
-}
-
-export const WrapperOverviewTab = () => {
- const isMarketplaceEnabled = useIsMarketplaceEnabled()
-
- if (isMarketplaceEnabled) return
-
- return (
- }>
-
-
-
-
- )
-}
+export const WrapperOverviewTab = () =>
diff --git a/apps/studio/components/interfaces/Integrations/templates/StripeSyncEngine/OverviewTab.tsx b/apps/studio/components/interfaces/Integrations/templates/StripeSyncEngine/OverviewTab.tsx
index e05b84a39ea..31c586de8cf 100644
--- a/apps/studio/components/interfaces/Integrations/templates/StripeSyncEngine/OverviewTab.tsx
+++ b/apps/studio/components/interfaces/Integrations/templates/StripeSyncEngine/OverviewTab.tsx
@@ -23,7 +23,6 @@ import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import * as z from 'zod'
-import { IntegrationOverviewTab } from '../../Integration/IntegrationOverviewTab'
import { RequiredExtensionsSection } from '../../Integration/RequiredExtensionsSection'
import { InstallationError } from './InstallationError'
import { IntegrationInstalledActions, IntegrationNotInstalledActions } from './IntegrationActions'
@@ -38,7 +37,6 @@ import {
isUninstalling,
} from './stripe-sync-status'
import { StripeSyncChangesCard } from './StripeSyncChangesCard'
-import { useIsMarketplaceEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { useStripeSyncStatus } from '@/components/interfaces/Integrations/templates/StripeSyncEngine/useStripeSyncStatus'
import { useStripeSyncInstallMutation } from '@/data/database-integrations/stripe/stripe-sync-install-mutation'
import { useStripeSyncUninstallMutation } from '@/data/database-integrations/stripe/stripe-sync-uninstall-mutation'
@@ -391,23 +389,9 @@ const StripeSyncContent = ({ hideInstallCTA = false }: { hideInstallCTA?: boolea
)
}
-export const StripeSyncEngineOverviewTab = () => {
- const isMarketplaceEnabled = useIsMarketplaceEnabled()
-
- if (isMarketplaceEnabled) {
- return (
- <>
-
-
- >
- )
- }
-
- return (
-
-
-
-
-
- )
-}
+export const StripeSyncEngineOverviewTab = () => (
+ <>
+
+
+ >
+)
diff --git a/apps/studio/components/layouts/ProjectIntegrationsLayout.tsx b/apps/studio/components/layouts/ProjectIntegrationsLayout.tsx
deleted file mode 100644
index 51ca726cb0b..00000000000
--- a/apps/studio/components/layouts/ProjectIntegrationsLayout.tsx
+++ /dev/null
@@ -1,174 +0,0 @@
-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/ShimmeringLoader'
-
-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 (
-
-
-
-
- >
- }
- >
- {children}
-
- )
-})
-
-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 ? (
-
-
- Explore} />
-
-
-
- ) : (
-
- )}
- >
- )
-}
-
-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: (
-
- {integration.icon({ className: 'p-1' })}
-
- ),
- items: [],
- }))
-
- return (
- <>
- {(isLoading || isError) && (
-
-
- Installed} />
-
- {isLoading &&
}
- {isError && (
-
- )}
-
- )}
- {isSuccess && (
-
- )}
- >
- )
-}
diff --git a/apps/studio/components/layouts/ProjectIntegrationsLayoutDispatch.tsx b/apps/studio/components/layouts/ProjectIntegrationsLayoutDispatch.tsx
deleted file mode 100644
index 31cd36043a5..00000000000
--- a/apps/studio/components/layouts/ProjectIntegrationsLayoutDispatch.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import type { PropsWithChildren } from 'react'
-
-import { ProjectIntegrationsLayout } from './ProjectIntegrationsLayout'
-import { ProjectMarketplaceLayout } from './ProjectMarketplaceLayout'
-import { useIsMarketplaceEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
-
-export const ProjectIntegrationsLayoutDispatch = ({ children }: PropsWithChildren) => {
- const isMarketplaceEnabled = useIsMarketplaceEnabled()
- if (isMarketplaceEnabled) {
- return {children}
- }
- return {children}
-}
diff --git a/apps/studio/pages/project/[ref]/integrations/[id]/[pageId]/[childId]/index.tsx b/apps/studio/pages/project/[ref]/integrations/[id]/[pageId]/[childId]/index.tsx
index 2e88ba89ded..bed0f884008 100644
--- a/apps/studio/pages/project/[ref]/integrations/[id]/[pageId]/[childId]/index.tsx
+++ b/apps/studio/pages/project/[ref]/integrations/[id]/[pageId]/[childId]/index.tsx
@@ -16,7 +16,7 @@ import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
import { useAvailableIntegrations } from '@/components/interfaces/Integrations/Landing/useAvailableIntegrations'
import { useInstalledIntegrations } from '@/components/interfaces/Integrations/Landing/useInstalledIntegrations'
import { DefaultLayout } from '@/components/layouts/DefaultLayout'
-import { ProjectIntegrationsLayoutDispatch } from '@/components/layouts/ProjectIntegrationsLayoutDispatch'
+import { ProjectMarketplaceLayout } from '@/components/layouts/ProjectMarketplaceLayout'
import type { NextPageWithLayout } from '@/types'
const IntegrationPage: NextPageWithLayout = () => {
@@ -98,7 +98,7 @@ const IntegrationPage: NextPageWithLayout = () => {
IntegrationPage.getLayout = (page) => (
- {page}
+ {page}
)
diff --git a/apps/studio/pages/project/[ref]/integrations/[id]/[pageId]/index.tsx b/apps/studio/pages/project/[ref]/integrations/[id]/[pageId]/index.tsx
index 6f2e09f5412..b0bab49bb82 100644
--- a/apps/studio/pages/project/[ref]/integrations/[id]/[pageId]/index.tsx
+++ b/apps/studio/pages/project/[ref]/integrations/[id]/[pageId]/index.tsx
@@ -1,19 +1,13 @@
-import { useIsMarketplaceEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
-import LegacyIntegrationPage from '@/components/interfaces/Integrations/Integration/LegacyIntegrationPage'
import { MarketplaceDetail } from '@/components/interfaces/Integrations/Marketplace/MarketplaceDetail'
import { DefaultLayout } from '@/components/layouts/DefaultLayout'
-import { ProjectIntegrationsLayoutDispatch } from '@/components/layouts/ProjectIntegrationsLayoutDispatch'
+import { ProjectMarketplaceLayout } from '@/components/layouts/ProjectMarketplaceLayout'
import type { NextPageWithLayout } from '@/types'
-const IntegrationPage: NextPageWithLayout = () => {
- const isMarketplaceEnabled = useIsMarketplaceEnabled()
- if (isMarketplaceEnabled) return
- return
-}
+const IntegrationPage: NextPageWithLayout = () =>
IntegrationPage.getLayout = (page) => (
- {page}
+ {page}
)
diff --git a/apps/studio/pages/project/[ref]/integrations/[id]/index.tsx b/apps/studio/pages/project/[ref]/integrations/[id]/index.tsx
index 7f1980fa16c..5de8b67e6f3 100644
--- a/apps/studio/pages/project/[ref]/integrations/[id]/index.tsx
+++ b/apps/studio/pages/project/[ref]/integrations/[id]/index.tsx
@@ -6,7 +6,7 @@ import { PageSection, PageSectionContent } from 'ui-patterns/PageSection'
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
import { DefaultLayout } from '@/components/layouts/DefaultLayout'
-import { ProjectIntegrationsLayoutDispatch } from '@/components/layouts/ProjectIntegrationsLayoutDispatch'
+import { ProjectMarketplaceLayout } from '@/components/layouts/ProjectMarketplaceLayout'
import type { NextPageWithLayout } from '@/types'
const INTEGRATION_FLAGS: Record = {
@@ -57,7 +57,7 @@ const IntegrationPage: NextPageWithLayout = () => {
IntegrationPage.getLayout = (page) => (
- {page}
+ {page}
)
diff --git a/apps/studio/pages/project/[ref]/integrations/index.tsx b/apps/studio/pages/project/[ref]/integrations/index.tsx
index b8c4ddbb007..a55ae603cba 100644
--- a/apps/studio/pages/project/[ref]/integrations/index.tsx
+++ b/apps/studio/pages/project/[ref]/integrations/index.tsx
@@ -1,325 +1,13 @@
-import { IS_PLATFORM, useFeatureFlags } from 'common'
-import { Database } from 'common/marketplace.types'
-import { Search } from 'lucide-react'
-import { useRouter } from 'next/router'
-import { parseAsString, useQueryState } from 'nuqs'
-import { useMemo, type ReactNode } from 'react'
-import { Input } from 'ui-patterns/DataInputs/Input'
-import { PageContainer } from 'ui-patterns/PageContainer'
-import {
- PageHeader,
- PageHeaderAside,
- PageHeaderDescription,
- PageHeaderMeta,
- PageHeaderSummary,
- PageHeaderTitle,
-} from 'ui-patterns/PageHeader'
-import { PageSection, PageSectionContent, PageSectionMeta } from 'ui-patterns/PageSection'
-import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
-
-import { useIsMarketplaceEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
-import {
- IntegrationCard,
- IntegrationLoadingCard,
-} from '@/components/interfaces/Integrations/Landing/IntegrationCard'
-import { IntegrationDefinition } from '@/components/interfaces/Integrations/Landing/Integrations.constants'
-import { useAvailableIntegrations } from '@/components/interfaces/Integrations/Landing/useAvailableIntegrations'
-import { useInstalledIntegrations } from '@/components/interfaces/Integrations/Landing/useInstalledIntegrations'
-import { useIntegrationFilteringAndSort } from '@/components/interfaces/Integrations/Landing/useIntegrationFilteringAndSort'
import { MarketplaceIndex } from '@/components/interfaces/Integrations/Marketplace/MarketplaceIndex'
import { DefaultLayout } from '@/components/layouts/DefaultLayout'
-import { ProjectIntegrationsLayoutDispatch } from '@/components/layouts/ProjectIntegrationsLayoutDispatch'
-import { AlertError } from '@/components/ui/AlertError'
-import { DocsButton } from '@/components/ui/DocsButton'
-import { NoSearchResults } from '@/components/ui/NoSearchResults'
-import { useMarketplaceCategoriesQuery } from '@/data/marketplace/integration-categories-query'
-import { BASE_PATH, DOCS_URL } from '@/lib/constants'
+import { ProjectMarketplaceLayout } from '@/components/layouts/ProjectMarketplaceLayout'
import type { NextPageWithLayout } from '@/types'
-const FEATURED_INTEGRATIONS = ['cron', 'queues', 'stripe_sync_engine']
-
-// Featured integration images
-const FEATURED_INTEGRATION_IMAGES: Record = {
- cron: `${BASE_PATH}/img/integrations/covers/cron-cover.webp`,
- queues: `${BASE_PATH}/img/integrations/covers/queues-cover.png`,
- stripe_wrapper: `${BASE_PATH}/img/integrations/covers/stripe-cover.png`,
- stripe_sync_engine: `${BASE_PATH}/img/integrations/covers/stripe-cover.png`,
-}
-
-function getIntegrationImage(integration: IntegrationDefinition) {
- let featured_image = FEATURED_INTEGRATION_IMAGES[integration.id]
- if (featured_image) {
- return featured_image
- }
-
- if (integration.files?.length) {
- const heroImage = integration?.files?.[0]
- return heroImage?.src ?? undefined
- }
-}
-
-type PageContent = {
- title: string
- subtitle: string
- secondaryActions?: ReactNode
-}
-
-const DEFAULT_PAGE_CONTENT: PageContent = {
- title: 'Extend your database',
- subtitle:
- 'Extensions and wrappers that add functionality to your database and connect to external services.',
-}
-
-const CATEGORY_PAGE_CONTENT = {
- wrapper: {
- title: 'Wrappers',
- subtitle:
- 'Connect to external data sources and services by querying APIs, databases, and files as if they were Postgres tables.',
- secondaryActions: (
-
- ),
- },
- postgres_extension: {
- title: 'Postgres Modules',
- subtitle: 'Extend your database with powerful Postgres extensions.',
- },
-} satisfies Record
-
-// Converts a category string to title
-// Example: some_catory -> Some Category
-function formatCategoryTitle(category: string) {
- return category
- .split(/[-_]/)
- .filter(Boolean)
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
- .join(' ')
-}
-
-const PageHeaderContentSkeleton = () => (
-
-
-
-
-)
-
-// Returns selected category to filter by
-function useFilterCategory() {
- const router = useRouter()
- const [selectedCategory] = useQueryState(
- 'category',
- parseAsString.withDefault('all').withOptions({ clearOnDefault: true })
- )
- const categoryFromUrl = useMemo(() => {
- const queryString = router.asPath.split('?')[1]
- if (!queryString) return null
-
- return new URLSearchParams(queryString).get('category')
- }, [router.asPath])
- const resolvedSelectedCategory = router.isReady
- ? (categoryFromUrl ?? selectedCategory)
- : undefined
- const filterCategory = resolvedSelectedCategory ?? 'all'
- return filterCategory
-}
-
-// Dynamic page content based on selected category
-function usePageContent(
- integrationFilterCategory: string,
- categories: Database['public']['Views']['categories']['Row'][]
-) {
- const pageContent = useMemo(() => {
- if (integrationFilterCategory === 'all') {
- return DEFAULT_PAGE_CONTENT
- }
-
- if (integrationFilterCategory in CATEGORY_PAGE_CONTENT) {
- return CATEGORY_PAGE_CONTENT[integrationFilterCategory as keyof typeof CATEGORY_PAGE_CONTENT]
- }
-
- const selectedMarketplaceCategory = categories.find(
- (category) => category.slug === integrationFilterCategory
- )
-
- return {
- title: selectedMarketplaceCategory?.name ?? formatCategoryTitle(integrationFilterCategory),
- subtitle: selectedMarketplaceCategory?.description ?? DEFAULT_PAGE_CONTENT.subtitle,
- }
- }, [categories, integrationFilterCategory])
- return pageContent
-}
-
-const IntegrationsPage: NextPageWithLayout = () => {
- const isMarketplaceEnabled = useIsMarketplaceEnabled()
- if (isMarketplaceEnabled) return
- return
-}
-
-const LegacyIntegrationsPage = () => {
- const { hasLoaded: flagsLoaded } = useFeatureFlags()
- const isMarketplaceEnabled = useIsMarketplaceEnabled()
- const [search, setSearch] = useQueryState(
- 'search',
- parseAsString.withDefault('').withOptions({ clearOnDefault: true })
- )
-
- const {
- data: availableIntegrations,
- error,
- isPending: isLoadingAvailableIntegrations,
- isError,
- isSuccess: isSuccessAvailableIntegrations,
- } = useAvailableIntegrations()
-
- const {
- installedIntegrations,
- isLoading: isLoadingInstalledIntegrations,
- isSuccess: isSuccessInstalledIntegrations,
- } = useInstalledIntegrations()
-
- const installedIds = installedIntegrations.map((i) => i.id)
- const isLoading = isLoadingAvailableIntegrations || isLoadingInstalledIntegrations
- const isSuccess = isSuccessAvailableIntegrations && isSuccessInstalledIntegrations
-
- const selectedCategory = useFilterCategory()
-
- const { data: categories = [], isPending: isPendingCategories } = useMarketplaceCategoriesQuery({
- enabled: isMarketplaceEnabled,
- })
-
- const isLoadingSelectedCategory =
- selectedCategory !== 'all' &&
- !(selectedCategory in CATEGORY_PAGE_CONTENT) &&
- (IS_PLATFORM ? !flagsLoaded || (isMarketplaceEnabled && isPendingCategories) : false)
-
- const pageContent = usePageContent(selectedCategory, categories)
-
- const filteredIntegrations = useMemo(() => {
- let filtered = availableIntegrations ?? []
-
- if (selectedCategory !== 'all') {
- filtered = filtered.filter(
- (i) => i.type === selectedCategory || i.categories?.includes(selectedCategory)
- )
- }
-
- if (search.length > 0) {
- filtered = filtered.filter((i) => i.name.toLowerCase().includes(search.toLowerCase()))
- }
-
- return filtered
- }, [availableIntegrations, selectedCategory, search])
-
- const hasActiveFilter = selectedCategory !== 'all' || search.length > 0
-
- const { sorted: filteredAndSortedIntegrations, featured: featuredIntegrations } =
- useIntegrationFilteringAndSort(filteredIntegrations, availableIntegrations, installedIds, {
- featuredIds: FEATURED_INTEGRATIONS,
- hasActiveFilter,
- includeFeaturedFlag: true,
- })
-
- return (
- <>
-
-
-
- {isLoadingSelectedCategory ? (
-
- ) : (
- <>
- {pageContent.title}
- {pageContent.subtitle}
- >
- )}
-
- {pageContent.secondaryActions && (
- {pageContent.secondaryActions}
- )}
-
-
-
-
-
-
- setSearch(e.target.value)}
- placeholder="Search integrations..."
- icon={ }
- className="w-52"
- />
-
-
-
- {isLoading && (
-
- {Array.from({ length: 8 }).map((_, idx) => (
-
- ))}
-
- )}
-
- {/* Error State */}
- {isError && (
-
- )}
-
- {/* Success State */}
- {isSuccess && (
- <>
- {/* No Search Results */}
- {search.length > 0 && filteredAndSortedIntegrations.length === 0 && (
- setSearch('')} />
- )}
-
- {/* Featured Integrations */}
- {featuredIntegrations && featuredIntegrations.length > 0 && (
-
- {featuredIntegrations.map((integration) => (
-
- ))}
-
- )}
-
- {/* All Filtered and Sorted Integrations */}
- {filteredAndSortedIntegrations.length > 0 && (
-
- {filteredAndSortedIntegrations.map((integration) => (
-
- ))}
-
- )}
- >
- )}
-
-
-
- >
- )
-}
+const IntegrationsPage: NextPageWithLayout = () =>
IntegrationsPage.getLayout = (page) => (
- {page}
+ {page}
)
diff --git a/apps/studio/public/img/previews/integrations-layout-preview.png b/apps/studio/public/img/previews/integrations-layout-preview.png
deleted file mode 100644
index d026663a373..00000000000
Binary files a/apps/studio/public/img/previews/integrations-layout-preview.png and /dev/null differ
diff --git a/apps/studio/routes/project/$ref/integrations.tsx b/apps/studio/routes/project/$ref/integrations.tsx
index 25dd7ae2c2f..4251afd7bc0 100644
--- a/apps/studio/routes/project/$ref/integrations.tsx
+++ b/apps/studio/routes/project/$ref/integrations.tsx
@@ -1,6 +1,6 @@
import { createFileRoute, Outlet } from '@tanstack/react-router'
-import { ProjectIntegrationsLayoutDispatch } from '@/components/layouts/ProjectIntegrationsLayoutDispatch'
+import { ProjectMarketplaceLayout } from '@/components/layouts/ProjectMarketplaceLayout'
export const Route = createFileRoute('/project/$ref/integrations')({
component: IntegrationsShell,
@@ -8,8 +8,8 @@ export const Route = createFileRoute('/project/$ref/integrations')({
function IntegrationsShell() {
return (
-
+
-
+
)
}
diff --git a/apps/studio/tests/components/Integrations/Landing/useIntegrationDetail.utils.test.ts b/apps/studio/tests/components/Integrations/Landing/useIntegrationDetail.utils.test.ts
index ef3e01da8ef..7cc654bbc59 100644
--- a/apps/studio/tests/components/Integrations/Landing/useIntegrationDetail.utils.test.ts
+++ b/apps/studio/tests/components/Integrations/Landing/useIntegrationDetail.utils.test.ts
@@ -1,11 +1,7 @@
import { describe, expect, it } from 'vitest'
import type { IntegrationDefinition } from '@/components/interfaces/Integrations/Landing/Integrations.constants'
-import {
- areRequiredExtensionsInstalledFor,
- getFilteredNavItems,
- getInstallActionType,
-} from '@/components/interfaces/Integrations/Landing/useIntegrationDetail.utils'
+import { getInstallActionType } from '@/components/interfaces/Integrations/Landing/useIntegrationDetail.utils'
// Minimal stubs — only fields exercised by the utils
const oauthIntegration = {
@@ -32,235 +28,44 @@ const extensionIntegration = {
navigation: [{ route: 'overview', label: 'Overview' }],
} as unknown as IntegrationDefinition
-// ─── areRequiredExtensionsInstalledFor ───────────────────────────────────────
-
-describe('areRequiredExtensionsInstalledFor', () => {
- it('returns false when integration is undefined', () => {
- expect(areRequiredExtensionsInstalledFor(undefined, [])).toBe(false)
- })
-
- it('returns false when extensions list is undefined', () => {
- expect(areRequiredExtensionsInstalledFor(extensionIntegration, undefined)).toBe(false)
- })
-
- it('returns false when integration has no required extensions', () => {
- const noExts = {
- ...extensionIntegration,
- requiredExtensions: [],
- } as unknown as IntegrationDefinition
- expect(
- areRequiredExtensionsInstalledFor(noExts, [{ name: 'pgmq', installed_version: '1.0' }])
- ).toBe(false)
- })
-
- it('returns false when a required extension is not installed', () => {
- expect(
- areRequiredExtensionsInstalledFor(extensionIntegration, [
- { name: 'pgmq', installed_version: null },
- ])
- ).toBe(false)
- })
-
- it('returns false when a required extension is missing from list entirely', () => {
- expect(areRequiredExtensionsInstalledFor(extensionIntegration, [])).toBe(false)
- })
-
- it('returns true when all required extensions are installed', () => {
- expect(
- areRequiredExtensionsInstalledFor(extensionIntegration, [
- { name: 'pgmq', installed_version: '1.4.4' },
- ])
- ).toBe(true)
- })
-
- it('returns true only when every required extension is installed (multiple)', () => {
- expect(
- areRequiredExtensionsInstalledFor(wrapperIntegration, [
- { name: 'wrappers', installed_version: '0.4.1' },
- { name: 'supabase_vault', installed_version: '0.2.8' },
- ])
- ).toBe(true)
- })
-
- it('returns false when only some of multiple required extensions are installed', () => {
- expect(
- areRequiredExtensionsInstalledFor(wrapperIntegration, [
- { name: 'wrappers', installed_version: '0.4.1' },
- { name: 'supabase_vault', installed_version: null },
- ])
- ).toBe(false)
- })
-})
-
-// ─── getFilteredNavItems ─────────────────────────────────────────────────────
-
-describe('getFilteredNavItems', () => {
- it('returns empty array when integration is undefined', () => {
- expect(
- getFilteredNavItems({
- integration: undefined,
- isInstalled: false,
- isMarketplaceEnabled: false,
- areRequiredExtensionsInstalled: false,
- })
- ).toEqual([])
- })
-
- it('returns empty array when integration has no navigation', () => {
- const noNav = {
- ...wrapperIntegration,
- navigation: undefined,
- } as unknown as IntegrationDefinition
- expect(
- getFilteredNavItems({
- integration: noNav,
- isInstalled: false,
- isMarketplaceEnabled: false,
- areRequiredExtensionsInstalled: false,
- })
- ).toEqual([])
- })
-
- it('returns full navigation for non-wrapper integrations regardless of flags', () => {
- const result = getFilteredNavItems({
- integration: extensionIntegration,
- isInstalled: false,
- isMarketplaceEnabled: false,
- areRequiredExtensionsInstalled: false,
- })
- expect(result).toEqual(extensionIntegration.navigation)
- })
-
- it('returns full navigation for installed wrapper integrations', () => {
- const result = getFilteredNavItems({
- integration: wrapperIntegration,
- isInstalled: true,
- isMarketplaceEnabled: false,
- areRequiredExtensionsInstalled: false,
- })
- expect(result).toEqual(wrapperIntegration.navigation)
- })
-
- it('hides wrappers tab for uninstalled wrapper when marketplace is off and extensions missing', () => {
- const result = getFilteredNavItems({
- integration: wrapperIntegration,
- isInstalled: false,
- isMarketplaceEnabled: false,
- areRequiredExtensionsInstalled: false,
- })
- expect(result.some((nav) => nav.route === 'wrappers')).toBe(false)
- expect(result.some((nav) => nav.route === 'overview')).toBe(true)
- })
-
- it('shows wrappers tab when marketplace flag is enabled', () => {
- const result = getFilteredNavItems({
- integration: wrapperIntegration,
- isInstalled: false,
- isMarketplaceEnabled: true,
- areRequiredExtensionsInstalled: false,
- })
- expect(result).toEqual(wrapperIntegration.navigation)
- })
-
- it('shows wrappers tab when required extensions are installed', () => {
- const result = getFilteredNavItems({
- integration: wrapperIntegration,
- isInstalled: false,
- isMarketplaceEnabled: false,
- areRequiredExtensionsInstalled: true,
- })
- expect(result).toEqual(wrapperIntegration.navigation)
- })
-})
-
-// ─── getInstallActionType ────────────────────────────────────────────────────
-
describe('getInstallActionType', () => {
it('returns null when integration is undefined', () => {
- expect(
- getInstallActionType({
- integration: undefined,
- isMarketplaceEnabled: false,
- areRequiredExtensionsInstalled: false,
- isInstalled: false,
- })
- ).toBeNull()
+ expect(getInstallActionType({ integration: undefined, isInstalled: false })).toBeNull()
})
it('returns "oauth" for OAuth integrations', () => {
- expect(
- getInstallActionType({
- integration: oauthIntegration,
- isMarketplaceEnabled: false,
- areRequiredExtensionsInstalled: false,
- isInstalled: false,
- })
- ).toBe('oauth')
+ expect(getInstallActionType({ integration: oauthIntegration, isInstalled: false })).toBe(
+ 'oauth'
+ )
})
it('returns "oauth" for installed OAuth integrations (type check takes priority)', () => {
- expect(
- getInstallActionType({
- integration: oauthIntegration,
- isMarketplaceEnabled: true,
- areRequiredExtensionsInstalled: true,
- isInstalled: true,
- })
- ).toBe('oauth')
+ expect(getInstallActionType({ integration: oauthIntegration, isInstalled: true })).toBe(
+ 'oauth'
+ )
})
- it('returns "add-wrapper" for wrapper when marketplace is enabled', () => {
- expect(
- getInstallActionType({
- integration: wrapperIntegration,
- isMarketplaceEnabled: true,
- areRequiredExtensionsInstalled: false,
- isInstalled: false,
- })
- ).toBe('add-wrapper')
+ it('returns "add-wrapper" for wrapper integrations', () => {
+ expect(getInstallActionType({ integration: wrapperIntegration, isInstalled: false })).toBe(
+ 'add-wrapper'
+ )
})
- it('returns "add-wrapper" for wrapper when required extensions are installed', () => {
- expect(
- getInstallActionType({
- integration: wrapperIntegration,
- isMarketplaceEnabled: false,
- areRequiredExtensionsInstalled: true,
- isInstalled: false,
- })
- ).toBe('add-wrapper')
+ it('returns "add-wrapper" for installed wrapper integrations (type check takes priority)', () => {
+ expect(getInstallActionType({ integration: wrapperIntegration, isInstalled: true })).toBe(
+ 'add-wrapper'
+ )
})
- it('returns "installed" for a non-wrapper installed integration when marketplace is off', () => {
- expect(
- getInstallActionType({
- integration: extensionIntegration,
- isMarketplaceEnabled: false,
- areRequiredExtensionsInstalled: false,
- isInstalled: true,
- })
- ).toBe('installed')
+ it('returns "installed" for a non-wrapper installed integration', () => {
+ expect(getInstallActionType({ integration: extensionIntegration, isInstalled: true })).toBe(
+ 'installed'
+ )
})
it('returns "install-sheet" for an uninstalled non-wrapper integration', () => {
- expect(
- getInstallActionType({
- integration: extensionIntegration,
- isMarketplaceEnabled: false,
- areRequiredExtensionsInstalled: false,
- isInstalled: false,
- })
- ).toBe('install-sheet')
- })
-
- it('returns "install-sheet" for wrapper when marketplace off and extensions missing and not installed', () => {
- expect(
- getInstallActionType({
- integration: wrapperIntegration,
- isMarketplaceEnabled: false,
- areRequiredExtensionsInstalled: false,
- isInstalled: false,
- })
- ).toBe('install-sheet')
+ expect(getInstallActionType({ integration: extensionIntegration, isInstalled: false })).toBe(
+ 'install-sheet'
+ )
})
})
diff --git a/packages/common/constants/local-storage.ts b/packages/common/constants/local-storage.ts
index 6b74bf66803..9a96b713c27 100644
--- a/packages/common/constants/local-storage.ts
+++ b/packages/common/constants/local-storage.ts
@@ -26,7 +26,6 @@ export const LOCAL_STORAGE_KEYS = {
UI_PREVIEW_PLATFORM_WEBHOOKS: 'supabase-ui-platform-webhooks',
UI_PREVIEW_JIT_DB_ACCESS: 'supabase-ui-jit-db-access',
UI_PREVIEW_SQL_EDITOR_MANUAL_SAVE: 'supabase-ui-sql-editor-manual-save',
- UI_PREVIEW_MARKETPLACE: 'supabase-ui-marketplace',
AI_ASSISTANT_MCP_OPT_IN: 'ai-assistant-mcp-opt-in',
SIGN_IN_CHATGPT_ENABLED: 'siwc-enabled',
@@ -155,7 +154,6 @@ const LOCAL_STORAGE_KEYS_ALLOWLIST = [
LOCAL_STORAGE_KEYS.UI_PREVIEW_PLATFORM_WEBHOOKS,
LOCAL_STORAGE_KEYS.UI_PREVIEW_JIT_DB_ACCESS,
LOCAL_STORAGE_KEYS.UI_PREVIEW_SQL_EDITOR_MANUAL_SAVE,
- LOCAL_STORAGE_KEYS.UI_PREVIEW_MARKETPLACE,
LOCAL_STORAGE_KEYS.LAST_SIGN_IN_METHOD,
LOCAL_STORAGE_KEYS.HIDE_PROMO_TOAST,
LOCAL_STORAGE_KEYS.BLOG_VIEW,