const { useState, useEffect, useCallback } = React; const API_BASE = '/api'; const fetchJSON = async (url, options = {}) => { const res = await fetch(url, options); const text = await res.text(); try { return JSON.parse(text); } catch (e) { console.error("Invalid JSON response:", text); throw new Error(`Server Error (Invalid JSON): ${text.substring(0, 100)}...`); } }; const fetchSites = async () => { return fetchJSON(`${API_BASE}/sites`); }; const createSite = async (data) => { return fetchJSON(`${API_BASE}/sites`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); }; const uploadSite = async (id, zipFile) => { const formData = new FormData(); formData.append('file', zipFile); return fetchJSON(`${API_BASE}/sites/${id}/upload`, { method: 'POST', body: formData }); }; const triggerDeploy = async (id) => { return fetchJSON(`${API_BASE}/sites/${id}/deploy`, { method: 'POST' }); }; const deleteSite = async (id) => { return fetchJSON(`${API_BASE}/sites/${id}`, { method: 'DELETE' }); }; const checkAuth = async () => { return fetchJSON(`${API_BASE}/auth/user`); }; const loginGithub = async () => { const data = await fetchJSON(`${API_BASE}/auth/login`); if (data.url) window.location.href = data.url; }; const loginGoogle = async () => { const data = await fetchJSON(`${API_BASE}/auth/google/login`); if (data.url) window.location.href = data.url; }; const loginEmail = async (email, password) => { return fetchJSON(`${API_BASE}/auth/login_email`, { method: 'POST', body: JSON.stringify({ email, password }) }); }; const registerUser = async (email, password, name) => { return fetchJSON(`${API_BASE}/auth/register`, { method: 'POST', body: JSON.stringify({ email, password, name }) }); }; const topUpBalance = async (amount) => { return fetchJSON(`${API_BASE}/billing/topup`, { method: 'POST', body: JSON.stringify({ amount }) }); }; const updateDomain = async (id, domain) => { return fetchJSON(`${API_BASE}/sites/${id}/update_domain`, { method: 'POST', body: JSON.stringify({ domain }) }); }; const logoutGithub = async () => { await fetch(`${API_BASE}/auth/logout`); return true; }; const fetchAdminStats = async () => fetchJSON(`${API_BASE}/admin/stats`); const fetchAdminUsers = async () => fetchJSON(`${API_BASE}/admin/users`); const fetchAdminSites = async () => fetchJSON(`${API_BASE}/admin/sites`); const fetchAdminSettings = async () => fetchJSON(`${API_BASE}/admin/settings`); const fetchAdminAffiliates = async () => fetchJSON(`${API_BASE}/admin/affiliates`); const adminUserAction = async (id, action, data = {}) => fetchJSON(`${API_BASE}/admin/users/${id}/${action}`, { method: 'POST', body: JSON.stringify(data) }); const adminSiteAction = async (id, action) => fetchJSON(`${API_BASE}/admin/sites/${id}/${action}`, { method: 'POST' }); const saveAdminSettings = async (data) => fetchJSON(`${API_BASE}/admin/settings`, { method: 'POST', body: JSON.stringify(data) }); const adminAffiliateAction = async (id, action) => fetchJSON(`${API_BASE}/admin/affiliates/${id}/${action}`, { method: 'POST' }); const fetchAdminCoupons = async () => fetchJSON(`${API_BASE}/admin/coupons`); const createAdminCoupon = async (data) => fetchJSON(`${API_BASE}/admin/coupons/create`, { method: 'POST', body: JSON.stringify(data) }); const deleteAdminCoupon = async (id) => fetchJSON(`${API_BASE}/admin/coupons/${id}/delete`, { method: 'POST' }); // Components const Spinner = () => ; const AdminPanel = ({ onClose }) => { const [tab, setTab] = useState('stats'); const [stats, setStats] = useState(null); const [users, setUsers] = useState([]); const [sites, setSites] = useState([]); const [settings, setSettings] = useState({}); const [affiliates, setAffiliates] = useState([]); const [coupons, setCoupons] = useState([]); const [loading, setLoading] = useState(false); const [creditModal, setCreditModal] = useState(null); useEffect(() => { loadData(); }, [tab]); const loadData = async () => { setLoading(true); try { if (tab === 'stats') setStats(await fetchAdminStats()); else if (tab === 'users') setUsers(await fetchAdminUsers()); else if (tab === 'sites') setSites(await fetchAdminSites()); else if (tab === 'settings') setSettings(await fetchAdminSettings()); else if (tab === 'affiliates') setAffiliates(await fetchAdminAffiliates()); else if (tab === 'coupons') setCoupons(await fetchAdminCoupons()); } catch (e) { alert(e.message); } finally { setLoading(false); } }; const handleBan = async (id, isBanned) => { if (!confirm(`Are you sure you want to ${isBanned ? 'unban' : 'ban'} this user?`)) return; try { await adminUserAction(id, isBanned ? 'unban' : 'ban'); loadData(); } catch(e) { alert(e.message); } }; const handlePause = async (id, isPaused) => { try { await adminSiteAction(id, isPaused ? 'resume' : 'pause'); loadData(); } catch(e) { alert(e.message); } }; const handleCredit = async (e) => { e.preventDefault(); const amount = e.target.amount.value; const reason = e.target.reason.value; try { await adminUserAction(creditModal, 'credit', { amount, reason }); setCreditModal(null); loadData(); } catch(e) { alert(e.message); } }; const handleSettingsSave = async (e) => { e.preventDefault(); const formData = new FormData(e.target); const data = Object.fromEntries(formData.entries()); try { await saveAdminSettings(data); alert('Settings saved'); } catch(e) { alert(e.message); } }; const handleAffiliate = async (id, action) => { try { const res = await adminAffiliateAction(id, action); if (action === 'approve') alert(`Approved! Code: ${res.code}`); loadData(); } catch(e) { alert(e.message); } }; const handleCreateCoupon = async (e) => { e.preventDefault(); const formData = new FormData(e.target); const data = Object.fromEntries(formData.entries()); try { await createAdminCoupon(data); e.target.reset(); loadData(); } catch(e) { alert(e.message); } }; const handleDeleteCoupon = async (id) => { if (!confirm('Delete this coupon?')) return; try { await deleteAdminCoupon(id); loadData(); } catch(e) { alert(e.message); } }; return (

