Replace React/Vite frontend + Fastify API with a single PHP application. - Front controller router in public/index.php (clean URLs via .htaccess) - Session-based auth (no JWT) — register, login, verify email, forgot/reset password, logout - Stripe checkout sessions, billing portal, webhook handler (checkout.session.completed, subscription.updated/deleted) - License IP change via internal licensing server API - PHP classes: DB (PDO), Auth, Mailer (PHPMailer), StripeClient - Views: home, pricing, install, login, register, dashboard, verify-email, forgot-password, reset-password, 404 - Tailwind CSS via CDN — no build step required - Composer dependencies: stripe/stripe-php, phpmailer/phpmailer, vlucas/phpdotenv, ramsey/uuid - CSRF protection on all POST forms - PRG pattern with session flash messages Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
57 lines
3.2 KiB
PHP
57 lines
3.2 KiB
PHP
<!DOCTYPE html>
|
|
<html lang="en" class="bg-gray-950">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title><?= e($pageTitle ?? 'NixPanel — Modern Web Hosting Control Panel') ?></title>
|
|
<meta name="description" content="<?= e($pageDesc ?? 'NixPanel is a modern, open-source cPanel alternative. Manage your Linux server with NixServer and NixClient.') ?>">
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script>
|
|
tailwind.config = {
|
|
theme: {
|
|
extend: {
|
|
colors: {
|
|
brand: {
|
|
50: '#eff6ff',
|
|
100: '#dbeafe',
|
|
400: '#60a5fa',
|
|
500: '#3b82f6',
|
|
600: '#2563eb',
|
|
700: '#1d4ed8',
|
|
900: '#1e3a5f',
|
|
},
|
|
},
|
|
fontFamily: {
|
|
sans: ['Inter', 'system-ui', 'sans-serif'],
|
|
},
|
|
},
|
|
},
|
|
}
|
|
</script>
|
|
<style type="text/tailwindcss">
|
|
@layer base {
|
|
body { @apply bg-gray-950 text-gray-100 font-sans antialiased; }
|
|
.btn-primary { @apply inline-flex items-center justify-center gap-2 px-5 py-2.5 rounded-lg bg-brand-600 hover:bg-brand-700 text-white font-semibold text-sm transition-colors; }
|
|
.btn-secondary { @apply inline-flex items-center justify-center gap-2 px-5 py-2.5 rounded-lg bg-transparent hover:bg-gray-800 text-gray-300 border border-gray-700 font-semibold text-sm transition-colors; }
|
|
.btn-outline { @apply inline-flex items-center justify-center gap-2 px-5 py-2.5 rounded-lg bg-transparent hover:bg-brand-900/30 text-brand-400 border border-brand-500/50 font-semibold text-sm transition-colors; }
|
|
.input { @apply w-full bg-gray-900 border border-gray-700 text-white rounded-lg px-4 py-2.5 text-sm placeholder-gray-500 focus:outline-none focus:border-brand-500 transition-colors; }
|
|
.card { @apply bg-gray-900/50 border border-gray-800 rounded-xl p-6; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="min-h-screen flex flex-col">
|
|
|
|
<?php if ($flash): ?>
|
|
<div id="flash" class="fixed top-4 right-4 z-50 max-w-sm w-full">
|
|
<div class="flex items-start gap-3 px-4 py-3 rounded-lg border shadow-lg
|
|
<?= $flash['type'] === 'success' ? 'bg-emerald-900/60 border-emerald-500/30 text-emerald-300' : 'bg-red-900/60 border-red-500/30 text-red-300' ?>">
|
|
<span class="text-sm flex-1"><?= e($flash['message']) ?></span>
|
|
<button onclick="document.getElementById('flash').remove()" class="opacity-60 hover:opacity-100 text-lg leading-none">×</button>
|
|
</div>
|
|
</div>
|
|
<script>setTimeout(() => { const f = document.getElementById('flash'); if (f) f.remove(); }, 5000);</script>
|
|
<?php endif ?>
|