mirror of
https://github.com/supabase/supabase.git
synced 2026-10-09 11:25:06 +03:00
## Summary - Clarifies Vercel integration settings copy for org-scoped and project-scoped contexts. - Updates GitHub and Vercel integration section titles to sentence case for in-page headings. - Contributes to DEPR-565. | Before | After | | --- | --- | | <img width="1242" height="759" alt="Integrations Basket Supabase" src="https://github.com/user-attachments/assets/df33a9d4-8fb3-40cf-87d2-e87fa33195e4" /> | <img width="1150" height="715" alt="Integrations Basket Supabase" src="https://github.com/user-attachments/assets/45478216-c426-4bc1-9292-9a6016ac7af9" /> | | <img width="1242" height="759" alt="18154" src="https://github.com/user-attachments/assets/8dfb7742-fca5-421f-88d0-4d24dad93450" /> | <img width="1150" height="715" alt="Integrations Settings Agua Basket Supabase" src="https://github.com/user-attachments/assets/aa119d0e-cf5e-45e5-b5d3-cd8c0d047e34" /> | <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Summary by CodeRabbit * **UI Text Updates** * Updated GitHub and Vercel headings and labels to use consistent casing (e.g., “GitHub connection”, “Vercel”). * Adjusted success and empty-state messaging for GitHub and Vercel integration actions. * **UX Improvements** * Improved GitHub and Vercel section descriptions by tailoring the text to project-scoped vs organization-scoped contexts. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
245 lines
10 KiB
TypeScript
245 lines
10 KiB
TypeScript
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||
import { ExternalLink } from 'lucide-react'
|
||
import { useCallback, useMemo } from 'react'
|
||
import { toast } from 'sonner'
|
||
import { cn } from 'ui'
|
||
import {
|
||
PageSection,
|
||
PageSectionContent,
|
||
PageSectionDescription,
|
||
PageSectionMeta,
|
||
PageSectionSummary,
|
||
PageSectionTitle,
|
||
} from 'ui-patterns/PageSection'
|
||
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
|
||
|
||
import { IntegrationSectionIcon } from '../IntegrationsSettings'
|
||
import VercelIntegrationConnectionForm from './VercelIntegrationConnectionForm'
|
||
import { IntegrationConnectionItem } from '@/components/interfaces/Integrations/VercelGithub/IntegrationConnection'
|
||
import {
|
||
EmptyIntegrationConnection,
|
||
IntegrationConnectionHeader,
|
||
IntegrationInstallation,
|
||
} from '@/components/interfaces/Integrations/VercelGithub/IntegrationPanels'
|
||
import { InlineLink } from '@/components/ui/InlineLink'
|
||
import { NoPermission } from '@/components/ui/NoPermission'
|
||
import { useOrgIntegrationsQuery } from '@/data/integrations/integrations-query-org-only'
|
||
import { useIntegrationsVercelInstalledConnectionDeleteMutation } from '@/data/integrations/integrations-vercel-installed-connection-delete-mutation'
|
||
import { useVercelProjectsQuery } from '@/data/integrations/integrations-vercel-projects-query'
|
||
import type {
|
||
Integration,
|
||
IntegrationName,
|
||
IntegrationProjectConnection,
|
||
} from '@/data/integrations/integrations.types'
|
||
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
|
||
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
|
||
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
|
||
import { pluralize } from '@/lib/helpers'
|
||
import { getIntegrationConfigurationUrl } from '@/lib/integration-utils'
|
||
import { useSidePanelsStateSnapshot } from '@/state/side-panels'
|
||
|
||
export const VercelSection = ({ isProjectScoped }: { isProjectScoped: boolean }) => {
|
||
const { data: project } = useSelectedProjectQuery()
|
||
const { data: org } = useSelectedOrganizationQuery()
|
||
const { data } = useOrgIntegrationsQuery({ orgSlug: org?.slug })
|
||
const sidePanelsStateSnapshot = useSidePanelsStateSnapshot()
|
||
const isBranch = project?.parent_project_ref !== undefined
|
||
|
||
const { can: canReadVercelConnection, isLoading: isLoadingPermissions } =
|
||
useAsyncCheckPermissions(PermissionAction.READ, 'integrations.vercel_connections')
|
||
const { can: canCreateVercelConnection } = useAsyncCheckPermissions(
|
||
PermissionAction.CREATE,
|
||
'integrations.vercel_connections'
|
||
)
|
||
const { can: canUpdateVercelConnection } = useAsyncCheckPermissions(
|
||
PermissionAction.UPDATE,
|
||
'integrations.vercel_connections'
|
||
)
|
||
|
||
const { mutate: deleteVercelConnection } = useIntegrationsVercelInstalledConnectionDeleteMutation(
|
||
{
|
||
onSuccess: () => {
|
||
toast.success('Successfully deleted Vercel connection')
|
||
},
|
||
}
|
||
)
|
||
|
||
const vercelIntegrations = useMemo(() => {
|
||
return data
|
||
?.filter((integration) => integration.integration.name === 'Vercel')
|
||
.map((integration) => {
|
||
if (integration.metadata && integration.integration.name === 'Vercel') {
|
||
const avatarSrc =
|
||
!integration.metadata.account.avatar && integration.metadata.account.type === 'Team'
|
||
? `https://vercel.com/api/www/avatar?teamId=${integration.metadata.account.team_id}&s=48&format=png`
|
||
: `https://vercel.com/api/www/avatar/${integration.metadata.account.avatar}?s=48`
|
||
|
||
return {
|
||
...integration,
|
||
metadata: {
|
||
...integration.metadata,
|
||
account: {
|
||
...integration.metadata.account,
|
||
avatar: avatarSrc,
|
||
},
|
||
},
|
||
} as Integration
|
||
}
|
||
|
||
return integration
|
||
})
|
||
}, [data])
|
||
|
||
// We're only supporting one Vercel integration per org for now
|
||
// this will need to be updated when we support multiple integrations
|
||
const vercelIntegration = vercelIntegrations?.[0]
|
||
const { data: vercelProjectsData } = useVercelProjectsQuery(
|
||
{
|
||
organization_integration_id: vercelIntegration?.id,
|
||
},
|
||
{ enabled: vercelIntegration?.id !== undefined }
|
||
)
|
||
const vercelProjectCount = vercelProjectsData?.length ?? 0
|
||
|
||
const onAddVercelConnection = useCallback(
|
||
(integrationId: string) => {
|
||
sidePanelsStateSnapshot.setVercelConnectionsIntegrationId(integrationId)
|
||
sidePanelsStateSnapshot.setVercelConnectionsOpen(true)
|
||
},
|
||
[sidePanelsStateSnapshot]
|
||
)
|
||
|
||
const onDeleteVercelConnection = useCallback(
|
||
async (connection: IntegrationProjectConnection) => {
|
||
deleteVercelConnection({
|
||
id: connection.id,
|
||
organization_integration_id: connection.organization_integration_id,
|
||
orgSlug: org?.slug,
|
||
})
|
||
},
|
||
[deleteVercelConnection, org?.slug]
|
||
)
|
||
|
||
const integrationUrl =
|
||
process.env.NEXT_PUBLIC_ENVIRONMENT === 'prod'
|
||
? 'https://vercel.com/integrations/supabase'
|
||
: process.env.NEXT_PUBLIC_ENVIRONMENT === 'staging'
|
||
? `https://vercel.com/integrations/supabase-staging`
|
||
: 'https://vercel.com/integrations/supabase-local'
|
||
|
||
let connections =
|
||
(isProjectScoped
|
||
? vercelIntegration?.connections.filter(
|
||
(connection) => connection.supabase_project_ref === project?.ref
|
||
)
|
||
: vercelIntegration?.connections) || []
|
||
|
||
const ConnectionHeaderTitle = `${connections.length} project ${pluralize(
|
||
connections.length,
|
||
'connection'
|
||
)} `
|
||
const description = isProjectScoped
|
||
? 'Connect Vercel projects to this Supabase project. Supabase keeps environment variables up to date in each connected Vercel project.'
|
||
: 'Connect your Vercel teams to this Supabase organization. Supabase keeps environment variables up to date in each connected project. You can also link multiple Vercel projects to the same Supabase project.'
|
||
|
||
return (
|
||
<PageSection>
|
||
<PageSectionMeta>
|
||
<div className="flex flex-1 items-start gap-6">
|
||
<IntegrationSectionIcon title="vercel" />
|
||
<PageSectionSummary>
|
||
<PageSectionTitle>Vercel</PageSectionTitle>
|
||
<PageSectionDescription>{description}</PageSectionDescription>
|
||
</PageSectionSummary>
|
||
</div>
|
||
</PageSectionMeta>
|
||
<PageSectionContent>
|
||
{isLoadingPermissions ? (
|
||
<GenericSkeletonLoader />
|
||
) : !canReadVercelConnection ? (
|
||
<NoPermission resourceText="view this organization's Vercel connections" />
|
||
) : (
|
||
<div className="space-y-6">
|
||
<div>
|
||
{vercelIntegration ? (
|
||
<div key={vercelIntegration.id}>
|
||
<IntegrationInstallation title={'Vercel'} integration={vercelIntegration} />
|
||
{connections.length > 0 ? (
|
||
<>
|
||
<IntegrationConnectionHeader
|
||
title={ConnectionHeaderTitle}
|
||
markdown={`Repository connections for Vercel`}
|
||
/>
|
||
<ul className="flex flex-col">
|
||
{connections.map((connection) => (
|
||
<div
|
||
key={connection.id}
|
||
className={cn(
|
||
isProjectScoped && 'relative flex flex-col -gap-[1px] [&>li]:pb-0'
|
||
)}
|
||
>
|
||
<IntegrationConnectionItem
|
||
connection={connection}
|
||
disabled={isBranch || !canUpdateVercelConnection}
|
||
type={'Vercel' as IntegrationName}
|
||
onDeleteConnection={onDeleteVercelConnection}
|
||
className={cn(isProjectScoped && 'rounded-b-none! mb-0!')}
|
||
/>
|
||
{isProjectScoped ? (
|
||
<div className="relative pl-8 ml-6 border-l border-muted pb-6">
|
||
<div className="border-b border-l border-r rounded-b-lg">
|
||
<VercelIntegrationConnectionForm
|
||
connection={connection}
|
||
integration={vercelIntegration}
|
||
disabled={isBranch || !canUpdateVercelConnection}
|
||
/>
|
||
</div>
|
||
</div>
|
||
) : null}
|
||
</div>
|
||
))}
|
||
</ul>
|
||
</>
|
||
) : (
|
||
<IntegrationConnectionHeader
|
||
title={ConnectionHeaderTitle}
|
||
className="pb-0"
|
||
markdown={`Repository connections for Vercel`}
|
||
/>
|
||
)}
|
||
<EmptyIntegrationConnection
|
||
disabled={isBranch || !canCreateVercelConnection}
|
||
onClick={() => onAddVercelConnection(vercelIntegration.id)}
|
||
>
|
||
Add new project connection
|
||
</EmptyIntegrationConnection>
|
||
</div>
|
||
) : (
|
||
<EmptyIntegrationConnection
|
||
showNode={false}
|
||
disabled={isBranch}
|
||
href={integrationUrl}
|
||
icon={<ExternalLink />}
|
||
disabledTooltip="Install the Vercel integration on your project's main branch"
|
||
>
|
||
Install Vercel integration
|
||
</EmptyIntegrationConnection>
|
||
)}
|
||
</div>
|
||
{vercelProjectCount > 0 && vercelIntegration !== undefined && (
|
||
<p className="text-sm text-foreground-light">
|
||
Your Vercel connection can access {vercelProjectCount} Vercel projects. To change
|
||
which projects Supabase may use, open your organization’s{' '}
|
||
<InlineLink href={getIntegrationConfigurationUrl(vercelIntegration)}>
|
||
Vercel integration settings
|
||
</InlineLink>
|
||
.
|
||
</p>
|
||
)}
|
||
</div>
|
||
)}
|
||
</PageSectionContent>
|
||
</PageSection>
|
||
)
|
||
}
|