← 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>