114 lines
5.1 KiB
HTML
114 lines
5.1 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Change Password — Nexus One AI</title>
|
|
<link rel="stylesheet" href="style.css?v=4">
|
|
<style>
|
|
body { background:var(--navy); min-height:100vh; display:flex; align-items:center; justify-content:center; padding:24px; }
|
|
.cp-card { background:var(--navy2); border-radius:20px; padding:40px 44px; width:100%; max-width:420px; box-shadow:0 24px 64px rgba(0,0,0,.35); }
|
|
.cp-brand { font-size:22px; font-weight:800; color:var(--navy); margin-bottom:6px; }
|
|
.cp-brand span { color:var(--teal); }
|
|
.cp-headline { font-size:18px; font-weight:700; color:var(--ink); margin-bottom:6px; }
|
|
.cp-sub { font-size:13px; color:var(--lt); margin-bottom:28px; line-height:1.5; }
|
|
.cp-field { margin-bottom:16px; }
|
|
.cp-label { display:block; font-size:13px; font-weight:600; color:var(--med); margin-bottom:5px; }
|
|
.cp-input { width:100%; padding:11px 13px; border:1.5px solid var(--bdr); border-radius:9px; font-size:14px; font-family:inherit; color:var(--ink); outline:none; box-sizing:border-box; }
|
|
.cp-input:focus { border-color:var(--teal); }
|
|
.cp-err { background:rgba(185,28,28,.08); border:1px solid rgba(239,68,68,.25); color:#B91C1C; padding:9px 12px; border-radius:7px; font-size:12px; margin-bottom:14px; display:none; }
|
|
.cp-err.show { display:block; }
|
|
.cp-ok { background:rgba(34,197,94,.08); border:1px solid #86EFAC; color:#15803D; padding:9px 12px; border-radius:7px; font-size:12px; margin-bottom:14px; display:none; }
|
|
.cp-ok.show { display:block; }
|
|
.cp-btn { width:100%; padding:12px; background:var(--teal); color:var(--ink); border:none; border-radius:9px; font-size:15px; font-weight:700; cursor:pointer; font-family:inherit; margin-top:6px; transition:background .15s; }
|
|
.cp-btn:hover { background:#0B7A70; }
|
|
.cp-btn:disabled { opacity:.6; cursor:not-allowed; }
|
|
.cp-rules { font-size:11px; color:var(--lt); margin-top:10px; line-height:1.8; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="cp-card">
|
|
<div class="cp-brand">Nexus One <span>AI</span></div>
|
|
<div class="cp-headline">Set New Password</div>
|
|
<div class="cp-sub">Your password needs to be changed before you can continue. Choose a strong password you haven't used before.</div>
|
|
|
|
<div class="cp-err" id="cp-err"></div>
|
|
<div class="cp-ok" id="cp-ok">Password changed! Redirecting…</div>
|
|
|
|
<div class="cp-field">
|
|
<label class="cp-label">New Password</label>
|
|
<input class="cp-input" id="new-pw" type="password" placeholder="New password" autocomplete="new-password">
|
|
</div>
|
|
<div class="cp-field">
|
|
<label class="cp-label">Confirm Password</label>
|
|
<input class="cp-input" id="conf-pw" type="password" placeholder="Confirm new password" autocomplete="new-password">
|
|
</div>
|
|
|
|
<button class="cp-btn" id="cp-btn" onclick="doChange()">Change Password</button>
|
|
|
|
<div class="cp-rules">
|
|
✓ At least 8 characters ✓ Mix letters & numbers
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const API = '/api';
|
|
|
|
document.querySelectorAll('.cp-input').forEach(el => {
|
|
el.addEventListener('keydown', e => { if (e.key === 'Enter') doChange(); });
|
|
});
|
|
|
|
async function doChange() {
|
|
const errEl = document.getElementById('cp-err');
|
|
const okEl = document.getElementById('cp-ok');
|
|
const btn = document.getElementById('cp-btn');
|
|
errEl.classList.remove('show');
|
|
|
|
const np = document.getElementById('new-pw').value;
|
|
const cp = document.getElementById('conf-pw').value;
|
|
|
|
if (!np) { errEl.textContent = 'Enter a new password.'; errEl.classList.add('show'); return; }
|
|
if (np.length < 8) { errEl.textContent = 'Password must be at least 8 characters.'; errEl.classList.add('show'); return; }
|
|
if (np !== cp) { errEl.textContent = 'Passwords do not match.'; errEl.classList.add('show'); return; }
|
|
|
|
btn.disabled = true;
|
|
btn.textContent = 'Changing…';
|
|
|
|
try {
|
|
const res = await fetch(`${API}/auth/change-password`, {
|
|
method: 'POST',
|
|
credentials: 'include',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ new_password: np })
|
|
});
|
|
|
|
if (res.ok) {
|
|
okEl.classList.add('show');
|
|
setTimeout(() => { window.location.href = '/index.html'; }, 1500);
|
|
} else {
|
|
const d = await res.json().catch(() => ({}));
|
|
const msg = Array.isArray(d.detail)
|
|
? d.detail.map(e => e.msg || JSON.stringify(e)).join(', ')
|
|
: (typeof d.detail === 'string' ? d.detail : 'Failed to change password.');
|
|
errEl.textContent = msg;
|
|
errEl.classList.add('show');
|
|
btn.disabled = false;
|
|
btn.textContent = 'Change Password';
|
|
}
|
|
} catch {
|
|
errEl.textContent = 'Network error. Try again.';
|
|
errEl.classList.add('show');
|
|
btn.disabled = false;
|
|
btn.textContent = 'Change Password';
|
|
}
|
|
}
|
|
|
|
// Verify user is logged in (don't redirect to login for change-password page)
|
|
fetch(`${API}/auth/me`, { credentials: 'include' }).then(r => {
|
|
if (!r.ok) window.location.href = '/login.html';
|
|
});
|
|
</script>
|
|
<script src="branding.js"></script>
|
|
</body>
|
|
</html>
|