Initial commit
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Create User{% endblock %}
|
||||
{% block page_title %}User Management{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-7">
|
||||
|
||||
<div class="mb-3">
|
||||
<a href="{{ url_for('admin.users') }}" style="font-size:13px;color:var(--accent);">
|
||||
<i class="bi bi-arrow-left me-1"></i>Back to Users
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<i class="bi bi-person-plus me-2"></i>Create New User
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" novalidate>
|
||||
|
||||
<!-- ── Identity ─────────────────────────────────────────────────── -->
|
||||
<div style="font-size:11px;font-weight:700;letter-spacing:1.2px;text-transform:uppercase;color:var(--muted);margin-bottom:14px;">
|
||||
Identity
|
||||
</div>
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Full Name *</label>
|
||||
<input type="text" class="form-control" name="full_name"
|
||||
value="{{ form.get('full_name', '') }}" required
|
||||
placeholder="Jane Smith"/>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Username *</label>
|
||||
<input type="text" class="form-control" name="username"
|
||||
value="{{ form.get('username', '') }}" required
|
||||
placeholder="jsmith"
|
||||
pattern="[a-zA-Z0-9_\-]+"
|
||||
title="Letters, numbers, underscores and hyphens only"/>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label">Work Email *</label>
|
||||
<input type="email" class="form-control" name="email"
|
||||
value="{{ form.get('email', '') }}" required
|
||||
placeholder="jane.smith@company.com"/>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Department</label>
|
||||
<input type="text" class="form-control" name="department"
|
||||
value="{{ form.get('department', '') }}"
|
||||
placeholder="Finance"/>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Phone</label>
|
||||
<input type="text" class="form-control" name="phone"
|
||||
value="{{ form.get('phone', '') }}"
|
||||
placeholder="+1 555 000 0000"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Role & Status ────────────────────────────────────────────── -->
|
||||
<div style="font-size:11px;font-weight:700;letter-spacing:1.2px;text-transform:uppercase;color:var(--muted);margin-bottom:14px;padding-top:4px;border-top:1px solid var(--border);">
|
||||
Role & Status
|
||||
</div>
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Role *</label>
|
||||
<select class="form-select" name="role">
|
||||
{% for r in roles %}
|
||||
<option value="{{ r }}"
|
||||
{% if form.get('role', 'employee') == r %}selected{% endif %}>
|
||||
{{ r.replace('_', ' ').title() }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div style="font-size:11px;color:var(--muted);margin-top:5px;">
|
||||
<strong>Employee</strong> — can submit & track own tickets.<br>
|
||||
<strong>IT Staff</strong> — can manage all tickets and KB.<br>
|
||||
<strong>Admin</strong> — full access including user management.
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 d-flex align-items-start pt-4">
|
||||
<label style="display:flex;align-items:center;gap:10px;cursor:pointer;margin-top:10px;">
|
||||
<input type="checkbox" name="is_active" value="1"
|
||||
style="accent-color:var(--accent);width:17px;height:17px;"
|
||||
{% if form.get('is_active', '1') != '0' %}checked{% endif %}/>
|
||||
<span style="font-size:14px;font-weight:500;">Account Active</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Password ─────────────────────────────────────────────────── -->
|
||||
<div style="font-size:11px;font-weight:700;letter-spacing:1.2px;text-transform:uppercase;color:var(--muted);margin-bottom:14px;padding-top:4px;border-top:1px solid var(--border);">
|
||||
Password
|
||||
</div>
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Password *</label>
|
||||
<div style="position:relative;">
|
||||
<input type="password" class="form-control" name="password"
|
||||
id="pw-field" required minlength="8"
|
||||
placeholder="Min. 8 characters"
|
||||
style="padding-right:44px;"/>
|
||||
<button type="button" onclick="togglePw('pw-field','eye1')"
|
||||
style="position:absolute;right:10px;top:50%;transform:translateY(-50%);background:none;border:none;color:var(--muted);cursor:pointer;padding:4px;">
|
||||
<i class="bi bi-eye" id="eye1"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Confirm Password *</label>
|
||||
<div style="position:relative;">
|
||||
<input type="password" class="form-control" name="confirm_password"
|
||||
id="pw-field2" required minlength="8"
|
||||
placeholder="Repeat password"
|
||||
style="padding-right:44px;"/>
|
||||
<button type="button" onclick="togglePw('pw-field2','eye2')"
|
||||
style="position:absolute;right:10px;top:50%;transform:translateY(-50%);background:none;border:none;color:var(--muted);cursor:pointer;padding:4px;">
|
||||
<i class="bi bi-eye" id="eye2"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<!-- Password strength bar -->
|
||||
<div style="height:4px;background:var(--border);border-radius:2px;margin-top:4px;">
|
||||
<div id="pw-strength-bar" style="height:100%;border-radius:2px;width:0%;transition:width .3s,background .3s;"></div>
|
||||
</div>
|
||||
<div id="pw-strength-label" style="font-size:11px;color:var(--muted);margin-top:4px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Actions ──────────────────────────────────────────────────── -->
|
||||
<div class="d-flex gap-2 pt-2" style="border-top:1px solid var(--border);">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-person-check me-2"></i>Create User
|
||||
</button>
|
||||
<a href="{{ url_for('admin.users') }}" class="btn btn-secondary">Cancel</a>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
// ── Show/hide password ────────────────────────────────────────────────────────
|
||||
function togglePw(fieldId, iconId) {
|
||||
const field = document.getElementById(fieldId);
|
||||
const icon = document.getElementById(iconId);
|
||||
if (field.type === 'password') {
|
||||
field.type = 'text';
|
||||
icon.className = 'bi bi-eye-slash';
|
||||
} else {
|
||||
field.type = 'password';
|
||||
icon.className = 'bi bi-eye';
|
||||
}
|
||||
}
|
||||
|
||||
// ── Password strength indicator ───────────────────────────────────────────────
|
||||
document.getElementById('pw-field').addEventListener('input', function () {
|
||||
const val = this.value;
|
||||
let score = 0;
|
||||
if (val.length >= 8) score++;
|
||||
if (val.length >= 12) score++;
|
||||
if (/[A-Z]/.test(val) && /[a-z]/.test(val))score++;
|
||||
if (/[0-9]/.test(val)) score++;
|
||||
if (/[^A-Za-z0-9]/.test(val)) score++;
|
||||
|
||||
const bar = document.getElementById('pw-strength-bar');
|
||||
const label = document.getElementById('pw-strength-label');
|
||||
const levels = [
|
||||
{ pct: '20%', bg: '#dc2626', text: 'Very weak' },
|
||||
{ pct: '40%', bg: '#d97706', text: 'Weak' },
|
||||
{ pct: '60%', bg: '#ca8a04', text: 'Fair' },
|
||||
{ pct: '80%', bg: '#16a34a', text: 'Strong' },
|
||||
{ pct: '100%', bg: '#059669', text: 'Very strong' },
|
||||
];
|
||||
if (val.length === 0) {
|
||||
bar.style.width = '0%';
|
||||
label.textContent = '';
|
||||
return;
|
||||
}
|
||||
const lvl = levels[Math.min(score - 1, 4)] || levels[0];
|
||||
bar.style.width = lvl.pct;
|
||||
bar.style.background = lvl.bg;
|
||||
label.textContent = lvl.text;
|
||||
label.style.color = lvl.bg;
|
||||
});
|
||||
|
||||
// ── Client-side password match check before submit ────────────────────────────
|
||||
document.querySelector('form').addEventListener('submit', function (e) {
|
||||
const pw = document.getElementById('pw-field').value;
|
||||
const pw2 = document.getElementById('pw-field2').value;
|
||||
if (pw !== pw2) {
|
||||
e.preventDefault();
|
||||
document.getElementById('pw-field2').style.borderColor = 'var(--danger)';
|
||||
const msg = document.createElement('div');
|
||||
msg.style.cssText = 'font-size:12px;color:var(--danger);margin-top:4px;';
|
||||
msg.textContent = 'Passwords do not match.';
|
||||
const existing = document.getElementById('pw-match-msg');
|
||||
if (existing) existing.remove();
|
||||
msg.id = 'pw-match-msg';
|
||||
document.getElementById('pw-field2').insertAdjacentElement('afterend', msg);
|
||||
}
|
||||
});
|
||||
document.getElementById('pw-field2').addEventListener('input', function () {
|
||||
this.style.borderColor = '';
|
||||
const msg = document.getElementById('pw-match-msg');
|
||||
if (msg) msg.remove();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user