1+ /**
2+ * Enhanced Deploy Button with Quick Deploy Option
3+ * Contributed by Keoma Wright
4+ *
5+ * This component provides both authenticated and quick deployment options
6+ */
7+
8+ import * as DropdownMenu from '@radix-ui/react-dropdown-menu' ;
9+ import { useStore } from '@nanostores/react' ;
10+ import { netlifyConnection } from '~/lib/stores/netlify' ;
11+ import { vercelConnection } from '~/lib/stores/vercel' ;
12+ import { workbenchStore } from '~/lib/stores/workbench' ;
13+ import { streamingState } from '~/lib/stores/streaming' ;
14+ import { classNames } from '~/utils/classNames' ;
15+ import { useState } from 'react' ;
16+ import { NetlifyDeploymentLink } from '~/components/chat/NetlifyDeploymentLink.client' ;
17+ import { VercelDeploymentLink } from '~/components/chat/VercelDeploymentLink.client' ;
18+ import { useVercelDeploy } from '~/components/deploy/VercelDeploy.client' ;
19+ import { useNetlifyDeploy } from '~/components/deploy/NetlifyDeploy.client' ;
20+ import { QuickNetlifyDeploy } from '~/components/deploy/QuickNetlifyDeploy.client' ;
21+ import * as Dialog from '@radix-ui/react-dialog' ;
22+
23+ interface EnhancedDeployButtonProps {
24+ onVercelDeploy ?: ( ) => Promise < void > ;
25+ onNetlifyDeploy ?: ( ) => Promise < void > ;
26+ }
27+
28+ export const EnhancedDeployButton = ( { onVercelDeploy, onNetlifyDeploy } : EnhancedDeployButtonProps ) => {
29+ const netlifyConn = useStore ( netlifyConnection ) ;
30+ const vercelConn = useStore ( vercelConnection ) ;
31+ const [ activePreviewIndex ] = useState ( 0 ) ;
32+ const previews = useStore ( workbenchStore . previews ) ;
33+ const activePreview = previews [ activePreviewIndex ] ;
34+ const [ isDeploying , setIsDeploying ] = useState ( false ) ;
35+ const [ deployingTo , setDeployingTo ] = useState < 'netlify' | 'vercel' | 'quick' | null > ( null ) ;
36+ const [ showQuickDeploy , setShowQuickDeploy ] = useState ( false ) ;
37+ const isStreaming = useStore ( streamingState ) ;
38+ const { handleVercelDeploy } = useVercelDeploy ( ) ;
39+ const { handleNetlifyDeploy } = useNetlifyDeploy ( ) ;
40+
41+ const handleVercelDeployClick = async ( ) => {
42+ setIsDeploying ( true ) ;
43+ setDeployingTo ( 'vercel' ) ;
44+
45+ try {
46+ if ( onVercelDeploy ) {
47+ await onVercelDeploy ( ) ;
48+ } else {
49+ await handleVercelDeploy ( ) ;
50+ }
51+ } finally {
52+ setIsDeploying ( false ) ;
53+ setDeployingTo ( null ) ;
54+ }
55+ } ;
56+
57+ const handleNetlifyDeployClick = async ( ) => {
58+ setIsDeploying ( true ) ;
59+ setDeployingTo ( 'netlify' ) ;
60+
61+ try {
62+ if ( onNetlifyDeploy ) {
63+ await onNetlifyDeploy ( ) ;
64+ } else {
65+ await handleNetlifyDeploy ( ) ;
66+ }
67+ } finally {
68+ setIsDeploying ( false ) ;
69+ setDeployingTo ( null ) ;
70+ }
71+ } ;
72+
73+ return (
74+ < >
75+ < div className = "flex border border-bolt-elements-borderColor rounded-md overflow-hidden text-sm" >
76+ < DropdownMenu . Root >
77+ < DropdownMenu . Trigger
78+ disabled = { isDeploying || ! activePreview || isStreaming }
79+ className = "rounded-md items-center justify-center [&:is(:disabled,.disabled)]:cursor-not-allowed [&:is(:disabled,.disabled)]:opacity-60 px-3 py-1.5 text-xs bg-accent-500 text-white hover:text-bolt-elements-item-contentAccent [&:not(:disabled,.disabled)]:hover:bg-bolt-elements-button-primary-backgroundHover outline-accent-500 flex gap-1.7"
80+ >
81+ { isDeploying ? `Deploying${ deployingTo ? ` to ${ deployingTo } ` : '' } ...` : 'Deploy' }
82+ < span className = { classNames ( 'i-ph:caret-down transition-transform' ) } />
83+ </ DropdownMenu . Trigger >
84+ < DropdownMenu . Content
85+ className = { classNames (
86+ 'z-[250]' ,
87+ 'bg-bolt-elements-background-depth-2' ,
88+ 'rounded-lg shadow-lg' ,
89+ 'border border-bolt-elements-borderColor' ,
90+ 'animate-in fade-in-0 zoom-in-95' ,
91+ 'py-1' ,
92+ ) }
93+ sideOffset = { 5 }
94+ align = "end"
95+ >
96+ { /* Quick Deploy Option - Always Available */ }
97+ < DropdownMenu . Item
98+ className = { classNames (
99+ 'cursor-pointer flex items-center w-full px-4 py-2 text-sm text-bolt-elements-textPrimary hover:bg-bolt-elements-item-backgroundActive gap-2 rounded-md group relative' ,
100+ {
101+ 'opacity-60 cursor-not-allowed' : isDeploying || ! activePreview ,
102+ } ,
103+ ) }
104+ disabled = { isDeploying || ! activePreview }
105+ onClick = { ( ) => setShowQuickDeploy ( true ) }
106+ >
107+ < div className = "relative" >
108+ < img
109+ className = "w-5 h-5"
110+ height = "24"
111+ width = "24"
112+ crossOrigin = "anonymous"
113+ src = "https://cdn.simpleicons.org/netlify"
114+ alt = "Netlify Quick Deploy"
115+ />
116+ < span className = "absolute -top-1 -right-1 bg-green-500 text-white text-[8px] px-1 rounded" > NEW</ span >
117+ </ div >
118+ < span className = "mx-auto font-medium" > Quick Deploy to Netlify (No Login)</ span >
119+ </ DropdownMenu . Item >
120+
121+ < DropdownMenu . Separator className = "h-px bg-bolt-elements-borderColor my-1" />
122+
123+ { /* Authenticated Netlify Deploy */ }
124+ < DropdownMenu . Item
125+ className = { classNames (
126+ 'cursor-pointer flex items-center w-full px-4 py-2 text-sm text-bolt-elements-textPrimary hover:bg-bolt-elements-item-backgroundActive gap-2 rounded-md group relative' ,
127+ {
128+ 'opacity-60 cursor-not-allowed' : isDeploying || ! activePreview || ! netlifyConn . user ,
129+ } ,
130+ ) }
131+ disabled = { isDeploying || ! activePreview || ! netlifyConn . user }
132+ onClick = { handleNetlifyDeployClick }
133+ >
134+ < img
135+ className = "w-5 h-5"
136+ height = "24"
137+ width = "24"
138+ crossOrigin = "anonymous"
139+ src = "https://cdn.simpleicons.org/netlify"
140+ />
141+ < span className = "mx-auto" > { ! netlifyConn . user ? 'No Netlify Account Connected' : 'Deploy to Netlify' } </ span >
142+ { netlifyConn . user && < NetlifyDeploymentLink /> }
143+ </ DropdownMenu . Item >
144+
145+ { /* Vercel Deploy */ }
146+ < DropdownMenu . Item
147+ className = { classNames (
148+ 'cursor-pointer flex items-center w-full px-4 py-2 text-sm text-bolt-elements-textPrimary hover:bg-bolt-elements-item-backgroundActive gap-2 rounded-md group relative' ,
149+ {
150+ 'opacity-60 cursor-not-allowed' : isDeploying || ! activePreview || ! vercelConn . user ,
151+ } ,
152+ ) }
153+ disabled = { isDeploying || ! activePreview || ! vercelConn . user }
154+ onClick = { handleVercelDeployClick }
155+ >
156+ < img
157+ className = "w-5 h-5 bg-black p-1 rounded"
158+ height = "24"
159+ width = "24"
160+ crossOrigin = "anonymous"
161+ src = "https://cdn.simpleicons.org/vercel/white"
162+ alt = "vercel"
163+ />
164+ < span className = "mx-auto" > { ! vercelConn . user ? 'No Vercel Account Connected' : 'Deploy to Vercel' } </ span >
165+ { vercelConn . user && < VercelDeploymentLink /> }
166+ </ DropdownMenu . Item >
167+
168+ { /* Cloudflare - Coming Soon */ }
169+ < DropdownMenu . Item
170+ disabled
171+ className = "flex items-center w-full rounded-md px-4 py-2 text-sm text-bolt-elements-textTertiary gap-2 opacity-60 cursor-not-allowed"
172+ >
173+ < img
174+ className = "w-5 h-5"
175+ height = "24"
176+ width = "24"
177+ crossOrigin = "anonymous"
178+ src = "https://cdn.simpleicons.org/cloudflare"
179+ alt = "cloudflare"
180+ />
181+ < span className = "mx-auto" > Deploy to Cloudflare (Coming Soon)</ span >
182+ </ DropdownMenu . Item >
183+ </ DropdownMenu . Content >
184+ </ DropdownMenu . Root >
185+ </ div >
186+
187+ { /* Quick Deploy Dialog */ }
188+ < Dialog . Root open = { showQuickDeploy } onOpenChange = { setShowQuickDeploy } >
189+ < Dialog . Portal >
190+ < Dialog . Overlay className = "fixed inset-0 bg-black/50 z-[999] animate-in fade-in-0" />
191+ < Dialog . Content className = "fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-[1000] w-full max-w-2xl animate-in fade-in-0 zoom-in-95" >
192+ < div className = "bg-bolt-elements-background rounded-lg shadow-xl border border-bolt-elements-borderColor" >
193+ < div className = "flex items-center justify-between p-4 border-b border-bolt-elements-borderColor" >
194+ < h2 className = "text-lg font-semibold text-bolt-elements-textPrimary" > Quick Deploy to Netlify</ h2 >
195+ < Dialog . Close className = "p-1 rounded hover:bg-bolt-elements-item-backgroundActive transition-colors" >
196+ < span className = "i-ph:x text-lg text-bolt-elements-textSecondary" />
197+ </ Dialog . Close >
198+ </ div >
199+ < div className = "p-4" >
200+ < QuickNetlifyDeploy />
201+ </ div >
202+ </ div >
203+ </ Dialog . Content >
204+ </ Dialog . Portal >
205+ </ Dialog . Root >
206+ </ >
207+ ) ;
208+ } ;
0 commit comments