First commit
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Set Your Password — Janitorial QC</title>
|
||||
<link rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
|
||||
<style>
|
||||
body { background: #eef0f4; font-family: 'Segoe UI', Arial, sans-serif; }
|
||||
.setup-card {
|
||||
max-width: 460px; margin: 80px auto;
|
||||
border-radius: 12px; box-shadow: 0 4px 24px rgba(0,0,0,.1);
|
||||
}
|
||||
.setup-header {
|
||||
background: #1a1d23; color: #fff;
|
||||
border-radius: 12px 12px 0 0;
|
||||
padding: 1.5rem 1.75rem 1.25rem;
|
||||
}
|
||||
.setup-header h4 { margin: 0; font-weight: 600; }
|
||||
.setup-header p { color: #94a3b8; font-size: .85rem; margin: .35rem 0 0; }
|
||||
.setup-body { background: #fff; border-radius: 0 0 12px 12px; padding: 1.75rem; }
|
||||
.req-item { font-size: .8rem; color: #64748b; }
|
||||
.req-item.met { color: #16a34a; }
|
||||
.strength-bar { height: 4px; border-radius: 2px; transition: all .3s; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="setup-card">
|
||||
<div class="setup-header">
|
||||
<h4><i class="bi bi-shield-lock me-2"></i>Set Your Password</h4>
|
||||
<p>Welcome, {{ user.display_name }}. Choose your username and a secure password to activate your account.</p>
|
||||
</div>
|
||||
<div class="setup-body">
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% for cat, msg in messages %}
|
||||
<div class="alert alert-{{ 'danger' if cat == 'danger' else 'warning' if cat == 'warning' else 'success' }}
|
||||
alert-dismissible fade show py-2 mb-3" role="alert">
|
||||
{{ msg }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endwith %}
|
||||
|
||||
<form method="POST" id="setPasswordForm" novalidate>
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label fw-semibold">Choose a Username</label>
|
||||
<input type="text"
|
||||
id="username"
|
||||
name="username"
|
||||
class="form-control {{ 'is-invalid' if form.username.errors else '' }}"
|
||||
autocomplete="username"
|
||||
autofocus
|
||||
maxlength="100">
|
||||
{% for error in form.username.errors %}
|
||||
<div class="invalid-feedback">{{ error }}</div>
|
||||
{% endfor %}
|
||||
<div class="form-text" style="font-size:.78rem;">
|
||||
3–100 characters. You will use this to log in.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label fw-semibold">New Password</label>
|
||||
<input type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
class="form-control {{ 'is-invalid' if form.password.errors else '' }}"
|
||||
autocomplete="new-password">
|
||||
{% for error in form.password.errors %}
|
||||
<div class="invalid-feedback">{{ error }}</div>
|
||||
{% endfor %}
|
||||
|
||||
{# Strength bar #}
|
||||
<div class="mt-2 mb-1">
|
||||
<div class="bg-light rounded" style="height:4px;">
|
||||
<div id="strengthBar" class="strength-bar bg-secondary" style="width:0%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Requirements checklist #}
|
||||
<div class="d-flex flex-wrap gap-3 mt-2">
|
||||
<span class="req-item" id="req-len">
|
||||
<i class="bi bi-circle me-1"></i>8+ characters
|
||||
</span>
|
||||
<span class="req-item" id="req-upper">
|
||||
<i class="bi bi-circle me-1"></i>Uppercase letter
|
||||
</span>
|
||||
<span class="req-item" id="req-num">
|
||||
<i class="bi bi-circle me-1"></i>Number
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="confirm_password" class="form-label fw-semibold">Confirm Password</label>
|
||||
<input type="password"
|
||||
id="confirm_password"
|
||||
name="confirm_password"
|
||||
class="form-control {{ 'is-invalid' if form.confirm_password.errors else '' }}"
|
||||
autocomplete="new-password">
|
||||
{% for error in form.confirm_password.errors %}
|
||||
<div class="invalid-feedback">{{ error }}</div>
|
||||
{% endfor %}
|
||||
<div id="matchFeedback" class="form-text" style="display:none;"></div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary w-100" id="submitBtn">
|
||||
<i class="bi bi-check2-circle me-1"></i>Activate Account & Log In
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
(function () {
|
||||
'use strict';
|
||||
var pwEl = document.getElementById('password');
|
||||
var cfEl = document.getElementById('confirm_password');
|
||||
var bar = document.getElementById('strengthBar');
|
||||
var reqLen = document.getElementById('req-len');
|
||||
var reqUpper = document.getElementById('req-upper');
|
||||
var reqNum = document.getElementById('req-num');
|
||||
var matchFb = document.getElementById('matchFeedback');
|
||||
var submitBtn = document.getElementById('submitBtn');
|
||||
|
||||
function markReq(el, met) {
|
||||
el.className = 'req-item' + (met ? ' met' : '');
|
||||
el.querySelector('i').className = (met ? 'bi bi-check-circle-fill' : 'bi bi-circle') + ' me-1';
|
||||
}
|
||||
|
||||
function updateStrength(pw) {
|
||||
var score = 0;
|
||||
var hasLen = pw.length >= 8;
|
||||
var hasUpper = /[A-Z]/.test(pw);
|
||||
var hasNum = /[0-9]/.test(pw);
|
||||
if (hasLen) score++;
|
||||
if (hasUpper) score++;
|
||||
if (hasNum) score++;
|
||||
if (pw.length >= 12) score++;
|
||||
|
||||
markReq(reqLen, hasLen);
|
||||
markReq(reqUpper, hasUpper);
|
||||
markReq(reqNum, hasNum);
|
||||
|
||||
var pct = score * 25;
|
||||
var color = score <= 1 ? 'danger' : score === 2 ? 'warning' : score === 3 ? 'info' : 'success';
|
||||
bar.style.width = pct + '%';
|
||||
bar.className = 'strength-bar bg-' + color;
|
||||
}
|
||||
|
||||
function checkMatch() {
|
||||
if (!cfEl.value) { matchFb.style.display = 'none'; return; }
|
||||
matchFb.style.display = '';
|
||||
if (pwEl.value === cfEl.value) {
|
||||
matchFb.textContent = '✓ Passwords match';
|
||||
matchFb.style.color = '#16a34a';
|
||||
} else {
|
||||
matchFb.textContent = '✗ Passwords do not match';
|
||||
matchFb.style.color = '#dc2626';
|
||||
}
|
||||
}
|
||||
|
||||
pwEl.addEventListener('input', function () {
|
||||
updateStrength(this.value);
|
||||
checkMatch();
|
||||
});
|
||||
cfEl.addEventListener('input', checkMatch);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user