{% extends "base.html" %} {% block title %}Profile{% endblock %} {% block breadcrumb %}Profile{% endblock %} {% block extra_head %} {% endblock %} {% block content %}
{# ── Left Column: Avatar & Meta ── #}
{% if user.avatar_path %} {# Normalise: old rows stored just the filename, new rows store the full path #} {% if user.avatar_path.startswith('/') %} {% set avatar_src = user.avatar_path %} {% else %} {% set avatar_src = (url_prefix|default('/admin')) + '/profile/avatar/' + user.avatar_path %} {% endif %} Profile photo {% else %} {{ (user.first_name or user.username)|truncate(1,True,'')|upper }}{{ (user.last_name or '')|truncate(1,True,'')|upper }} {% endif %}
{{ user.first_name or '' }} {{ user.last_name or '' }}
@{{ user.username }}
{% if user.is_superuser %} Superadmin {% elif user.is_staff %} Staff {% else %} Viewer {% endif %}
Account Info
Username {{ user.username }}
Email {{ user.email or '—' }}
{% if user.phone %}
Phone {{ user.phone }}
{% endif %} {% if user.timezone %}
Timezone {{ user.timezone }}
{% endif %}
Status ● Active
{% if user.last_login %}
Last Login {{ user.last_login|truncate(19,True,'') }}
{% endif %} {% if user.date_joined %}
Joined {{ user.date_joined|truncate(10,True,'') }}
{% endif %}
{# ── Right Column: Edit Forms ── #}
{# Personal Information #}
Personal Information
{% if csrf_token %}{% endif %}
{# Change Password #}
Change Password
{% if csrf_token %}{% endif %}
{# Danger Zone #}
Danger Zone

Irreversible actions. Proceed with caution.

{% set logout_url = (url_prefix if url_prefix else '/admin') + '/logout' %}
{% endblock %} {% block extra_js %} function updatePasswordStrength(pw) { var score = 0; if (pw.length >= 8) score++; if (pw.length >= 12) score++; if (/[a-z]/.test(pw) && /[A-Z]/.test(pw)) score++; if (/\d/.test(pw)) score++; if (/[^a-zA-Z0-9]/.test(pw)) score++; var bar = document.getElementById('passwordStrengthBar'); var text = document.getElementById('passwordStrengthText'); var labels = ['Very Weak','Weak','Fair','Strong','Very Strong']; var colors = ['var(--danger)','#f97316','#eab308','var(--info)','var(--success)']; var widths = ['20%','40%','60%','80%','100%']; var idx = Math.min(score, 4); bar.style.width = pw ? widths[idx] : '0'; bar.style.background = colors[idx]; text.textContent = pw ? labels[idx] : ''; } (function() { var fileInput = document.getElementById('avatarFileInput'); var uploadBtn = document.getElementById('avatarUploadBtn'); var btnIcon = document.getElementById('avatarBtnIcon'); var statusEl = document.getElementById('avatarStatus'); var avatarEl = document.getElementById('profileAvatar'); var uploadUrl = '{{ url_prefix|default("/admin") }}/profile/upload-avatar'; if (!fileInput) return; fileInput.addEventListener('change', function() { var file = fileInput.files && fileInput.files[0]; if (!file) return; /* Instant local preview */ var reader = new FileReader(); reader.onload = function(e) { var existing = avatarEl.querySelector('img'); var initials = avatarEl.querySelector('#avatarInitials'); if (existing) { existing.src = e.target.result; } else { var img = document.createElement('img'); img.src = e.target.result; img.alt = 'Profile photo'; if (initials) avatarEl.removeChild(initials); avatarEl.insertBefore(img, avatarEl.firstChild); } }; reader.readAsDataURL(file); /* Upload via fetch */ uploadBtn.classList.add('uploading'); btnIcon.className = 'icon-loader'; statusEl.textContent = 'Uploading…'; statusEl.className = 'avatar-status'; var fd = new FormData(); fd.append('avatar', file); fetch(uploadUrl, { method: 'POST', body: fd }) .then(function(resp) { uploadBtn.classList.remove('uploading'); btnIcon.className = 'icon-camera'; if (resp.ok || resp.redirected || resp.status === 302 || resp.status === 200) { statusEl.textContent = 'Photo saved \u2713'; statusEl.className = 'avatar-status ok'; /* Reload so server renders the canonical URL — localStorage is then updated by base.html sync block from the actual server img src */ setTimeout(function() { window.location.reload(); }, 800); } else { statusEl.textContent = 'Upload failed (' + resp.status + ')'; statusEl.className = 'avatar-status err'; } setTimeout(function() { statusEl.textContent = ''; statusEl.className = 'avatar-status'; }, 4000); fileInput.value = ''; }) .catch(function() { uploadBtn.classList.remove('uploading'); btnIcon.className = 'icon-camera'; statusEl.textContent = 'Network error'; statusEl.className = 'avatar-status err'; fileInput.value = ''; }); }); })(); {% endblock %}