mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-06-26 18:26:38 +00:00
Update deploy modal to clarify process and update Header buttons
This commit is contained in:
@@ -6,7 +6,7 @@ import { generateRandomId } from '~/lib/replay/ReplayProtocolClient';
|
||||
import { workbenchStore } from '~/lib/stores/workbench';
|
||||
import { chatStore } from '~/lib/stores/chat';
|
||||
import { database } from '~/lib/persistence/chats';
|
||||
import { deployRepository } from '~/lib/replay/Deploy';
|
||||
import { deployRepository, downloadRepository } from '~/lib/replay/Deploy';
|
||||
import DeployChatModal from './components/DeployChatModal';
|
||||
|
||||
ReactModal.setAppElement('#root');
|
||||
@@ -22,14 +22,61 @@ export function DeployChatButton() {
|
||||
const [deploySettings, setDeploySettings] = useState<DeploySettingsDatabase | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [status, setStatus] = useState<DeployStatus>(DeployStatus.NotStarted);
|
||||
const [databaseFound, setDatabaseFound] = useState(false);
|
||||
|
||||
const handleCheckDatabase = async () => {
|
||||
const repositoryId = workbenchStore.repositoryId.get();
|
||||
if (!repositoryId) {
|
||||
toast.error('No repository ID found');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const repositoryContents = await downloadRepository(repositoryId);
|
||||
|
||||
// Convert base64 to blob
|
||||
const byteCharacters = atob(repositoryContents);
|
||||
const byteNumbers = new Array(byteCharacters.length);
|
||||
for (let i = 0; i < byteCharacters.length; i++) {
|
||||
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
||||
}
|
||||
const byteArray = new Uint8Array(byteNumbers);
|
||||
const blob = new Blob([byteArray], { type: 'application/zip' });
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
if (!event.target?.result) {
|
||||
setIsModalOpen(false);
|
||||
toast.error('Could not read repository contents');
|
||||
return;
|
||||
}
|
||||
|
||||
const zipContents = event.target.result as string;
|
||||
const directoryToFind = 'supabase';
|
||||
|
||||
if (zipContents.includes(directoryToFind)) {
|
||||
setDatabaseFound(true);
|
||||
} else {
|
||||
setDatabaseFound(false);
|
||||
}
|
||||
};
|
||||
|
||||
reader.readAsText(blob);
|
||||
} catch (error) {
|
||||
setIsModalOpen(false);
|
||||
console.error('Error downloading repository:', error);
|
||||
toast.error('Failed to download repository');
|
||||
}
|
||||
};
|
||||
|
||||
const handleOpenModal = async () => {
|
||||
const chatId = chatStore.currentChat.get()?.id;
|
||||
if (!chatId) {
|
||||
toast.error('No chat open');
|
||||
toast.error('No chat ID found');
|
||||
return;
|
||||
}
|
||||
|
||||
await handleCheckDatabase();
|
||||
const existingSettings = await database.getChatDeploySettings(chatId);
|
||||
|
||||
setIsModalOpen(true);
|
||||
@@ -146,11 +193,10 @@ export function DeployChatButton() {
|
||||
<>
|
||||
<button
|
||||
className="flex gap-2 bg-bolt-elements-sidebar-buttonBackgroundDefault text-bolt-elements-sidebar-buttonText hover:bg-bolt-elements-sidebar-buttonBackgroundHover rounded-md p-2 transition-theme"
|
||||
onClick={() => {
|
||||
handleOpenModal();
|
||||
}}
|
||||
onClick={handleOpenModal}
|
||||
>
|
||||
Deploy
|
||||
<div className="i-ph:rocket-launch-fill text-[1.3em]" />
|
||||
Deploy App
|
||||
</button>
|
||||
<DeployChatModal
|
||||
isModalOpen={isModalOpen}
|
||||
@@ -160,6 +206,7 @@ export function DeployChatButton() {
|
||||
setDeploySettings={setDeploySettings}
|
||||
error={error}
|
||||
handleDeploy={handleDeploy}
|
||||
databaseFound={databaseFound}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { DeployStatus } from '~/components/header/DeployChat/DeployChatButton';
|
||||
import DeploymentSuccessful from './DeploymentSuccessful';
|
||||
|
||||
interface DeployChatModalProps {
|
||||
isModalOpen: boolean;
|
||||
@@ -8,6 +9,7 @@ interface DeployChatModalProps {
|
||||
setDeploySettings: (settings: any) => void;
|
||||
error: string | null;
|
||||
handleDeploy: () => void;
|
||||
databaseFound: boolean;
|
||||
}
|
||||
|
||||
const DeployChatModal = ({
|
||||
@@ -18,6 +20,7 @@ const DeployChatModal = ({
|
||||
setDeploySettings,
|
||||
error,
|
||||
handleDeploy,
|
||||
databaseFound,
|
||||
}: DeployChatModalProps) => {
|
||||
const handleOverlayClick = (e: React.MouseEvent) => {
|
||||
if (e.target === e.currentTarget) {
|
||||
@@ -32,51 +35,31 @@ const DeployChatModal = ({
|
||||
className="fixed inset-0 bg-black bg-opacity-50 backdrop-blur-sm z-40 flex items-center justify-center"
|
||||
onClick={handleOverlayClick}
|
||||
>
|
||||
<div className="bg-bolt-elements-background-depth-1 rounded-lg p-8 max-w-2xl w-full z-50 border border-bolt-elements-borderColor" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="bg-bolt-elements-background-depth-1 rounded-lg p-8 max-w-2xl w-full z-50 border border-bolt-elements-borderColor overflow-y-auto max-h-[95vh]" onClick={(e) => e.stopPropagation()}>
|
||||
{status === DeployStatus.Succeeded ? (
|
||||
<>
|
||||
<h2 className="text-2xl font-bold mb-6 text-bolt-elements-textPrimary text-center">
|
||||
Deployment Succeeded! 🎉
|
||||
</h2>
|
||||
<div className="text-center">
|
||||
<p className="text-bolt-elements-textSecondary mb-6">
|
||||
Your application has been successfully deployed. You can now access it at the URL below.
|
||||
</p>
|
||||
<div className="flex justify-center gap-2">
|
||||
<a href={deploySettings?.siteURL} target="_blank" rel="noopener noreferrer">
|
||||
<button className="px-4 py-3 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors font-medium">
|
||||
{deploySettings?.siteURL}
|
||||
</button>
|
||||
</a>
|
||||
<button
|
||||
onClick={() => setIsModalOpen(false)}
|
||||
className="px-4 py-3 bg-gray-300 rounded-lg hover:bg-gray-400 transition-colors font-medium"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<DeploymentSuccessful deploySettings={deploySettings} setIsModalOpen={setIsModalOpen} />
|
||||
) : (
|
||||
<>
|
||||
<h2 className="text-2xl font-bold mb-6 text-bolt-elements-textPrimary text-center">
|
||||
Deploy Your Application
|
||||
</h2>
|
||||
<div className="text-center mb-6 text-bolt-elements-textSecondary">
|
||||
<p className="mb-2">Deploy your chat application to production using Netlify and Supabase.</p>
|
||||
<p className="mb-2">Deploy your chat application to production using Netlify{databaseFound ? ' and Supabase' : ''}.</p>
|
||||
<p className="mb-2">This process will:</p>
|
||||
<ul className="text-left list-disc list-inside mb-4">
|
||||
<li>Create a new Netlify site or update an existing one</li>
|
||||
<li>Set up your database with Supabase</li>
|
||||
<li>Configure all necessary environment variables</li>
|
||||
<li>Deploy your application with production settings</li>
|
||||
</ul>
|
||||
<div className="flex justify-center">
|
||||
<ul className="text-left list-disc list-inside mb-4 inline-block">
|
||||
<li>Create a new Netlify site or update an existing one</li>
|
||||
{databaseFound && <li>Set up your database with Supabase</li>}
|
||||
<li>Configure all necessary environment variables</li>
|
||||
<li>Deploy your application with production settings</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-8 p-4 bg-bolt-elements-background-depth-2 rounded-lg border border-bolt-elements-borderColor">
|
||||
<h3 className="text-sm font-medium text-bolt-elements-textPrimary mb-2">Before you begin:</h3>
|
||||
<p className="text-xs text-bolt-elements-textSecondary mb-3 whitespace-pre-wrap">
|
||||
You'll need accounts with both Netlify and Supabase to deploy your application. If you haven't already, please sign up using the links below:
|
||||
You'll need accounts with both Netlify and {databaseFound ? 'Supabase ' : ''}to deploy your application. If you haven't already, please sign up using the links below:
|
||||
</p>
|
||||
<div className="flex flex-col gap-2">
|
||||
<a
|
||||
@@ -87,14 +70,14 @@ const DeployChatModal = ({
|
||||
>
|
||||
→ Sign up for Netlify
|
||||
</a>
|
||||
<a
|
||||
href="https://supabase.com/dashboard/sign-up"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-xs text-green-500 hover:text-green-600 transition-colors"
|
||||
{databaseFound && <a
|
||||
href="https://supabase.com/dashboard/sign-up"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-xs text-green-500 hover:text-green-600 transition-colors"
|
||||
>
|
||||
→ Sign up for Supabase
|
||||
</a>
|
||||
→ Sign up for Supabase
|
||||
</a>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -230,121 +213,125 @@ const DeployChatModal = ({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block mb-2 text-sm font-medium text-bolt-elements-textPrimary">
|
||||
Supabase Database URL
|
||||
</label>
|
||||
<div className="w-full mb-2">
|
||||
<p className="text-xs text-bolt-elements-textSecondary whitespace-pre-wrap">
|
||||
The URL of your Supabase project, used to connect to your database.
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
name="supabaseDatabaseURL"
|
||||
className="w-full p-3 border rounded-lg bg-bolt-elements-background-depth-2 text-bolt-elements-textPrimary border-bolt-elements-borderColor focus:ring-2 focus:ring-green-500 focus:border-transparent"
|
||||
value={deploySettings?.supabase?.databaseURL}
|
||||
placeholder="https://abc...def.supabase.co"
|
||||
onChange={(e) => {
|
||||
const supabase = {
|
||||
databaseURL: e.target.value,
|
||||
anonKey: deploySettings?.supabase?.anonKey || '',
|
||||
serviceRoleKey: deploySettings?.supabase?.serviceRoleKey || '',
|
||||
postgresURL: deploySettings?.supabase?.postgresURL || '',
|
||||
};
|
||||
setDeploySettings({
|
||||
...deploySettings,
|
||||
supabase,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{databaseFound && (
|
||||
<>
|
||||
<div>
|
||||
<label className="block mb-2 text-sm font-medium text-bolt-elements-textPrimary">
|
||||
Supabase Database URL
|
||||
</label>
|
||||
<div className="w-full mb-2">
|
||||
<p className="text-xs text-bolt-elements-textSecondary whitespace-pre-wrap">
|
||||
The URL of your Supabase project, used to connect to your database.
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
name="supabaseDatabaseURL"
|
||||
className="w-full p-3 border rounded-lg bg-bolt-elements-background-depth-2 text-bolt-elements-textPrimary border-bolt-elements-borderColor focus:ring-2 focus:ring-green-500 focus:border-transparent"
|
||||
value={deploySettings?.supabase?.databaseURL}
|
||||
placeholder="https://abc...def.supabase.co"
|
||||
onChange={(e) => {
|
||||
const supabase = {
|
||||
databaseURL: e.target.value,
|
||||
anonKey: deploySettings?.supabase?.anonKey || '',
|
||||
serviceRoleKey: deploySettings?.supabase?.serviceRoleKey || '',
|
||||
postgresURL: deploySettings?.supabase?.postgresURL || '',
|
||||
};
|
||||
setDeploySettings({
|
||||
...deploySettings,
|
||||
supabase,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block mb-2 text-sm font-medium text-bolt-elements-textPrimary">
|
||||
Supabase Anonymous Key
|
||||
</label>
|
||||
<div className="w-full mb-2">
|
||||
<p className="text-xs text-bolt-elements-textSecondary whitespace-pre-wrap">
|
||||
Public API key for client-side database access with restricted permissions.
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
name="supabaseAnonKey"
|
||||
className="w-full p-3 border rounded-lg bg-bolt-elements-background-depth-2 text-bolt-elements-textPrimary border-bolt-elements-borderColor focus:ring-2 focus:ring-green-500 focus:border-transparent"
|
||||
value={deploySettings?.supabase?.anonKey}
|
||||
placeholder="ey..."
|
||||
onChange={(e) => {
|
||||
const supabase = {
|
||||
databaseURL: deploySettings?.supabase?.databaseURL || '',
|
||||
anonKey: e.target.value,
|
||||
serviceRoleKey: deploySettings?.supabase?.serviceRoleKey || '',
|
||||
postgresURL: deploySettings?.supabase?.postgresURL || '',
|
||||
};
|
||||
setDeploySettings({
|
||||
...deploySettings,
|
||||
supabase,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block mb-2 text-sm font-medium text-bolt-elements-textPrimary">
|
||||
Supabase Anonymous Key
|
||||
</label>
|
||||
<div className="w-full mb-2">
|
||||
<p className="text-xs text-bolt-elements-textSecondary whitespace-pre-wrap">
|
||||
Public API key for client-side database access with restricted permissions.
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
name="supabaseAnonKey"
|
||||
className="w-full p-3 border rounded-lg bg-bolt-elements-background-depth-2 text-bolt-elements-textPrimary border-bolt-elements-borderColor focus:ring-2 focus:ring-green-500 focus:border-transparent"
|
||||
value={deploySettings?.supabase?.anonKey}
|
||||
placeholder="ey..."
|
||||
onChange={(e) => {
|
||||
const supabase = {
|
||||
databaseURL: deploySettings?.supabase?.databaseURL || '',
|
||||
anonKey: e.target.value,
|
||||
serviceRoleKey: deploySettings?.supabase?.serviceRoleKey || '',
|
||||
postgresURL: deploySettings?.supabase?.postgresURL || '',
|
||||
};
|
||||
setDeploySettings({
|
||||
...deploySettings,
|
||||
supabase,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block mb-2 text-sm font-medium text-bolt-elements-textPrimary">
|
||||
Supabase Service Role Key
|
||||
</label>
|
||||
<div className="w-full mb-2">
|
||||
<p className="text-xs text-bolt-elements-textSecondary whitespace-pre-wrap">
|
||||
Admin API key for server-side operations with full database access.
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
name="supabaseServiceRoleKey"
|
||||
className="w-full p-3 border rounded-lg bg-bolt-elements-background-depth-2 text-bolt-elements-textPrimary border-bolt-elements-borderColor focus:ring-2 focus:ring-green-500 focus:border-transparent"
|
||||
value={deploySettings?.supabase?.serviceRoleKey}
|
||||
placeholder="ey..."
|
||||
onChange={(e) => {
|
||||
const supabase = {
|
||||
databaseURL: deploySettings?.supabase?.databaseURL || '',
|
||||
anonKey: deploySettings?.supabase?.anonKey || '',
|
||||
serviceRoleKey: e.target.value,
|
||||
postgresURL: deploySettings?.supabase?.postgresURL || '',
|
||||
};
|
||||
setDeploySettings({
|
||||
...deploySettings,
|
||||
supabase,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block mb-2 text-sm font-medium text-bolt-elements-textPrimary">
|
||||
Supabase Service Role Key
|
||||
</label>
|
||||
<div className="w-full mb-2">
|
||||
<p className="text-xs text-bolt-elements-textSecondary whitespace-pre-wrap">
|
||||
Admin API key for server-side operations with full database access.
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
name="supabaseServiceRoleKey"
|
||||
className="w-full p-3 border rounded-lg bg-bolt-elements-background-depth-2 text-bolt-elements-textPrimary border-bolt-elements-borderColor focus:ring-2 focus:ring-green-500 focus:border-transparent"
|
||||
value={deploySettings?.supabase?.serviceRoleKey}
|
||||
placeholder="ey..."
|
||||
onChange={(e) => {
|
||||
const supabase = {
|
||||
databaseURL: deploySettings?.supabase?.databaseURL || '',
|
||||
anonKey: deploySettings?.supabase?.anonKey || '',
|
||||
serviceRoleKey: e.target.value,
|
||||
postgresURL: deploySettings?.supabase?.postgresURL || '',
|
||||
};
|
||||
setDeploySettings({
|
||||
...deploySettings,
|
||||
supabase,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block mb-2 text-sm font-medium text-bolt-elements-textPrimary">
|
||||
Supabase Postgres URL
|
||||
</label>
|
||||
<div className="w-full mb-2">
|
||||
<p className="text-xs text-bolt-elements-textSecondary whitespace-pre-wrap">
|
||||
Direct connection URL to your Postgres database for advanced operations.
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
name="supabasePostgresURL"
|
||||
className="w-full p-3 border rounded-lg bg-bolt-elements-background-depth-2 text-bolt-elements-textPrimary border-bolt-elements-borderColor focus:ring-2 focus:ring-green-500 focus:border-transparent"
|
||||
value={deploySettings?.supabase?.postgresURL}
|
||||
placeholder="postgresql://postgres:<password>@db.abc...def.supabase.co:5432/postgres"
|
||||
onChange={(e) => {
|
||||
const supabase = {
|
||||
databaseURL: deploySettings?.supabase?.databaseURL || '',
|
||||
anonKey: deploySettings?.supabase?.anonKey || '',
|
||||
serviceRoleKey: deploySettings?.supabase?.serviceRoleKey || '',
|
||||
postgresURL: e.target.value,
|
||||
};
|
||||
setDeploySettings({
|
||||
...deploySettings,
|
||||
supabase,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block mb-2 text-sm font-medium text-bolt-elements-textPrimary">
|
||||
Supabase Postgres URL
|
||||
</label>
|
||||
<div className="w-full mb-2">
|
||||
<p className="text-xs text-bolt-elements-textSecondary whitespace-pre-wrap">
|
||||
Direct connection URL to your Postgres database for advanced operations.
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
name="supabasePostgresURL"
|
||||
className="w-full p-3 border rounded-lg bg-bolt-elements-background-depth-2 text-bolt-elements-textPrimary border-bolt-elements-borderColor focus:ring-2 focus:ring-green-500 focus:border-transparent"
|
||||
value={deploySettings?.supabase?.postgresURL}
|
||||
placeholder="postgresql://postgres:<password>@db.abc...def.supabase.co:5432/postgres"
|
||||
onChange={(e) => {
|
||||
const supabase = {
|
||||
databaseURL: deploySettings?.supabase?.databaseURL || '',
|
||||
anonKey: deploySettings?.supabase?.anonKey || '',
|
||||
serviceRoleKey: deploySettings?.supabase?.serviceRoleKey || '',
|
||||
postgresURL: e.target.value,
|
||||
};
|
||||
setDeploySettings({
|
||||
...deploySettings,
|
||||
supabase,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center gap-3">
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
const DeploymentSuccessful = ({ deploySettings, setIsModalOpen }: { deploySettings: any, setIsModalOpen: (isOpen: boolean) => void }) => {
|
||||
return (
|
||||
<>
|
||||
<h2 className="text-2xl font-bold mb-6 text-bolt-elements-textPrimary text-center">
|
||||
Deployment Succeeded! 🎉
|
||||
</h2>
|
||||
<div className="text-center">
|
||||
<p className="text-bolt-elements-textSecondary mb-6">
|
||||
Your application has been successfully deployed. You can now access it at the URL below.
|
||||
</p>
|
||||
<div className="flex justify-center gap-2">
|
||||
<a href={deploySettings?.siteURL} target="_blank" rel="noopener noreferrer">
|
||||
<button className="px-4 py-3 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors font-medium">
|
||||
{deploySettings?.siteURL}
|
||||
</button>
|
||||
</a>
|
||||
<button
|
||||
onClick={() => setIsModalOpen(false)}
|
||||
className="px-4 py-3 bg-gray-300 rounded-lg hover:bg-gray-400 transition-colors font-medium"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeploymentSuccessful;
|
||||
@@ -51,6 +51,7 @@ export function DownloadButton() {
|
||||
onClick={handleDownload}
|
||||
>
|
||||
<div className="i-ph:download-fill text-[1.3em]" />
|
||||
Download Repo
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -45,10 +45,10 @@ export function Header() {
|
||||
|
||||
{chatStarted && (
|
||||
<>
|
||||
<span className="flex-1 px-4 truncate text-center text-bolt-elements-textPrimary">
|
||||
<span className="flex-1 min-w-fit px-4 truncate text-center text-bolt-elements-textPrimary">
|
||||
<ClientOnly>{() => <DeployChatButton />}</ClientOnly>
|
||||
</span>
|
||||
<span className="flex-1 px-4 truncate text-center text-bolt-elements-textPrimary">
|
||||
<span className="flex-1 min-w-fit px-4 truncate text-center text-bolt-elements-textPrimary">
|
||||
<ClientOnly>{() => <DownloadButton />}</ClientOnly>
|
||||
</span>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user