Files
mailova/app/templates/settings.html

292 lines
16 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block title %}{{ 'settings.title'|t(user.language) }} - {{ 'brand'|t(user.language) }}{% endblock %}
{% block content %}
<div class="section-header">
<h1>{{ 'settings.title'|t(user.language) }}</h1>
</div>
<div class="tabs" id="tabs">
<button class="tab {% if active_tab != 'showcase' and active_tab != 'apikeys' %}active{% endif %}" onclick="switchTab('account')">{{ 'settings.tab_account'|t(user.language) }}</button>
<button class="tab {% if active_tab == 'showcase' %}active{% endif %}" onclick="switchTab('showcase')">{{ 'settings.tab_showcase'|t(user.language) }}</button>
<button class="tab {% if active_tab == 'apikeys' %}active{% endif %}" onclick="switchTab('apikeys')">{{ 'settings.tab_apikeys'|t(user.language) }}</button>
</div>
{% if success %}
<div class="alert alert-success">{{ success }}</div>
{% endif %}
{% if error %}
<div class="alert alert-error">{{ error }}</div>
{% endif %}
<div id="tab-account" class="tab-panel" {% if active_tab == 'showcase' or active_tab == 'apikeys' %}style="display:none"{% endif %}>
<div class="form-card">
<h3 style="margin-bottom:.75rem">{{ 'settings.change_username'|t(user.language) }}</h3>
<form method="post" action="/settings/change-username">
<label>{{ 'settings.current_username'|t(user.language) }}</label>
<input type="text" value="{{ user.username }}" disabled>
<label>{{ 'settings.new_username'|t(user.language) }}</label>
<input type="text" name="new_username" required maxlength="64">
<div class="form-actions">
<button type="submit" class="btn btn-primary">{{ 'settings.change_username'|t(user.language) }}</button>
</div>
</form>
</div>
<div class="form-card" style="margin-top:1.5rem">
<h3 style="margin-bottom:.75rem">{{ 'settings.change_password'|t(user.language) }}</h3>
<form method="post" action="/settings/change-password">
<label>{{ 'settings.current_password'|t(user.language) }}</label>
<input type="password" name="old_password" required>
<label>{{ 'settings.new_password'|t(user.language) }}</label>
<input type="password" name="new_password" required>
<label>{{ 'settings.confirm_password'|t(user.language) }}</label>
<input type="password" name="confirm_password" required>
<div class="form-actions">
<button type="submit" class="btn btn-primary">{{ 'settings.change_password'|t(user.language) }}</button>
</div>
</form>
</div>
<div class="form-card" style="margin-top:1.5rem">
<h3 style="margin-bottom:.75rem">{{ 'settings.language'|t(user.language) }}</h3>
<form method="post" action="/settings/language" style="display:flex;align-items:center;gap:.75rem;flex-wrap:wrap">
<select name="language" style="width:auto">
{% for code, name in available_languages.items() %}
<option value="{{ code }}" {% if user.language == code %}selected{% endif %}>{{ name }}</option>
{% endfor %}
</select>
<button type="submit" class="btn btn-primary btn-sm">{{ 'settings.save'|t(user.language) }}</button>
</form>
</div>
</div>
<div id="tab-showcase" class="tab-panel" {% if active_tab != 'showcase' %}style="display:none"{% endif %}>
<div class="form-card">
<h3 style="margin-bottom:.75rem">{{ 'settings.showcase_global'|t(user.language) }}</h3>
<form method="post" action="/showcase/manage">
<div style="display:flex;align-items:center;gap:1rem;flex-wrap:wrap">
<label style="margin:0;white-space:nowrap">{{ 'settings.showcase_mode'|t(user.language) }}</label>
<select name="showcase_mode" style="width:auto">
<option value="profile" {% if user.showcase_mode == 'profile' %}selected{% endif %}>{{ 'settings.mode_profile'|t(user.language) }}</option>
<option value="flat" {% if user.showcase_mode == 'flat' %}selected{% endif %}>{{ 'settings.mode_flat'|t(user.language) }}</option>
</select>
<button type="submit" class="btn btn-primary btn-sm">{{ 'settings.save'|t(user.language) }}</button>
</div>
</form>
<p style="margin-top:.75rem;font-size:.8rem;color:var(--text-muted)">
{{ 'settings.public_url_label'|t(user.language) }}<a href="/u/{{ user.username }}" target="_blank">/u/{{ user.username }}</a>
</p>
</div>
<div style="margin-top:1.5rem">
<h2 style="font-size:1rem;margin-bottom:1rem">{{ 'settings.profiles_showcase'|t(user.language) }}</h2>
{% if profiles %}
{% for p in profiles %}
{% set sent_cards = [] %}
{% set received_cards = [] %}
{% if profile_cards and p.id in profile_cards %}
{% for pc in profile_cards[p.id] %}
{% if pc.status in ('sent', 'delivered') %}
{% if sent_cards.append(pc) %}{% endif %}
{% else %}
{% if received_cards.append(pc) %}{% endif %}
{% endif %}
{% endfor %}
{% endif %}
<div class="showcase-profile-card" style="background:var(--card-bg);border:1px solid var(--border);border-radius:10px;padding:1rem;margin-bottom:.75rem">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:.75rem">
<strong>{{ p.nickname }}</strong>
<span class="badge" style="{% if p.showcase_enabled %}background:var(--sent){% else %}background:var(--text-muted){% endif %}">{% if p.showcase_enabled %}{{ 'settings.showcase_on'|t(user.language) }}{% else %}{{ 'settings.showcase_off'|t(user.language) }}{% endif %}</span>
</div>
<form method="post" action="/profiles/{{ p.id }}/showcase">
<div style="display:flex;flex-wrap:wrap;gap:1rem;align-items:center">
<div style="display:flex;align-items:center;gap:.5rem">
<label style="margin:0;font-size:.85rem">{{ 'settings.enable_showcase'|t(user.language) }}</label>
<label class="toggle-switch">
<input type="checkbox" name="showcase_enabled" value="1" {% if p.showcase_enabled %}checked{% endif %} onchange="this.form.submit()">
<span class="toggle-slider"></span>
</label>
</div>
<div style="display:flex;align-items:center;gap:.5rem">
<label style="margin:0;font-size:.85rem">{{ 'settings.showcase_content'|t(user.language) }}</label>
<select name="showcase_visible" onchange="this.form.submit()" style="width:auto">
<option value="both" {% if p.showcase_visible == 'both' %}selected{% endif %}>{{ 'settings.content_both'|t(user.language) }}</option>
<option value="sent" {% if p.showcase_visible == 'sent' %}selected{% endif %}>{{ 'settings.content_sent'|t(user.language) }}</option>
<option value="received" {% if p.showcase_visible == 'received' %}selected{% endif %}>{{ 'settings.content_received'|t(user.language) }}</option>
</select>
</div>
</div>
</form>
{% if p.showcase_enabled and (sent_cards or received_cards) %}
<div style="margin-top:.75rem;border-top:1px solid var(--border);padding-top:.75rem">
<p style="font-size:.8rem;color:var(--text-muted);margin-bottom:.5rem">{{ 'profile_showcase.single_control'|t(user.language) }}</p>
{% if sent_cards %}
<div style="margin-bottom:.4rem">
<span style="font-size:.78rem;color:var(--text-muted);margin-right:.5rem">{{ 'showcase.tab_sent'|t(user.language) }}</span>
{% for pc in sent_cards %}
<form method="post" action="/postcards/{{ pc.id }}/showcase-toggle" style="display:inline-flex;align-items:center;gap:.3rem;margin-right:.75rem;margin-bottom:.25rem;font-size:.85rem">
<input type="checkbox" name="showcase_visible" value="1" {% if not pc.showcase_hidden %}checked{% endif %} onchange="this.form.submit()" style="margin:0">
<span>{{ pc.card_number }}</span>
</form>
{% endfor %}
</div>
{% endif %}
{% if received_cards %}
<div>
<span style="font-size:.78rem;color:var(--text-muted);margin-right:.5rem">{{ 'showcase.tab_received'|t(user.language) }}</span>
{% for pc in received_cards %}
<form method="post" action="/postcards/{{ pc.id }}/showcase-toggle" style="display:inline-flex;align-items:center;gap:.3rem;margin-right:.75rem;margin-bottom:.25rem;font-size:.85rem">
<input type="checkbox" name="showcase_visible" value="1" {% if not pc.showcase_hidden %}checked{% endif %} onchange="this.form.submit()" style="margin:0">
<span>{{ pc.card_number }}</span>
</form>
{% endfor %}
</div>
{% endif %}
</div>
{% elif p.showcase_enabled %}
<p style="margin-top:.75rem;font-size:.8rem;color:var(--text-muted)">{{ 'showcase.empty'|t(user.language) }}</p>
{% endif %}
</div>
{% endfor %}
{% else %}
<div class="empty-state">
<p>{{ 'profiles.new_ph'|t(user.language) }}</p>
</div>
{% endif %}
</div>
</div>
<div id="tab-apikeys" class="tab-panel" {% if active_tab != 'apikeys' %}style="display:none"{% endif %}>
<div class="form-card">
<h3 style="margin-bottom:.75rem">{{ 'apikey.title'|t(user.language) }}</h3>
<p style="color:var(--text-muted);font-size:.85rem;margin-bottom:1rem">{{ 'apikey.hint'|t(user.language) }}</p>
{% if new_key %}
<div class="alert alert-success" style="margin-bottom:1rem">
<strong>{{ 'apikey.created'|t(user.language) }}</strong><br>
<code style="word-break:break-all;font-size:.85rem">{{ new_key }}</code><br>
<small style="color:var(--text-muted)">{{ 'apikey.copy_now'|t(user.language) }}</small>
</div>
{% endif %}
<form method="post" action="/settings/apikeys/create" style="margin-bottom:1rem">
<div style="display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;margin-bottom:.75rem">
<input type="text" name="name" placeholder="{{ 'apikey.name_ph'|t(user.language) }}" style="max-width:200px">
<button type="submit" class="btn btn-primary btn-sm">{{ 'apikey.create'|t(user.language) }}</button>
</div>
<details>
<summary style="font-size:.85rem;color:var(--text-muted);cursor:pointer;margin-bottom:.5rem">{{ 'apikey.permissions'|t(user.language) }}</summary>
<div style="display:flex;flex-wrap:wrap;gap:.75rem 1.5rem">
{% for scope, label in [
('profiles:read', 'apikey.perm.profiles_read'),
('profiles:write', 'apikey.perm.profiles_write'),
('postcards:read', 'apikey.perm.postcards_read'),
('postcards:write', 'apikey.perm.postcards_write'),
('images:upload', 'apikey.perm.images_upload')
] %}
<label style="display:flex;align-items:center;gap:.3rem;font-size:.85rem;white-space:nowrap">
<input type="checkbox" name="permissions" value="{{ scope }}">
{{ label|t(user.language) }}
</label>
{% endfor %}
</div>
<p style="font-size:.78rem;color:var(--text-muted);margin-top:.5rem">{{ 'apikey.perm.hint'|t(user.language) }}</p>
</details>
</form>
{% if api_keys %}
<div style="overflow-x:auto;-webkit-overflow-scrolling:touch">
<table class="table" style="min-width:700px;white-space:nowrap">
<thead>
<tr>
<th>{{ 'apikey.name'|t(user.language) }}</th>
<th>{{ 'apikey.key'|t(user.language) }}</th>
<th>{{ 'apikey.permissions'|t(user.language) }}</th>
<th>{{ 'apikey.created_at'|t(user.language) }}</th>
<th>{{ 'apikey.last_used'|t(user.language) }}</th>
<th style="white-space:nowrap"></th>
</tr>
</thead>
<tbody>
{% for ak in api_keys %}
{% set perms = ak.permissions|default('[]', true)|json_loads %}
<tr>
<td style="white-space:nowrap"><strong>{{ ak.name or '—' }}</strong></td>
<td><code style="font-size:.78rem">{{ ak.key[:8] }}****</code></td>
<td style="white-space:nowrap">
{% if not perms %}
<span class="badge" style="background:var(--sent);font-size:.75rem">{{ 'apikey.perm_full_access'|t(user.language) }}</span>
{% else %}
{% for p in perms %}
<span class="badge" style="font-size:.7rem;margin:1px">{{ p }}</span>
{% endfor %}
{% endif %}
</td>
<td style="white-space:nowrap;font-size:.82rem">{{ ak.created_at|to_local|strftime('%Y-%m-%d %H:%M:%S') }}</td>
<td style="white-space:nowrap;font-size:.82rem">{% if ak.last_used_at %}{{ ak.last_used_at|to_local|strftime('%Y-%m-%d %H:%M:%S') }}{% else %}—{% endif %}</td>
<td style="white-space:nowrap">
<button type="button" class="btn btn-sm" onclick="toggleAkEdit({{ ak.id }})">{{ 'apikey.edit'|t(user.language) }}</button>
<form method="post" action="/settings/apikeys/{{ ak.id }}/delete" style="display:inline" onsubmit="return confirm('{{ 'apikey.confirm_delete'|t(user.language) }}')">
<button type="submit" class="btn btn-danger btn-sm">{{ 'apikey.delete'|t(user.language) }}</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% for ak in api_keys %}
{% set perms = ak.permissions|default('[]', true)|json_loads %}
<div id="ak-edit-{{ ak.id }}" style="display:none;margin-top:.5rem;background:var(--card-bg);border:1px solid var(--border);border-radius:8px;padding:1rem">
<h4 style="font-size:.85rem;margin-bottom:.75rem;color:var(--text-muted)">{{ 'apikey.edit'|t(user.language) }}: {{ ak.name or '—' }}</h4>
<form method="post" action="/settings/apikeys/{{ ak.id }}/edit" style="display:flex;flex-direction:column;gap:.5rem">
<div style="display:flex;align-items:center;gap:.5rem;flex-wrap:wrap">
<label style="font-size:.85rem;white-space:nowrap">{{ 'apikey.name'|t(user.language) }}</label>
<input type="text" name="name" value="{{ ak.name }}" style="max-width:200px">
</div>
<div style="display:flex;flex-wrap:wrap;gap:.6rem 1.25rem">
{% for scope, label in [
('profiles:read', 'apikey.perm.profiles_read'),
('profiles:write', 'apikey.perm.profiles_write'),
('postcards:read', 'apikey.perm.postcards_read'),
('postcards:write', 'apikey.perm.postcards_write'),
('images:upload', 'apikey.perm.images_upload')
] %}
<label style="display:flex;align-items:center;gap:.3rem;font-size:.85rem;white-space:nowrap">
<input type="checkbox" name="permissions" value="{{ scope }}" {% if scope in perms %}checked{% endif %}>
{{ label|t(user.language) }}
</label>
{% endfor %}
</div>
<div style="display:flex;gap:.5rem;margin-top:.25rem">
<button type="submit" class="btn btn-primary btn-sm">{{ 'apikey.save'|t(user.language) }}</button>
<button type="button" class="btn btn-sm" onclick="toggleAkEdit({{ ak.id }})">{{ 'apikey.cancel'|t(user.language) }}</button>
</div>
</form>
</div>
{% endfor %}
{% else %}
<div class="empty-state">
<p>{{ 'apikey.empty'|t(user.language) }}</p>
</div>
{% endif %}
</div>
</div>
<script>
function switchTab(name) {
document.querySelectorAll('.tab').forEach(function(t) { t.classList.remove('active'); });
document.querySelectorAll('.tab-panel').forEach(function(p) { p.style.display = 'none'; });
event.currentTarget.classList.add('active');
document.getElementById('tab-' + name).style.display = '';
}
function toggleAkEdit(id) {
var el = document.getElementById('ak-edit-' + id);
if (el) el.style.display = el.style.display === 'none' ? '' : 'none';
}
</script>
{% endblock %}