Files
ReSendit/templates/changePassword.html
2026-03-21 03:12:13 +01:00

307 lines
11 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</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
<style>
* { border-radius: 0 !important; }
body { font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', monospace; background: #fff; color: #000; padding: 20px; }
.box { border: 3px solid #000; background: #fff; }
/* Chunky Buttons */
button, .button {
border: 2px solid #000;
background: #fff;
padding: 4px 10px;
cursor: pointer;
font-size: 11px;
font-weight: 900;
text-decoration: none;
text-transform: uppercase;
box-shadow: 3px 3px 0px #000;
}
button:hover, .button:hover { background: #000; color: #fff; box-shadow: none; transform: translate(2px, 2px); }
button:active { background: #ff0000; color: #fff; }
.nav-link { font-weight: 900; text-decoration: underline; text-transform: uppercase; font-size: 12px; }
.nav-link:hover { background: #000; color: #fff; }
/* Inputs */
.field-group { margin-bottom: 20px; }
label {
display: block;
font-size: 11px;
font-weight: 900;
text-transform: uppercase;
margin-bottom: 4px;
letter-spacing: 0.05em;
}
input[type="password"] {
width: 100%;
border: 3px solid #000;
padding: 10px 12px;
font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', monospace;
font-size: 14px;
font-weight: 700;
background: #fff;
outline: none;
box-sizing: border-box;
transition: background 0.1s;
}
input[type="password"]:focus {
background: #ffff00;
border-color: #000;
}
/* Strength Meter */
.strength-bar-wrap {
height: 8px;
border: 2px solid #000;
margin-top: 6px;
background: #eee;
overflow: hidden;
}
.strength-bar {
height: 100%;
width: 0%;
transition: width 0.2s, background 0.2s;
}
.strength-label {
font-size: 10px;
font-weight: 900;
text-transform: uppercase;
margin-top: 3px;
}
/* Error / Success */
.msg {
border: 3px solid #000;
padding: 10px 14px;
font-size: 12px;
font-weight: 900;
text-transform: uppercase;
display: none;
margin-bottom: 20px;
}
.msg-error { background: #ff0000; color: #fff; }
.msg-success { background: #00ff00; color: #000; }
/* Match indicator */
.match-hint {
font-size: 10px;
font-weight: 900;
text-transform: uppercase;
margin-top: 4px;
min-height: 14px;
}
.match-ok { color: #007700; }
.match-bad { color: #ff0000; }
/* Submit button — big */
#submit-btn {
width: 100%;
padding: 14px;
font-size: 16px;
border: 4px solid #000;
box-shadow: 6px 6px 0px #000;
}
#submit-btn:hover { box-shadow: none; transform: translate(4px, 4px); }
/* Requirements list */
.req-list { list-style: none; padding: 0; margin: 0; }
.req-list li {
font-size: 11px;
font-weight: 700;
padding: 2px 0;
display: flex;
gap: 6px;
align-items: center;
}
.req-list li span.icon { font-style: normal; font-size: 12px; }
.req-met { color: #007700; }
.req-unmet { color: #aaa; }
</style>
</head>
<body>
<div class="max-w-lg mx-auto">
<header class="mb-6 border-b-8 border-black pb-4 flex justify-between items-start">
<div>
<h1 class="text-4xl font-black uppercase tracking-tighter leading-none">Change_Password</h1>
</div>
<div class="flex flex-col items-end gap-2">
<a href="/admin" class="nav-link">← BACK_TO_ADMIN</a>
<a href="/logout" class="nav-link text-red-600">LOGOUT_SESSION</a>
</div>
</header>
<div class="box p-6">
<div id="msg-error" class="msg msg-error">⚠ ERROR: Passwords do not match.</div>
<div id="msg-success" class="msg msg-success">✓ PASSWORD UPDATED. Credential change committed.</div>
<div class="field-group">
<label for="current-pw">Current_Password</label>
<input type="password" id="current-pw" placeholder="••••••••••••" autocomplete="current-password">
</div>
<div class="field-group">
<label for="new-pw">New_Password</label>
<input type="password" id="new-pw" placeholder="••••••••••••" autocomplete="new-password" oninput="onNewPwInput()">
<div class="strength-bar-wrap"><div class="strength-bar" id="strength-bar"></div></div>
<div class="strength-label" id="strength-label" style="color:#aaa"></div>
</div>
<div class="box p-3 mb-6 bg-gray-50">
<div class="text-[10px] font-black uppercase mb-2 tracking-widest">Requirements</div>
<ul class="req-list" id="req-list">
<li id="req-len" class="req-unmet"><span class="icon"></span> Min 8 characters</li>
<li id="req-upper" class="req-unmet"><span class="icon"></span> Uppercase letter</li>
<li id="req-num" class="req-unmet"><span class="icon"></span> Number</li>
</ul>
</div>
<div class="field-group">
<label for="confirm-pw">Confirm_New_Password</label>
<input type="password" id="confirm-pw" placeholder="••••••••••••" autocomplete="new-password" oninput="onConfirmInput()">
<div class="match-hint" id="match-hint"></div>
</div>
<button id="submit-btn" onclick="handleSubmit()">
CHANGE PASSWORD
</button>
</div>
</div>
<script>
const requirements = [
{ id: 'req-len', test: v => v.length >= 8 },
{ id: 'req-upper', test: v => /[A-Z]/.test(v) },
{ id: 'req-num', test: v => /[0-9]/.test(v) },
];
function getStrength(v) {
const score = requirements.filter(r => r.test(v)).length * 2;
if (v.length === 0) return { score: 0, label: '—', color: '#eee', pct: 0 };
if (score <= 1) return { score, label: 'WEAK', color: '#ff0000', pct: 20 };
if (score === 2) return { score, label: 'POOR', color: '#ff6600', pct: 40 };
if (score === 3) return { score, label: 'FAIR', color: '#ffcc00', pct: 60 };
if (score === 4) return { score, label: 'STRONG', color: '#88cc00', pct: 80 };
return { score, label: 'MAXIMUM', color: '#00cc44', pct: 100 };
}
function onNewPwInput() {
const v = document.getElementById('new-pw').value;
const { label, color, pct } = getStrength(v);
const bar = document.getElementById('strength-bar');
bar.style.width = pct + '%';
bar.style.background = color;
const lbl = document.getElementById('strength-label');
lbl.innerText = label;
lbl.style.color = pct === 0 ? '#aaa' : color;
requirements.forEach(r => {
const el = document.getElementById(r.id);
const met = r.test(v);
el.className = met ? 'req-met' : 'req-unmet';
el.querySelector('.icon').innerText = met ? '■' : '□';
});
onConfirmInput();
}
function onConfirmInput() {
const nv = document.getElementById('new-pw').value;
const cv = document.getElementById('confirm-pw').value;
const hint = document.getElementById('match-hint');
if (!cv) { hint.innerText = ''; return; }
if (nv === cv) {
hint.innerText = '✓ PASSWORDS MATCH';
hint.className = 'match-hint match-ok';
} else {
hint.innerText = '✗ MISMATCH';
hint.className = 'match-hint match-bad';
}
}
function hideMessages() {
document.getElementById('msg-error').style.display = 'none';
document.getElementById('msg-success').style.display = 'none';
}
async function handleSubmit() {
hideMessages();
const current = document.getElementById('current-pw').value;
const nv = document.getElementById('new-pw').value;
const cv = document.getElementById('confirm-pw').value;
if (!current || !nv || !cv) {
showError('All fields are required.');
return;
}
if (nv !== cv) {
showError('Passwords do not match.');
return;
}
const { score } = getStrength(nv);
if (score < 3) {
showError('Password is too weak.');
return;
}
try {
const res = await fetch('/api/user/change-password', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
old_password: current,
new_password: nv
})
});
const data = await res.json().catch(() => ({}));
if (!res.ok) {
showError(data.error || 'Something went wrong.');
return;
}
showSuccess('Password updated successfully.');
document.getElementById('current-pw').value = '';
document.getElementById('new-pw').value = '';
document.getElementById('confirm-pw').value = '';
onNewPwInput();
setTimeout(() => {
window.location.href = '/login';
}, 3000);
} catch (err) {
showError('Network error. Try again.');
}
}
function showSuccess(msg) {
const el = document.getElementById('msg-success');
el.innerText = '✓ ' + msg;
el.style.display = 'block';
}
function showError(msg) {
const el = document.getElementById('msg-error');
el.innerText = '⚠ ERROR: ' + msg;
el.style.display = 'block';
}
</script>
</body>
</html>