← back to Rentv 2026
public/login.html
70 lines
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Sign in — RENTV.com</title>
<meta name="robots" content="noindex,nofollow">
<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=Playfair+Display:wght@700&family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
<style>
:root{--red:#c8102e;--ink:#14171a;--sub:#5b636b;--line:#e4e7ea;--wash:#f5f6f8;--sans:'Inter',system-ui,sans-serif;--serif:'Playfair Display',Georgia,serif}
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:var(--sans);color:var(--ink);background:var(--wash);min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}
.card{background:#fff;border:1px solid var(--line);border-radius:16px;box-shadow:0 24px 60px rgba(20,23,26,.08);width:100%;max-width:400px;padding:36px 34px}
.logo{font-family:var(--serif);font-weight:700;font-size:30px;letter-spacing:-1px;text-align:center}.logo span{color:var(--red)}
.logo small{display:block;font-size:10px;font-weight:600;letter-spacing:3px;color:var(--sub);text-transform:uppercase;margin-top:3px;font-family:var(--sans)}
h1{font-family:var(--serif);font-size:22px;text-align:center;margin:22px 0 4px}
p.sub{text-align:center;color:var(--sub);font-size:13px;margin-bottom:22px}
label{display:block;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--sub);margin:14px 0 5px}
input{width:100%;font-family:var(--sans);font-size:15px;padding:11px 13px;border:1px solid var(--line);border-radius:9px;background:#fff}
input:focus{outline:none;border-color:var(--ink)}
button{width:100%;margin-top:22px;font-family:var(--sans);font-size:15px;font-weight:700;padding:12px;border:none;border-radius:9px;background:var(--red);color:#fff;cursor:pointer}
button:disabled{opacity:.6;cursor:default}
.msg{margin-top:14px;font-size:13px;text-align:center;min-height:18px}
.msg.err{color:var(--red)}
.foot{margin-top:20px;text-align:center;font-size:11px;color:var(--sub)}
</style>
</head>
<body>
<div class="card">
<div class="logo">REN<span>TV</span>.com<small>Commercial Real Estate News</small></div>
<h1>Sign in</h1>
<p class="sub">Enter your credentials to access the site.</p>
<form id="f" autocomplete="on">
<label for="u">Username</label>
<input id="u" name="username" autocomplete="username" autofocus required>
<label for="p">Password</label>
<input id="p" name="password" type="password" autocomplete="current-password" required>
<button id="btn" type="submit">Sign in</button>
<div class="msg" id="msg"></div>
</form>
<div class="foot">© RENTV.com — Commercial Real Estate News</div>
</div>
<script>
const $=s=>document.querySelector(s);
const nextParam=new URLSearchParams(location.search).get('next')||'';
function safeNext(role){
// only allow same-origin relative paths; default by role
if(nextParam && nextParam.startsWith('/') && !nextParam.startsWith('//')) return nextParam;
return role==='admin' ? '/backend' : '/';
}
$('#f').addEventListener('submit', async (e)=>{
e.preventDefault();
const btn=$('#btn'), msg=$('#msg'); msg.className='msg'; msg.textContent='';
btn.disabled=true; btn.textContent='Signing in…';
try{
const r=await fetch(location.origin+'/api/login',{method:'POST',headers:{'Content-Type':'application/json'},
body:JSON.stringify({username:$('#u').value,password:$('#p').value})});
const j=await r.json();
if(!r.ok||!j.ok) throw new Error(j.error||('HTTP '+r.status));
msg.textContent='Welcome — redirecting…';
location.href=safeNext(j.role);
}catch(err){
msg.className='msg err'; msg.textContent=err.message||'Sign-in failed';
btn.disabled=false; btn.textContent='Sign in';
}
});
</script>
</body>
</html>