diff --git a/apps/studio/components/interfaces/Integrations/Vercel/VercelIntegration.utils.ts b/apps/studio/components/interfaces/Integrations/Vercel/VercelIntegration.utils.ts
index 3c55db6f9e6..a6802341ab7 100644
--- a/apps/studio/components/interfaces/Integrations/Vercel/VercelIntegration.utils.ts
+++ b/apps/studio/components/interfaces/Integrations/Vercel/VercelIntegration.utils.ts
@@ -1,3 +1,5 @@
+import type { Integration } from '@/data/integrations/integrations.types'
+
export function isVercelUrl(url: string): boolean {
try {
const u = new URL(url)
@@ -7,3 +9,17 @@ export function isVercelUrl(url: string): boolean {
return false
}
}
+
+export function findVercelIntegrationByConfigurationId(
+ integrations: Integration[] | undefined,
+ configurationId: string | undefined
+): Integration | undefined {
+ if (!configurationId) return undefined
+
+ return integrations?.find(
+ (integration) =>
+ integration.metadata !== undefined &&
+ 'configuration_id' in integration.metadata &&
+ integration.metadata.configuration_id === configurationId
+ )
+}
diff --git a/apps/studio/components/interfaces/Integrations/Vercel/VercelIntegrationInterstitial.tsx b/apps/studio/components/interfaces/Integrations/Vercel/VercelIntegrationInterstitial.tsx
new file mode 100644
index 00000000000..f6c8960261f
--- /dev/null
+++ b/apps/studio/components/interfaces/Integrations/Vercel/VercelIntegrationInterstitial.tsx
@@ -0,0 +1,97 @@
+import Link from 'next/link'
+import { Button, HoverCard, HoverCardContent, HoverCardTrigger } from 'ui'
+import { Admonition } from 'ui-patterns/admonition'
+
+import { ENV_VAR_RAW_KEYS } from './Integrations-Vercel.constants'
+import { LogoPair, PartnerLogo, SupabaseLogo } from '@/components/layouts/InterstitialLayout'
+import { BASE_PATH } from '@/lib/constants'
+
+const VERCEL_ENV_VAR_COUNT = ENV_VAR_RAW_KEYS.length
+const VERCEL_ICON_SRC = `${BASE_PATH}/img/icons/vercel-icon.svg`
+
+export const VERCEL_INTEGRATION_ICON =
+
+export function VercelIntegrationLogo() {
+ return (
+
+ }
+ right={}
+ />
+ )
+}
+
+export function VercelIntegrationFooter() {
+ return (
+
+ You can remove this integration at any time from Vercel or the Supabase dashboard.
+
+ )
+}
+
+export function VercelEnvVarsSyncDescription() {
+ return (
+ <>
+
+
+
+
+
+
+
Environment variables
+
+ {ENV_VAR_RAW_KEYS.map((key) => (
+ -
+ {key}
+
+ ))}
+
+
+
+ {' '}
+ will be synced to your Vercel project.
+ >
+ )
+}
+
+interface VercelIntegrationInterstitialErrorStateProps {
+ title: string
+ errorMessage?: string | null
+}
+
+export function VercelIntegrationInterstitialErrorState({
+ title,
+ errorMessage,
+}: VercelIntegrationInterstitialErrorStateProps) {
+ return (
+
+
+ Retry the installation request from Vercel.
+ {errorMessage && (
+ Error: {errorMessage}
+ )}
+ >
+ }
+ />
+
+
+ )
+}
diff --git a/apps/studio/components/interfaces/Integrations/VercelGithub/IntegrationPanels.tsx b/apps/studio/components/interfaces/Integrations/VercelGithub/IntegrationPanels.tsx
index 7d4ef739ef9..63c447fd0dd 100644
--- a/apps/studio/components/interfaces/Integrations/VercelGithub/IntegrationPanels.tsx
+++ b/apps/studio/components/interfaces/Integrations/VercelGithub/IntegrationPanels.tsx
@@ -18,7 +18,7 @@ import { getIntegrationConfigurationUrl } from '@/lib/integration-utils'
const ICON_STROKE_WIDTH = 2
const ICON_SIZE = 14
-export interface IntegrationInstallationProps extends RefAttributes {
+interface IntegrationInstallationProps extends RefAttributes {
title: string
integration: Integration
disabled?: boolean
@@ -72,16 +72,16 @@ const Avatar = ({ src }: { src: string | undefined }) => {
)
}
+const IntegrationIconBlock = ({ integration }: { integration: Integration }) => {
+ return (
+
+
+
+ )
+}
+
export const IntegrationInstallation = forwardRef(
({ integration, disabled, ...props }, ref) => {
- const IntegrationIconBlock = () => {
- return (
-
-
-
- )
- }
-
return (
@@ -299,7 +299,7 @@ export const EmptyIntegrationConnection = forwardRef<
{...props}
className={cn(
showNode && 'ml-6 pl-8 mt-4 border-l',
- 'relative pb-2',
+ 'relative',
'last:border-l-transparent',
className
)}
@@ -361,7 +361,7 @@ export const IntegrationConnectionHeader = forwardRef
{props.title && {props.title}
}
-
+
)
}
diff --git a/apps/studio/components/interfaces/Integrations/VercelGithub/ProjectLinker.tsx b/apps/studio/components/interfaces/Integrations/VercelGithub/ProjectLinker.tsx
index 238e5f1c7e0..a256f0dd418 100644
--- a/apps/studio/components/interfaces/Integrations/VercelGithub/ProjectLinker.tsx
+++ b/apps/studio/components/interfaces/Integrations/VercelGithub/ProjectLinker.tsx
@@ -1,7 +1,7 @@
import { Check, ChevronDown, Plus, PlusIcon } from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/router'
-import { ReactNode, useEffect, useState } from 'react'
+import { HTMLAttributes, ReactNode, useEffect, useState } from 'react'
import { toast } from 'sonner'
import {
Badge,
@@ -26,14 +26,13 @@ import {
IntegrationProjectConnection,
} from '@/data/integrations/integrations.types'
import { useOrgProjectsInfiniteQuery } from '@/data/projects/org-projects-infinite-query'
-import { useProjectDetailQuery } from '@/data/projects/project-detail-query'
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
import { BASE_PATH } from '@/lib/constants'
import { openInstallGitHubIntegrationWindow } from '@/lib/github'
import { EMPTY_ARR } from '@/lib/void'
-export interface Project {
+interface Project {
name: string
ref: string
}
@@ -44,7 +43,21 @@ export interface ForeignProject {
installation_id?: number
}
-export interface ProjectLinkerProps {
+const Panel = ({ children, className, ...props }: HTMLAttributes) => {
+ return (
+
+ {children}
+
+ )
+}
+
+interface ProjectLinkerProps {
slug?: string
organizationIntegrationId?: string
foreignProjects: ForeignProject[]
@@ -57,13 +70,13 @@ export interface ProjectLinkerProps {
onSkip?: () => void
loadingForeignProjects?: boolean
showNoEntitiesState?: boolean
-
- defaultSupabaseProjectRef?: string
+ defaultSupabaseProject?: Project
defaultForeignProjectId?: string
mode: 'Vercel' | 'GitHub'
+ variant?: 'default' | 'interstitial'
}
-const ProjectLinker = ({
+export const ProjectLinker = ({
slug,
organizationIntegrationId,
foreignProjects,
@@ -76,10 +89,10 @@ const ProjectLinker = ({
onSkip,
loadingForeignProjects,
showNoEntitiesState = true,
-
- defaultSupabaseProjectRef,
+ defaultSupabaseProject,
defaultForeignProjectId,
mode,
+ variant = 'default',
}: ProjectLinkerProps) => {
const router = useRouter()
const projectCreationEnabled = useIsFeatureEnabled('projects:create')
@@ -89,9 +102,7 @@ const ProjectLinker = ({
const [foreignProjectId, setForeignProjectId] = useState(
defaultForeignProjectId
)
- const [supabaseProjectRef, setSupabaseProjectRef] = useState(
- defaultSupabaseProjectRef
- )
+ const [selectedSupabaseProject, setSelectedSupabaseProject] = useState()
const { data: selectedOrganization } = useSelectedOrganizationQuery()
const { data: orgProjects, isPending: loadingSupabaseProjects } = useOrgProjectsInfiniteQuery({
@@ -100,9 +111,9 @@ const ProjectLinker = ({
const numProjects = orgProjects?.pages[0].pagination.count ?? 0
useEffect(() => {
- if (defaultSupabaseProjectRef !== undefined && supabaseProjectRef === undefined)
- setSupabaseProjectRef(defaultSupabaseProjectRef)
- }, [defaultSupabaseProjectRef, supabaseProjectRef])
+ if (defaultSupabaseProject !== undefined && selectedSupabaseProject === undefined)
+ setSelectedSupabaseProject(defaultSupabaseProject)
+ }, [defaultSupabaseProject, selectedSupabaseProject])
useEffect(() => {
if (defaultForeignProjectId !== undefined && foreignProjectId === undefined)
@@ -112,8 +123,6 @@ const ProjectLinker = ({
// create a flat array of foreign project ids. ie, ["prj_MlkO6AiLG5ofS9ojKrkS3PhhlY3f", ..]
const flatInstalledConnectionsIds = new Set(installedConnections.map((x) => x.foreign_project_id))
- const { data: selectedSupabaseProject } = useProjectDetailQuery({ ref: supabaseProjectRef })
-
const selectedForeignProject = foreignProjectId
? foreignProjects.find((x) => x.id?.toLowerCase() === foreignProjectId?.toLowerCase())
: undefined
@@ -150,25 +159,248 @@ const ProjectLinker = ({
})
}
- const Panel = ({ children, className, ...props }: React.HTMLAttributes) => {
- return (
-
- {children}
-
- )
- }
-
const noSupabaseProjects = numProjects === 0
const noForeignProjects = foreignProjects.length === 0
const missingEntity = noSupabaseProjects ? 'Supabase' : mode
const oppositeMissingEntity = noSupabaseProjects ? mode : 'Supabase'
+ const connectDisabled =
+ loadingForeignProjects ||
+ loadingSupabaseProjects ||
+ isLoading ||
+ !selectedSupabaseProject ||
+ !selectedForeignProject
+
+ const supabaseProjectSelector = (
+ {
+ setSelectedSupabaseProject(project)
+ setOpenProjectsDropdown(false)
+ }}
+ renderRow={(project) => {
+ return (
+
+
+ {variant === 'default' && (
+
+

+
+ )}
+
{project.name}
+ {project.status === 'INACTIVE' &&
Paused}
+ {project.status === 'GOING_DOWN' &&
Pausing}
+
+ {project.ref === selectedSupabaseProject?.ref &&
}
+
+ )
+ }}
+ renderTrigger={() => {
+ return (
+
+ )
+ }}
+ renderActions={() => {
+ return (
+ projectCreationEnabled && (
+
+ {
+ setOpenProjectsDropdown(false)
+ router.push(`/new/${selectedOrganization?.slug}`)
+ }}
+ onClick={() => setOpenProjectsDropdown(false)}
+ >
+ {
+ setOpenProjectsDropdown(false)
+ }}
+ className="w-full flex items-center gap-2"
+ >
+
+ Create a new project
+
+
+
+ )
+ )
+ }}
+ />
+ )
+
+ const foreignProjectSelector = (
+
+
+
+
+
+
+
+
+ No results found.
+
+ {foreignProjects.map((project, i) => {
+ return (
+ {
+ if (project.id) setForeignProjectId(project.id)
+ setOpenForeignProjectsComboBox(false)
+ }}
+ >
+ {getForeignProjectIcon?.(project) ?? integrationIcon}
+
+ {project.name}
+
+
+ )
+ })}
+ {foreignProjects.length === 0 && No results found.}
+
+ {mode === 'GitHub' && (
+ <>
+
+
+ openInstallGitHubIntegrationWindow('install')}
+ >
+
+ Add GitHub Repositories
+
+
+ >
+ )}
+
+
+
+
+ )
+
+ const actionButtons = (
+
+
+ {onSkip !== undefined && (
+
+ )}
+
+ )
+
+ if (variant === 'interstitial') {
+ return (
+
+ {loadingForeignProjects || loadingSupabaseProjects ? (
+
+ ) : showNoEntitiesState && (noSupabaseProjects || noForeignProjects) ? (
+
+ No {missingEntity} projects found. Create a {missingEntity} project to link to a{' '}
+ {oppositeMissingEntity} project, or skip and connect later.
+
+ ) : (
+ <>
+
+
+ Supabase project
+
+ {supabaseProjectSelector}
+
+
+
+
+ Vercel project
+
+ {foreignProjectSelector}
+
+ >
+ )}
+
+ {actionButtons}
+
+ )
+ }
+
return (
@@ -199,94 +431,7 @@ const ProjectLinker = ({
-
{
- setSupabaseProjectRef(project.ref)
- setOpenProjectsDropdown(false)
- }}
- renderRow={(project) => {
- return (
-
-
-
-

-
-
{project.name}
- {project.status === 'INACTIVE' &&
Paused}
- {project.status === 'GOING_DOWN' &&
Pausing}
-
- {project.ref === supabaseProjectRef &&
}
-
- )
- }}
- renderTrigger={() => {
- return (
-
- )
- }}
- renderActions={() => {
- return (
- projectCreationEnabled && (
-
- {
- setOpenProjectsDropdown(false)
- router.push(`/new/${selectedOrganization?.slug}`)
- }}
- onClick={() => setOpenProjectsDropdown(false)}
- >
- {
- setOpenProjectsDropdown(false)
- }}
- className="w-full flex items-center gap-2"
- >
-
- Create a new project
-
-
-
- )
- )
- }}
- />
+ {supabaseProjectSelector}
@@ -296,116 +441,13 @@ const ProjectLinker = ({
{integrationIcon}
-
-
-
-
-
-
-
-
- No results found.
-
- {foreignProjects.map((project, i) => {
- return (
- {
- if (project.id) setForeignProjectId(project.id)
- setOpenForeignProjectsComboBox(false)
- }}
- >
- {getForeignProjectIcon?.(project) ?? integrationIcon}
-
- {project.name}
-
-
- )
- })}
- {foreignProjects.length === 0 && (
- No results found.
- )}
-
- {mode === 'GitHub' && (
- <>
-
-
- openInstallGitHubIntegrationWindow('install')}
- >
-
- Add GitHub Repositories
-
-
- >
- )}
-
-
-
-
+ {foreignProjectSelector}
)}
-
- {onSkip !== undefined && (
-
- )}
-
-
+ {actionButtons}
)
}
-
-export default ProjectLinker
diff --git a/apps/studio/components/interfaces/Organization/IntegrationSettings/SidePanelVercelProjectLinker.tsx b/apps/studio/components/interfaces/Organization/IntegrationSettings/SidePanelVercelProjectLinker.tsx
index dfc6aae960a..9a5d547a858 100644
--- a/apps/studio/components/interfaces/Organization/IntegrationSettings/SidePanelVercelProjectLinker.tsx
+++ b/apps/studio/components/interfaces/Organization/IntegrationSettings/SidePanelVercelProjectLinker.tsx
@@ -1,12 +1,12 @@
-import { useParams } from 'common'
import { keyBy } from 'lodash'
import { useCallback, useMemo } from 'react'
import { toast } from 'sonner'
import { SidePanel } from 'ui'
import { ENV_VAR_RAW_KEYS } from '@/components/interfaces/Integrations/Vercel/Integrations-Vercel.constants'
-import ProjectLinker, {
+import {
ForeignProject,
+ ProjectLinker,
} from '@/components/interfaces/Integrations/VercelGithub/ProjectLinker'
import { Markdown } from '@/components/interfaces/Markdown'
import { vercelIcon } from '@/components/to-be-cleaned/ListIcons'
@@ -14,6 +14,7 @@ import { useOrgIntegrationsQuery } from '@/data/integrations/integrations-query-
import { useIntegrationVercelConnectionsCreateMutation } from '@/data/integrations/integrations-vercel-connections-create-mutation'
import { useVercelProjectsQuery } from '@/data/integrations/integrations-vercel-projects-query'
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
+import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import { BASE_PATH } from '@/lib/constants'
import { EMPTY_ARR } from '@/lib/void'
import { useSidePanelsStateSnapshot } from '@/state/side-panels'
@@ -25,7 +26,7 @@ const VERCEL_ICON = (
)
export const SidePanelVercelProjectLinker = () => {
- const { ref } = useParams()
+ const { data: selectedProject } = useSelectedProjectQuery()
const { data: selectedOrganization } = useSelectedOrganizationQuery()
const sidePanelStateSnapshot = useSidePanelsStateSnapshot()
const organizationIntegrationId = sidePanelStateSnapshot.vercelConnectionsIntegrationId
@@ -126,7 +127,7 @@ Check the details below before proceeding
) : (
-
+
{connections?.map((connection) => (
{vercelIntegration ? (
-
- {connections.length > 0 ? (
- <>
-
-
- {connections.map((connection) => (
- li]:pb-0'
- )}
- >
-
- {isProjectScoped ? (
-
-
-
-
+
+
+ {connections.length > 0 && (
+
+ {connections.map((connection) => (
+ li]:pb-0'
+ )}
+ >
+
+ {isProjectScoped ? (
+
- ))}
-
- >
- ) : (
-
+
+ ) : null}
+
+ ))}
+
)}
(
{displayName || Loading account}
+ {detail && {detail}
}
{action}
diff --git a/apps/studio/pages/integrations/vercel/[slug]/marketplace/choose-project.tsx b/apps/studio/pages/integrations/vercel/[slug]/marketplace/choose-project.tsx
index aa6de0620fe..239ec8e56e9 100644
--- a/apps/studio/pages/integrations/vercel/[slug]/marketplace/choose-project.tsx
+++ b/apps/studio/pages/integrations/vercel/[slug]/marketplace/choose-project.tsx
@@ -1,58 +1,82 @@
import { useParams } from 'common'
import { keyBy } from 'lodash'
+import Head from 'next/head'
import { useCallback, useMemo } from 'react'
import { toast } from 'sonner'
+import { Card, CardContent } from 'ui'
+import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
-import { ENV_VAR_RAW_KEYS } from '@/components/interfaces/Integrations/Vercel/Integrations-Vercel.constants'
-import { isVercelUrl } from '@/components/interfaces/Integrations/Vercel/VercelIntegration.utils'
-import ProjectLinker, {
- ForeignProject,
+import {
+ findVercelIntegrationByConfigurationId,
+ isVercelUrl,
+} from '@/components/interfaces/Integrations/Vercel/VercelIntegration.utils'
+import {
+ VERCEL_INTEGRATION_ICON,
+ VercelEnvVarsSyncDescription,
+ VercelIntegrationFooter,
+ VercelIntegrationInterstitialErrorState,
+ VercelIntegrationLogo,
+} from '@/components/interfaces/Integrations/Vercel/VercelIntegrationInterstitial'
+import {
+ ProjectLinker,
+ type ForeignProject,
} from '@/components/interfaces/Integrations/VercelGithub/ProjectLinker'
-import { Markdown } from '@/components/interfaces/Markdown'
-import VercelIntegrationWindowLayout from '@/components/layouts/IntegrationsLayout/VercelIntegrationWindowLayout'
-import { ScaffoldColumn, ScaffoldContainer } from '@/components/layouts/Scaffold'
+import { InterstitialAccountRow, InterstitialLayout } from '@/components/layouts/InterstitialLayout'
import { vercelIcon } from '@/components/to-be-cleaned/ListIcons'
import { useOrgIntegrationsQuery } from '@/data/integrations/integrations-query-org-only'
import { useIntegrationVercelConnectionsCreateMutation } from '@/data/integrations/integrations-vercel-connections-create-mutation'
import { useVercelProjectsQuery } from '@/data/integrations/integrations-vercel-projects-query'
import { useOrganizationsQuery } from '@/data/organizations/organizations-query'
+import { withAuth } from '@/hooks/misc/withAuth'
import { BASE_PATH } from '@/lib/constants'
+import { getErrorMessage } from '@/lib/get-error-message'
+import { buildStudioPageTitle } from '@/lib/page-title'
+import { useProfileNameAndPicture } from '@/lib/profile'
import { EMPTY_ARR } from '@/lib/void'
import { useIntegrationInstallationSnapshot } from '@/state/integration-installation'
import type { NextPageWithLayout, Organization } from '@/types'
-const VERCEL_ICON = (
-
-)
+const PAGE_TITLE = buildStudioPageTitle({
+ section: 'Connect Vercel Project',
+ brand: 'Supabase',
+})
-const VercelIntegration: NextPageWithLayout = () => {
+const VercelChooseProjectPage: NextPageWithLayout = () => {
const { slug, configurationId, next } = useParams()
+ const { username, primaryEmail, avatarUrl } = useProfileNameAndPicture()
+ const displayName = primaryEmail ?? username ?? ''
- /**
- * Fetch the list of organization based integration installations for Vercel.
- *
- * Array of integrations installed on all
- */
- const { data: integrationData } = useOrgIntegrationsQuery({ orgSlug: slug })
+ const {
+ data: integrationData,
+ isPending: isLoadingIntegrationsQuery,
+ isError: isIntegrationsError,
+ error: integrationsError,
+ } = useOrgIntegrationsQuery({ orgSlug: slug })
- const { data } = useOrganizationsQuery({ enabled: slug !== undefined })
+ const {
+ data: organizationsData,
+ isPending: isLoadingOrganizationsQuery,
+ isError: isOrganizationsError,
+ error: organizationsError,
+ } = useOrganizationsQuery({ enabled: slug !== undefined })
- const organization = data?.find((organization: Organization) => organization.slug === slug)
-
- const integration = integrationData?.find(
- (x) =>
- x.metadata !== undefined &&
- 'configuration_id' in x.metadata &&
- x.metadata?.configuration_id === configurationId
+ const organization = organizationsData?.find(
+ (organization: Organization) => organization.slug === slug
)
- const { data: vercelProjectsData, isPending: isLoadingVercelProjectsData } =
- useVercelProjectsQuery(
- {
- organization_integration_id: integration?.id,
- },
- { enabled: integration?.id !== undefined }
- )
+ const integration = findVercelIntegrationByConfigurationId(integrationData, configurationId)
+
+ const {
+ data: vercelProjectsData,
+ isPending: isLoadingVercelProjectsData,
+ isError: isVercelProjectsError,
+ error: vercelProjectsError,
+ } = useVercelProjectsQuery(
+ {
+ organization_integration_id: integration?.id,
+ },
+ { enabled: integration?.id !== undefined }
+ )
const vercelProjects = useMemo(() => vercelProjectsData ?? EMPTY_ARR, [vercelProjectsData])
const vercelProjectsById = useMemo(() => keyBy(vercelProjects, 'id'), [vercelProjects])
@@ -68,7 +92,7 @@ const VercelIntegration: NextPageWithLayout = () => {
src={`${BASE_PATH}/img/icons/frameworks/${project.framework}.svg`}
width={21}
height={21}
- alt={`icon`}
+ alt="Framework icon"
/>
)
},
@@ -95,7 +119,7 @@ const VercelIntegration: NextPageWithLayout = () => {
})
const onCreateConnections = useCallback(
- (vars: any) => {
+ (vars: Parameters[0]) => {
createConnections({
...vars,
connection: {
@@ -114,54 +138,106 @@ const VercelIntegration: NextPageWithLayout = () => {
[createConnections]
)
+ const showLoadingState =
+ isLoadingOrganizationsQuery ||
+ isLoadingIntegrationsQuery ||
+ (integration?.id !== undefined && isLoadingVercelProjectsData)
+
+ const isError = isOrganizationsError || isIntegrationsError || isVercelProjectsError
+ const errorMessage =
+ getErrorMessage(organizationsError) ??
+ getErrorMessage(integrationsError) ??
+ getErrorMessage(vercelProjectsError)
+ const integrationNotFound =
+ !isLoadingIntegrationsQuery && integrationData !== undefined && integration === undefined
+
return (
<>
-
-
-
- Create your first Project Connection
-
-
- {
- if (next && isVercelUrl(next)) {
- window.location.href = next
- }
- }}
- loadingForeignProjects={isLoadingVercelProjectsData}
- mode="Vercel"
- />
-
+ {PAGE_TITLE}
+
-${ENV_VAR_RAW_KEYS.map((x) => {
- return `\n - \`${x}\``
-})}
-`}
- />
-
-
+ }
+ title="Connect Vercel project"
+ description={}
+ footer={}
+ >
+
+ {showLoadingState ? (
+
+ ) : isError ? (
+
+ ) : organization === undefined ? (
+
+ ) : integrationNotFound ? (
+
+ ) : (
+
+
+
+
{
+ if (next && isVercelUrl(next)) {
+ window.location.href = next
+ }
+ }}
+ loadingForeignProjects={isLoadingVercelProjectsData}
+ mode="Vercel"
+ />
+
+ )}
+
+
>
)
}
-VercelIntegration.getLayout = (page) => (
- {page}
+const ChooseProjectLoadingState = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
)
-export default VercelIntegration
+export default withAuth(VercelChooseProjectPage)
diff --git a/apps/studio/pages/integrations/vercel/install.tsx b/apps/studio/pages/integrations/vercel/install.tsx
index bc01578a213..ea4d72d9bd3 100644
--- a/apps/studio/pages/integrations/vercel/install.tsx
+++ b/apps/studio/pages/integrations/vercel/install.tsx
@@ -18,19 +18,17 @@ import {
import { Admonition } from 'ui-patterns/admonition'
import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
-import { getHasInstalledObject } from '@/components/layouts/IntegrationsLayout/Integrations.utils'
import {
- InterstitialAccountRow,
- InterstitialLayout,
- LogoPair,
- PartnerLogo,
- SupabaseLogo,
-} from '@/components/layouts/InterstitialLayout'
+ VercelIntegrationFooter,
+ VercelIntegrationInterstitialErrorState,
+ VercelIntegrationLogo,
+} from '@/components/interfaces/Integrations/Vercel/VercelIntegrationInterstitial'
+import { getHasInstalledObject } from '@/components/layouts/IntegrationsLayout/Integrations.utils'
+import { InterstitialAccountRow, InterstitialLayout } from '@/components/layouts/InterstitialLayout'
import { useIntegrationsQuery } from '@/data/integrations/integrations-query'
import { useVercelIntegrationCreateMutation } from '@/data/integrations/vercel-integration-create-mutation'
import { useOrganizationsQuery } from '@/data/organizations/organizations-query'
import { withAuth } from '@/hooks/misc/withAuth'
-import { BASE_PATH } from '@/lib/constants'
import {
buildVercelInstallRouteQuery,
getErrorMessage,
@@ -38,6 +36,7 @@ import {
} from '@/lib/integrations/vercel-install.utils'
import { buildStudioPageTitle } from '@/lib/page-title'
import { useProfileNameAndPicture } from '@/lib/profile'
+import { useTrack } from '@/lib/telemetry/track'
import { useIntegrationInstallationSnapshot } from '@/state/integration-installation'
import type { NextPageWithLayout, Organization } from '@/types'
@@ -61,6 +60,7 @@ const VercelIntegration: NextPageWithLayout = () => {
const { code, configurationId, currentProjectId, externalId, next, teamId, source } = useParams()
const [selectedOrg, setSelectedOrg] = useState(null)
const { username, primaryEmail, avatarUrl } = useProfileNameAndPicture()
+ const track = useTrack()
const snapshot = useIntegrationInstallationSnapshot()
const displayName = primaryEmail ?? username ?? ''
@@ -197,6 +197,11 @@ const VercelIntegration: NextPageWithLayout = () => {
* Only install if integration hasn't already been installed
*/
if (!isIntegrationInstalled) {
+ track(
+ 'integration_install_submitted',
+ { integrationName: 'Vercel', method: source },
+ { organization: orgSlug }
+ )
mutate({
code,
configurationId,
@@ -243,7 +248,6 @@ const VercelIntegration: NextPageWithLayout = () => {
!selectedOrg ||
missingParams.length > 0 ||
isError
-
return (
<>
@@ -251,27 +255,19 @@ const VercelIntegration: NextPageWithLayout = () => {
- }
- right={}
- />
- }
- title="Install Vercel integration"
- description="Choose an organization to connect to Vercel"
+ logo={}
+ title="Install Vercel Integration"
+ description="Choose the Supabase organization Vercel can connect to"
+ footer={}
>
{showLoadingState ? (
) : isError ? (
-
+
) : (
@@ -357,26 +353,6 @@ const InstallationLoadingState = () => (
)
-const InstallationErrorState = ({ errorMessage }: { errorMessage?: string }) => (
-
-
- Retry the installation request from Vercel.
- {errorMessage && (
- Error: {errorMessage}
- )}
- >
- }
- />
-
-
-)
-
interface OrganizationSelectProps {
organizations: Organization[]
selectedOrg: Organization | null