From 7edbedcbec7372c6a6dde78281e37be4969c4c5f Mon Sep 17 00:00:00 2001 From: Francesco Sansalvadore Date: Mon, 25 May 2026 09:08:38 +0200 Subject: [PATCH] fix integration overview tab contents (#46179) Restores proper content in new marketplace detail overview pages compared to the legacy overview pages. ## Summary by CodeRabbit * **New Features** * Added Data API URL settings and a visible "Required extensions" section across integration overviews. * Unified install/manage UIs for webhooks, Stripe Sync, wrappers, queues, and others; marketplace mode now shows marketplace-specific overview content. * **Style** * Improved marketplace detail rail and filter-bar button styling; refined list/link row visuals. * **Refactor** * Overview pages reorganized to branch on marketplace mode and extract shared overview content for consistency. * **Tests** * Stabilized integration overview test data for deterministic runs. [![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/46179?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) --------- Co-authored-by: Joshen Lim --- .../Integrations/CronJobs/OverviewTab.tsx | 10 + .../DataApi/DataApiDisabledState.tsx | 4 +- .../DataApi/DataApiURLSettings.tsx | 47 +++ .../Integrations/DataApi/OverviewTab.tsx | 73 +--- .../Integrations/DataApi/SettingsTab.tsx | 2 +- .../Integrations/GraphQL/OverviewTab.tsx | 10 + .../IntegrationOverviewTab.test.tsx | 11 + .../Integration/IntegrationOverviewTab.tsx | 110 ++--- .../InstallationOverview.tsx | 8 +- .../IntegrationOverviewTabWrapper.tsx | 7 +- .../MarketplaceIntegrationOverviewTab.tsx | 11 + .../Integration/RequiredExtensionsSection.tsx | 84 ++++ .../Landing/Integrations.constants.tsx | 12 +- .../Landing/useAvailableIntegrations.tsx | 4 +- .../Marketplace/MarketplaceDetail.tsx | 10 +- .../Marketplace/MarketplaceDetailRail.tsx | 40 +- .../Marketplace/MarketplaceFilterBar.tsx | 4 +- .../Integrations/Marketplace/OverviewTab.tsx | 9 +- .../Integrations/Queues/OverviewTab.tsx | 23 +- .../Integrations/Vault/OverviewTab.tsx | 10 + .../Integrations/Webhooks/OverviewTab.tsx | 75 ++-- .../Integrations/Wrappers/OverviewTab.tsx | 46 +-- .../Integrations/Wrappers/WrappersTab.tsx | 20 +- .../StripeSyncEngine/OverviewTab.tsx | 387 ++++++++---------- 24 files changed, 556 insertions(+), 461 deletions(-) create mode 100644 apps/studio/components/interfaces/Integrations/CronJobs/OverviewTab.tsx create mode 100644 apps/studio/components/interfaces/Integrations/DataApi/DataApiURLSettings.tsx create mode 100644 apps/studio/components/interfaces/Integrations/GraphQL/OverviewTab.tsx create mode 100644 apps/studio/components/interfaces/Integrations/Integration/MarketplaceIntegrationOverviewTab.tsx create mode 100644 apps/studio/components/interfaces/Integrations/Integration/RequiredExtensionsSection.tsx create mode 100644 apps/studio/components/interfaces/Integrations/Vault/OverviewTab.tsx diff --git a/apps/studio/components/interfaces/Integrations/CronJobs/OverviewTab.tsx b/apps/studio/components/interfaces/Integrations/CronJobs/OverviewTab.tsx new file mode 100644 index 00000000000..491455185b1 --- /dev/null +++ b/apps/studio/components/interfaces/Integrations/CronJobs/OverviewTab.tsx @@ -0,0 +1,10 @@ +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 +} diff --git a/apps/studio/components/interfaces/Integrations/DataApi/DataApiDisabledState.tsx b/apps/studio/components/interfaces/Integrations/DataApi/DataApiDisabledState.tsx index e087da46dff..0a9bd324e8d 100644 --- a/apps/studio/components/interfaces/Integrations/DataApi/DataApiDisabledState.tsx +++ b/apps/studio/components/interfaces/Integrations/DataApi/DataApiDisabledState.tsx @@ -12,8 +12,8 @@ export const DataApiDisabledState = ({ description }: DataApiDisabledStateProps) const { ref: projectRef } = useParams() return ( -
- +
+ Data API is disabled diff --git a/apps/studio/components/interfaces/Integrations/DataApi/DataApiURLSettings.tsx b/apps/studio/components/interfaces/Integrations/DataApi/DataApiURLSettings.tsx new file mode 100644 index 00000000000..db601d2f0c5 --- /dev/null +++ b/apps/studio/components/interfaces/Integrations/DataApi/DataApiURLSettings.tsx @@ -0,0 +1,47 @@ +import { useParams } from 'common' +import { AlertCircle } from 'lucide-react' +import { Alert, AlertTitle, cn } from 'ui' +import { Admonition } from 'ui-patterns/admonition' + +import { DataApiEnableSwitch } from '@/components/interfaces/Settings/API/DataApiEnableSwitch' +import { DataApiProjectUrlCard } from '@/components/interfaces/Settings/API/DataApiProjectUrlCard' +import { useIsDataApiEnabled } from '@/hooks/misc/useIsDataApiEnabled' +import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' +import { IS_PLATFORM, PROJECT_STATUS } from '@/lib/constants' + +export const DataApiURLSettings = () => { + const { ref: projectRef } = useParams() + const { data: project, isPending: isProjectLoading } = useSelectedProjectQuery() + const { isEnabled, isPending: isConfigLoading } = useIsDataApiEnabled({ projectRef }) + const isLoading = isProjectLoading || isConfigLoading + + return ( +
+ {!isProjectLoading && project?.status !== PROJECT_STATUS.ACTIVE_HEALTHY ? ( + + + API settings are unavailable as the project is not active + + ) : ( + <> +
+ +
+ {IS_PLATFORM ? ( + + ) : ( + + )} + + )} +
+ ) +} diff --git a/apps/studio/components/interfaces/Integrations/DataApi/OverviewTab.tsx b/apps/studio/components/interfaces/Integrations/DataApi/OverviewTab.tsx index e4e0f82de23..54e702b3748 100644 --- a/apps/studio/components/interfaces/Integrations/DataApi/OverviewTab.tsx +++ b/apps/studio/components/interfaces/Integrations/DataApi/OverviewTab.tsx @@ -1,70 +1,17 @@ -import { useParams } from 'common' -import { AlertCircle } from 'lucide-react' -import { Alert, AlertTitle, cn } from 'ui' -import { Admonition } from 'ui-patterns/admonition' - import { IntegrationOverviewTab } from '../Integration/IntegrationOverviewTab' -import { IntegrationOverviewTabV2 } from '../Integration/IntegrationOverviewTabV2' +import { DataApiURLSettings } from './DataApiURLSettings' import { useIsMarketplaceEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext' -import { DataApiEnableSwitch } from '@/components/interfaces/Settings/API/DataApiEnableSwitch' -import { DataApiProjectUrlCard } from '@/components/interfaces/Settings/API/DataApiProjectUrlCard' -import { useIsDataApiEnabled } from '@/hooks/misc/useIsDataApiEnabled' -import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' -import { IS_PLATFORM, PROJECT_STATUS } from '@/lib/constants' - -const DataApiOverview = () => { - const { ref: projectRef } = useParams() - const { data: project, isPending: isProjectLoading } = useSelectedProjectQuery() - const { isEnabled, isPending: isConfigLoading } = useIsDataApiEnabled({ projectRef }) - const isLoading = isProjectLoading || isConfigLoading - - return ( -
- {!isProjectLoading && project?.status !== PROJECT_STATUS.ACTIVE_HEALTHY ? ( - - - API settings are unavailable as the project is not active - - ) : ( - <> -
- -
- {IS_PLATFORM ? ( - - ) : ( - - )} - - )} -
- ) -} export const DataApiOverviewTab = () => { const isMarketplaceEnabled = useIsMarketplaceEnabled() - if (isMarketplaceEnabled) { - return ( - - - - ) - } else { - return ( - -
- -
-
- ) - } + if (isMarketplaceEnabled) return + + return ( + +
+ +
+
+ ) } diff --git a/apps/studio/components/interfaces/Integrations/DataApi/SettingsTab.tsx b/apps/studio/components/interfaces/Integrations/DataApi/SettingsTab.tsx index baab4258916..fe2eb615d30 100644 --- a/apps/studio/components/interfaces/Integrations/DataApi/SettingsTab.tsx +++ b/apps/studio/components/interfaces/Integrations/DataApi/SettingsTab.tsx @@ -12,7 +12,7 @@ export const DataApiSettingsTab = () => { if (IS_PLATFORM && !isPending && !isEnabled) { return ( - + ) diff --git a/apps/studio/components/interfaces/Integrations/GraphQL/OverviewTab.tsx b/apps/studio/components/interfaces/Integrations/GraphQL/OverviewTab.tsx new file mode 100644 index 00000000000..573b2b59e21 --- /dev/null +++ b/apps/studio/components/interfaces/Integrations/GraphQL/OverviewTab.tsx @@ -0,0 +1,10 @@ +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 +} diff --git a/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTab.test.tsx b/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTab.test.tsx index 734b2281218..805ba873c3a 100644 --- a/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTab.test.tsx +++ b/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTab.test.tsx @@ -54,6 +54,17 @@ 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') diff --git a/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTab.tsx b/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTab.tsx index 210c520fe44..f9ec67855ae 100644 --- a/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTab.tsx +++ b/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTab.tsx @@ -1,11 +1,11 @@ import { useParams } from 'common' import { PropsWithChildren, ReactNode } from 'react' -import { Badge, Card, CardContent, cn, Separator } from 'ui' +import { cn } from 'ui' import { INTEGRATIONS } from '../Landing/Integrations.constants' import { BuiltBySection } from './BuildBySection' import { MarkdownContent } from './MarkdownContent' -import { MissingExtensionAlert } from './MissingExtensionAlert' +import { RequiredExtensionsSection } from './RequiredExtensionsSection' import { useDatabaseExtensionsQuery } from '@/data/database-extensions/database-extensions-query' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' @@ -16,19 +16,20 @@ export interface IntegrationOverviewTabProps { hideRequiredExtensionsSection?: boolean } -/** [Joshen] This will eventually get replaced by IntegrationOverviewTabV2 */ -export const IntegrationOverviewTab = ({ +export const OverviewTabSharedContent = ({ + integration, + hideRequiredExtensionsSection = false, actions, alert, - status, children, - hideRequiredExtensionsSection = false, -}: PropsWithChildren) => { - const { id } = useParams() +}: PropsWithChildren<{ + integration: any + hideRequiredExtensionsSection?: boolean + actions?: ReactNode + alert?: ReactNode +}>) => { const { data: project } = useSelectedProjectQuery() - const integration = INTEGRATIONS.find((i) => i.id === id) - const { data: extensions } = useDatabaseExtensionsQuery({ projectRef: project?.ref, connectionString: project?.connectionString, @@ -38,71 +39,21 @@ export const IntegrationOverviewTab = ({ return
Unsupported integration type
} - const dependsOnExtension = (integration.requiredExtensions ?? []).length > 0 - const installableExtensions = (extensions ?? []).filter((ext) => (integration.requiredExtensions ?? []).includes(ext.name) ) const hasToInstallExtensions = installableExtensions.some((x) => !x.installed_version) - // The integration requires extensions that are not available to install on the current database image - const hasMissingExtensions = - installableExtensions.length !== integration.requiredExtensions.length - return ( -
- - + <> {!!alert &&
{alert}
} - - - {dependsOnExtension && !hideRequiredExtensionsSection && ( -
-

Required extensions

- - -
    - {(integration.requiredExtensions ?? []).map((requiredExtension, idx) => { - const extension = (extensions ?? []).find((ext) => ext.name === requiredExtension) - const isInstalled = !!extension?.installed_version - const isLastRow = idx === (integration.requiredExtensions?.length ?? 0) - 1 - - return ( -
  • - {requiredExtension} - -
    - {extension ? ( - isInstalled ? ( - Installed - ) : ( - - ) - ) : ( - Unavailable - )} -
    -
  • - ) - })} -
- - {hasMissingExtensions && ( -
{integration.missingExtensionsAlert}
- )} -
-
-
- )} + {!!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/IntegrationOverviewTabV2/InstallIntegrationSheet/InstallationOverview.tsx b/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTabV2/InstallIntegrationSheet/InstallationOverview.tsx index b46b190a6d3..541989a7ebe 100644 --- a/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTabV2/InstallIntegrationSheet/InstallationOverview.tsx +++ b/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTabV2/InstallIntegrationSheet/InstallationOverview.tsx @@ -104,13 +104,9 @@ export const InstallationOverview = ({ {!ext ? ( Unavailable ) : ext.installed_version ? ( - - Installed - + Installed ) : ( - - Required - + Required )}
) diff --git a/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTabWrapper.tsx b/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTabWrapper.tsx index 45c52ac651c..5369bef2347 100644 --- a/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTabWrapper.tsx +++ b/apps/studio/components/interfaces/Integrations/Integration/IntegrationOverviewTabWrapper.tsx @@ -6,7 +6,6 @@ import { PropsWithChildren } from 'react' import { IntegrationOverviewTab, IntegrationOverviewTabProps } from './IntegrationOverviewTab' -import { IntegrationOverviewTabV2 } from './IntegrationOverviewTabV2' import { useIsMarketplaceEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext' export const IntegrationOverviewTabWrapper = ( @@ -15,8 +14,8 @@ export const IntegrationOverviewTabWrapper = ( const isMarketplaceEnabled = useIsMarketplaceEnabled() if (isMarketplaceEnabled) { - return {props.children} - } else { - return + return null } + + return } diff --git a/apps/studio/components/interfaces/Integrations/Integration/MarketplaceIntegrationOverviewTab.tsx b/apps/studio/components/interfaces/Integrations/Integration/MarketplaceIntegrationOverviewTab.tsx new file mode 100644 index 00000000000..b6cc6f0aed3 --- /dev/null +++ b/apps/studio/components/interfaces/Integrations/Integration/MarketplaceIntegrationOverviewTab.tsx @@ -0,0 +1,11 @@ +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/RequiredExtensionsSection.tsx b/apps/studio/components/interfaces/Integrations/Integration/RequiredExtensionsSection.tsx new file mode 100644 index 00000000000..070985b792c --- /dev/null +++ b/apps/studio/components/interfaces/Integrations/Integration/RequiredExtensionsSection.tsx @@ -0,0 +1,84 @@ +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' + +export const RequiredExtensionsSection = ({ + hide = false, + hideSeparator = false, + className, +}: { + hide?: boolean + hideSeparator?: boolean + className?: string +}) => { + const { integration } = useIntegrationDetail() + const { data: project } = useSelectedProjectQuery() + const { data: extensions } = useDatabaseExtensionsQuery({ + projectRef: project?.ref, + connectionString: project?.connectionString, + }) + const isMarketplaceEnabled = useIsMarketplaceEnabled() + + const requiredExtensions = integration?.requiredExtensions ?? [] + + if (hide || requiredExtensions.length === 0) return null + + const installableExtensions = (extensions ?? []).filter((ext) => + requiredExtensions.includes(ext.name) + ) + const hasMissingExtensions = installableExtensions.length !== requiredExtensions.length + + return ( + <> + {!hideSeparator && } +
+

Required extensions

+ + +
    + {requiredExtensions.map((requiredExtension, idx) => { + const extension = (extensions ?? []).find((ext) => ext.name === requiredExtension) + const isInstalled = !!extension?.installed_version + const isLastRow = idx === requiredExtensions.length - 1 + + return ( +
  • + {requiredExtension} + +
    + {extension ? ( + isInstalled ? ( + Installed + ) : isMarketplaceEnabled ? ( + Required + ) : ( + + ) + ) : ( + Unavailable + )} +
    +
  • + ) + })} +
+ + {hasMissingExtensions && integration?.missingExtensionsAlert && ( +
{integration.missingExtensionsAlert}
+ )} +
+
+
+ + ) +} diff --git a/apps/studio/components/interfaces/Integrations/Landing/Integrations.constants.tsx b/apps/studio/components/interfaces/Integrations/Landing/Integrations.constants.tsx index ca63108e40b..617ba40e1e7 100644 --- a/apps/studio/components/interfaces/Integrations/Landing/Integrations.constants.tsx +++ b/apps/studio/components/interfaces/Integrations/Landing/Integrations.constants.tsx @@ -231,8 +231,8 @@ const SUPABASE_INTEGRATIONS: Array = [ case 'overview': return dynamic( () => - import('@/components/interfaces/Integrations/Integration/IntegrationOverviewTabWrapper').then( - (mod) => mod.IntegrationOverviewTabWrapper + import('@/components/interfaces/Integrations/CronJobs/OverviewTab').then( + (mod) => mod.CronOverviewTab ), { loading: Loading, @@ -275,8 +275,8 @@ const SUPABASE_INTEGRATIONS: Array = [ case 'overview': return dynamic( () => - import('@/components/interfaces/Integrations/Integration/IntegrationOverviewTabWrapper').then( - (mod) => mod.IntegrationOverviewTabWrapper + import('@/components/interfaces/Integrations/Vault/OverviewTab').then( + (mod) => mod.VaultOverviewTab ), { loading: Loading, @@ -445,8 +445,8 @@ const SUPABASE_INTEGRATIONS: Array = [ case 'overview': return dynamic( () => - import('@/components/interfaces/Integrations/Integration/IntegrationOverviewTabWrapper').then( - (mod) => mod.IntegrationOverviewTabWrapper + import('@/components/interfaces/Integrations/GraphQL/OverviewTab').then( + (mod) => mod.GraphQLOverviewTab ), { loading: Loading, diff --git a/apps/studio/components/interfaces/Integrations/Landing/useAvailableIntegrations.tsx b/apps/studio/components/interfaces/Integrations/Landing/useAvailableIntegrations.tsx index 066a5752649..0d13f17615d 100644 --- a/apps/studio/components/interfaces/Integrations/Landing/useAvailableIntegrations.tsx +++ b/apps/studio/components/interfaces/Integrations/Landing/useAvailableIntegrations.tsx @@ -114,8 +114,8 @@ export const useAvailableIntegrations = () => { case 'overview': return dynamic( () => - import('@/components/interfaces/Integrations/Integration/IntegrationOverviewTabV2/index').then( - (mod) => mod.IntegrationOverviewTabV2 + import('@/components/interfaces/Integrations/Integration/MarketplaceIntegrationOverviewTab').then( + (mod) => mod.MarketplaceIntegrationOverviewTab ), { loading: Loading, diff --git a/apps/studio/components/interfaces/Integrations/Marketplace/MarketplaceDetail.tsx b/apps/studio/components/interfaces/Integrations/Marketplace/MarketplaceDetail.tsx index f9aae45156d..58c13225e3f 100644 --- a/apps/studio/components/interfaces/Integrations/Marketplace/MarketplaceDetail.tsx +++ b/apps/studio/components/interfaces/Integrations/Marketplace/MarketplaceDetail.tsx @@ -1,4 +1,4 @@ -import { ArrowUpRight, BookOpen, Settings } from 'lucide-react' +import { ArrowUpRight, BookOpen } from 'lucide-react' import { Button, cn } from 'ui' import { GenericSkeletonLoader, ShimmeringLoader } from 'ui-patterns' import { Admonition } from 'ui-patterns/admonition' @@ -70,7 +70,7 @@ export const MarketplaceDetail = () => { } if (isInstalled) { return ( - ) @@ -78,6 +78,8 @@ export const MarketplaceDetail = () => { return } + // For overview route, get the integration-specific overview component if available + const OverviewComponent = activeRoute === 'overview' ? Component : null const CustomPageComponent = activeRoute !== 'overview' && isKnownRoute ? Component : null return ( @@ -114,7 +116,9 @@ export const MarketplaceDetail = () => { {activeRoute === 'overview' ? (
- + + {OverviewComponent && } +
) : CustomPageComponent ? ( layout === 'constrained' ? ( diff --git a/apps/studio/components/interfaces/Integrations/Marketplace/MarketplaceDetailRail.tsx b/apps/studio/components/interfaces/Integrations/Marketplace/MarketplaceDetailRail.tsx index 32ec02633c6..101c76773b9 100644 --- a/apps/studio/components/interfaces/Integrations/Marketplace/MarketplaceDetailRail.tsx +++ b/apps/studio/components/interfaces/Integrations/Marketplace/MarketplaceDetailRail.tsx @@ -1,4 +1,4 @@ -import { ArrowUpRight } from 'lucide-react' +import { ArrowUpRight, BookOpen, Globe } from 'lucide-react' import type { ReactNode } from 'react' import { cn } from 'ui' @@ -6,16 +6,17 @@ import { getMarketplaceType, getMarketplaceTypeLabel } from './Marketplace.const import type { IntegrationDefinition } from '@/components/interfaces/Integrations/Landing/Integrations.constants' interface RailRowProps { - label: string + label?: string value: ReactNode href?: string mono?: boolean + icon?: ReactNode } -const RailRow = ({ label, value, href, mono }: RailRowProps) => { +const RailRow = ({ label, value, href, mono, icon }: RailRowProps) => { const valueCls = cn( 'flex items-center gap-1 text-sm', - href ? 'text-brand-link' : 'text-foreground', + href ? 'text-foreground-lighter hover:text-foreground' : 'text-foreground', mono && 'font-mono' ) const content = ( @@ -26,14 +27,17 @@ const RailRow = ({ label, value, href, mono }: RailRowProps) => { ) return (
-
{label}
- {href ? ( - - {content} - - ) : ( -
{content}
- )} + {label &&
{label}
} +
+ {icon && {icon}} + {href ? ( + + {content} + + ) : ( +
{content}
+ )} +
) } @@ -45,9 +49,7 @@ interface RailGroupProps { const RailGroup = ({ title, children }: RailGroupProps) => (
-
- {title} -
+
{title}
{children}
) @@ -82,8 +84,12 @@ export const MarketplaceDetailRail = ({ integration, isInstalled }: MarketplaceD {(docsUrl || siteUrl) && ( - {docsUrl && } - {siteUrl && siteHost && } + {docsUrl && ( + } href={docsUrl} /> + )} + {siteUrl && siteHost && ( + } href={siteUrl} /> + )} )} diff --git a/apps/studio/components/interfaces/Integrations/Marketplace/MarketplaceFilterBar.tsx b/apps/studio/components/interfaces/Integrations/Marketplace/MarketplaceFilterBar.tsx index 0ff6e79b859..1123a2d8d4a 100644 --- a/apps/studio/components/interfaces/Integrations/Marketplace/MarketplaceFilterBar.tsx +++ b/apps/studio/components/interfaces/Integrations/Marketplace/MarketplaceFilterBar.tsx @@ -210,7 +210,7 @@ export const MarketplaceFilterBar = ({ aria-label="Grid view" onClick={() => onViewModeChange('grid')} className={cn( - 'border-r px-2 py-1.5 rounded-l-md', + 'border-r px-2 py-1.5 rounded-l-md cursor-pointer', 'focus-visible:z-10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground-muted focus-visible:ring-offset-1 focus-visible:ring-offset-background', viewMode === 'grid' ? 'bg-surface-200 text-foreground' @@ -224,7 +224,7 @@ export const MarketplaceFilterBar = ({ aria-label="List view" onClick={() => onViewModeChange('list')} className={cn( - 'px-2 py-1.5 rounded-r-md', + 'px-2 py-1.5 rounded-r-md cursor-pointer', 'focus-visible:z-10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground-muted focus-visible:ring-offset-1 focus-visible:ring-offset-background', viewMode === 'list' ? 'bg-surface-200 text-foreground' diff --git a/apps/studio/components/interfaces/Integrations/Marketplace/OverviewTab.tsx b/apps/studio/components/interfaces/Integrations/Marketplace/OverviewTab.tsx index 3c2af5379e5..ccd26681f7d 100644 --- a/apps/studio/components/interfaces/Integrations/Marketplace/OverviewTab.tsx +++ b/apps/studio/components/interfaces/Integrations/Marketplace/OverviewTab.tsx @@ -1,23 +1,26 @@ +import type { PropsWithChildren } from 'react' + import { MarketplaceDetailRail } from './MarketplaceDetailRail' import { ConstrainedIntegrationTabScaffold } from '@/components/interfaces/Integrations/ConstrainedIntegrationTabScaffold' import { FilesViewer } from '@/components/interfaces/Integrations/Integration/IntegrationOverviewTabV2/FilesViewer' import { MarkdownContent } from '@/components/interfaces/Integrations/Integration/IntegrationOverviewTabV2/MarkdownContent' import type { IntegrationDefinition } from '@/components/interfaces/Integrations/Landing/Integrations.constants' -interface OverviewTabProps { +interface OverviewTabProps extends PropsWithChildren { integration: IntegrationDefinition isInstalled: boolean } -export const OverviewTab = ({ integration, isInstalled }: OverviewTabProps) => { +export const OverviewTab = ({ integration, isInstalled, children }: OverviewTabProps) => { const { content, files = [] } = integration return (
-
+
{files.length > 0 && } + {children}
diff --git a/apps/studio/components/interfaces/Integrations/Queues/OverviewTab.tsx b/apps/studio/components/interfaces/Integrations/Queues/OverviewTab.tsx index d0295b878f0..3a89b11a2f7 100644 --- a/apps/studio/components/interfaces/Integrations/Queues/OverviewTab.tsx +++ b/apps/studio/components/interfaces/Integrations/Queues/OverviewTab.tsx @@ -4,15 +4,19 @@ import { Button } from 'ui' import { Admonition } from 'ui-patterns' import { IntegrationOverviewTab } from '../Integration/IntegrationOverviewTab' -import { IntegrationOverviewTabV2 } from '../Integration/IntegrationOverviewTabV2' +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 QueuesAdmonition = () => { +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, @@ -20,6 +24,8 @@ const QueuesAdmonition = () => { }) const isQueuesInstalled = !!extensions.find((x) => x.name === 'pgmq')?.installed_version + if (isExposed) return null + return ( { } export const QueuesOverviewTab = () => { - const { data: project } = useSelectedProjectQuery() const isMarketplaceEnabled = useIsMarketplaceEnabled() - const { data: isExposed } = useQueuesExposePostgrestStatusQuery({ - projectRef: project?.ref, - connectionString: project?.connectionString, - }) - - if (isMarketplaceEnabled) { - return {!isExposed && } - } else { - return : null} /> - } + if (isMarketplaceEnabled) return + return } /> } diff --git a/apps/studio/components/interfaces/Integrations/Vault/OverviewTab.tsx b/apps/studio/components/interfaces/Integrations/Vault/OverviewTab.tsx new file mode 100644 index 00000000000..120e6f7c16b --- /dev/null +++ b/apps/studio/components/interfaces/Integrations/Vault/OverviewTab.tsx @@ -0,0 +1,10 @@ +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 +} diff --git a/apps/studio/components/interfaces/Integrations/Webhooks/OverviewTab.tsx b/apps/studio/components/interfaces/Integrations/Webhooks/OverviewTab.tsx index c88dac94694..371c1e5d51e 100644 --- a/apps/studio/components/interfaces/Integrations/Webhooks/OverviewTab.tsx +++ b/apps/studio/components/interfaces/Integrations/Webhooks/OverviewTab.tsx @@ -5,7 +5,7 @@ import { Admonition } from 'ui-patterns' import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' import { IntegrationOverviewTab } from '../Integration/IntegrationOverviewTab' -import { IntegrationOverviewTabV2 } from '../Integration/IntegrationOverviewTabV2' +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' @@ -14,12 +14,10 @@ import { useSchemasQuery } from '@/data/database/schemas-query' import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' -export const WebhooksOverviewTab = () => { +const WebhooksContent = () => { const { ref: projectRef } = useParams() const { data: project } = useSelectedProjectQuery() - const isMarketplaceEnabled = useIsMarketplaceEnabled() - const { data: schemas, isSuccess: isSchemasLoaded, @@ -63,42 +61,39 @@ export const WebhooksOverviewTab = () => { ) } + 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 - } else { - 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 - -
- ) - } - /> - ) + return } + + return } /> } diff --git a/apps/studio/components/interfaces/Integrations/Wrappers/OverviewTab.tsx b/apps/studio/components/interfaces/Integrations/Wrappers/OverviewTab.tsx index 5854ba3f4d4..0c8ee3f9c59 100644 --- a/apps/studio/components/interfaces/Integrations/Wrappers/OverviewTab.tsx +++ b/apps/studio/components/interfaces/Integrations/Wrappers/OverviewTab.tsx @@ -7,7 +7,7 @@ import { Button, Sheet, SheetContent } from 'ui' import { Admonition } from 'ui-patterns/admonition' import { IntegrationOverviewTab } from '../Integration/IntegrationOverviewTab' -import { IntegrationOverviewTabV2 } from '../Integration/IntegrationOverviewTabV2' +import { RequiredExtensionsSection } from '../Integration/RequiredExtensionsSection' import { useAvailableIntegrations } from '../Landing/useAvailableIntegrations' import { CreateIcebergWrapperSheet } from './CreateIcebergWrapperSheet' import { CreateWrapperSheet } from './CreateWrapperSheet' @@ -155,10 +155,9 @@ const AddNewWrapperCTA = () => { ) } -export const WrapperOverviewTab = () => { +export const WrapperContent = () => { const { id } = useParams() const { data: project } = useSelectedProjectQuery() - const isMarketplaceEnabled = useIsMarketplaceEnabled() const { data: integrations = [] } = useAvailableIntegrations() const integration = integrations.find((i) => i.id === id) @@ -183,24 +182,25 @@ export const WrapperOverviewTab = () => { ) } - if (isMarketplaceEnabled) { - return ( - - {isInstalled && ( - <> - - - - )} - - ) - } else { - return ( - }> -
- -
-
- ) - } + return ( + <> + + + {isInstalled && } + + ) +} + +export const WrapperOverviewTab = () => { + const isMarketplaceEnabled = useIsMarketplaceEnabled() + + if (isMarketplaceEnabled) return + + return ( + }> +
+ +
+
+ ) } diff --git a/apps/studio/components/interfaces/Integrations/Wrappers/WrappersTab.tsx b/apps/studio/components/interfaces/Integrations/Wrappers/WrappersTab.tsx index 2c2de5dd6e3..4b14d20689e 100644 --- a/apps/studio/components/interfaces/Integrations/Wrappers/WrappersTab.tsx +++ b/apps/studio/components/interfaces/Integrations/Wrappers/WrappersTab.tsx @@ -9,6 +9,7 @@ import { wrapperMetaComparator } from './Wrappers.utils' import { WrapperTable } from './WrapperTable' import { DiscardChangesConfirmationDialog } from '@/components/ui-patterns/Dialogs/DiscardChangesConfirmationDialog' import { ButtonTooltip } from '@/components/ui/ButtonTooltip' +import { DocsButton } from '@/components/ui/DocsButton' import { useFDWsQuery } from '@/data/fdw/fdws-query' import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' @@ -48,7 +49,7 @@ export const WrappersTab = () => { const Container = useCallback( ({ ...props }: { children: ReactNode } & HTMLProps) => ( -
+
{props.children} @@ -101,6 +102,23 @@ export const WrappersTab = () => { return ( +
+ + setCreateWrapperShown(true)} + disabled={!canCreateWrapper} + tooltip={{ + content: { + text: !canCreateWrapper + ? 'You need additional permissions to create a foreign data wrapper' + : undefined, + }, + }} + > + Add new wrapper + +
diff --git a/apps/studio/components/interfaces/Integrations/templates/StripeSyncEngine/OverviewTab.tsx b/apps/studio/components/interfaces/Integrations/templates/StripeSyncEngine/OverviewTab.tsx index bdf33fee751..8641d799f47 100644 --- a/apps/studio/components/interfaces/Integrations/templates/StripeSyncEngine/OverviewTab.tsx +++ b/apps/studio/components/interfaces/Integrations/templates/StripeSyncEngine/OverviewTab.tsx @@ -24,10 +24,9 @@ import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import * as z from 'zod' import { IntegrationOverviewTab } from '../../Integration/IntegrationOverviewTab' -import { IntegrationOverviewTabV2 } from '../../Integration/IntegrationOverviewTabV2' +import { RequiredExtensionsSection } from '../../Integration/RequiredExtensionsSection' import { InstallationError } from './InstallationError' import { IntegrationInstalledActions, IntegrationNotInstalledActions } from './IntegrationActions' -import { StatusDisplay } from './StatusDisplay' import { canInstall as checkCanInstall, hasInstallError, @@ -52,11 +51,10 @@ const installFormSchema = z.object({ stripeSecretKey: z.string().min(1, 'Stripe API key is required'), }) -export const StripeSyncEngineOverviewTab = () => { +const StripeSyncContent = () => { const track = useTrack() const hasTrackedInstallFailed = useRef(false) const { data: project } = useSelectedProjectQuery() - const isMarketplaceEnabled = useIsMarketplaceEnabled() const [showUninstallModal, setShowUninstallModal] = useState(false) const [shouldShowInstallSheet, setShouldShowInstallSheet] = useState(false) @@ -203,219 +201,167 @@ export const StripeSyncEngineOverviewTab = () => { return ( <> - {isMarketplaceEnabled ? ( - - {hasError && ( - + )} + + {!installed && !uninstalling && !uninstallError ? ( + <> + + + + ) : ( + (installed || uninstalling || uninstallError) && ( + <> + + - )} - - {!installed && !uninstalling && !uninstallError ? ( - - ) : ( - (installed || uninstalling || uninstallError) && ( - - ) - )} - - ) : ( - - ) : null - } - status={ - - } - actions={ - !installed && !uninstalling && !uninstallError ? ( - <> - - - - ) : installed || uninstalling || uninstallError ? ( - <> - - - - ) : null - } - > - - -
- { - if (!project?.ref) return - installStripeSync({ - projectRef: project.ref, - stripeSecretKey, - startTime: Date.now(), - }) - })} - className="overflow-auto grow px-0 flex flex-col" - > - - - {upgradeAvailable ? 'Upgrade' : 'Install'} Stripe Sync Engine - - - - - -

Configuration

- -
- ( - - - field.onChange(e.target.value)} - /> - - - )} - /> - -
- - -
-
- - {installRequestError && ( - - )} -
- - - - - -
- -
-
-
+ + ) )} + + + +
+ { + if (!project?.ref) return + installStripeSync({ + projectRef: project.ref, + stripeSecretKey, + startTime: Date.now(), + }) + })} + className="overflow-auto grow px-0 flex flex-col" + > + + + {upgradeAvailable ? 'Upgrade' : 'Install'} Stripe Sync Engine + + + + + +

Configuration

+ +
+ ( + + + field.onChange(e.target.value)} + /> + + + )} + /> + +
+ + +
+
+ + {installRequestError && ( + + )} +
+ + + + + +
+ +
+
+ { ) } + +export const StripeSyncEngineOverviewTab = () => { + const isMarketplaceEnabled = useIsMarketplaceEnabled() + + if (isMarketplaceEnabled) { + return ( + <> + + + + ) + } + + return ( + +
+ +
+
+ ) +}