eerrrrrr
All checks were successful
CI - Build Tonehaus Docker image / tonehaus-ci-build (push) Successful in 1m57s
All checks were successful
CI - Build Tonehaus Docker image / tonehaus-ci-build (push) Successful in 1m57s
This commit is contained in:
@@ -22,41 +22,22 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<div class="text-secondary">User Type</div>
|
||||
<div class="display-6">{{ userType }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h2 class="h6">Profile</h2>
|
||||
<div class="row g-3 align-items-center">
|
||||
<div class="col-auto">
|
||||
<a class="card h-100 text-reset text-decoration-none" href="{{ path('account_profile') }}">
|
||||
<div class="card-body d-flex align-items-center gap-3">
|
||||
{% if profileImage %}
|
||||
<img src="{{ profileImage }}" alt="Profile picture" class="rounded-circle border" width="72" height="72" style="object-fit: cover;">
|
||||
<img src="{{ profileImage }}" alt="Profile picture" class="rounded-circle border" width="64" height="64" style="object-fit: cover;">
|
||||
{% else %}
|
||||
<div class="rounded-circle bg-secondary d-flex align-items-center justify-content-center text-white" style="width:72px;height:72px;">
|
||||
<div class="rounded-circle bg-secondary d-flex align-items-center justify-content-center text-white flex-shrink-0" style="width:64px;height:64px;">
|
||||
<span class="fw-semibold">{{ (displayName ?? email)|slice(0,1)|upper }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div>
|
||||
<div class="text-secondary">User Type</div>
|
||||
<div class="fw-semibold">{{ displayName ?? email }}</div>
|
||||
<div class="text-secondary small">{{ userType }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
<label class="form-label">Email</label>
|
||||
<input class="form-control" value="{{ email }}" readonly />
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
<label class="form-label">Display name</label>
|
||||
<input class="form-control" value="{{ displayName }}" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<a class="btn btn-outline-primary me-2" href="{{ path('account_profile') }}">Edit profile</a>
|
||||
<a class="btn btn-outline-secondary" href="{{ path('account_password') }}">Change password</a>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -6,41 +6,73 @@
|
||||
<div class="alert alert-success">{{ msg }}</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-md-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-body text-center">
|
||||
<h2 class="h6 mb-3">Current picture</h2>
|
||||
{% if profileImage %}
|
||||
<img src="{{ profileImage }}" alt="Profile picture" class="rounded-circle border mb-3" width="160" height="160" style="object-fit: cover;">
|
||||
{% else %}
|
||||
<div class="rounded-circle bg-secondary text-white d-inline-flex align-items-center justify-content-center mb-3" style="width:160px;height:160px;">
|
||||
<span class="fs-3">{{ (app.user.displayName ?? app.user.userIdentifier)|slice(0,1)|upper }}</span>
|
||||
{{ form_start(form, {attr: {novalidate: 'novalidate'}}) }}
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-5">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h2 class="h6 mb-3">Profile details</h2>
|
||||
<div class="text-center mb-3">
|
||||
{% if profileImage %}
|
||||
<img src="{{ profileImage }}" alt="Profile picture" class="rounded-circle border mb-3" width="160" height="160" style="object-fit: cover;">
|
||||
{% else %}
|
||||
<div class="rounded-circle bg-secondary text-white d-inline-flex align-items-center justify-content-center mb-3" style="width:160px;height:160px;">
|
||||
<span class="fs-3">{{ (app.user.displayName ?? app.user.userIdentifier)|slice(0,1)|upper }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
<p class="text-secondary small mb-0">Images up to 4MB. JPG or PNG recommended.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
<p class="text-secondary small mb-0">Images up to 4MB. JPG or PNG recommended.</p>
|
||||
<div class="mb-3">
|
||||
{{ form_label(form.profileImage, null, {label_attr: {class: 'form-label'}}) }}
|
||||
{{ form_widget(form.profileImage, {attr: {class: 'form-control'}}) }}
|
||||
{{ form_errors(form.profileImage) }}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
{{ form_label(form.displayName, null, {label_attr: {class: 'form-label'}}) }}
|
||||
{{ form_widget(form.displayName, {attr: {class: 'form-control'}}) }}
|
||||
{{ form_errors(form.displayName) }}
|
||||
</div>
|
||||
<div class="mb-0">
|
||||
{{ form_label(form.email, null, {label_attr: {class: 'form-label'}}) }}
|
||||
{{ form_widget(form.email, {attr: {class: 'form-control'}}) }}
|
||||
{{ form_errors(form.email) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
{{ form_start(form, {attr: {novalidate: 'novalidate'}}) }}
|
||||
<div class="mb-3">{{ form_row(form.email) }}</div>
|
||||
<div class="mb-3">{{ form_row(form.displayName) }}</div>
|
||||
<div class="mb-3">{{ form_row(form.profileImage) }}</div>
|
||||
<hr>
|
||||
<p class="text-secondary small mb-3">Password change is optional. Provide your current password only if you want to update it.</p>
|
||||
<div class="mb-3">{{ form_row(form.currentPassword) }}</div>
|
||||
<div class="mb-3">{{ form_row(form.newPassword) }}</div>
|
||||
<div class="col-lg-7">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h2 class="h6 mb-3">Password</h2>
|
||||
<p class="text-secondary small">Leave the fields below blank to keep your current password. You'll need to supply your existing password whenever you create a new one.</p>
|
||||
<div class="mb-3">
|
||||
{{ form_label(form.currentPassword, null, {label_attr: {class: 'form-label'}}) }}
|
||||
{{ form_widget(form.currentPassword, {attr: {class: 'form-control'}}) }}
|
||||
{{ form_errors(form.currentPassword) }}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
{{ form_errors(form.newPassword) }}
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
{{ form_label(form.newPassword.first, null, {label_attr: {class: 'form-label'}}) }}
|
||||
{{ form_widget(form.newPassword.first, {attr: {class: 'form-control'}}) }}
|
||||
{{ form_errors(form.newPassword.first) }}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{{ form_label(form.newPassword.second, null, {label_attr: {class: 'form-label'}}) }}
|
||||
{{ form_widget(form.newPassword.second, {attr: {class: 'form-control'}}) }}
|
||||
{{ form_errors(form.newPassword.second) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-success" type="submit">Save changes</button>
|
||||
<a class="btn btn-link" href="{{ path('account_dashboard') }}">Cancel</a>
|
||||
</div>
|
||||
{{ form_end(form) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ form_end(form) }}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -10,24 +10,76 @@
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="themeToggle">
|
||||
<label class="form-check-label" for="themeToggle">Dark mode</label>
|
||||
</div>
|
||||
<small class="text-secondary">Your choice is saved in a cookie.</small>
|
||||
<small class="text-secondary d-block mb-3">Theme and accent settings are stored in cookies.</small>
|
||||
|
||||
<div class="mt-2">
|
||||
<label class="form-label mb-2" for="accentPicker">Accent colour</label>
|
||||
<div class="d-flex align-items-center gap-3 flex-wrap">
|
||||
<input class="form-control form-control-color" type="color" id="accentPicker" value="#6750a4" title="Choose accent colour">
|
||||
<span class="text-secondary small">Matches the Material-inspired palette across buttons, links, and highlights.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
const key = 'theme';
|
||||
const root = document.documentElement;
|
||||
const current = (document.cookie.match(/(?:^|; )theme=([^;]+)/)?.[1] || '').replace(/\+/g,' ');
|
||||
const initial = current || root.getAttribute('data-bs-theme') || 'light';
|
||||
const THEME_KEY = 'theme';
|
||||
const ACCENT_KEY = 'accentColor';
|
||||
const DEFAULT_ACCENT = '#6750a4';
|
||||
const toggle = document.getElementById('themeToggle');
|
||||
toggle.checked = initial === 'dark';
|
||||
function setTheme(t){
|
||||
root.setAttribute('data-bs-theme', t);
|
||||
const d = new Date(); d.setFullYear(d.getFullYear()+1);
|
||||
document.cookie = key+'='+t+'; path=/; SameSite=Lax; expires='+d.toUTCString();
|
||||
const accentPicker = document.getElementById('accentPicker');
|
||||
|
||||
function getCookie(name) {
|
||||
const match = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;]+)'));
|
||||
return match ? decodeURIComponent(match[1]) : '';
|
||||
}
|
||||
|
||||
function persist(name, value) {
|
||||
const d = new Date();
|
||||
d.setFullYear(d.getFullYear() + 1);
|
||||
document.cookie = name + '=' + encodeURIComponent(value) + '; path=/; SameSite=Lax; expires=' + d.toUTCString();
|
||||
}
|
||||
|
||||
function accentContrast(hex) {
|
||||
const normalized = hex.replace('#', '');
|
||||
if (normalized.length !== 6) {
|
||||
return '#ffffff';
|
||||
}
|
||||
const r = parseInt(normalized.substring(0, 2), 16);
|
||||
const g = parseInt(normalized.substring(2, 4), 16);
|
||||
const b = parseInt(normalized.substring(4, 6), 16);
|
||||
const luminance = (0.299 * r) + (0.587 * g) + (0.114 * b);
|
||||
return luminance > 180 ? '#1c1b20' : '#ffffff';
|
||||
}
|
||||
|
||||
if (toggle) {
|
||||
const initialTheme = getCookie(THEME_KEY) || root.getAttribute('data-bs-theme') || 'light';
|
||||
toggle.checked = initialTheme === 'dark';
|
||||
const setTheme = (theme) => {
|
||||
root.setAttribute('data-bs-theme', theme);
|
||||
persist(THEME_KEY, theme);
|
||||
};
|
||||
setTheme(initialTheme);
|
||||
toggle.addEventListener('change', () => setTheme(toggle.checked ? 'dark' : 'light'));
|
||||
}
|
||||
|
||||
if (accentPicker) {
|
||||
const storedAccent = getCookie(ACCENT_KEY) || DEFAULT_ACCENT;
|
||||
const validAccent = /^#([0-9a-f]{6})$/i.test(storedAccent) ? storedAccent : DEFAULT_ACCENT;
|
||||
accentPicker.value = validAccent;
|
||||
|
||||
const applyAccent = (value) => {
|
||||
const hex = /^#([0-9a-f]{6})$/i.test(value) ? value : DEFAULT_ACCENT;
|
||||
root.style.setProperty('--accent-color', hex);
|
||||
root.style.setProperty('--accent-on-color', accentContrast(hex));
|
||||
persist(ACCENT_KEY, hex);
|
||||
};
|
||||
|
||||
applyAccent(validAccent);
|
||||
accentPicker.addEventListener('input', (event) => applyAccent(event.target.value));
|
||||
}
|
||||
toggle.addEventListener('change', ()=> setTheme(toggle.checked ? 'dark' : 'light'));
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user