Admin Panel

{['stats', 'users', 'sites', 'settings', 'affiliates', 'coupons'].map(t => ( ))}
{loading ?
: ( <> {tab === 'stats' && stats && (
{stats.users}
Users
{stats.sites}
Sites
{stats.deployments}
Deployments
€{parseFloat(stats.total_balance).toFixed(2)}
Total Balance
)} {tab === 'users' && ( {users.map(u => ( ))}
ID Name Email Balance Actions
{u.id} {u.name} {u.is_banned && BANNED} {u.email} €{u.balance}
)} {tab === 'sites' && ( {sites.map(s => ( ))}
Site Owner Domain Status Actions
{s.name} {s.user_email} {s.subdomain} {s.status || 'active'}
)} {tab === 'settings' && (
)} {tab === 'affiliates' && ( {affiliates.length === 0 && } {affiliates.map(a => ( ))}
User Email Date Actions
No pending applications
{a.name} {a.email} {new Date(a.created_at).toLocaleDateString()}
)} {tab === 'coupons' && (

Create Coupon

{coupons.length === 0 && } {coupons.map(c => ( ))}
Code Discount Uses Expires Action
No coupons found
{c.code} {c.discount_percent}% {c.used_count} / {c.max_uses === -1 ? '∞' : c.max_uses} {c.expires_at ? new Date(c.expires_at).toLocaleDateString() : 'Never'}
)} )}
{creditModal && (

Add Credit

)}
); }; const AuthModal = ({ onClose, onLogin }) => { const [isRegister, setIsRegister] = useState(false); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [name, setName] = useState(''); const [error, setError] = useState(''); const [loading, setLoading] = useState(false); const handleSubmit = async (e) => { e.preventDefault(); setError(''); setLoading(true); try { if (isRegister) { const res = await registerUser(email, password, name); if (res.error) throw new Error(res.error); // Auto login after register const loginRes = await loginEmail(email, password); if (loginRes.error) throw new Error(loginRes.error); onLogin(loginRes.user); } else { const res = await loginEmail(email, password); if (res.error) throw new Error(res.error); onLogin(res.user); } onClose(); } catch (err) { setError(err.message); } finally { setLoading(false); } }; return (

{isRegister ? 'Create Account' : 'Login'}

{error &&
{error}
}
{isRegister && (
setName(e.target.value)} className="mt-1 block w-full border border-gray-300 rounded-md p-2" />
)}
setEmail(e.target.value)} className="mt-1 block w-full border border-gray-300 rounded-md p-2" />
setPassword(e.target.value)} className="mt-1 block w-full border border-gray-300 rounded-md p-2" />
Or continue with
); }; const TopUpModal = ({ onClose, onTopUp }) => { const [amount, setAmount] = useState('5.00'); const [loading, setLoading] = useState(false); const handleSubmit = async (e) => { e.preventDefault(); setLoading(true); try { const res = await fetchJSON(`${API_BASE}/payment/create`, { method: 'POST', body: JSON.stringify({ amount: parseFloat(amount) }) }); if (res.error) throw new Error(res.error); if (res.url) window.location.href = res.url; } catch (err) { alert(err.message); setLoading(false); } }; return (

Add Funds

Add balance to your account to create more sites.

setAmount(e.target.value)} className="mt-1 block w-full border border-gray-300 rounded-md p-2" />
); }; const SiteCard = ({ site, onClick }) => (
onClick(site)} className="bg-white p-6 rounded-lg shadow hover:shadow-md cursor-pointer transition">

