preferences.html
406 lignes · 22024 octets
{% extends "base.html" %} {% block title %}{{ page_title }}{% endblock %} {% block navbar %} {% include "components/navbar.html" %} {% endblock %} {% block content %} <div class="container mx-auto px-4 py-8"> <!-- Quick Language Selector (Feature 014) --> <div class="flex justify-end mb-4"> <div class="dropdown dropdown-end"> <label tabindex="0" class="btn btn-ghost btn-sm gap-2"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5h12M9 3v2m1.048 9.5A18.022 18.022 0 016.412 9m6.088 9h7M11 21l5-10 5 10M12.751 5C11.783 10.77 8.07 15.61 3 18.129" /> </svg> <span id="current-lang-display"> {% for (code, name, is_current) in supported_languages %} {% if is_current %}{{ name }}{% endif %} {% endfor %} </span> <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" /> </svg> </label> <ul tabindex="0" class="dropdown-content menu bg-base-100 rounded-box z-[1] w-52 p-2 shadow"> {% for (code, name, is_current) in supported_languages %} <li> <a href="#" onclick="return window.changeLanguage('{{ code }}');" class="{% if is_current %}active{% endif %}"> {{ name }} {% if is_current %} <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"> <path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" /> </svg> {% endif %} </a> </li> {% endfor %} </ul> </div> </div> <!-- Breadcrumb --> <div class="text-sm breadcrumbs mb-6"> <ul> <li><a href="/">{{ breadcrumb_home }}</a></li> <li><a href="/profile">{{ breadcrumb_settings }}</a></li> <li>{{ breadcrumb_preferences }}</li> </ul> </div> <!-- Page Header --> <div class="mb-8"> <h1 class="text-3xl font-bold mb-2">{{ title }}</h1> <p class="text-base-content/70">{{ subtitle }}</p> </div> <!-- Preferences Form --> <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 max-w-6xl"> <!-- Theme Preferences Card --> <div class="card bg-base-100 shadow-xl"> <div class="card-body"> <h2 class="card-title mb-4"> <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01" /></svg> {{ theme_label }} </h2> <form id="preferencesForm" class="space-y-4"> <input type="hidden" name="csrf_token" value="{{ csrf_token }}"> <!-- Theme Options --> <div class="form-control"> <label class="label cursor-pointer justify-start gap-4 p-4 rounded-lg border border-base-300 hover:bg-base-200 transition-colors"> <input type="radio" name="theme" value="light" class="radio radio-primary" {% if is_theme_light %}checked{% endif %} > <div class="flex-1"> <div class="flex items-center gap-2"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-warning" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" /></svg> <span class="label-text font-semibold">{{ theme_light }}</span> </div> <span class="label-text-alt text-base-content/70">{{ theme_light_desc }}</span> </div> </label> </div> <div class="form-control"> <label class="label cursor-pointer justify-start gap-4 p-4 rounded-lg border border-base-300 hover:bg-base-200 transition-colors"> <input type="radio" name="theme" value="dark" class="radio radio-primary" {% if is_theme_dark %}checked{% endif %} > <div class="flex-1"> <div class="flex items-center gap-2"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-info" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" /></svg> <span class="label-text font-semibold">{{ theme_dark }}</span> </div> <span class="label-text-alt text-base-content/70">{{ theme_dark_desc }}</span> </div> </label> </div> <div class="form-control"> <label class="label cursor-pointer justify-start gap-4 p-4 rounded-lg border border-base-300 hover:bg-base-200 transition-colors"> <input type="radio" name="theme" value="auto" class="radio radio-primary" {% if is_theme_auto %}checked{% endif %} > <div class="flex-1"> <div class="flex items-center gap-2"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-secondary" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" /></svg> <span class="label-text font-semibold">{{ theme_auto }}</span> </div> <span class="label-text-alt text-base-content/70">{{ theme_auto_desc }}</span> </div> </label> </div> <!-- Theme Preview --> <div class="alert" id="themePreview"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="stroke-current shrink-0 w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg> <span>{{ theme_preview }}</span> </div> </form> </div> </div> <!-- Language Preferences Card --> <div class="card bg-base-100 shadow-xl"> <div class="card-body"> <h2 class="card-title mb-4"> <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5h12M9 3v2m1.048 9.5A18.022 18.022 0 016.412 9m6.088 9h7M11 21l5-10 5 10M12.751 5C11.783 10.77 8.07 15.61 3 18.129" /></svg> {{ language_label }} </h2> <div class="space-y-4"> <!-- Language Options --> <div class="form-control"> <label class="label cursor-pointer justify-start gap-4 p-4 rounded-lg border border-base-300 hover:bg-base-200 transition-colors"> <input type="radio" name="language" value="fr" class="radio radio-primary" form="preferencesForm" {% if is_language_fr %}checked{% endif %} > <div class="flex-1"> <div class="flex items-center gap-2"> <span class="text-2xl">🇫🇷</span> <span class="label-text font-semibold">{{ language_fr }}</span> </div> <span class="label-text-alt text-base-content/70">{{ language_fr_desc }}</span> </div> </label> </div> <div class="form-control"> <label class="label cursor-pointer justify-start gap-4 p-4 rounded-lg border border-base-300 hover:bg-base-200 transition-colors"> <input type="radio" name="language" value="en" class="radio radio-primary" form="preferencesForm" {% if is_language_en %}checked{% endif %} > <div class="flex-1"> <div class="flex items-center gap-2"> <span class="text-2xl">🇬🇧</span> <span class="label-text font-semibold">{{ language_en }}</span> </div> <span class="label-text-alt text-base-content/70">{{ language_en_desc }}</span> </div> </label> </div> <div class="form-control"> <label class="label cursor-pointer justify-start gap-4 p-4 rounded-lg border border-base-300 hover:bg-base-200 transition-colors"> <input type="radio" name="language" value="de" class="radio radio-primary" form="preferencesForm" {% if is_language_de %}checked{% endif %} > <div class="flex-1"> <div class="flex items-center gap-2"> <span class="text-2xl">🇩🇪</span> <span class="label-text font-semibold">{{ language_de }}</span> </div> <span class="label-text-alt text-base-content/70">{{ language_de_desc }}</span> </div> </label> </div> <div class="form-control"> <label class="label cursor-pointer justify-start gap-4 p-4 rounded-lg border border-base-300 hover:bg-base-200 transition-colors"> <input type="radio" name="language" value="es" class="radio radio-primary" form="preferencesForm" {% if is_language_es %}checked{% endif %} > <div class="flex-1"> <div class="flex items-center gap-2"> <span class="text-2xl">🇪🇸</span> <span class="label-text font-semibold">{{ language_es }}</span> </div> <span class="label-text-alt text-base-content/70">{{ language_es_desc }}</span> </div> </label> </div> <div class="form-control"> <label class="label cursor-pointer justify-start gap-4 p-4 rounded-lg border border-base-300 hover:bg-base-200 transition-colors"> <input type="radio" name="language" value="it" class="radio radio-primary" form="preferencesForm" {% if is_language_it %}checked{% endif %} > <div class="flex-1"> <div class="flex items-center gap-2"> <span class="text-2xl">🇮🇹</span> <span class="label-text font-semibold">{{ language_it }}</span> </div> <span class="label-text-alt text-base-content/70">{{ language_it_desc }}</span> </div> </label> </div> <div class="form-control"> <label class="label cursor-pointer justify-start gap-4 p-4 rounded-lg border border-base-300 hover:bg-base-200 transition-colors"> <input type="radio" name="language" value="pt" class="radio radio-primary" form="preferencesForm" {% if is_language_pt %}checked{% endif %} > <div class="flex-1"> <div class="flex items-center gap-2"> <span class="text-2xl">🇧🇷</span> <span class="label-text font-semibold">{{ language_pt }}</span> </div> <span class="label-text-alt text-base-content/70">{{ language_pt_desc }}</span> </div> </label> </div> <div class="alert alert-warning"> <svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg> <span class="text-xs">{{ translation_warning }}</span> </div> </div> </div> </div> </div> <!-- Save Button (Fixed at Bottom) --> <div class="sticky bottom-4 mt-8 max-w-6xl"> <div class="card bg-base-100 shadow-2xl border-2 border-primary"> <div class="card-body flex-row items-center justify-between p-4"> <div> <h3 class="font-semibold">Préférences modifiées</h3> <p class="text-sm text-base-content/70">Enregistrez vos changements pour les appliquer</p> </div> <div class="flex gap-2"> <a href="/profile" class="btn btn-ghost">Annuler</a> <button type="submit" form="preferencesForm" class="btn btn-primary" id="saveBtn"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /></svg> Enregistrer les préférences </button> </div> </div> </div> </div> <!-- Info Card --> <div class="card bg-info text-info-content shadow-xl max-w-6xl mt-6"> <div class="card-body"> <h3 class="card-title text-base"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg> {{ sync_title }} </h3> <p class="text-sm"> {{ sync_desc }} </p> </div> </div> </div> <script> var SAVED_RELOAD_MSG = {{ saved_reload_json|safe }}; var SAVED_SUCCESS_MSG = {{ saved_success_json|safe }}; var ERROR_MSG = {{ error_prefix_json|safe }}; var NETWORK_ERROR_MSG = {{ network_error_json|safe }}; var SAVE_BUTTON_TEXT = {{ save_button_json|safe }}; var SAVE_BUTTON_SVG = '<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /></svg> '; function resetSaveBtn() { var saveBtn = document.getElementById('saveBtn'); saveBtn.disabled = false; saveBtn.innerHTML = SAVE_BUTTON_SVG + SAVE_BUTTON_TEXT; } document.getElementById('preferencesForm').addEventListener('submit', async function(e) { e.preventDefault(); var formData = new FormData(this); var saveBtn = document.getElementById('saveBtn'); var theme = formData.get('theme'); var language = formData.get('language'); saveBtn.disabled = true; saveBtn.innerHTML = '<span class="loading loading-spinner"></span> ' + "{{ saving }}"; try { var params = new URLSearchParams(); if (theme) params.set('theme', theme); if (language) params.set('language', language); params.set('csrf_token', formData.get('csrf_token') || ''); var response = await fetch('/settings/preferences', { method: 'PUT', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-CSRF-Token': '{{ csrf_token }}' }, credentials: 'include', body: params.toString() }); var result = await response.json(); if (response.ok) { document.cookie = 'theme=' + (theme || 'auto') + '; path=/; max-age=31536000; SameSite=Lax'; if (theme === 'dark') { document.documentElement.setAttribute('data-theme', 'dark'); } else if (theme === 'light') { document.documentElement.setAttribute('data-theme', 'light'); } else { var prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; document.documentElement.setAttribute('data-theme', prefersDark ? 'dark' : 'light'); } if (result.reload_required) { document.cookie = 'locale=' + language + '; path=/; max-age=31536000; SameSite=Lax'; if (confirm('✅ ' + SAVED_RELOAD_MSG)) { window.location.reload(); return; } } else { alert('✅ ' + SAVED_SUCCESS_MSG); } resetSaveBtn(); } else { var detail = result.error || result.message || ''; alert('❌ ' + ERROR_MSG.replace('{message}', detail)); resetSaveBtn(); } } catch (error) { console.error('Error:', error); alert('❌ ' + NETWORK_ERROR_MSG); resetSaveBtn(); } }); // Preview theme changes in real-time document.querySelectorAll('input[name="theme"]').forEach(radio => { radio.addEventListener('change', function() { const preview = document.getElementById('themePreview'); const previewDark = "{{ preview_dark }}"; const previewLight = "{{ preview_light }}"; const previewAuto = "{{ preview_auto }}"; if (this.value === 'dark') { preview.className = 'alert alert-info'; preview.querySelector('span').textContent = previewDark; } else if (this.value === 'light') { preview.className = 'alert alert-success'; preview.querySelector('span').textContent = previewLight; } else { preview.className = 'alert'; preview.querySelector('span').textContent = previewAuto; } }); }); </script> {% endblock %}
GitRust