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.deployments}
Deployments
€{parseFloat(stats.total_balance).toFixed(2)}
Total Balance
)}
{tab === 'users' && (
| ID |
Name |
Email |
Balance |
Actions |
{users.map(u => (
| {u.id} |
{u.name}
{u.is_banned && BANNED}
|
{u.email} |
€{u.balance} |
|
))}
)}
{tab === 'sites' && (
| Site |
Owner |
Domain |
Status |
Actions |
{sites.map(s => (
| {s.name} |
{s.user_email} |
{s.subdomain} |
{s.status || 'active'}
|
|
))}
)}
{tab === 'settings' && (
)}
{tab === 'affiliates' && (
| User |
Email |
Date |
Actions |
{affiliates.length === 0 && | No pending applications |
}
{affiliates.map(a => (
| {a.name} |
{a.email} |
{new Date(a.created_at).toLocaleDateString()} |
|
))}
)}
{tab === 'coupons' && (
| Code |
Discount |
Uses |
Expires |
Action |
{coupons.length === 0 && | No coupons found |
}
{coupons.map(c => (
| {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 && (
)}
);
};
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}
}
);
};
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.
);
};
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
);
};
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' && (
)}
{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 (
Deployment
{site.repo_url ? (
Connected to: {site.repo_url}
) : (
Drag and drop your project folder (containing index.html) to deploy.
alert('Deployed successfully!')} />
)}
);
};
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
| Date |
Description |
Amount |
{loading ? (
|
) : transactions.length === 0 ? (
| No transactions found |
) : (
transactions.map(t => (
| {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
{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.
}
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
);
};
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();