import React, { useState } from 'react';
import * as RadixDialog from '@radix-ui/react-dialog';
import { Dialog, DialogTitle, DialogDescription } from '~/components/ui/Dialog';
import { useStore } from '@nanostores/react';
import { netlifyConnection, updateNetlifyConnection } from '~/lib/stores/netlify';
import { vercelConnection } from '~/lib/stores/vercel';
import { useNetlifyDeploy } from './NetlifyDeploy.client';
import { useVercelDeploy } from './VercelDeploy.client';
import { useGitHubDeploy } from './GitHubDeploy.client';
import { GitHubDeploymentDialog } from './GitHubDeploymentDialog';
import { toast } from 'react-toastify';
import { classNames } from '~/utils/classNames';
interface DeployDialogProps {
isOpen: boolean;
onClose: () => void;
}
interface DeployProvider {
id: 'netlify' | 'vercel' | 'github' | 'cloudflare';
name: string;
iconClass: string;
iconColor?: string;
connected: boolean;
comingSoon?: boolean;
description: string;
features: string[];
}
const NetlifyConnectForm: React.FC<{ onSuccess: () => void }> = ({ onSuccess }) => {
const [token, setToken] = useState('');
const [isConnecting, setIsConnecting] = useState(false);
const handleConnect = async () => {
if (!token.trim()) {
toast.error('Please enter your Netlify API token');
return;
}
setIsConnecting(true);
try {
// Validate token with Netlify API
const response = await fetch('https://api.netlify.com/api/v1/user', {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (!response.ok) {
throw new Error('Invalid token or authentication failed');
}
const userData = (await response.json()) as any;
// Update the connection store
updateNetlifyConnection({
user: userData,
token,
});
toast.success(`Connected to Netlify as ${userData.email || userData.name || 'User'}`);
onSuccess();
} catch (error) {
console.error('Netlify connection error:', error);
toast.error('Failed to connect to Netlify. Please check your token.');
} finally {
setIsConnecting(false);
}
};
return (
Connect to Netlify
To deploy your project to Netlify, you need to connect your account using a Personal Access Token.
Personal Access Token
setToken(e.target.value)}
placeholder="Enter your Netlify API token"
className={classNames(
'w-full px-3 py-2 rounded-lg text-sm',
'bg-bolt-elements-background-depth-1',
'border border-bolt-elements-borderColor',
'text-bolt-elements-textPrimary placeholder-bolt-elements-textTertiary',
'focus:outline-none focus:ring-2 focus:ring-accent-500 focus:border-transparent',
'disabled:opacity-50',
)}
disabled={isConnecting}
/>
How to get your token:
Go to your Netlify account settings
Navigate to "Applications" → "Personal access tokens"
Click "New access token"
Give it a descriptive name (e.g., "bolt.diy deployment")
Copy the token and paste it here
{isConnecting ? (
<>
Connecting...
>
) : (
<>
Connect Account
>
)}
);
};
export const DeployDialog: React.FC = ({ isOpen, onClose }) => {
const netlifyConn = useStore(netlifyConnection);
const vercelConn = useStore(vercelConnection);
const [selectedProvider, setSelectedProvider] = useState<'netlify' | 'vercel' | 'github' | null>(null);
const [isDeploying, setIsDeploying] = useState(false);
const [showGitHubDialog, setShowGitHubDialog] = useState(false);
const [githubFiles, setGithubFiles] = useState | null>(null);
const [githubProjectName, setGithubProjectName] = useState('');
const { handleNetlifyDeploy } = useNetlifyDeploy();
const { handleVercelDeploy } = useVercelDeploy();
const { handleGitHubDeploy } = useGitHubDeploy();
const providers: DeployProvider[] = [
{
id: 'netlify',
name: 'Netlify',
iconClass: 'i-simple-icons:netlify',
iconColor: 'text-[#00C7B7]',
connected: !!netlifyConn.user,
description: 'Deploy your site with automatic SSL, global CDN, and continuous deployment',
features: [
'Automatic SSL certificates',
'Global CDN',
'Instant rollbacks',
'Deploy previews',
'Form handling',
'Serverless functions',
],
},
{
id: 'vercel',
name: 'Vercel',
iconClass: 'i-simple-icons:vercel',
connected: !!vercelConn.user,
description: 'Deploy with the platform built for frontend developers',
features: [
'Zero-config deployments',
'Edge Functions',
'Analytics',
'Web Vitals monitoring',
'Preview deployments',
'Automatic HTTPS',
],
},
{
id: 'github',
name: 'GitHub',
iconClass: 'i-simple-icons:github',
connected: true, // GitHub doesn't require separate auth
description: 'Deploy to GitHub Pages or create a repository',
features: [
'Free hosting with GitHub Pages',
'Version control integration',
'Collaborative development',
'Actions & Workflows',
'Issue tracking',
'Pull requests',
],
},
{
id: 'cloudflare',
name: 'Cloudflare Pages',
iconClass: 'i-simple-icons:cloudflare',
iconColor: 'text-[#F38020]',
connected: false,
comingSoon: true,
description: "Deploy on Cloudflare's global network",
features: [
'Unlimited bandwidth',
'DDoS protection',
'Web Analytics',
'Edge Workers',
'Custom domains',
'Automatic builds',
],
},
];
const handleDeploy = async (provider: 'netlify' | 'vercel' | 'github') => {
setIsDeploying(true);
try {
let success = false;
if (provider === 'netlify') {
success = await handleNetlifyDeploy();
} else if (provider === 'vercel') {
success = await handleVercelDeploy();
} else if (provider === 'github') {
const result = await handleGitHubDeploy();
if (result && typeof result === 'object' && result.success && result.files) {
setGithubFiles(result.files);
setGithubProjectName(result.projectName);
setShowGitHubDialog(true);
onClose();
return;
}
success = result && typeof result === 'object' ? result.success : false;
}
if (success) {
toast.success(
`Successfully deployed to ${provider === 'netlify' ? 'Netlify' : provider === 'vercel' ? 'Vercel' : 'GitHub'}`,
);
onClose();
}
} catch (error) {
console.error('Deployment error:', error);
toast.error(
`Failed to deploy to ${provider === 'netlify' ? 'Netlify' : provider === 'vercel' ? 'Vercel' : 'GitHub'}`,
);
} finally {
setIsDeploying(false);
}
};
const renderProviderContent = () => {
if (!selectedProvider) {
return (
{providers.map((provider) => (
!provider.comingSoon && setSelectedProvider(provider.id as 'netlify' | 'vercel' | 'github')
}
disabled={provider.comingSoon}
className={classNames(
'p-4 rounded-lg border-2 transition-all text-left',
'hover:border-accent-500 hover:bg-bolt-elements-background-depth-2',
provider.comingSoon
? 'border-bolt-elements-borderColor opacity-50 cursor-not-allowed'
: 'border-bolt-elements-borderColor cursor-pointer',
)}
>
{provider.name}
{provider.connected && (
Connected
)}
{provider.comingSoon && (
Coming Soon
)}
{provider.description}
{provider.features.slice(0, 3).map((feature, index) => (
{feature}
))}
{provider.features.length > 3 && (
+{provider.features.length - 3} more
)}
))}
);
}
const provider = providers.find((p) => p.id === selectedProvider);
if (!provider) {
return null;
}
// If provider is not connected, show connection form
if (!provider.connected) {
if (selectedProvider === 'netlify') {
return (
{
handleDeploy('netlify');
}}
/>
);
}
// Add Vercel connection form here if needed
return Vercel connection form coming soon...
;
}
// If connected, show deployment confirmation
return (
{provider.name}
Ready to deploy to your {provider.name} account
Connected
Deployment Features:
{provider.features.map((feature, index) => (
{feature}
))}
setSelectedProvider(null)}
className="px-4 py-2 rounded-lg border border-bolt-elements-borderColor text-bolt-elements-textPrimary hover:bg-bolt-elements-background-depth-2"
>
Back
handleDeploy(selectedProvider as 'netlify' | 'vercel' | 'github')}
disabled={isDeploying}
className={classNames(
'flex-1 px-4 py-2 rounded-lg font-medium transition-all',
'bg-accent-500 text-white',
'hover:bg-accent-600',
'disabled:opacity-50 disabled:cursor-not-allowed',
'flex items-center justify-center gap-2',
)}
>
{isDeploying ? (
<>
Deploying...
>
) : (
<>
Deploy Now
>
)}
);
};
return (
<>
!open && onClose()}>
Deploy Your Project
Choose a deployment platform to publish your project to the web
{renderProviderContent()}
{!selectedProvider && (
Cancel
)}
{/* GitHub Deployment Dialog */}
{showGitHubDialog && githubFiles && (
setShowGitHubDialog(false)}
projectName={githubProjectName}
files={githubFiles}
/>
)}
>
);
};