mirror of
https://github.com/supabase/supabase.git
synced 2026-10-11 04:15:04 +03:00
## Problem Now that we migrated old components to their new shadcn alternatives, we don't need the `_Shadcn_` suffix anymore. ## Solution Remove it <img width="659" height="609" alt="image" src="https://github.com/user-attachments/assets/2d7271a9-066a-4dcc-92fe-729b106d2c2f" />
260 lines
9.0 KiB
TypeScript
260 lines
9.0 KiB
TypeScript
import { useParams } from 'common'
|
|
import { AlertTriangle, Info } from 'lucide-react'
|
|
import Link from 'next/link'
|
|
import { useRouter } from 'next/router'
|
|
import { useEffect, useMemo, useState } from 'react'
|
|
import { toast } from 'sonner'
|
|
import { Alert, AlertDescription, AlertTitle, Button } from 'ui'
|
|
|
|
import OrganizationPicker from '@/components/interfaces/Integrations/Vercel/OrganizationPicker'
|
|
import { Markdown } from '@/components/interfaces/Markdown'
|
|
import { getHasInstalledObject } from '@/components/layouts/IntegrationsLayout/Integrations.utils'
|
|
import VercelIntegrationWindowLayout from '@/components/layouts/IntegrationsLayout/VercelIntegrationWindowLayout'
|
|
import { ScaffoldColumn, ScaffoldContainer } from '@/components/layouts/Scaffold'
|
|
import { useIntegrationsQuery } from '@/data/integrations/integrations-query'
|
|
import { useVercelIntegrationCreateMutation } from '@/data/integrations/vercel-integration-create-mutation'
|
|
import { useOrganizationsQuery } from '@/data/organizations/organizations-query'
|
|
import { useIntegrationInstallationSnapshot } from '@/state/integration-installation'
|
|
import type { NextPageWithLayout, Organization } from '@/types'
|
|
|
|
/**
|
|
* Variations of the Vercel integration flow.
|
|
* They require different UI and logic.
|
|
*
|
|
* Deploy Button - the flow that starts from the Deploy Button - https://vercel.com/docs/integrations#deploy-button
|
|
* Marketplace - the flow that starts from the Marketplace - https://vercel.com/integrations
|
|
*
|
|
*/
|
|
export type VercelIntegrationFlow = 'deploy-button' | 'marketing'
|
|
|
|
const VercelIntegration: NextPageWithLayout = () => {
|
|
const router = useRouter()
|
|
const { code, configurationId, teamId, source } = useParams()
|
|
const [selectedOrg, setSelectedOrg] = useState<Organization | null>(null)
|
|
|
|
const snapshot = useIntegrationInstallationSnapshot()
|
|
|
|
/**
|
|
* Fetch the list of organization based integration installations for Vercel.
|
|
*
|
|
* Array of integrations installed on all
|
|
*/
|
|
const { data: integrationData } = useIntegrationsQuery()
|
|
|
|
const {
|
|
data: organizationsData,
|
|
isPending: isLoadingOrganizationsQuery,
|
|
isSuccess: isOrganizationsDataSuccess,
|
|
} = useOrganizationsQuery()
|
|
|
|
useEffect(() => {
|
|
if (organizationsData !== undefined && integrationData !== undefined) {
|
|
const firstOrg = organizationsData[0]
|
|
|
|
if (firstOrg && selectedOrg === null) {
|
|
setSelectedOrg(firstOrg)
|
|
router.query.organizationSlug = firstOrg.slug
|
|
}
|
|
}
|
|
}, [organizationsData, integrationData])
|
|
|
|
/**
|
|
* Organizations with extra `installationInstalled` attribute
|
|
*
|
|
* Used to show label/badge and allow/disallow installing
|
|
*
|
|
*/
|
|
const installed = useMemo(
|
|
() =>
|
|
integrationData && organizationsData
|
|
? getHasInstalledObject({
|
|
integrationName: 'Vercel',
|
|
integrationData,
|
|
organizationsData,
|
|
installationId: configurationId,
|
|
})
|
|
: {},
|
|
[configurationId, integrationData, organizationsData]
|
|
)
|
|
|
|
/**
|
|
* Handle the correct route change based on whether the vercel integration
|
|
* is following the 'marketplace/external' flow or 'deploy button' flow.
|
|
* See:
|
|
* - https://vercel.com/docs/integrations/create-integration/submit-integration#query-parameters-for-marketplace
|
|
* - https://vercel.com/docs/integrations/create-integration/submit-integration#query-parameters-for-external-flow
|
|
* - https://vercel.com/docs/integrations/create-integration/submit-integration#query-parameters-for-deploy-button
|
|
*/
|
|
function handleRouteChange() {
|
|
const orgSlug = selectedOrg?.slug
|
|
|
|
switch (source) {
|
|
case 'deploy-button': {
|
|
router.push({
|
|
pathname: `/integrations/vercel/${orgSlug}/deploy-button/new-project`,
|
|
query: router.query,
|
|
})
|
|
break
|
|
}
|
|
case 'marketplace':
|
|
case 'external': {
|
|
router.push({
|
|
pathname: `/integrations/vercel/${orgSlug}/marketplace/choose-project`,
|
|
query: router.query,
|
|
})
|
|
break
|
|
}
|
|
default:
|
|
toast.error(
|
|
`Unsupported Vercel installation source: ${source}. Please contact support if this error persists.`
|
|
)
|
|
}
|
|
}
|
|
|
|
const { mutate, isPending: isLoadingVercelIntegrationCreateMutation } =
|
|
useVercelIntegrationCreateMutation({
|
|
onMutate() {
|
|
snapshot.setLoading(true)
|
|
},
|
|
onSuccess() {
|
|
handleRouteChange()
|
|
snapshot.setLoading(false)
|
|
},
|
|
onError(error) {
|
|
toast.error(`Creating Vercel integration failed: ${error.message}`)
|
|
},
|
|
})
|
|
|
|
function onInstall() {
|
|
const orgSlug = selectedOrg?.slug
|
|
|
|
const isIntegrationInstalled = orgSlug ? installed[orgSlug] : false
|
|
|
|
if (!orgSlug) {
|
|
return toast.error('Please select an organization')
|
|
}
|
|
|
|
if (!code) {
|
|
return toast.error('Vercel code missing')
|
|
}
|
|
|
|
if (!configurationId) {
|
|
return toast.error('Vercel Configuration ID missing')
|
|
}
|
|
|
|
if (!source) {
|
|
return toast.error('Vercel Configuration source missing')
|
|
}
|
|
|
|
/**
|
|
* Only install if integration hasn't already been installed
|
|
*/
|
|
if (!isIntegrationInstalled) {
|
|
mutate({
|
|
code,
|
|
configurationId,
|
|
orgSlug,
|
|
metadata: {},
|
|
source,
|
|
teamId: teamId,
|
|
})
|
|
} else {
|
|
handleRouteChange()
|
|
}
|
|
}
|
|
|
|
const dataLoading = isLoadingVercelIntegrationCreateMutation || isLoadingOrganizationsQuery
|
|
|
|
const noOrganizations = useMemo(() => {
|
|
return isOrganizationsDataSuccess && organizationsData?.length === 0 ? true : false
|
|
}, [isOrganizationsDataSuccess, organizationsData])
|
|
|
|
const alreadyInstalled = useMemo(() => {
|
|
return selectedOrg && installed[selectedOrg.slug] && source === 'marketplace' && !dataLoading
|
|
? true
|
|
: false
|
|
}, [installed, selectedOrg, source, dataLoading])
|
|
|
|
const disableInstallationForm =
|
|
(isLoadingVercelIntegrationCreateMutation && !dataLoading) ||
|
|
// disables installation button if integration is already installed and it is Marketplace flow
|
|
alreadyInstalled ||
|
|
noOrganizations
|
|
|
|
const isLoading = useMemo(() => {
|
|
return isLoadingVercelIntegrationCreateMutation || isLoadingOrganizationsQuery
|
|
}, [isLoadingVercelIntegrationCreateMutation, isLoadingOrganizationsQuery])
|
|
|
|
return (
|
|
<>
|
|
<ScaffoldContainer className="flex flex-col gap-6 grow py-8">
|
|
<ScaffoldColumn className="mx-auto w-full max-w-md">
|
|
<h2>Choose organization</h2>
|
|
<>
|
|
<Markdown content={`Choose the Supabase organization you wish to install in`} />
|
|
<OrganizationPicker
|
|
integrationName="Vercel"
|
|
selectedOrg={selectedOrg}
|
|
disabled={noOrganizations || isLoading}
|
|
onSelectedOrgChange={(org) => {
|
|
setSelectedOrg(org)
|
|
router.query.organizationSlug = org.slug
|
|
}}
|
|
configurationId={configurationId}
|
|
/>
|
|
{alreadyInstalled && (
|
|
<Alert variant="warning">
|
|
<AlertTriangle className="h-4 w-4" strokeWidth={2} />
|
|
<AlertTitle>Vercel Integration is already installed.</AlertTitle>
|
|
<AlertDescription>
|
|
You will need to choose another organization to install the integration.
|
|
</AlertDescription>
|
|
</Alert>
|
|
)}
|
|
{noOrganizations && (
|
|
<Alert variant="warning">
|
|
<AlertTriangle className="h-4 w-4" strokeWidth={2} />
|
|
<AlertTitle>No Supabase Organizations to install Integration.</AlertTitle>
|
|
<AlertDescription className="prose">
|
|
You will need to create a Supabase Organization before you can install the Vercel
|
|
Integration. You can create a new organization{' '}
|
|
<Link href="https://supabase.com/dashboard/new" target="_blank">
|
|
here
|
|
</Link>
|
|
.
|
|
</AlertDescription>
|
|
</Alert>
|
|
)}
|
|
<div className="flex flex-row w-full justify-end">
|
|
<Button
|
|
size="medium"
|
|
className="self-end"
|
|
disabled={disableInstallationForm || isLoadingVercelIntegrationCreateMutation}
|
|
loading={isLoadingVercelIntegrationCreateMutation}
|
|
onClick={onInstall}
|
|
>
|
|
Install integration
|
|
</Button>
|
|
</div>
|
|
</>
|
|
</ScaffoldColumn>
|
|
</ScaffoldContainer>
|
|
<ScaffoldContainer className="flex flex-col gap-6 py-3">
|
|
<Alert variant="default">
|
|
<Info className="h-4 w-4" strokeWidth={2} />
|
|
<AlertTitle>You can uninstall this Integration at any time.</AlertTitle>
|
|
<AlertDescription>
|
|
Remove this integration at any time from Vercel or the Supabase dashboard.
|
|
</AlertDescription>
|
|
</Alert>
|
|
</ScaffoldContainer>
|
|
</>
|
|
)
|
|
}
|
|
|
|
VercelIntegration.getLayout = (page) => (
|
|
<VercelIntegrationWindowLayout>{page}</VercelIntegrationWindowLayout>
|
|
)
|
|
|
|
export default VercelIntegration
|