## Issues Fixed - Toast messages appearing in background/blurred due to z-index conflicts - Missing success toast notifications for deployment completions - Scoped ToastContainer limiting toast visibility to chat component only ## Changes Made ### Global Toast System - Move ToastContainer from Chat.client.tsx to root.tsx for global availability - Add highest z-index (1000) to ensure toasts appear above all UI elements - Remove duplicate ToastContainer from chat component ### Z-Index System Updates - Add .z-toast class with z-index: $zIndexMax + 1 (1000) - Apply toast z-index to .Toastify__toast-container in toast.scss - Ensure proper layering hierarchy for all toast messages ### Deployment Success Notifications - Add missing toast.success() calls to all deployment services: - NetlifyDeploy: "🚀 Netlify deployment completed successfully!" - VercelDeploy: "🚀 Vercel deployment completed successfully!" - GitHubDeploy: "🚀 GitHub deployment preparation completed successfully!" - GitLabDeploy: "🚀 GitLab deployment preparation completed successfully!" ## Result - All toast messages now appear in foreground with proper z-index - Deployment success notifications are clearly visible to users - Consistent toast behavior across the entire application - No more blurred or background toast messages 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
236 lines
7.6 KiB
TypeScript
236 lines
7.6 KiB
TypeScript
import { toast } from 'react-toastify';
|
|
import { useStore } from '@nanostores/react';
|
|
import { vercelConnection } from '~/lib/stores/vercel';
|
|
import { workbenchStore } from '~/lib/stores/workbench';
|
|
import { webcontainer } from '~/lib/webcontainer';
|
|
import { path } from '~/utils/path';
|
|
import { useState } from 'react';
|
|
import type { ActionCallbackData } from '~/lib/runtime/message-parser';
|
|
import { chatId } from '~/lib/persistence/useChatHistory';
|
|
|
|
export function useVercelDeploy() {
|
|
const [isDeploying, setIsDeploying] = useState(false);
|
|
const vercelConn = useStore(vercelConnection);
|
|
const currentChatId = useStore(chatId);
|
|
|
|
const handleVercelDeploy = async () => {
|
|
if (!vercelConn.user || !vercelConn.token) {
|
|
toast.error('Please connect to Vercel first in the settings tab!');
|
|
return false;
|
|
}
|
|
|
|
if (!currentChatId) {
|
|
toast.error('No active chat found');
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
setIsDeploying(true);
|
|
|
|
const artifact = workbenchStore.firstArtifact;
|
|
|
|
if (!artifact) {
|
|
throw new Error('No active project found');
|
|
}
|
|
|
|
// Create a deployment artifact for visual feedback
|
|
const deploymentId = `deploy-vercel-project`;
|
|
workbenchStore.addArtifact({
|
|
id: deploymentId,
|
|
messageId: deploymentId,
|
|
title: 'Vercel Deployment',
|
|
type: 'standalone',
|
|
});
|
|
|
|
const deployArtifact = workbenchStore.artifacts.get()[deploymentId];
|
|
|
|
// Notify that build is starting
|
|
deployArtifact.runner.handleDeployAction('building', 'running', { source: 'vercel' });
|
|
|
|
const actionId = 'build-' + Date.now();
|
|
const actionData: ActionCallbackData = {
|
|
messageId: 'vercel build',
|
|
artifactId: artifact.id,
|
|
actionId,
|
|
action: {
|
|
type: 'build' as const,
|
|
content: 'npm run build',
|
|
},
|
|
};
|
|
|
|
// Add the action first
|
|
artifact.runner.addAction(actionData);
|
|
|
|
// Then run it
|
|
await artifact.runner.runAction(actionData);
|
|
|
|
if (!artifact.runner.buildOutput) {
|
|
// Notify that build failed
|
|
deployArtifact.runner.handleDeployAction('building', 'failed', {
|
|
error: 'Build failed. Check the terminal for details.',
|
|
source: 'vercel',
|
|
});
|
|
throw new Error('Build failed');
|
|
}
|
|
|
|
// Notify that build succeeded and deployment is starting
|
|
deployArtifact.runner.handleDeployAction('deploying', 'running', { source: 'vercel' });
|
|
|
|
// Get the build files
|
|
const container = await webcontainer;
|
|
|
|
// Remove /home/project from buildPath if it exists
|
|
const buildPath = artifact.runner.buildOutput.path.replace('/home/project', '');
|
|
|
|
// Check if the build path exists
|
|
let finalBuildPath = buildPath;
|
|
|
|
// List of common output directories to check if the specified build path doesn't exist
|
|
const commonOutputDirs = [buildPath, '/dist', '/build', '/out', '/output', '/.next', '/public'];
|
|
|
|
// Verify the build path exists, or try to find an alternative
|
|
let buildPathExists = false;
|
|
|
|
for (const dir of commonOutputDirs) {
|
|
try {
|
|
await container.fs.readdir(dir);
|
|
finalBuildPath = dir;
|
|
buildPathExists = true;
|
|
break;
|
|
} catch {
|
|
// Directory doesn't exist, expected — just skip it
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (!buildPathExists) {
|
|
throw new Error('Could not find build output directory. Please check your build configuration.');
|
|
}
|
|
|
|
// Get all files recursively
|
|
async function getAllFiles(dirPath: string): Promise<Record<string, string>> {
|
|
const files: Record<string, string> = {};
|
|
const entries = await container.fs.readdir(dirPath, { withFileTypes: true });
|
|
|
|
for (const entry of entries) {
|
|
const fullPath = path.join(dirPath, entry.name);
|
|
|
|
if (entry.isFile()) {
|
|
const content = await container.fs.readFile(fullPath, 'utf-8');
|
|
|
|
// Remove build path prefix from the path
|
|
const deployPath = fullPath.replace(finalBuildPath, '');
|
|
files[deployPath] = content;
|
|
} else if (entry.isDirectory()) {
|
|
const subFiles = await getAllFiles(fullPath);
|
|
Object.assign(files, subFiles);
|
|
}
|
|
}
|
|
|
|
return files;
|
|
}
|
|
|
|
const fileContents = await getAllFiles(finalBuildPath);
|
|
|
|
// Get all source project files for framework detection
|
|
const allProjectFiles: Record<string, string> = {};
|
|
|
|
async function getAllProjectFiles(dirPath: string): Promise<void> {
|
|
const entries = await container.fs.readdir(dirPath, { withFileTypes: true });
|
|
|
|
for (const entry of entries) {
|
|
const fullPath = path.join(dirPath, entry.name);
|
|
|
|
if (entry.isFile()) {
|
|
try {
|
|
const content = await container.fs.readFile(fullPath, 'utf-8');
|
|
|
|
// Store with relative path from project root
|
|
let relativePath = fullPath;
|
|
|
|
if (fullPath.startsWith('/home/project/')) {
|
|
relativePath = fullPath.replace('/home/project/', '');
|
|
} else if (fullPath.startsWith('./')) {
|
|
relativePath = fullPath.replace('./', '');
|
|
}
|
|
|
|
allProjectFiles[relativePath] = content;
|
|
} catch (error) {
|
|
// Skip binary files or files that can't be read as text
|
|
console.log(`Skipping file ${entry.name}: ${error}`);
|
|
}
|
|
} else if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
|
|
await getAllProjectFiles(fullPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Try to read from the current directory first
|
|
try {
|
|
await getAllProjectFiles('.');
|
|
} catch {
|
|
// Fallback to /home/project if current directory doesn't work
|
|
await getAllProjectFiles('/home/project');
|
|
}
|
|
|
|
// Use chatId instead of artifact.id
|
|
const existingProjectId = localStorage.getItem(`vercel-project-${currentChatId}`);
|
|
|
|
const response = await fetch('/api/vercel-deploy', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
projectId: existingProjectId || undefined,
|
|
files: fileContents,
|
|
sourceFiles: allProjectFiles,
|
|
token: vercelConn.token,
|
|
chatId: currentChatId,
|
|
}),
|
|
});
|
|
|
|
const data = (await response.json()) as any;
|
|
|
|
if (!response.ok || !data.deploy || !data.project) {
|
|
console.error('Invalid deploy response:', data);
|
|
|
|
// Notify that deployment failed
|
|
deployArtifact.runner.handleDeployAction('deploying', 'failed', {
|
|
error: data.error || 'Invalid deployment response',
|
|
source: 'vercel',
|
|
});
|
|
throw new Error(data.error || 'Invalid deployment response');
|
|
}
|
|
|
|
if (data.project) {
|
|
localStorage.setItem(`vercel-project-${currentChatId}`, data.project.id);
|
|
}
|
|
|
|
// Notify that deployment completed successfully
|
|
deployArtifact.runner.handleDeployAction('complete', 'complete', {
|
|
url: data.deploy.url,
|
|
source: 'vercel',
|
|
});
|
|
|
|
// Show success toast notification
|
|
toast.success(`🚀 Vercel deployment completed successfully!`);
|
|
|
|
return true;
|
|
} catch (err) {
|
|
console.error('Vercel deploy error:', err);
|
|
toast.error(err instanceof Error ? err.message : 'Vercel deployment failed');
|
|
|
|
return false;
|
|
} finally {
|
|
setIsDeploying(false);
|
|
}
|
|
};
|
|
|
|
return {
|
|
isDeploying,
|
|
handleVercelDeploy,
|
|
isConnected: !!vercelConn.user,
|
|
};
|
|
}
|