Spaces:
Running
Running
Okay now add nitro as ssr and add an express backend with a simple login and authentication system that creates session id and stores session id as http only cookie and in the ssr middleware for all routes we do have a check for the http only cookie to check if is present or expired to redirect to home page or to login page
d502d70 verified | <html lang="en" class="dark"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Login - Vite React TS</title> | |
| <link rel="stylesheet" href="style.css"> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> | |
| <script> | |
| tailwind.config = { | |
| darkMode: 'class', | |
| theme: { | |
| extend: { | |
| colors: { | |
| primary: '#4f46e5', | |
| secondary: '#ec4899', | |
| dark: '#0f172a', | |
| darker: '#020617', | |
| surface: '#1e293b', | |
| } | |
| } | |
| } | |
| } | |
| </script> | |
| </head> | |
| <body class="bg-darker text-slate-200 font-sans antialiased min-h-screen flex items-center justify-center relative overflow-hidden"> | |
| <!-- Background decorative blobs --> | |
| <div class="absolute top-0 left-0 w-96 h-96 bg-primary/20 rounded-full blur-3xl -translate-x-1/2 -translate-y-1/2 pointer-events-none"></div> | |
| <div class="absolute bottom-0 right-0 w-96 h-96 bg-secondary/10 rounded-full blur-3xl translate-x-1/2 translate-y-1/2 pointer-events-none"></div> | |
| <div class="relative z-10 w-full max-w-md p-8"> | |
| <!-- Logo --> | |
| <div class="text-center mb-8"> | |
| <a href="/" class="inline-flex items-center gap-2 text-2xl font-bold text-white"> | |
| <svg height="32" viewBox="0 0 32 32" width="32" xmlns="http://www.w3.org/2000/svg"><path d="M29,1H3A2,2,0,0,0,1,3V29a2,2,0,0,0,2,2H29a2,2,0,0,0,2-2V3A2,2,0,0,0,29,1ZM11,27H7V17h4ZM9,15.5A2.5,2.5,0,1,1,11.5,13,2.5,2.5,0,0,1,9,15.5ZM27,27H23V21c0-1.38-.5-2.5-2-2.5s-2,1.12-2,2.5v6H15V17h4v2.08a4,4,0,0,1,3.5-1.75c2.51,0,4.5,1.75,4.5,5.17Z" fill="#6366f1"/></svg> | |
| Vite<span>ReactTS</span> | |
| </a> | |
| <p class="text-slate-400 mt-2">Sign in to your account</p> | |
| </div> | |
| <!-- Login Form --> | |
| <div class="bg-surface border border-slate-700/50 rounded-2xl p-8 shadow-2xl"> | |
| <form id="loginForm" class="space-y-6"> | |
| <!-- Email Input --> | |
| <div> | |
| <label for="email" class="block text-sm font-medium text-slate-300 mb-2">Email Address</label> | |
| <div class="relative"> | |
| <i data-feather="mail" class="absolute left-3 top-1/2 -translate-y-1/2 text-slate-500 w-5 h-5"></i> | |
| <input | |
| type="email" | |
| id="email" | |
| name="email" | |
| required | |
| class="w-full bg-dark border border-slate-700 rounded-lg py-3 pl-10 pr-4 text-white placeholder-slate-500 focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-colors" | |
| placeholder="admin@vite.com" | |
| > | |
| </div> | |
| </div> | |
| <!-- Password Input --> | |
| <div> | |
| <label for="password" class="block text-sm font-medium text-slate-300 mb-2">Password</label> | |
| <div class="relative"> | |
| <i data-feather="lock" class="absolute left-3 top-1/2 -translate-y-1/2 text-slate-500 w-5 h-5"></i> | |
| <input | |
| type="password" | |
| id="password" | |
| name="password" | |
| required | |
| class="w-full bg-dark border border-slate-700 rounded-lg py-3 pl-10 pr-4 text-white placeholder-slate-500 focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-colors" | |
| placeholder="••••••••" | |
| > | |
| </div> | |
| </div> | |
| <!-- Error Message --> | |
| <div id="errorMessage" class="hidden bg-red-500/10 border border-red-500/50 text-red-400 px-4 py-3 rounded-lg text-sm"></div> | |
| <!-- Submit Button --> | |
| <button | |
| type="submit" | |
| id="submitBtn" | |
| class="w-full bg-primary hover:bg-indigo-700 text-white font-semibold py-3 px-4 rounded-lg transition-all shadow-lg shadow-primary/20 active:scale-[0.98] flex items-center justify-center gap-2" | |
| > | |
| <i data-feather="log-in"></i> | |
| <span>Sign In</span> | |
| </button> | |
| </form> | |
| <!-- Demo Credentials --> | |
| <div class="mt-6 p-4 bg-dark/50 rounded-lg border border-slate-700/50"> | |
| <p class="text-xs text-slate-500 mb-2">Demo Credentials:</p> | |
| <div class="text-xs space-y-1"> | |
| <p class="text-slate-400">Admin: <span class="text-primary font-mono">admin@vite.com / admin123</span></p> | |
| <p class="text-slate-400">User: <span class="text-primary font-mono">user@vite.com / user123</span></p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Footer --> | |
| <p class="text-center text-sm text-slate-500 mt-8"> | |
| Protected by Nitro SSR + Express Sessions | |
| </p> | |
| </div> | |
| <script> | |
| feather.replace(); | |
| const loginForm = document.getElementById('loginForm'); | |
| const errorMessage = document.getElementById('errorMessage'); | |
| const submitBtn = document.getElementById('submitBtn'); | |
| loginForm.addEventListener('submit', async (e) => { | |
| e.preventDefault(); | |
| const email = document.getElementById('email').value; | |
| const password = document.getElementById('password').value; | |
| // Show loading state | |
| submitBtn.disabled = true; | |
| submitBtn.innerHTML = ` | |
| <svg class="animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> | |
| <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> | |
| <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> | |
| </svg> | |
| <span>Signing in...</span> | |
| `; | |
| try { | |
| const response = await fetch('/api/login', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json' | |
| }, | |
| credentials: 'include', | |
| body: JSON.stringify({ email, password }) | |
| }); | |
| const data = await response.json(); | |
| if (response.ok) { | |
| // Redirect to dashboard on success | |
| window.location.href = '/'; | |
| } else { | |
| // Show error message | |
| errorMessage.textContent = data.message || 'Login failed. Please try again.'; | |
| errorMessage.classList.remove('hidden'); | |
| } | |
| } catch (error) { | |
| errorMessage.textContent = 'Network error. Please try again.'; | |
| errorMessage.classList.remove('hidden'); | |
| } finally { | |
| // Reset button state | |
| submitBtn.disabled = false; | |
| submitBtn.innerHTML = ` | |
| <i data-feather="log-in"></i> | |
| <span>Sign In</span> | |
| `; | |
| feather.replace(); | |
| } | |
| }); | |
| // Hide error message when user starts typing | |
| document.getElementById('email').addEventListener('input', () => { | |
| errorMessage.classList.add('hidden'); | |
| }); | |
| document.getElementById('password').addEventListener('input', () => { | |
| errorMessage.classList.add('hidden'); | |
| }); | |
| </script> | |
| </body> | |
| </html> |