import { zodResolver } from '@hookform/resolvers/zod' import Link from 'next/link' import { useState } from 'react' import { useForm, useWatch } from 'react-hook-form' import { toast } from 'sonner' import { Alert, AlertDescription, AlertTitle, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, Input, Switch, } from 'ui' import { Admonition } from 'ui-patterns/Admonition' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import * as z from 'zod' import { FormActions } from '@/components/ui/Forms/FormActions' import { InlineLink } from '@/components/ui/InlineLink' import type { EnvironmentTargets, Integration, IntegrationProjectConnection, } from '@/data/integrations/integrations.types' import { useVercelConnectionUpdateMutation } from '@/data/integrations/vercel-connection-update-mutate' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { DOCS_URL } from '@/lib/constants' const VercelIntegrationConnectionForm = ({ disabled, connection, integration, }: { disabled?: boolean connection: IntegrationProjectConnection integration: Integration }) => { // NOTE(kamil): Ignore sync targets for Vercel Marketplace as it's not synchronized using integration, // but through a separate marketplace mechanism. It's not theoretically necessary, but we might have some stale data. const { data: org } = useSelectedOrganizationQuery() const { data: project } = useSelectedProjectQuery() const isBranchingEnabled = project?.is_branch_enabled === true const envSyncTargets = org?.managed_by === 'vercel-marketplace' ? [] : (connection.env_sync_targets ?? []) const FormSchema = z.object({ environmentVariablesProduction: z.boolean().default(envSyncTargets.includes('production')), environmentVariablesPreview: z.boolean().default(envSyncTargets.includes('preview')), environmentVariablesDevelopment: z.boolean().default(envSyncTargets.includes('development')), publicEnvVarPrefix: z.string().optional(), }) const form = useForm>({ resolver: zodResolver(FormSchema), defaultValues: { environmentVariablesProduction: envSyncTargets.includes('production'), environmentVariablesPreview: envSyncTargets.includes('preview'), environmentVariablesDevelopment: envSyncTargets.includes('development'), publicEnvVarPrefix: connection.public_env_var_prefix, }, }) const [showConfirmation, setShowConfirmation] = useState(false) const [pendingValues, setPendingValues] = useState | null>(null) const { mutate: updateVercelConnection, isPending } = useVercelConnectionUpdateMutation({ onSuccess: () => { form.reset(form.getValues()) setShowConfirmation(false) setPendingValues(null) toast.success(`Updated Vercel connection`) }, }) // Syncing the `preview` or `development` target sends this project's production credentials // (anon and service role keys, database connection strings) to those Vercel environments. Watch // the toggles so we can warn inline and gate the save behind a confirmation. const [syncPreview, syncDevelopment] = useWatch({ control: form.control, name: ['environmentVariablesPreview', 'environmentVariablesDevelopment'], }) const isSyncingNonProdEnvs = syncPreview || syncDevelopment const selectedNonProdTargets: string[] = [] if (pendingValues?.environmentVariablesPreview) selectedNonProdTargets.push('Preview') if (pendingValues?.environmentVariablesDevelopment) selectedNonProdTargets.push('Development') function performUpdate(data: z.infer) { const targets: EnvironmentTargets[] = [] if (data.environmentVariablesProduction) targets.push('production') if (data.environmentVariablesPreview) targets.push('preview') if (data.environmentVariablesDevelopment) targets.push('development') updateVercelConnection({ id: connection.id, envSyncTargets: targets, publicEnvVarPrefix: data.publicEnvVarPrefix?.trim(), organizationIntegrationId: integration.id, }) } function onSubmit(data: z.infer) { // Always confirm when Preview/Development sync is enabled to discourage leaking production // credentials into preview deployments and steer users toward Branching instead. if (data.environmentVariablesPreview || data.environmentVariablesDevelopment) { setPendingValues(data) setShowConfirmation(true) return } performUpdate(data) } const vercelConnectionFormId = `vercel-connection-form-${connection.id}` return (
{org?.managed_by === 'vercel-marketplace' ? ( Vercel Marketplace managed project This project is managed via Vercel Marketplace. Environment variables are automatically synchronized for your connected Vercel projects. This integration purpose is synchronizing preview deployments environment variables with our{' '} Branching {' '} feature. ) : (
Sync environment variables to Vercel

Choose which Vercel environments receive this project's{' '} production credentials. Most projects only need production.

With{' '} Branching{' '} enabled, leave Preview and Development off: Branching gives each preview branch its own database and syncs those credentials to your Vercel Preview deployments.

( Syncs to the Vercel production env. } > )} /> ( Syncs to the Vercel preview env. } > )} /> ( Syncs to the Vercel development env. } > )} /> {isSyncingNonProdEnvs && (

Preview and Development sync your production credentials, including the service role key and database password, to those Vercel environments.

{isBranchingEnabled ? (

Branching {' '} already provisions isolated credentials for each preview branch. With Preview enabled, deployments use production credentials until a branch finishes provisioning, which can affect production data. We recommend leaving Preview and Development off.

) : (

To give preview deployments their own isolated credentials, use{' '} Branching {' '} instead.

)}
)}
)}
Customize public environment variable prefix
( Prefix e.g.{' '} {( [ ['NEXT_PUBLIC_', 'NEXT_PUBLIC_'], ['VITE_PUBLIC_', 'VITE_PUBLIC_'], ['PUBLIC_', 'PUBLIC_'], ] as const ).map(([label, value], index) => ( {index > 0 && ', '} ))} , etc. )} />
{form.formState.isDirty ? (

Note: Changing these settings will not trigger a resync of environment variables.

) : (
)} form.reset()} />
{ setShowConfirmation(false) setPendingValues(null) }} onConfirm={() => { if (pendingValues) performUpdate(pendingValues) }} alert={{ title: 'These environments will use production credentials', description: 'Including the project ref, API URL, anon and service role keys, and database connection strings.', }} >

This syncs your production credentials to the{' '} {selectedNonProdTargets.join(' and ')}{' '} environment{selectedNonProdTargets.length > 1 ? 's' : ''} of your connected Vercel project.

{isBranchingEnabled ? (

Branching already provisions isolated credentials for each preview branch. With this enabled, deployments use production credentials until a branch finishes provisioning, which can affect production data. We recommend leaving it off.

) : (

To use isolated credentials for preview deployments instead, enable{' '} Branching.

)}
) } export default VercelIntegrationConnectionForm