{site.name}

Active

{site.subdomain}.{window.location.hostname}

{site.repo_url ? <> Linked to Git : <> Manual Deploy}
); const FileUploader = ({ siteId, onUploadComplete }) => { const [status, setStatus] = useState('idle'); // idle, zipping, uploading, done, error const [progress, setProgress] = useState(''); const handleDrop = useCallback(async (e) => { e.preventDefault(); e.stopPropagation(); setStatus('zipping'); setProgress('Preparing files...'); const items = e.dataTransfer.items; const zip = new JSZip(); // Helper to read entries const processEntry = async (entry, path = '') => { if (entry.isFile) { const file = await new Promise((resolve, reject) => entry.file(resolve, reject)); zip.file(path + entry.name, file); } else if (entry.isDirectory) { const dirReader = entry.createReader(); const entries = await new Promise((resolve) => { let allEntries = []; const read = () => { dirReader.readEntries((results) => { if (results.length) { allEntries = allEntries.concat(results); read(); } else { resolve(allEntries); } }); }; read(); }); for (let i = 0; i < entries.length; i++) { await processEntry(entries[i], path + entry.name + '/'); } } }; try { for (let i = 0; i < items.length; i++) { const item = items[i]; const entry = item.webkitGetAsEntry(); if (entry) { await processEntry(entry); } } setProgress('Compressing...'); const blob = await zip.generateAsync({ type: 'blob' }); setStatus('uploading'); setProgress('Uploading...'); await uploadSite(siteId, blob); setStatus('done'); onUploadComplete(); } catch (err) { console.error(err); setStatus('error'); setProgress(err.message); } }, [siteId, onUploadComplete]); return (
{ e.preventDefault(); e.currentTarget.classList.add('drop-active'); }} onDragLeave={(e) => { e.currentTarget.classList.remove('drop-active'); }} onDrop={handleDrop} className="border-2 border-dashed border-gray-300 rounded-lg p-8 text-center cursor-pointer hover:border-blue-500 transition" > {status === 'idle' && ( <>

Drag & Drop your build folder (dist) here to deploy

)} {(status === 'zipping' || status === 'uploading') && (
{progress}
)} {status === 'done' &&
Deployed!
} {status === 'error' &&
{progress}
}
); }; const NewSiteModal = ({ user, sites, onClose, onCreated }) => { const [mode, setMode] = useState('git'); // git, manual const [name, setName] = useState(''); const [repo, setRepo] = useState(''); const [code, setCode] = useState(''); const [loading, setLoading] = useState(false); const freeSlots = user?.free_slots || 0; const usedSlots = sites?.length || 0; const isFree = usedSlots < (1 + freeSlots); const price = isFree ? 0 : (user?.server_price || 1.99); const handleSubmit = async (e) => { e.preventDefault(); setLoading(true); try { const data = { name, repo: mode === 'git' ? repo : null, code }; const res = await createSite(data); if (res.error) throw new Error(res.error); onCreated(res); onClose(); } catch (err) { alert(err.message); } finally { setLoading(false); } }; return (

New Site

setName(e.target.value)} className="mt-1 block w-full border border-gray-300 rounded-md p-2" placeholder="My Awesome Site" />
{mode === 'git' && (
setRepo(e.target.value)} className="mt-1 block w-full border border-gray-300 rounded-md p-2" placeholder="https://github.com/user/repo" />

{user ? Authenticated. Private repos supported. : Login for private repos.}

)}
setCode(e.target.value)} className="mt-1 block w-full border border-gray-300 rounded-md p-2 uppercase" placeholder="CODE123" />
Total Price:
{isFree ? ( Free ) : ( €{price.toFixed(2)} / month )} {isFree &&
Free slot available ({usedSlots}/{1 + freeSlots})
}
); }; const getLatestDeployment = async (siteId) => { return fetchJSON(`${API_BASE}/sites/${siteId}/deployments/latest`); }; const DeploymentStatus = ({ siteId }) => { const [deploy, setDeploy] = useState(null); useEffect(() => { let interval; const fetchStatus = async () => { const data = await getLatestDeployment(siteId); setDeploy(data); if (data.status === 'success' || data.status === 'error') { clearInterval(interval); } }; fetchStatus(); interval = setInterval(fetchStatus, 2000); // Poll every 2s return () => clearInterval(interval); }, [siteId]); if (!deploy || deploy.status === 'none') return null; const isRunning = deploy.status === 'pending' || deploy.status === 'building'; return (
Deployment Status
{isRunning && } {deploy.status}
{deploy.status === 'error' && (

Deployment Failed

Please contact support: https://support.hostigo-cloud.de

)}
{deploy.log}
); }; const SiteDetail = ({ site, onBack, onError }) => { const [deploying, setDeploying] = useState(false); // Force re-render of status component const [deployKey, setDeployKey] = useState(0); const [customDomain, setCustomDomain] = useState(site.custom_domain || ''); const [savingDomain, setSavingDomain] = useState(false); const handleDeploy = async () => { if (!confirm('Start new deployment?')) return; setDeploying(true); try { const res = await triggerDeploy(site.id); if (res.error) { if (onError) onError(res.error); else alert(res.error); } else { setDeployKey(k => k + 1); // Reset polling } } catch (e) { if (onError) onError(e.message); else alert(e.message); } finally { setDeploying(false); } }; const handleDelete = async () => { if (!confirm('Are you sure you want to delete this site?')) return; await deleteSite(site.id); onBack(); }; const handleSaveDomain = async () => { setSavingDomain(true); try { const res = await updateDomain(site.id, customDomain); if (res.error) { if (onError) onError(res.error); else alert(res.error); } else { alert('Domain updated successfully!'); } } catch (e) { if (onError) onError(e.message); else alert(e.message); } finally { setSavingDomain(false); } }; // Ensure port is included in URL if needed const port = window.location.port ? ':' + window.location.port : ''; const siteUrl = `http://${site.subdomain}.${window.location.hostname}${port}`; const webhookUrl = `${window.location.origin}/api/webhook/${site.id}`; return (

{site.name}

{siteUrl}

Deployment

{site.repo_url ? (

Connected to: {site.repo_url}

Add this to your GitHub repository settings under Webhooks.

) : (

Drag and drop your project folder (containing index.html) to deploy.

alert('Deployed successfully!')} />
)}

Configuration

setCustomDomain(e.target.value)} className="block w-full border border-gray-300 rounded-l-md p-2" placeholder="example.com" />

