'use client'; import { useState, useRef, useCallback } from 'react'; import { useRouter } from 'next/navigation'; import { useMutation } from '@tanstack/react-query'; import api from '@/lib/api'; import toast from 'react-hot-toast'; import type { CreateApplicationDto } from '@/types'; const steps = ['Basic Info', 'Runtime & Database', 'Resources', 'Review']; export default function DeployPage() { const router = useRouter(); const [step, setStep] = useState(0); const [form, setForm] = useState({ name: '', description: '', runtime: 'nodejs', databaseType: 'none', gitUrl: '', envVars: {}, cpuRequest: '100m', cpuLimit: '500m', memoryRequest: '128Mi', memoryLimit: '512Mi', replicas: 1, port: 3000, }); const [envKey, setEnvKey] = useState(''); const [envVal, setEnvVal] = useState(''); const [sourceMethod, setSourceMethod] = useState<'git' | 'upload'>('upload'); const [zipFile, setZipFile] = useState(null); const [uploadProgress, setUploadProgress] = useState(0); const [isDragging, setIsDragging] = useState(false); const fileInputRef = useRef(null); const createMutation = useMutation({ mutationFn: async (data: CreateApplicationDto) => { const res = await api.post('/applications', data); const appId = res.data.id; // Upload zip file if selected if (sourceMethod === 'upload' && zipFile) { const formData = new FormData(); formData.append('file', zipFile); await api.post(`/applications/${appId}/upload`, formData, { headers: { 'Content-Type': 'multipart/form-data' }, onUploadProgress: (e) => { if (e.total) setUploadProgress(Math.round((e.loaded * 100) / e.total)); }, }); } return res; }, onSuccess: (res) => { toast.success('Application created! Triggering deployment...'); api.post(`/deployments/applications/${res.data.id}/deploy`).catch(() => {}); router.push(`/dashboard/apps/${res.data.id}`); }, onError: (err: any) => { toast.error(err.response?.data?.message || 'Failed to create application'); setUploadProgress(0); }, }); const addEnvVar = () => { if (envKey.trim()) { setForm({ ...form, envVars: { ...form.envVars, [envKey]: envVal } }); setEnvKey(''); setEnvVal(''); } }; const removeEnvVar = (key: string) => { const updated = { ...form.envVars }; delete updated[key]; setForm({ ...form, envVars: updated }); }; const handleSubmit = () => { createMutation.mutate(form); }; const handleFileSelect = useCallback((file: File) => { const validTypes = ['application/zip', 'application/x-zip-compressed', 'application/gzip', 'application/x-tar']; const validExtensions = ['.zip', '.tar.gz', '.tgz']; const hasValidExt = validExtensions.some((ext) => file.name.toLowerCase().endsWith(ext)); if (!hasValidExt && !validTypes.includes(file.type)) { toast.error('Only .zip or .tar.gz files are allowed'); return; } if (file.size > 100 * 1024 * 1024) { toast.error('File size must be less than 100MB'); return; } setZipFile(file); toast.success(`Selected: ${file.name}`); }, []); const handleDrop = useCallback((e: React.DragEvent) => { e.preventDefault(); setIsDragging(false); const file = e.dataTransfer.files[0]; if (file) handleFileSelect(file); }, [handleFileSelect]); const handleDragOver = useCallback((e: React.DragEvent) => { e.preventDefault(); setIsDragging(true); }, []); const handleDragLeave = useCallback((e: React.DragEvent) => { e.preventDefault(); setIsDragging(false); }, []); const canNext = () => { if (step === 0) { if (form.name.length < 2) return false; if (sourceMethod === 'upload' && !zipFile) return false; if (sourceMethod === 'git' && !form.gitUrl) return false; return true; } return true; }; return (

Deploy New Application

Follow the steps to deploy your app to the cloud.

{/* Step indicator */}
{steps.map((label, i) => (
{i + 1}
{label} {i < steps.length - 1 &&
}
))}
{/* Step 0: Basic Info */} {step === 0 && (

Basic Information

setForm({ ...form, name: e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, '-') })} />

Lowercase letters, numbers, and hyphens only