Point your domain's A-Record to {window.location.hostname} (or CNAME to this subdomain).

); }; const TransactionsView = ({ onBack }) => { const [transactions, setTransactions] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { fetchJSON(`${API_BASE}/auth/transactions`) .then(setTransactions) .catch(err => alert(err.message)) .finally(() => setLoading(false)); }, []); return (

Transaction History

{loading ? ( ) : transactions.length === 0 ? ( ) : ( transactions.map(t => ( )) )}
Date Description Amount
No transactions found
{new Date(t.created_at).toLocaleString()} {t.description} = 0 ? 'text-green-600' : 'text-red-600'}`}> {parseFloat(t.amount) >= 0 ? '+' : ''}{parseFloat(t.amount).toFixed(2)} €
); }; const SettingsView = ({ user, onBack, onUserUpdate }) => { const [email, setEmail] = useState(user.email || ''); const [isEditing, setIsEditing] = useState(!user.email); const [loading, setLoading] = useState(false); const handleSave = async () => { setLoading(true); try { await fetchJSON(`${API_BASE}/auth/update_email`, { method: 'POST', body: JSON.stringify({ email }) }); await onUserUpdate(); setIsEditing(false); } catch (err) { alert(err.message); } finally { setLoading(false); } }; return (

Account Settings

{user.name}
{isEditing ? ( <> setEmail(e.target.value)} className="flex-1 border border-gray-300 rounded-md p-2 focus:ring-blue-500 focus:border-blue-500" placeholder="Enter your email" /> {user.email && ( )} ) : (
{user.email || 'No email linked (GitHub)'}
)}
{!user.email &&

Please link an email address for notifications and account recovery.

}
{user.id}

Linked Accounts

{user.github_id && GitHub Connected} {user.google_id && Google Connected}
); }; const LandingPage = ({ onGetStarted }) => (

Deploy your site in seconds.

The best way to build, deploy, and scale your web projects. Drag & Drop or connect via Git.

Fast Deploys

Push to Git or drag a folder. Your site is live instantly.

Start for Free

One site is always free. Scale up as you grow for just €1.99/mo.

Git Integration

Automatic builds for every push. Supports private repositories.

© 2026 PHP-lify. All rights reserved.
); const ErrorModal = ({ error, onClose }) => { if (!error) return null; return (

Error

{error}
); }; const AffiliateView = ({ user, onClose }) => { const [info, setInfo] = useState(null); const [loading, setLoading] = useState(false); useEffect(() => { loadInfo(); }, []); const loadInfo = async () => { setLoading(true); try { const data = await fetchJSON(`${API_BASE}/auth/affiliate/info`); setInfo(data); } catch(e) { alert(e.message); } finally { setLoading(false); } }; const handleApply = async () => { setLoading(true); try { await fetchJSON(`${API_BASE}/auth/affiliate/apply`, { method: 'POST' }); loadInfo(); } catch(e) { alert(e.message); } finally { setLoading(false); } }; return (

Affiliate Program

{loading ?
: ( <> {!info?.has_code && !info?.is_pending && (

Join our Partner Program

Earn 12% commission on every server purchase made using your unique code. Get free server slots when you refer 1000+ users!

)} {info?.is_pending && (

Application Pending

We are reviewing your application. Please check back later.

)} {info?.has_code && (

Your Unique Code

{info.code}

Share this code with your audience

{info.referrals}
Referrals
€{info.earnings}
Earnings
Free slots progress: {info.referrals} / {info.threshold || 1000}
)} )}
); }; const App = () => { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [sites, setSites] = useState([]); const [showNewModal, setShowNewModal] = useState(false); const [showTopUp, setShowTopUp] = useState(false); const [showAdmin, setShowAdmin] = useState(false); const [selectedSite, setSelectedSite] = useState(null); const [authModal, setAuthModal] = useState(false); const [globalError, setGlobalError] = useState(null); const [userMenuOpen, setUserMenuOpen] = useState(false); const [showAffiliate, setShowAffiliate] = useState(false); const [view, setView] = useState('list'); // list, detail, settings, transactions const refreshData = useCallback(async () => { setLoading(true); try { const data = await fetchSites(); setSites(data); const auth = await checkAuth(); if (auth.authenticated) setUser(auth.user); } catch (err) { setGlobalError(err.message); } finally { setLoading(false); } }, []); useEffect(() => { refreshData(); }, [refreshData]); const handleSiteClick = (site) => { setSelectedSite(site); setView('detail'); }; const handleBack = () => { setSelectedSite(null); setView('list'); refreshData(); }; const handleLogout = async () => { await logoutGithub(); setUser(null); setSites([]); setView('list'); setUserMenuOpen(false); }; // If not logged in and not loading, show Landing Page if (!loading && !user) { return ( <> setAuthModal(true)} /> {authModal && ( setAuthModal(false)} onLogin={(u) => { setUser(u); refreshData(); }} /> )} setGlobalError(null)} /> ); } return (
setGlobalError(null)} />
{view === 'list' && ( <>

Your Sites

{loading ? (
Loading...
) : (
{sites.length === 0 ? (
No sites yet. Create one to get started!
) : ( sites.map(site => ) )}
)} )} {view === 'detail' && selectedSite && ( )} {view === 'settings' && setView('list')} onUserUpdate={refreshData} />} {view === 'transactions' && setView('list')} />}
{showNewModal && ( setShowNewModal(false)} onCreated={(site) => { setSites([site, ...sites]); handleSiteClick(site); refreshData(); }} /> )} {authModal && ( setAuthModal(false)} onLogin={(u) => { setUser(u); refreshData(); }} /> )} {showTopUp && ( setShowTopUp(false)} onTopUp={(newBalance) => setUser({...user, balance: newBalance})} /> )} {showAdmin && setShowAdmin(false)} />} {showAffiliate && setShowAffiliate(false)} />}
); }; const root = ReactDOM.createRoot(document.getElementById('root')); root.render();