Add bilingual UI and improve event actions layout
Add persistent German/English switching, translated UI messages and English 12-hour times. Align desktop event actions while preserving mobile layouts and record bilingual project guidance. Include pending diary deletion exclusions. Validate both languages with nine tests.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<nav class="admin-nav" aria-label="Adminbereiche">
|
||||
<a href="/admin/users" class="{% if admin_section == 'users' %}active{% endif %}">👥 Benutzer</a>
|
||||
<a href="/admin/venues" class="{% if admin_section == 'venues' %}active{% endif %}">📍 Veranstaltungsorte</a>
|
||||
<a href="/admin/patches" class="{% if admin_section == 'patches' %}active{% endif %}">🧵 Patch-Bilder</a>
|
||||
<a href="/admin/statistics" class="{% if admin_section == 'statistics' %}active{% endif %}">📊 Nutzungsstatistik</a>
|
||||
<nav class="admin-nav" aria-label="{{ _('Adminbereiche') }}">
|
||||
<a href="/admin/users" class="{% if admin_section == 'users' %}active{% endif %}">{{ _('👥 Benutzer') }}</a>
|
||||
<a href="/admin/venues" class="{% if admin_section == 'venues' %}active{% endif %}">{{ _('📍 Veranstaltungsorte') }}</a>
|
||||
<a href="/admin/patches" class="{% if admin_section == 'patches' %}active{% endif %}">{{ _('🧵 Patch-Bilder') }}</a>
|
||||
<a href="/admin/statistics" class="{% if admin_section == 'statistics' %}active{% endif %}">{{ _('📊 Nutzungsstatistik') }}</a>
|
||||
</nav>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<nav class="language-switch" aria-label="{{ _('Sprache') }}">
|
||||
<a href="{{ language_url('de') }}" lang="de" hreflang="de" aria-label="Deutsch"{% if language() == 'de' %} aria-current="true" class="active"{% endif %}>DE</a>
|
||||
<a href="{{ language_url('en') }}" lang="en" hreflang="en" aria-label="English"{% if language() == 'en' %} aria-current="true" class="active"{% endif %}>EN</a>
|
||||
</nav>
|
||||
@@ -1,15 +1,15 @@
|
||||
<details class="user-menu">
|
||||
<summary>☠ {{ user.display_name }}{% if user.notification_count %} <span class="metal-notification" title="{{ user.notification_count }} neue Nachricht(en) oder Anfrage(n)" aria-label="Neue Benachrichtigungen">🤘<b>{{ user.notification_count }}</b></span>{% endif %}</summary>
|
||||
<summary>☠ {{ user.display_name }}{% if user.notification_count %} <span class="metal-notification" title="{{ user.notification_count }} {{ _('neue Nachricht(en) oder Anfrage(n)') }}" aria-label="{{ _('Neue Benachrichtigungen') }}">🤘<b>{{ user.notification_count }}</b></span>{% endif %}</summary>
|
||||
<div class="user-menu-panel">
|
||||
<a href="/profile">Mein Profil</a>
|
||||
<a href="/messages">Nachrichten{% if user.notification_count %} <span class="metal-notification" title="Neue Nachrichten oder Anfragen" aria-label="Neue Nachrichten oder Anfragen">🤘<b>{{ user.notification_count }}</b></span>{% endif %}</a>
|
||||
<a href="/diary">Konzerttagebuch</a>
|
||||
<a href="/following">Gefolgte Bands & Locations</a>
|
||||
{% if user.is_admin %}<a href="/admin">⚙️ Verwaltung</a>{% endif %}
|
||||
<a href="/datenschutz">Datenschutz</a>
|
||||
<a href="/impressum">Impressum</a>
|
||||
<a href="/profile">{{ _('Mein Profil') }}</a>
|
||||
<a href="/messages">{{ _('Nachrichten') }}{% if user.notification_count %} <span class="metal-notification" title="{{ _('Neue Nachrichten oder Anfragen') }}" aria-label="{{ _('Neue Nachrichten oder Anfragen') }}">🤘<b>{{ user.notification_count }}</b></span>{% endif %}</a>
|
||||
<a href="/diary">{{ _('Konzerttagebuch') }}</a>
|
||||
<a href="/following">{{ _('Gefolgte Bands & Locations') }}</a>
|
||||
{% if user.is_admin %}<a href="/admin">{{ _('⚙️ Verwaltung') }}</a>{% endif %}
|
||||
<a href="/datenschutz">{{ _('Datenschutz') }}</a>
|
||||
<a href="/impressum">{{ _('Impressum') }}</a>
|
||||
<form method="post" action="/logout">
|
||||
<button type="submit">Abmelden</button>
|
||||
<button type="submit">{{ _('Abmelden') }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<!DOCTYPE html><html lang="de"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Account-Link · MetalCircle</title><link rel="icon" type="image/png" href="/static/images/metalcircle-circle.png"><link rel="stylesheet" href="/static/css/style.css"></head><body>
|
||||
<header><div class="header-inner"><a href="/" class="logo"><img class="brand-logo" src="/static/images/metalcircle-full.png" alt="MetalCircle"></a>{% include '_user_menu.html' %}</div></header>
|
||||
<main><div class="page-title"><h1>Passwort zurücksetzen</h1><p>Reset-Link für @{{ target_username }} · {{ target_email }}</p></div>
|
||||
<section class="admin-section"><p>Diesen persönlichen Link sicher an den User senden. Er ist zwei Stunden gültig und nur einmal verwendbar.</p><label class="invite-link" style="display:block;overflow-wrap:anywhere">{{ account_url }}</label><p><a class="button button-secondary" href="/admin/users">Zurück</a></p></section></main></body></html>
|
||||
<!DOCTYPE html><html lang="{{ language() }}"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>{{ _('Account-Link · MetalCircle') }}</title><link rel="icon" type="image/png" href="/static/images/metalcircle-circle.png"><link rel="stylesheet" href="/static/css/style.css"><link rel="stylesheet" href="/static/css/language.css">
|
||||
</head><body>
|
||||
<header><div class="header-inner"><a href="/" class="logo"><img class="brand-logo" src="/static/images/metalcircle-full.png" alt="MetalCircle"></a>{% include '_user_menu.html' %}</div> {% include '_language_switch.html' %}
|
||||
</header>
|
||||
<main><div class="page-title"><h1>{{ _('Passwort zurücksetzen') }}</h1><p>{{ _('Reset-Link für @') }}{{ target_username }} · {{ target_email }}</p></div>
|
||||
<section class="admin-section"><p>{{ _('Diesen persönlichen Link sicher an den User senden. Er ist zwei Stunden gültig und nur einmal verwendbar.') }}</p><label class="invite-link" style="display:block;overflow-wrap:anywhere">{{ account_url }}</label><p><a class="button button-secondary" href="/admin/users">{{ _('Zurück') }}</a></p></section></main></body></html>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<html lang="{{ language() }}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Patch-Bilder · MetalCircle</title>
|
||||
<title>{{ _('Patch-Bilder · MetalCircle') }}</title>
|
||||
<link rel="icon" type="image/png" href="/static/images/metalcircle-circle.png">
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
<style>
|
||||
@@ -18,28 +18,30 @@
|
||||
.patch-upload { display: grid; gap: 8px; }
|
||||
.patch-upload-status { min-height: 1.2em; color: var(--muted); font-size: .85rem; }
|
||||
</style>
|
||||
<link rel="stylesheet" href="/static/css/language.css">
|
||||
</head>
|
||||
<body>
|
||||
<header><div class="header-inner"><a href="/" class="logo"><img class="brand-logo" src="/static/images/metalcircle-full.png" alt="MetalCircle"></a>{% include '_user_menu.html' %}</div></header>
|
||||
<header><div class="header-inner"><a href="/" class="logo"><img class="brand-logo" src="/static/images/metalcircle-full.png" alt="MetalCircle"></a>{% include '_user_menu.html' %}</div> {% include '_language_switch.html' %}
|
||||
</header>
|
||||
<main>
|
||||
<div class="page-title"><h1>🧵 Patch-Bilder</h1><p>Grafiken für die Patches auf der virtuellen Kutte verwalten.</p></div>
|
||||
<div class="page-title"><h1>{{ _('🧵 Patch-Bilder') }}</h1><p>{{ _('Grafiken für die Patches auf der virtuellen Kutte verwalten.') }}</p></div>
|
||||
{% set admin_section = 'patches' %}{% include '_admin_nav.html' %}
|
||||
<section class="admin-section">
|
||||
<div class="patch-admin-groups">
|
||||
{% for group in patch_groups %}
|
||||
<details class="patch-admin-group">
|
||||
<summary>{{ group.label }} · {{ group.patches|length }}</summary>
|
||||
<summary>{{ group.label | t }} · {{ group.patches|length }}</summary>
|
||||
<div class="patch-admin-grid">
|
||||
{% for patch in group.patches %}
|
||||
<article class="admin-row patch-admin-card">
|
||||
{% if patch.image_path %}<img class="patch-preview" src="{{ patch.image_path }}" alt="{{ patch.name }}">{% else %}<div class="patch-preview patch-placeholder">{{ patch.icon }}</div>{% endif %}
|
||||
<div><strong>{{ patch.name }}</strong><div class="admin-meta">{{ patch.description }} · {{ patch.code }}</div></div>
|
||||
{% if patch.image_path %}<img class="patch-preview" src="{{ patch.image_path }}" alt="{{ patch.name | t }}">{% else %}<div class="patch-preview patch-placeholder">{{ patch.icon }}</div>{% endif %}
|
||||
<div><strong>{{ patch.name | t }}</strong><div class="admin-meta">{{ patch.description | t }} · {{ patch.code }}</div></div>
|
||||
<form class="patch-upload" method="post" action="/admin/patches/{{ patch.code }}" enctype="multipart/form-data">
|
||||
<input type="file" name="image" accept="image/jpeg,image/png,image/webp" required>
|
||||
<span class="patch-upload-status">Das Bild wird vor dem Upload automatisch optimiert.</span>
|
||||
<button class="button" type="submit">{% if patch.image_path %}Bild ersetzen{% else %}Bild hochladen{% endif %}</button>
|
||||
<span class="patch-upload-status">{{ _('Das Bild wird vor dem Upload automatisch optimiert.') }}</span>
|
||||
<button class="button" type="submit">{% if patch.image_path %}{{ _('Bild ersetzen') }}{% else %}{{ _('Bild hochladen') }}{% endif %}</button>
|
||||
</form>
|
||||
{% if patch.image_path %}<form method="post" action="/admin/patches/{{ patch.code }}/delete" onsubmit="return confirm('Patch-Bild entfernen und wieder das Symbol verwenden?');"><button class="admin-danger" type="submit">Bild entfernen</button></form>{% endif %}
|
||||
{% if patch.image_path %}<form method="post" action="/admin/patches/{{ patch.code }}/delete" onsubmit="return confirm({{ _('Patch-Bild entfernen und wieder das Symbol verwenden?') | tojson | forceescape }});"><button class="admin-danger" type="submit">{{ _('Bild entfernen') }}</button></form>{% endif %}
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -99,7 +101,7 @@ patchForms.forEach(form => {
|
||||
form.requestSubmit();
|
||||
} catch (error) {
|
||||
console.error("Patch image optimization failed:", error);
|
||||
status.textContent = "Das Bild konnte nicht optimiert werden. Bitte ein kleineres JPG, PNG oder WebP wählen.";
|
||||
status.textContent = {{ _('Das Bild konnte nicht optimiert werden. Bitte ein kleineres JPG, PNG oder WebP wählen.') | tojson }};
|
||||
button.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<!DOCTYPE html><html lang="de"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Nutzungsstatistik · MetalCircle</title><link rel="icon" type="image/png" href="/static/images/metalcircle-circle.png"><link rel="stylesheet" href="/static/css/style.css"></head><body>
|
||||
<header><div class="header-inner"><a href="/" class="logo"><img class="brand-logo" src="/static/images/metalcircle-full.png" alt="MetalCircle"></a>{% include '_user_menu.html' %}</div></header>
|
||||
<main><div class="page-title"><h1>📊 Nutzungsstatistik</h1><p>Aktuelle Gesamtzahlen der Plattform.</p></div>{% set admin_section = 'statistics' %}{% include '_admin_nav.html' %}
|
||||
<!DOCTYPE html><html lang="{{ language() }}"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>{{ _('Nutzungsstatistik · MetalCircle') }}</title><link rel="icon" type="image/png" href="/static/images/metalcircle-circle.png"><link rel="stylesheet" href="/static/css/style.css"><link rel="stylesheet" href="/static/css/language.css">
|
||||
</head><body>
|
||||
<header><div class="header-inner"><a href="/" class="logo"><img class="brand-logo" src="/static/images/metalcircle-full.png" alt="MetalCircle"></a>{% include '_user_menu.html' %}</div> {% include '_language_switch.html' %}
|
||||
</header>
|
||||
<main><div class="page-title"><h1>{{ _('📊 Nutzungsstatistik') }}</h1><p>{{ _('Aktuelle Gesamtzahlen der Plattform.') }}</p></div>{% set admin_section = 'statistics' %}{% include '_admin_nav.html' %}
|
||||
<section class="stats-grid">
|
||||
{% for key, label in [('users','Benutzer'),('events','Veranstaltungen'),('upcoming','Bevorstehende Veranstaltungen'),('comments','Kommentare'),('attendance','Teilnahmen'),('friendships','Freundschaften'),('messages','Nachrichten')] %}<div class="stat-card"><strong>{{ stats[key] }}</strong><span>{{ label }}</span></div>{% endfor %}
|
||||
{% for key, label in [('users',_('Benutzer')),('events',_('Veranstaltungen')),('upcoming',_('Bevorstehende Veranstaltungen')),('comments',_('Kommentare')),('attendance',_('Teilnahmen')),('friendships',_('Freundschaften')),('messages',_('Nachrichten'))] %}<div class="stat-card"><strong>{{ stats[key] }}</strong><span>{{ label | t }}</span></div>{% endfor %}
|
||||
</section></main></body></html>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<html lang="{{ language() }}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Benutzerverwaltung · MetalCircle</title>
|
||||
<title>{{ _('Benutzerverwaltung · MetalCircle') }}</title>
|
||||
<link rel="icon" type="image/png" href="/static/images/metalcircle-circle.png">
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
<style>
|
||||
@@ -16,36 +16,38 @@
|
||||
.founder-lock { color: #fbbf24; font-weight: 700; white-space: nowrap; }
|
||||
@media (max-width: 700px) { .user-row { grid-template-columns: 1fr; } }
|
||||
</style>
|
||||
<link rel="stylesheet" href="/static/css/language.css">
|
||||
</head>
|
||||
<body>
|
||||
<header><div class="header-inner"><a href="/" class="logo"><img class="brand-logo" src="/static/images/metalcircle-full.png" alt="MetalCircle"></a>{% include '_user_menu.html' %}</div></header>
|
||||
<header><div class="header-inner"><a href="/" class="logo"><img class="brand-logo" src="/static/images/metalcircle-full.png" alt="MetalCircle"></a>{% include '_user_menu.html' %}</div> {% include '_language_switch.html' %}
|
||||
</header>
|
||||
<main>
|
||||
<div class="page-title"><h1>⚙️ Verwaltung</h1><p>Benutzerkonten und Einladungen verwalten.</p></div>
|
||||
<div class="page-title"><h1>{{ _('⚙️ Verwaltung') }}</h1><p>{{ _('Benutzerkonten und Einladungen verwalten.') }}</p></div>
|
||||
{% set admin_section = 'users' %}{% include '_admin_nav.html' %}
|
||||
<section class="admin-section">
|
||||
<h2>Einladung erstellen</h2>
|
||||
<p>Der Link kann einmalig verwendet werden.</p>
|
||||
<form method="post" action="/admin/invites"><button class="button" type="submit">+ Einladungslink erzeugen</button></form>
|
||||
<h2>{{ _('Einladung erstellen') }}</h2>
|
||||
<p>{{ _('Der Link kann einmalig verwendet werden.') }}</p>
|
||||
<form method="post" action="/admin/invites"><button class="button" type="submit">{{ _('+ Einladungslink erzeugen') }}</button></form>
|
||||
{% if invite_url %}<label class="invite-link">{{ invite_url }}</label>{% endif %}
|
||||
</section>
|
||||
<section class="admin-section">
|
||||
<h2>Benutzer</h2>
|
||||
<h2>{{ _('Benutzer') }}</h2>
|
||||
<div class="admin-list">
|
||||
{% for managed_user in users %}
|
||||
<div class="admin-row user-row">
|
||||
<div><strong>{% if managed_user.username|lower == 'kai' %}🏴☠️ {% endif %}{{ managed_user.display_name }}</strong><div class="admin-meta">@{{ managed_user.username }} · {{ managed_user.email }} · seit {{ managed_user.created_at }}</div></div>
|
||||
<div><strong>{% if managed_user.username|lower == 'kai' %}🏴☠️ {% endif %}{{ managed_user.display_name }}</strong><div class="admin-meta">@{{ managed_user.username }} · {{ managed_user.email }} {{ _('· seit') }} {{ managed_user.created_at }}</div></div>
|
||||
{% if managed_user.username|lower == 'kai' %}
|
||||
<span class="founder-lock">🏴☠️ Gründer · Admin (fest)</span>
|
||||
<span class="founder-lock">{{ _('🏴\u200d☠️ Gründer · Admin (fest)') }}</span>
|
||||
{% else %}
|
||||
<form class="role-form" method="post" action="/admin/users/{{ managed_user.id }}">
|
||||
<select name="role" aria-label="Rolle für {{ managed_user.username }}"><option value="user" {% if not managed_user.is_admin %}selected{% endif %}>Benutzer</option><option value="admin" {% if managed_user.is_admin %}selected{% endif %}>🏴☠️ Admin</option></select>
|
||||
<button class="button" type="submit">Speichern</button>
|
||||
<select name="role" aria-label="{{ _('Rolle für') }} {{ managed_user.username }}"><option value="user" {% if not managed_user.is_admin %}selected{% endif %}>{{ _('Benutzer') }}</option><option value="admin" {% if managed_user.is_admin %}selected{% endif %}>🏴☠️ Admin</option></select>
|
||||
<button class="button" type="submit">{{ _('Speichern') }}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
<div class="role-form">
|
||||
<form method="post" action="/admin/users/{{ managed_user.id }}/account-link"><input type="hidden" name="purpose" value="password_reset"><button class="button button-secondary" type="submit">Passwort-Reset</button></form>
|
||||
<form method="post" action="/admin/users/{{ managed_user.id }}/account-link"><input type="hidden" name="purpose" value="password_reset"><button class="button button-secondary" type="submit">{{ _('Passwort-Reset') }}</button></form>
|
||||
</div>
|
||||
{% if managed_user.id != user.id and managed_user.username|lower != 'kai' %}<form method="post" action="/admin/users/{{ managed_user.id }}/delete" onsubmit="return confirm('Diesen Benutzer wirklich löschen?');"><button class="admin-danger" type="submit">Löschen</button></form>{% endif %}
|
||||
{% if managed_user.id != user.id and managed_user.username|lower != 'kai' %}<form method="post" action="/admin/users/{{ managed_user.id }}/delete" onsubmit="return confirm({{ _('Diesen Benutzer wirklich löschen?') | tojson | forceescape }});"><button class="admin-danger" type="submit">{{ _('Löschen') }}</button></form>{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<html lang="{{ language() }}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Veranstaltungsorte · MetalCircle</title>
|
||||
<title>{{ _('Veranstaltungsorte · MetalCircle') }}</title>
|
||||
<link rel="icon" type="image/png" href="/static/images/metalcircle-circle.png">
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
<style>
|
||||
@@ -28,11 +28,13 @@
|
||||
@media (max-width: 900px) { .venue-fields { grid-template-columns: 1fr 1fr; } }
|
||||
@media (max-width: 560px) { .venue-fields, .merge-form { grid-template-columns: 1fr; } }
|
||||
</style>
|
||||
<link rel="stylesheet" href="/static/css/language.css">
|
||||
</head>
|
||||
<body>
|
||||
<header><div class="header-inner"><a href="/" class="logo"><img class="brand-logo" src="/static/images/metalcircle-full.png" alt="MetalCircle"></a>{% include '_user_menu.html' %}</div></header>
|
||||
<header><div class="header-inner"><a href="/" class="logo"><img class="brand-logo" src="/static/images/metalcircle-full.png" alt="MetalCircle"></a>{% include '_user_menu.html' %}</div> {% include '_language_switch.html' %}
|
||||
</header>
|
||||
<main>
|
||||
<div class="page-title"><h1>📍 Veranstaltungsorte</h1><p>Orte korrigieren, bestätigen, zusammenführen oder entfernen.</p></div>
|
||||
<div class="page-title"><h1>{{ _('📍 Veranstaltungsorte') }}</h1><p>{{ _('Orte korrigieren, bestätigen, zusammenführen oder entfernen.') }}</p></div>
|
||||
{% set admin_section = 'venues' %}{% include '_admin_nav.html' %}
|
||||
<section class="admin-section">
|
||||
<div class="admin-list">
|
||||
@@ -41,37 +43,37 @@
|
||||
<summary class="venue-summary">
|
||||
<span>
|
||||
<strong class="venue-summary-title">{{ venue.name }}{% if venue.city %} · {{ venue.city }}{% endif %}{% if venue.country %} · {{ venue.country }}{% endif %}</strong>
|
||||
<span class="venue-summary-meta"><span class="{% if venue.is_verified %}venue-verified{% else %}venue-unverified{% endif %}">{% if venue.is_verified %}✓ bestätigt{% else %}● unbestätigt{% endif %}</span> · {{ venue.concert_count }} Veranstaltung(en) · ID {{ venue.id }}</span>
|
||||
<span class="venue-summary-meta"><span class="{% if venue.is_verified %}venue-verified{% else %}venue-unverified{% endif %}">{% if venue.is_verified %}{{ _('✓ bestätigt') }}{% else %}{{ _('● unbestätigt') }}{% endif %}</span> · {{ venue.concert_count }} {{ _('Veranstaltung(en) · ID') }} {{ venue.id }}</span>
|
||||
</span>
|
||||
</summary>
|
||||
<div class="venue-editor">
|
||||
<form method="post" action="/admin/venues/{{ venue.id }}">
|
||||
<div class="venue-fields">
|
||||
<input name="name" value="{{ venue.name }}" aria-label="Name" required>
|
||||
<input name="street" value="{{ venue.street }}" placeholder="Straße" aria-label="Straße">
|
||||
<input name="postal_code" value="{{ venue.postal_code }}" placeholder="PLZ" aria-label="PLZ">
|
||||
<input name="city" value="{{ venue.city }}" placeholder="Ort" aria-label="Ort">
|
||||
<input name="country" value="{{ venue.country }}" placeholder="Land" aria-label="Land">
|
||||
<input name="street" value="{{ venue.street }}" placeholder="{{ _('Straße') }}" aria-label="{{ _('Straße') }}">
|
||||
<input name="postal_code" value="{{ venue.postal_code }}" placeholder="{{ _('PLZ') }}" aria-label="{{ _('PLZ') }}">
|
||||
<input name="city" value="{{ venue.city }}" placeholder="{{ _('Ort') }}" aria-label="{{ _('Ort') }}">
|
||||
<input name="country" value="{{ venue.country }}" placeholder="{{ _('Land') }}" aria-label="{{ _('Land') }}">
|
||||
</div>
|
||||
<input class="venue-aliases" name="aliases" value="{{ venue.aliases }}" placeholder="Aliase, durch Komma getrennt" aria-label="Aliase">
|
||||
<input class="venue-aliases" name="aliases" value="{{ venue.aliases }}" placeholder="{{ _('Aliase, durch Komma getrennt') }}" aria-label="{{ _('Aliase') }}">
|
||||
<div class="admin-actions venue-actions">
|
||||
<label><input type="checkbox" name="is_verified" {% if venue.is_verified %}checked{% endif %}> bestätigt</label>
|
||||
<span class="admin-meta">Quelle: {{ venue.source }} · {{ venue.concert_count }} Konzert(e) · ID {{ venue.id }}</span>
|
||||
<button class="button" type="submit">Speichern</button>
|
||||
<label><input type="checkbox" name="is_verified" {% if venue.is_verified %}checked{% endif %}> {{ _('bestätigt') }}</label>
|
||||
<span class="admin-meta">{{ _('Quelle:') }} {{ venue.source }} · {{ venue.concert_count }} {{ _('Konzert(e) · ID') }} {{ venue.id }}</span>
|
||||
<button class="button" type="submit">{{ _('Speichern') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
{% if venues|length > 1 %}
|
||||
<form class="merge-form" method="post" action="/admin/venues/{{ venue.id }}/merge" onsubmit="return confirm('Alle Konzerte werden dem Zielort zugeordnet und dieser Eintrag wird gelöscht. Fortfahren?');">
|
||||
<select name="target_venue_id" required><option value="">Mit anderem Ort zusammenführen …</option>{% for target in venues %}{% if target.id != venue.id %}<option value="{{ target.id }}">{{ target.name }}{% if target.city %}, {{ target.city }}{% endif %}</option>{% endif %}{% endfor %}</select>
|
||||
<button class="admin-danger" type="submit">Zusammenführen</button>
|
||||
<form class="merge-form" method="post" action="/admin/venues/{{ venue.id }}/merge" onsubmit="return confirm({{ _('Alle Konzerte werden dem Zielort zugeordnet und dieser Eintrag wird gelöscht. Fortfahren?') | tojson | forceescape }});">
|
||||
<select name="target_venue_id" required><option value="">{{ _('Mit anderem Ort zusammenführen …') }}</option>{% for target in venues %}{% if target.id != venue.id %}<option value="{{ target.id }}">{{ target.name }}{% if target.city %}, {{ target.city }}{% endif %}</option>{% endif %}{% endfor %}</select>
|
||||
<button class="admin-danger" type="submit">{{ _('Zusammenführen') }}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
<form class="delete-form" method="post" action="/admin/venues/{{ venue.id }}/delete" onsubmit="return confirm('{% if venue.concert_count %}Der Ort ist mit {{ venue.concert_count }} Konzert(en) verknüpft. Diese Konzerte haben danach keinen Veranstaltungsort mehr. {% endif %}Ort endgültig löschen?');">
|
||||
<button class="admin-danger" type="submit">Ort löschen{% if venue.concert_count %} ({{ venue.concert_count }} Verknüpfung(en)){% endif %}</button>
|
||||
<form class="delete-form" method="post" action="/admin/venues/{{ venue.id }}/delete" onsubmit="return confirm({{ (_('Der Ort ist mit {count} Konzert(en) verknüpft. Diese Konzerte haben danach keinen Veranstaltungsort mehr. Ort endgültig löschen?').format(count=venue.concert_count) if venue.concert_count else _('Ort endgültig löschen?')) | tojson | forceescape }});">
|
||||
<button class="admin-danger" type="submit">{{ _('Ort löschen') }}{% if venue.concert_count %} ({{ venue.concert_count }} {{ _('Verknüpfung(en))') }}{% endif %}</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
{% else %}<p>Noch keine Veranstaltungsorte vorhanden.</p>{% endfor %}
|
||||
{% else %}<p>{{ _('Noch keine Veranstaltungsorte vorhanden.') }}</p>{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<html lang="{{ language() }}">
|
||||
|
||||
<head>
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
.diary-form textarea { min-height:130px; resize:vertical; }
|
||||
</style>
|
||||
|
||||
<link rel="stylesheet" href="/static/css/language.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@@ -42,7 +43,8 @@
|
||||
<div class="container">
|
||||
|
||||
<div class="detail-topbar">
|
||||
<a href="/" class="back-link">← Zurück zum Kalender</a>
|
||||
<a href="/" class="back-link">{{ _('← Zurück zum Kalender') }}</a>
|
||||
{% include '_language_switch.html' %}
|
||||
{% include '_user_menu.html' %}
|
||||
</div>
|
||||
|
||||
@@ -61,7 +63,7 @@
|
||||
>
|
||||
|
||||
{% if concert.flyer_url %}
|
||||
<p class="flyer-source"><a href="{{ concert.flyer_url }}" target="_blank" rel="noopener noreferrer nofollow external">Quelle</a><span class="flyer-disclaimer">Extern eingebundener Flyer. Rechte liegen beim jeweiligen Veranstalter bzw. Rechteinhaber. Hinweise zur Entfernung bitte an die Administration.</span></p>
|
||||
<p class="flyer-source"><a href="{{ concert.flyer_url }}" target="_blank" rel="noopener noreferrer nofollow external">{{ _('Quelle') }}</a><span class="flyer-disclaimer">{{ _('Extern eingebundener Flyer. Rechte liegen beim jeweiligen Veranstalter bzw. Rechteinhaber. Hinweise zur Entfernung bitte an die Administration.') }}</span></p>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
@@ -71,8 +73,8 @@
|
||||
|
||||
<div class="concert-content">
|
||||
|
||||
<span class="event-type-badge">{{ concert.event_type_label }}</span>
|
||||
{% if concert.visibility == 'friends' %}<span class="event-type-badge">🔒 Nur Freunde</span>{% elif concert.visibility == 'private' %}<span class="event-type-badge">🔐 Privat / eingeladen</span>{% endif %}
|
||||
<span class="event-type-badge">{{ concert.event_type_label | t }}</span>
|
||||
{% if concert.visibility == 'friends' %}<span class="event-type-badge">{{ _('🔒 Nur Freunde') }}</span>{% elif concert.visibility == 'private' %}<span class="event-type-badge">{{ _('🔐 Privat / eingeladen') }}</span>{% endif %}
|
||||
|
||||
<h1>
|
||||
{{ concert.artist }}
|
||||
@@ -82,13 +84,13 @@
|
||||
{% for band in concert_bands %}<form method="post" action="/concerts/{{ concert.id }}/follow-band">
|
||||
<input type="hidden" name="band_key" value="{{ band.key }}">
|
||||
<input type="hidden" name="action" value="{% if band.is_following %}unfollow{% else %}follow{% endif %}">
|
||||
<button class="follow-button {% if band.is_following %}is-following{% endif %}" type="submit">{% if band.is_following %}★ {{ band.name }} gefolgt{% else %}☆ {{ band.name }} folgen{% endif %}</button>
|
||||
<button class="follow-button {% if band.is_following %}is-following{% endif %}" type="submit">{% if band.is_following %}★ {{ band.name }} {{ _('gefolgt') }}{% else %}☆ {{ band.name }} {{ _('folgen') }}{% endif %}</button>
|
||||
</form>{% endfor %}
|
||||
{% if concert.venue.id %}<form method="post" action="/concerts/{{ concert.id }}/follow-venue">
|
||||
<input type="hidden" name="action" value="{% if follows_venue %}unfollow{% else %}follow{% endif %}">
|
||||
<button class="follow-button {% if follows_venue %}is-following{% endif %}" type="submit">{% if follows_venue %}★ Location gefolgt{% else %}☆ Location folgen{% endif %}</button>
|
||||
<button class="follow-button {% if follows_venue %}is-following{% endif %}" type="submit">{% if follows_venue %}{{ _('★ Location gefolgt') }}{% else %}{{ _('☆ Location folgen') }}{% endif %}</button>
|
||||
</form>{% endif %}
|
||||
<a class="follow-button" href="/following">Meine gefolgten Inhalte</a>
|
||||
<a class="follow-button" href="/following">{{ _('Meine gefolgten Inhalte') }}</a>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -108,11 +110,11 @@
|
||||
|
||||
<br>
|
||||
|
||||
{{ concert.time }} Uhr
|
||||
{{ concert.time }} {{ _('Uhr') }}
|
||||
|
||||
{% if concert.event_type == 'festival' and concert.end_date %}
|
||||
<br>
|
||||
bis {{ concert.end_date }}{% if concert.end_time %}, {{ concert.end_time }} Uhr{% endif %}
|
||||
{{ _('bis') }} {{ concert.end_date }}{% if concert.end_time %}, {{ concert.end_time }} {{ _('Uhr') }}{% endif %}
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
@@ -191,9 +193,9 @@
|
||||
|
||||
{% if concert.parent_event %}
|
||||
<p>
|
||||
Gehört zu:
|
||||
{{ _('Gehört zu:') }}
|
||||
<a class="parent-event-link" href="/concerts/{{ concert.parent_event.id }}">
|
||||
{{ concert.parent_event.event_type_label }} · {{ concert.parent_event.title }}
|
||||
{{ concert.parent_event.event_type_label | t }} · {{ concert.parent_event.title }}
|
||||
</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
@@ -204,7 +206,7 @@
|
||||
<section class="description">
|
||||
|
||||
<h2>
|
||||
Über die Veranstaltung
|
||||
{{ _('Über die Veranstaltung') }}
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
@@ -216,6 +218,7 @@
|
||||
{% endif %}
|
||||
|
||||
|
||||
<div class="event-actions">
|
||||
{% if concert.ticket_url %}
|
||||
|
||||
<div class="ticket-button-container">
|
||||
@@ -233,13 +236,13 @@
|
||||
|
||||
{% endif %}
|
||||
|
||||
<div class="calendar-export"><a href="/concerts/{{ concert.id }}.ics" class="concert-action">📅 In Kalender speichern (.ics)</a></div>
|
||||
<div class="calendar-export"><a href="/concerts/{{ concert.id }}.ics" class="concert-action">{{ _('📅 In Kalender speichern (.ics)') }}</a></div>
|
||||
|
||||
{% if can_edit %}
|
||||
|
||||
<div class="detail-actions" aria-label="Veranstaltung verwalten">
|
||||
<div class="detail-actions" aria-label="{{ _('Veranstaltung verwalten') }}">
|
||||
<a href="/concerts/{{ concert.id }}/edit" class="concert-action concert-action-edit">
|
||||
✏️ Veranstaltung bearbeiten
|
||||
{{ _('✏️ Veranstaltung bearbeiten') }}
|
||||
</a>
|
||||
|
||||
{% if user.is_admin and can_delete %}
|
||||
@@ -247,10 +250,10 @@
|
||||
<form
|
||||
method="post"
|
||||
action="/concerts/{{ concert.id }}/delete"
|
||||
onsubmit="return confirm('Diese Veranstaltung wirklich löschen?');"
|
||||
onsubmit="return confirm({{ _('Diese Veranstaltung wirklich löschen?') | tojson | forceescape }});"
|
||||
>
|
||||
<button type="submit" class="concert-action concert-action-delete">
|
||||
🗑️ Veranstaltung löschen
|
||||
{{ _('🗑️ Veranstaltung löschen') }}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -260,15 +263,17 @@
|
||||
{% endif %}
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
{% if not concert.is_past %}
|
||||
|
||||
<section class="attendance-section" id="attendance">
|
||||
|
||||
<h2>
|
||||
🐧 Wer ist dabei?
|
||||
{{ _('🐧 Wer ist dabei?') }}
|
||||
</h2>
|
||||
|
||||
<p>Wähle, wie es bei dir aussieht. Deine Auswahl kann jederzeit geändert werden.</p>
|
||||
<p>{{ _('Wähle, wie es bei dir aussieht. Deine Auswahl kann jederzeit geändert werden.') }}</p>
|
||||
|
||||
<div class="attendance-actions">
|
||||
|
||||
@@ -278,7 +283,7 @@
|
||||
type="submit"
|
||||
class="attendance-option {% if current_attendance == 'attending' %}is-selected{% endif %}"
|
||||
>
|
||||
✓ Zugesagt
|
||||
{{ _('✓ Zugesagt') }}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -288,7 +293,7 @@
|
||||
type="submit"
|
||||
class="attendance-option {% if current_attendance == 'maybe' %}is-selected{% endif %}"
|
||||
>
|
||||
? Vielleicht
|
||||
{{ _('? Vielleicht') }}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -298,7 +303,7 @@
|
||||
type="submit"
|
||||
class="attendance-option {% if current_attendance == 'ticket_search' %}is-selected{% endif %}"
|
||||
>
|
||||
🔎 Suche Ticket
|
||||
{{ _('🔎 Suche Ticket') }}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -308,7 +313,7 @@
|
||||
type="submit"
|
||||
class="attendance-option {% if current_attendance == 'ticket_offer' %}is-selected{% endif %}"
|
||||
>
|
||||
🎟️ Biete Ticket
|
||||
{{ _('🎟️ Biete Ticket') }}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -316,16 +321,14 @@
|
||||
|
||||
<details class="attendance-list">
|
||||
<summary>
|
||||
{{ attending_count }} Zusage{% if attending_count != 1 %}n{% endif %}
|
||||
· {{ maybe_count }} Vielleicht
|
||||
· {{ ticket_seeker_count }} sucht Ticket
|
||||
· {{ ticket_offer_count }} bietet Ticket
|
||||
{{ attending_count }} {{ _('Zusage') if attending_count == 1 else _('Zusagen') }}
|
||||
· {{ maybe_count }} {{ _('Vielleicht') }} · {{ ticket_seeker_count }} {{ _('sucht Ticket') }} · {{ ticket_offer_count }} {{ _('bietet Ticket') }}
|
||||
</summary>
|
||||
|
||||
<div class="attendance-groups">
|
||||
|
||||
<div>
|
||||
<h3>✓ Zugesagt</h3>
|
||||
<h3>{{ _('✓ Zugesagt') }}</h3>
|
||||
|
||||
{% if attending_users %}
|
||||
<ul>
|
||||
@@ -334,12 +337,12 @@
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p>Noch niemand hat zugesagt.</p>
|
||||
<p>{{ _('Noch niemand hat zugesagt.') }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3>? Vielleicht</h3>
|
||||
<h3>{{ _('? Vielleicht') }}</h3>
|
||||
|
||||
{% if maybe_users %}
|
||||
<ul>
|
||||
@@ -348,12 +351,12 @@
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p>Noch keine Vielleicht-Angaben.</p>
|
||||
<p>{{ _('Noch keine Vielleicht-Angaben.') }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3>🔎 Sucht ein Ticket</h3>
|
||||
<h3>{{ _('🔎 Sucht ein Ticket') }}</h3>
|
||||
|
||||
{% if ticket_seekers %}
|
||||
<ul>
|
||||
@@ -362,12 +365,12 @@
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p>Aktuell sucht niemand ein Ticket.</p>
|
||||
<p>{{ _('Aktuell sucht niemand ein Ticket.') }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3>🎟️ Bietet ein Ticket</h3>
|
||||
<h3>{{ _('🎟️ Bietet ein Ticket') }}</h3>
|
||||
|
||||
{% if ticket_offers %}
|
||||
<ul>
|
||||
@@ -376,7 +379,7 @@
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p>Aktuell bietet niemand ein Ticket an.</p>
|
||||
<p>{{ _('Aktuell bietet niemand ein Ticket an.') }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -389,31 +392,31 @@
|
||||
|
||||
{% if concert.is_past %}
|
||||
<section class="diary-section" id="diary">
|
||||
<h2>📓 Mein Konzerttagebuch</h2>
|
||||
<h2>{{ _('📓 Mein Konzerttagebuch') }}</h2>
|
||||
{% if can_write_diary %}
|
||||
<p>Der private Eintrag wurde automatisch angelegt. <a class="button" href="/diary#concert-{{ concert.id }}">Im Konzerttagebuch bearbeiten</a></p>
|
||||
<p>{{ _('Der private Eintrag wurde automatisch angelegt.') }} <a class="button" href="/diary#concert-{{ concert.id }}">{{ _('Im Konzerttagebuch bearbeiten') }}</a></p>
|
||||
{% else %}
|
||||
<p>Ein Tagebucheintrag wird automatisch angelegt, sobald du das vergangene Konzert als „Zugesagt“ markierst.</p>
|
||||
<p>{{ _('Ein Tagebucheintrag wird automatisch angelegt, sobald du das vergangene Konzert als „Zugesagt“ markierst.') }}</p>
|
||||
{% endif %}
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
<section class="comments-section" id="comments">
|
||||
|
||||
<h2>💬 Kommentare</h2>
|
||||
<h2>{{ _('💬 Kommentare') }}</h2>
|
||||
|
||||
<form method="post" action="/concerts/{{ concert.id }}/comments" class="comment-form">
|
||||
<label for="comment-body">Mit anderen Konzertbesuchern austauschen</label>
|
||||
<label for="comment-body">{{ _('Mit anderen Konzertbesuchern austauschen') }}</label>
|
||||
<textarea
|
||||
id="comment-body"
|
||||
name="body"
|
||||
rows="4"
|
||||
maxlength="2000"
|
||||
required
|
||||
placeholder="Schreib einen Kommentar …"
|
||||
placeholder="{{ _('Schreib einen Kommentar …') }}"
|
||||
></textarea>
|
||||
<button type="submit" class="attendance-option comment-submit">
|
||||
Kommentar senden
|
||||
{{ _('Kommentar senden') }}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -433,7 +436,7 @@
|
||||
|
||||
{% else %}
|
||||
|
||||
<p class="comments-empty">Noch keine Kommentare. Mach den Anfang!</p>
|
||||
<p class="comments-empty">{{ _('Noch keine Kommentare. Mach den Anfang!') }}</p>
|
||||
|
||||
{% endif %}
|
||||
|
||||
|
||||
@@ -1,26 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Datenschutz · MetalCircle</title><link rel="icon" type="image/png" href="/static/images/metalcircle-circle.png"><link rel="stylesheet" href="/static/css/style.css"></head>
|
||||
<html lang="{{ language() }}">
|
||||
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>{{ _('Datenschutz · MetalCircle') }}</title><link rel="icon" type="image/png" href="/static/images/metalcircle-circle.png"><link rel="stylesheet" href="/static/css/style.css"><link rel="stylesheet" href="/static/css/language.css">
|
||||
</head>
|
||||
<body>
|
||||
<header><div class="header-inner"><a href="/" class="logo"><img class="brand-logo" src="/static/images/metalcircle-full.png" alt="MetalCircle"></a></div></header>
|
||||
<header><div class="header-inner"><a href="/" class="logo"><img class="brand-logo" src="/static/images/metalcircle-full.png" alt="MetalCircle"></a></div> {% include '_language_switch.html' %}
|
||||
</header>
|
||||
<main>
|
||||
<div class="page-title"><h1>Datenschutz</h1><p>Hinweise zur Verarbeitung deiner Daten</p></div>
|
||||
<div class="page-title"><h1>{{ _('Datenschutz') }}</h1><p>{{ _('Hinweise zur Verarbeitung deiner Daten') }}</p></div>
|
||||
<section class="admin-section legal-copy">
|
||||
<h2>Verantwortlicher</h2>
|
||||
<h2>{{ _('Verantwortlicher') }}</h2>
|
||||
<p>Kai Piekny, Fleyerstr. 33, 58097 Hagen<br><a href="mailto:konzert@pinguholic.de">konzert@pinguholic.de</a></p>
|
||||
<h2>Welche Daten werden gespeichert?</h2>
|
||||
<p>Für die Nutzung werden insbesondere Benutzername, E-Mail-Adresse, Passwort-Hash, Anzeigename, optionale Profil- und Instagram-Angaben, Profilbild, Freundschaften, Nachrichten, Veranstaltungsteilnahmen, gefolgte Bands und Locations, private Konzerttagebuch-Einträge, Kommentare, Fotos, Patches sowie von dir angelegte Veranstaltungen gespeichert.</p>
|
||||
<h2>Wofür werden sie verwendet?</h2>
|
||||
<p>Die Daten werden ausschließlich für Anmeldung, Kontoverwaltung, Veranstaltungsfunktionen, Freundschaften, Nachrichten, Benachrichtigungen und die von dir gewählten Sichtbarkeitseinstellungen verarbeitet.</p>
|
||||
<h2>Rechtsgrundlage und Speicherdauer</h2>
|
||||
<p>Die Verarbeitung erfolgt im geschlossenen Projektbetrieb zur Bereitstellung der gewünschten Funktionen und – soweit erforderlich – auf Grundlage deiner Einwilligung. Daten bleiben gespeichert, solange dein Konto besteht oder gesetzliche Aufbewahrungspflichten gelten. Nicht mehr benötigte Daten werden gelöscht.</p>
|
||||
<h2>Weitergabe und externe Dienste</h2>
|
||||
<p>Es werden keine Werbe- oder Trackingdienste eingesetzt. Bei der Veranstaltungsortsuche können Suchanfragen an einen externen Geocoding-Dienst übermittelt werden. Externe Flyer- und Instagram-Links werden beim Aufruf direkt von deinem Browser geladen; dafür gelten die Datenschutzbestimmungen des jeweiligen Anbieters.</p>
|
||||
<h2>Deine Rechte</h2>
|
||||
<p>Du kannst Auskunft, Berichtigung, Löschung, Einschränkung der Verarbeitung und – soweit anwendbar – Datenübertragbarkeit verlangen. Einen Export deiner gespeicherten Anwendungsdaten findest du klein am Ende des eigenen Profilbereichs. Anfragen bitte an <a href="mailto:konzert@pinguholic.de">konzert@pinguholic.de</a>.</p>
|
||||
<h2>Cookies und Sicherheit</h2>
|
||||
<p>Für die Anmeldung wird ausschließlich ein technisch notwendiges, HttpOnly-Sitzungscookie verwendet. Im späteren HTTPS-Betrieb wird es zusätzlich mit dem Secure-Flag gesetzt. Passwörter werden nicht im Klartext gespeichert.</p>
|
||||
<p class="legal-note">Diese Information ist eine technische Projektgrundlage und ersetzt keine individuelle rechtliche Prüfung. Vor einer öffentlichen Veröffentlichung sollten Hostinganbieter, Auftragsverarbeiter, Löschfristen und der externe Geocoding-Dienst konkret ergänzt und geprüft werden.</p>
|
||||
<h2>{{ _('Welche Daten werden gespeichert?') }}</h2>
|
||||
<p>{{ _('Für die Nutzung werden insbesondere Benutzername, E-Mail-Adresse, Passwort-Hash, Anzeigename, optionale Profil- und Instagram-Angaben, Profilbild, Freundschaften, Nachrichten, Veranstaltungsteilnahmen, gefolgte Bands und Locations, private Konzerttagebuch-Einträge, Kommentare, Fotos, Patches sowie von dir angelegte Veranstaltungen gespeichert.') }}</p>
|
||||
<h2>{{ _('Wofür werden sie verwendet?') }}</h2>
|
||||
<p>{{ _('Die Daten werden ausschließlich für Anmeldung, Kontoverwaltung, Veranstaltungsfunktionen, Freundschaften, Nachrichten, Benachrichtigungen und die von dir gewählten Sichtbarkeitseinstellungen verarbeitet.') }}</p>
|
||||
<h2>{{ _('Rechtsgrundlage und Speicherdauer') }}</h2>
|
||||
<p>{{ _('Die Verarbeitung erfolgt im geschlossenen Projektbetrieb zur Bereitstellung der gewünschten Funktionen und – soweit erforderlich – auf Grundlage deiner Einwilligung. Daten bleiben gespeichert, solange dein Konto besteht oder gesetzliche Aufbewahrungspflichten gelten. Nicht mehr benötigte Daten werden gelöscht.') }}</p>
|
||||
<h2>{{ _('Weitergabe und externe Dienste') }}</h2>
|
||||
<p>{{ _('Es werden keine Werbe- oder Trackingdienste eingesetzt. Bei der Veranstaltungsortsuche können Suchanfragen an einen externen Geocoding-Dienst übermittelt werden. Externe Flyer- und Instagram-Links werden beim Aufruf direkt von deinem Browser geladen; dafür gelten die Datenschutzbestimmungen des jeweiligen Anbieters.') }}</p>
|
||||
<h2>{{ _('Deine Rechte') }}</h2>
|
||||
<p>{{ _('Du kannst Auskunft, Berichtigung, Löschung, Einschränkung der Verarbeitung und – soweit anwendbar – Datenübertragbarkeit verlangen. Einen Export deiner gespeicherten Anwendungsdaten findest du klein am Ende des eigenen Profilbereichs. Anfragen bitte an') }} <a href="mailto:konzert@pinguholic.de">konzert@pinguholic.de</a>.</p>
|
||||
<h2>{{ _('Cookies und Sicherheit') }}</h2>
|
||||
<p>{{ _('Für die Anmeldung wird ausschließlich ein technisch notwendiges, HttpOnly-Sitzungscookie verwendet. Im späteren HTTPS-Betrieb wird es zusätzlich mit dem Secure-Flag gesetzt. Passwörter werden nicht im Klartext gespeichert.') }}</p>
|
||||
<p>{{ _('Ein technisch notwendiges Cookie speichert außerdem deine gewählte Sprache (Deutsch oder Englisch) für ein Jahr.') }}</p>
|
||||
<p class="legal-note">{{ _('Diese Information ist eine technische Projektgrundlage und ersetzt keine individuelle rechtliche Prüfung. Vor einer öffentlichen Veröffentlichung sollten Hostinganbieter, Auftragsverarbeiter, Löschfristen und der externe Geocoding-Dienst konkret ergänzt und geprüft werden.') }}</p>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
+37
-25
@@ -1,50 +1,62 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<html lang="{{ language() }}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Konzerttagebuch · MetalCircle</title>
|
||||
<title>{{ _('Konzerttagebuch · MetalCircle') }}</title>
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
<style>
|
||||
.diary-list{display:grid;gap:14px}.diary-entry{padding:18px;background:linear-gradient(145deg,#101010,#211111);border:1px solid var(--border);border-left:3px solid #b91c1c;border-radius:12px}.diary-entry h2{margin:0 0 5px}.diary-meta{color:var(--muted)}.diary-rating{color:#fbbf24;font-size:1.2rem}.diary-notes{white-space:pre-wrap}.diary-song{color:#fca5a5}.diary-gallery{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;margin:14px 0}.diary-gallery-item{position:relative;aspect-ratio:4/3;overflow:hidden;border-radius:9px;background:#050505}.diary-gallery-item img{width:100%;height:100%;object-fit:cover}.diary-gallery-delete{position:absolute;right:5px;top:5px;margin:0}.diary-gallery-delete button{width:30px;height:30px;padding:0;border-radius:50%;background:rgba(0,0,0,.82);color:#fff;border:1px solid #ef4444}.diary-patches{display:flex;flex-wrap:wrap;gap:10px;margin-top:14px}.diary-patch{display:flex;align-items:center;gap:8px;padding:8px 10px;background:#080808;border:1px solid #7f1d1d;border-radius:9px}.diary-patch img{width:48px;height:48px;object-fit:contain}.diary-patch-icon{font-size:1.7rem}.diary-edit{margin-top:16px;padding-top:12px;border-top:1px solid var(--border)}.diary-edit summary{color:#fca5a5;cursor:pointer;font-weight:700}.diary-form{display:grid;gap:8px;margin-top:14px;max-width:none}.diary-form select,.diary-form input,.diary-form textarea{width:100%}.diary-form textarea{min-height:130px;resize:vertical}.diary-empty-note{color:var(--muted)}
|
||||
.diary-list{display:grid;gap:14px}.diary-entry{background:linear-gradient(145deg,#101010,#211111);border:1px solid var(--border);border-left:3px solid #b91c1c;border-radius:12px}.diary-summary{position:relative;padding:18px 48px 18px 18px;cursor:pointer;list-style:none}.diary-summary::-webkit-details-marker{display:none}.diary-summary::after{content:"⌄";position:absolute;right:20px;top:50%;color:#fca5a5;font-size:1.5rem;transform:translateY(-50%);transition:transform .2s}.diary-entry[open]>.diary-summary::after{transform:translateY(-50%) rotate(180deg)}.diary-entry[open]>.diary-summary{border-bottom:1px solid var(--border)}.diary-entry h2{margin:0 0 5px}.diary-meta{color:var(--muted)}.diary-summary .diary-meta,.diary-summary .diary-empty-note{margin:5px 0}.diary-content{padding:4px 18px 18px}.diary-rating{color:#fbbf24;font-size:1.2rem}.diary-notes{white-space:pre-wrap}.diary-song{color:#fca5a5}.diary-gallery{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;margin:14px 0}.diary-gallery-item{position:relative;aspect-ratio:4/3;overflow:hidden;border-radius:9px;background:#050505}.diary-gallery-item img{width:100%;height:100%;object-fit:cover}.diary-gallery-delete{position:absolute;right:5px;top:5px;margin:0}.diary-gallery-delete button{width:30px;height:30px;padding:0;border-radius:50%;background:rgba(0,0,0,.82);color:#fff;border:1px solid #ef4444}.diary-patches{display:flex;flex-wrap:wrap;gap:10px;margin-top:14px}.diary-patch{display:flex;align-items:center;gap:8px;padding:8px 10px;background:#080808;border:1px solid #7f1d1d;border-radius:9px}.diary-patch img{width:48px;height:48px;object-fit:contain}.diary-patch-icon{font-size:1.7rem}.diary-edit{margin-top:16px;padding-top:12px;border-top:1px solid var(--border)}.diary-edit summary{color:#fca5a5;cursor:pointer;font-weight:700}.diary-form{display:grid;gap:8px;margin-top:14px;max-width:none}.diary-form select,.diary-form input,.diary-form textarea{width:100%}.diary-form textarea{min-height:130px;resize:vertical}.diary-delete-form{display:flex;justify-content:flex-end;width:100%;max-width:none;margin:18px 0 0}.diary-delete-link{padding:0;border:0;background:none;color:var(--muted);font-size:.78rem;text-decoration:underline;cursor:pointer}.diary-delete-link:hover{color:#fca5a5}.diary-empty-note{color:var(--muted)}
|
||||
</style>
|
||||
<link rel="stylesheet" href="/static/css/language.css">
|
||||
</head>
|
||||
<body>
|
||||
<header><div class="header-inner"><a href="/" class="logo"><img class="brand-logo" src="/static/images/metalcircle-full.png" alt="MetalCircle"></a>{% include '_user_menu.html' %}</div></header>
|
||||
<header><div class="header-inner"><a href="/" class="logo"><img class="brand-logo" src="/static/images/metalcircle-full.png" alt="MetalCircle"></a>{% include '_user_menu.html' %}</div> {% include '_language_switch.html' %}
|
||||
</header>
|
||||
<main>
|
||||
<div class="page-title"><h1>📓 Konzerttagebuch</h1><p>Besuchte Konzerte werden automatisch eingetragen. Deine Erinnerungen bleiben privat.</p></div>
|
||||
<div class="page-title"><h1>{{ _('📓 Konzerttagebuch') }}</h1><p>{{ _('Besuchte Konzerte werden automatisch eingetragen. Deine Erinnerungen bleiben privat.') }}</p></div>
|
||||
<div class="diary-list">
|
||||
{% for entry in entries %}
|
||||
<article class="diary-entry" id="concert-{{ entry.concert_id }}">
|
||||
<h2><a href="/concerts/{{ entry.concert_id }}">{{ entry.artist }}</a></h2>
|
||||
<p class="diary-meta">{{ entry.date }}{% if entry.venue %} · {{ entry.venue }}{% endif %}</p>
|
||||
{% if entry.rating %}<div class="diary-rating" aria-label="{{ entry.rating }} von 5 Sternen">{% for _ in range(entry.rating) %}★{% endfor %}{% for _ in range(5-entry.rating) %}☆{% endfor %}</div>{% else %}<p class="diary-empty-note">Noch nicht bewertet.</p>{% endif %}
|
||||
{% if entry.favorite_song %}<p class="diary-song">🎵 Lieblingssong: {{ entry.favorite_song }}</p>{% endif %}
|
||||
<details class="diary-entry" id="concert-{{ entry.concert_id }}">
|
||||
<summary class="diary-summary">
|
||||
<h2><a href="/concerts/{{ entry.concert_id }}">{{ entry.artist }}</a></h2>
|
||||
<p class="diary-meta">{{ entry.date }}{% if entry.venue %} · {{ entry.venue }}{% endif %}</p>
|
||||
{% if entry.rating %}<div class="diary-rating" aria-label="{{ entry.rating }} {{ _('von 5 Sternen') }}">{% for _ in range(entry.rating) %}★{% endfor %}{% for _ in range(5-entry.rating) %}☆{% endfor %}</div>{% else %}<p class="diary-empty-note">{{ _('Noch nicht bewertet.') }}</p>{% endif %}
|
||||
</summary>
|
||||
<div class="diary-content">
|
||||
{% if entry.favorite_song %}<p class="diary-song">{{ _('🎵 Lieblingssong:') }} {{ entry.favorite_song }}</p>{% endif %}
|
||||
{% if entry.notes %}<p class="diary-notes">{{ entry.notes }}</p>{% endif %}
|
||||
{% if entry.photos %}<div class="diary-gallery" aria-label="Bildergalerie zu {{ entry.artist }}">{% for photo in entry.photos %}<div class="diary-gallery-item"><img src="{{ photo.path }}" alt="Erinnerungsfoto {{ loop.index }} zu {{ entry.artist }}" loading="lazy"><form class="diary-gallery-delete" method="post" action="/diary/photos/{{ photo.id }}/delete" onsubmit="return confirm('Dieses Bild entfernen?');"><button type="submit" aria-label="Bild {{ loop.index }} entfernen">×</button></form></div>{% endfor %}</div>{% endif %}
|
||||
{% if entry.badges %}<div class="diary-patches">{% for badge in entry.badges %}<div class="diary-patch" title="{{ badge.description }}">{% if badge.image_path %}<img src="{{ badge.image_path }}" alt="Patch {{ badge.name }}">{% else %}<span class="diary-patch-icon">{{ badge.icon }}</span>{% endif %}<span><strong>{{ badge.name }}</strong><br><small>Hier erhalten · {{ badge.awarded_at }}</small></span></div>{% endfor %}</div>{% endif %}
|
||||
{% if entry.photos %}<div class="diary-gallery" aria-label="{{ _('Bildergalerie zu') }} {{ entry.artist }}">{% for photo in entry.photos %}<div class="diary-gallery-item"><img src="{{ photo.path }}" alt="{{ _('Erinnerungsfoto') }} {{ loop.index }} {{ _('zu') }} {{ entry.artist }}" loading="lazy"><form class="diary-gallery-delete" method="post" action="/diary/photos/{{ photo.id }}/delete" onsubmit="return confirm({{ _('Dieses Bild entfernen?') | tojson | forceescape }});"><button type="submit" aria-label="{{ _('Bild') }} {{ loop.index }} {{ _('entfernen') }}">×</button></form></div>{% endfor %}</div>{% endif %}
|
||||
{% if entry.badges %}<div class="diary-patches">{% for badge in entry.badges %}<div class="diary-patch" title="{{ badge.description | t }}">{% if badge.image_path %}<img src="{{ badge.image_path }}" alt="Patch {{ badge.name | t }}">{% else %}<span class="diary-patch-icon">{{ badge.icon }}</span>{% endif %}<span><strong>{{ badge.name | t }}</strong><br><small>{{ _('Hier erhalten ·') }} {{ badge.awarded_at }}</small></span></div>{% endfor %}</div>{% endif %}
|
||||
<details class="diary-edit" {% if not entry.rating and not entry.notes and not entry.favorite_song and not entry.photos %}open{% endif %}>
|
||||
<summary>Eintrag bearbeiten</summary>
|
||||
<summary>{{ _('Eintrag bearbeiten') }}</summary>
|
||||
<form class="diary-form" method="post" action="/concerts/{{ entry.concert_id }}/diary" enctype="multipart/form-data">
|
||||
<label>Bewertung <small>(optional)</small></label>
|
||||
<select name="rating"><option value="">Noch nicht bewertet</option>{% for value in range(1,6) %}<option value="{{ value }}" {% if entry.rating == value %}selected{% endif %}>{{ value }} von 5 Sternen</option>{% endfor %}</select>
|
||||
<label>Lieblingssong <small>(optional)</small></label>
|
||||
<label>{{ _('Bewertung') }} <small>(optional)</small></label>
|
||||
<select name="rating"><option value="">{{ _('Noch nicht bewertet') }}</option>{% for value in range(1,6) %}<option value="{{ value }}" {% if entry.rating == value %}selected{% endif %}>{{ value }} {{ _('von 5 Sternen') }}</option>{% endfor %}</select>
|
||||
<label>{{ _('Lieblingssong') }} <small>(optional)</small></label>
|
||||
<input name="favorite_song" maxlength="255" value="{{ entry.favorite_song }}">
|
||||
<label>Erinnerungen <small>(optional)</small></label>
|
||||
<textarea name="notes" maxlength="5000" placeholder="Was ist dir von diesem Abend geblieben?">{{ entry.notes }}</textarea>
|
||||
<label>Galeriebilder <small>({{ entry.photos|length }}/3, optional)</small></label>
|
||||
<label>{{ _('Erinnerungen') }} <small>(optional)</small></label>
|
||||
<textarea name="notes" maxlength="5000" placeholder="{{ _('Was ist dir von diesem Abend geblieben?') }}">{{ entry.notes }}</textarea>
|
||||
<label>{{ _('Galeriebilder') }} <small>({{ entry.photos|length }}/3, optional)</small></label>
|
||||
<input class="diary-photo-input" name="photos" type="file" accept="image/jpeg,image/png,image/webp" multiple data-free-slots="{{ 3-entry.photos|length }}" {% if entry.photos|length >= 3 %}disabled{% endif %}>
|
||||
<small class="diary-photo-status">Bis zu drei Bilder; große Bilder werden automatisch optimiert.</small>
|
||||
<button class="button" type="submit">Eintrag speichern</button>
|
||||
<small class="diary-photo-status">{{ _('Bis zu drei Bilder; große Bilder werden automatisch optimiert.') }}</small>
|
||||
<button class="button" type="submit">{{ _('Eintrag speichern') }}</button>
|
||||
</form>
|
||||
<form class="diary-delete-form" method="post" action="/concerts/{{ entry.concert_id }}/diary/delete" onsubmit="return confirm({{ _('Wirklich unwiderruflich löschen? Die Veranstaltung kann nicht mehr eingetragen werden.') | tojson | forceescape }});">
|
||||
<button class="diary-delete-link" type="submit">{{ _('Konzerteintrag löschen') }}</button>
|
||||
</form>
|
||||
</details>
|
||||
</article>
|
||||
</div>
|
||||
</details>
|
||||
{% else %}
|
||||
<section class="empty"><p>Noch keine Einträge. Vergangene zugesagte Konzerte erscheinen hier automatisch.</p></section>
|
||||
<section class="empty"><p>{{ _('Noch keine Einträge. Vergangene zugesagte Konzerte erscheinen hier automatisch.') }}</p></section>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</main>
|
||||
<script>
|
||||
const selectedEntry = document.querySelector(location.hash);
|
||||
if (selectedEntry?.classList.contains("diary-entry")) selectedEntry.open = true;
|
||||
|
||||
document.querySelectorAll(".diary-form").forEach(form => {
|
||||
form.addEventListener("submit", async event => {
|
||||
if (form.dataset.optimized === "true") return;
|
||||
@@ -56,7 +68,7 @@ document.querySelectorAll(".diary-form").forEach(form => {
|
||||
const button = form.querySelector('button[type="submit"]');
|
||||
button.disabled = true;
|
||||
if (files.length > Number(input.dataset.freeSlots)) {
|
||||
status.textContent = `Du kannst noch ${input.dataset.freeSlots} Bild(er) hinzufügen.`;
|
||||
status.textContent = {{ _('Du kannst noch {count} Bild(er) hinzufügen.') | tojson }}.replace('{count}', input.dataset.freeSlots);
|
||||
button.disabled = false; return;
|
||||
}
|
||||
status.textContent = "Erinnerungsfoto wird optimiert …";
|
||||
@@ -81,7 +93,7 @@ document.querySelectorAll(".diary-form").forEach(form => {
|
||||
form.requestSubmit();
|
||||
} catch (error) {
|
||||
console.error("Diary image optimization failed:", error);
|
||||
status.textContent = "Das Bild konnte nicht optimiert werden. Bitte ein kleineres JPG, PNG oder WebP wählen.";
|
||||
status.textContent = {{ _('Das Bild konnte nicht optimiert werden. Bitte ein kleineres JPG, PNG oder WebP wählen.') | tojson }};
|
||||
button.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<html lang="{{ language() }}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ concert.artist }} bearbeiten · MetalCircle</title>
|
||||
<title>{{ concert.artist }} {{ _('bearbeiten · MetalCircle') }}</title>
|
||||
<link rel="icon" type="image/png" href="/static/images/metalcircle-circle.png">
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
<link rel="stylesheet" href="/static/css/language.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
@@ -13,127 +14,128 @@
|
||||
<a href="/" class="logo"><img class="brand-logo" src="/static/images/metalcircle-full.png" alt="MetalCircle"></a>
|
||||
{% include '_user_menu.html' %}
|
||||
</div>
|
||||
{% include '_language_switch.html' %}
|
||||
</header>
|
||||
<main>
|
||||
<div class="page-title">
|
||||
<h1>✏️ Veranstaltung bearbeiten</h1>
|
||||
<p>Änderungen werden direkt bei der Veranstaltung gespeichert.</p>
|
||||
<h1>{{ _('✏️ Veranstaltung bearbeiten') }}</h1>
|
||||
<p>{{ _('Änderungen werden direkt bei der Veranstaltung gespeichert.') }}</p>
|
||||
</div>
|
||||
<section class="empty">
|
||||
<form method="post" action="/concerts/{{ concert.id }}/edit" enctype="multipart/form-data">
|
||||
{% if can_edit_details %}
|
||||
<p>
|
||||
<label>Kategorie<br>
|
||||
<label>{{ _('Kategorie') }}<br>
|
||||
<select name="event_type" id="event-type" required {% if not can_manage_access %}disabled{% endif %}>
|
||||
<option value="concert" {% if concert.event_type == 'concert' %}selected{% endif %}>Konzert</option>
|
||||
<option value="concert" {% if concert.event_type == 'concert' %}selected{% endif %}>{{ _('Konzert') }}</option>
|
||||
<option value="festival" {% if concert.event_type == 'festival' %}selected{% endif %}>Festival</option>
|
||||
<option value="other" {% if concert.event_type == 'other' %}selected{% endif %}>Sonstiges</option>
|
||||
<option value="other" {% if concert.event_type == 'other' %}selected{% endif %}>{{ _('Sonstiges') }}</option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p id="parent-event-field" {% if concert.event_type != 'other' %}hidden{% endif %}>
|
||||
<label>Gehört zu<br>
|
||||
<label>{{ _('Gehört zu') }}<br>
|
||||
<select name="parent_event_id" id="parent-event-id">
|
||||
<option value="">Keine Verknüpfung – eigenständige Karte</option>
|
||||
<option value="">{{ _('Keine Verknüpfung – eigenständige Karte') }}</option>
|
||||
{% for event in linkable_events %}
|
||||
<option value="{{ event.id }}" {% if concert.parent_event and concert.parent_event.id == event.id %}selected{% endif %}>{{ event.event_type_label }} · {{ event.title }} · {{ event.date }}</option>
|
||||
<option value="{{ event.id }}" {% if concert.parent_event and concert.parent_event.id == event.id %}selected{% endif %}>{{ event.event_type_label | t }} · {{ event.title }} · {{ event.date }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
{% if can_manage_access %}<fieldset id="event-visibility-field" {% if concert.event_type != 'other' %}hidden{% endif %}>
|
||||
<legend>Sichtbarkeit</legend>
|
||||
<label><input type="radio" name="visibility" value="public" {% if concert.visibility == 'public' %}checked{% endif %}> Öffentlich</label><br>
|
||||
<label><input type="radio" name="visibility" value="friends" {% if concert.visibility == 'friends' %}checked{% endif %}> Nur Freunde</label><br>
|
||||
<label><input type="radio" name="visibility" value="private" {% if concert.visibility == 'private' %}checked{% endif %}> Privat – nur eingeladene Mitglieder</label>
|
||||
<legend>{{ _('Sichtbarkeit') }}</legend>
|
||||
<label><input type="radio" name="visibility" value="public" {% if concert.visibility == 'public' %}checked{% endif %}> {{ _('Öffentlich') }}</label><br>
|
||||
<label><input type="radio" name="visibility" value="friends" {% if concert.visibility == 'friends' %}checked{% endif %}> {{ _('Nur Freunde') }}</label><br>
|
||||
<label><input type="radio" name="visibility" value="private" {% if concert.visibility == 'private' %}checked{% endif %}> {{ _('Privat – nur eingeladene Mitglieder') }}</label>
|
||||
</fieldset>
|
||||
<fieldset id="event-invitations-field" {% if concert.event_type != 'other' or concert.visibility != 'private' %}hidden{% endif %}>
|
||||
<legend>Mitglieder einladen</legend>
|
||||
<legend>{{ _('Mitglieder einladen') }}</legend>
|
||||
{% for member in invitable_users %}
|
||||
<label style="display:block;margin:7px 0"><input type="checkbox" name="invited_user_ids" value="{{ member.id }}" {% if member.id in invited_user_ids %}checked{% endif %}> {{ member.display_name }} <small>@{{ member.username }}</small></label>
|
||||
{% else %}<p>Noch keine weiteren Mitglieder vorhanden.</p>{% endfor %}
|
||||
{% else %}<p>{{ _('Noch keine weiteren Mitglieder vorhanden.') }}</p>{% endfor %}
|
||||
</fieldset>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<p>
|
||||
<label>Titel / Künstler / Band<br>
|
||||
<label>{{ _('Titel / Künstler / Band') }}<br>
|
||||
<input type="text" name="artist" value="{{ concert.artist }}" {% if not can_edit_title %}disabled{% endif %} required>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label>Bands / Line-up <small>(eine Band pro Zeile)</small><br>
|
||||
<label>{{ _('Bands / Line-up') }} <small>{{ _('(eine Band pro Zeile)') }}</small><br>
|
||||
<textarea name="band_names" rows="3" maxlength="4000" {% if not can_edit_title %}disabled{% endif %}>{{ band_names }}</textarea>
|
||||
</label>
|
||||
<small>Diese Namen werden einzeln für die Folgen-Funktion verwendet.</small>
|
||||
<small>{{ _('Diese Namen werden einzeln für die Folgen-Funktion verwendet.') }}</small>
|
||||
</p>
|
||||
{% if can_edit_details %}
|
||||
<p>
|
||||
<label>Veranstaltungsort<br>
|
||||
<input type="text" name="venue_name" value="{{ concert.venue.name if concert.venue.id else '' }}" placeholder="Name des Veranstaltungsorts">
|
||||
<label>{{ _('Veranstaltungsort') }}<br>
|
||||
<input type="text" name="venue_name" value="{{ concert.venue.name if concert.venue.id else '' }}" placeholder="{{ _('Name des Veranstaltungsorts') }}">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label>Straße<br>
|
||||
<label>{{ _('Straße') }}<br>
|
||||
<input type="text" name="street" value="{{ concert.venue.street or '' }}">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label>PLZ und Ort<br>
|
||||
<input type="text" name="postal_code" value="{{ concert.venue.postal_code or '' }}" placeholder="PLZ">
|
||||
<input type="text" name="city" value="{{ concert.venue.city or '' }}" placeholder="Ort">
|
||||
<label>{{ _('PLZ und Ort') }}<br>
|
||||
<input type="text" name="postal_code" value="{{ concert.venue.postal_code or '' }}" placeholder="{{ _('PLZ') }}">
|
||||
<input type="text" name="city" value="{{ concert.venue.city or '' }}" placeholder="{{ _('Ort') }}">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label>Land<br>
|
||||
<input type="text" name="country" value="{{ concert.venue.country or '' }}" placeholder="Land">
|
||||
<label>{{ _('Land') }}<br>
|
||||
<input type="text" name="country" value="{{ concert.venue.country or '' }}" placeholder="{{ _('Land') }}">
|
||||
</label>
|
||||
</p>
|
||||
<input type="hidden" name="latitude" value="{{ concert.venue.latitude or '' }}">
|
||||
<input type="hidden" name="longitude" value="{{ concert.venue.longitude or '' }}">
|
||||
<p>
|
||||
<label>Beginn<br>
|
||||
<label>{{ _('Beginn') }}<br>
|
||||
<input type="datetime-local" name="start_datetime" value="{{ concert.start_local }}" required>
|
||||
</label>
|
||||
</p>
|
||||
<p id="festival-end-field" {% if concert.event_type != 'festival' %}hidden{% endif %}>
|
||||
<label>Ende<br>
|
||||
<label>{{ _('Ende') }}<br>
|
||||
<input type="datetime-local" name="end_datetime" id="festival-end-datetime" value="{{ concert.end_local }}" {% if concert.event_type == 'festival' %}required{% endif %}>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label>Beschreibung<br>
|
||||
<label>{{ _('Beschreibung') }}<br>
|
||||
<textarea name="description" rows="5">{{ concert.description or '' }}</textarea>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label>Ticket-Link<br>
|
||||
<label>{{ _('Ticket-Link') }}<br>
|
||||
<input type="url" name="ticket_url" value="{{ concert.ticket_url or '' }}" placeholder="https://...">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label>Ticketpreis<br>
|
||||
<label>{{ _('Ticketpreis') }}<br>
|
||||
<input type="number" name="ticket_price" value="{{ concert.ticket_price or '' }}" step="0.01" min="0">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label>Flyer extern verlinken <small>(bevorzugt)</small><br>
|
||||
<label>{{ _('Flyer extern verlinken') }} <small>{{ _('(bevorzugt)') }}</small><br>
|
||||
<input type="url" name="flyer_url" value="{{ concert.flyer_url or '' }}" placeholder="https://veranstalter.example/flyer.jpg">
|
||||
</label>
|
||||
<small>Bitte möglichst die offizielle Quelle verwenden. Ein externer Link wird gegenüber einem Upload bevorzugt und unter dem Bild genannt. Feld leeren, um wieder den Upload zu verwenden.</small>
|
||||
<small>{{ _('Bitte möglichst die offizielle Quelle verwenden. Ein externer Link wird gegenüber einem Upload bevorzugt und unter dem Bild genannt. Feld leeren, um wieder den Upload zu verwenden.') }}</small>
|
||||
</p>
|
||||
<p>
|
||||
<label>Neuen Flyer hochladen<br>
|
||||
<label>{{ _('Neuen Flyer hochladen') }}<br>
|
||||
<input type="file" name="flyer" accept="image/jpeg,image/png,image/webp">
|
||||
</label>
|
||||
<small>Ohne neue Datei bleibt der bisherige Flyer erhalten.</small>
|
||||
<small>{{ _('Ohne neue Datei bleibt der bisherige Flyer erhalten.') }}</small>
|
||||
</p>
|
||||
{% if concert.flyer_url or concert.flyer_path %}
|
||||
<p><img src="{{ concert.flyer_url or concert.flyer_path }}" alt="Aktueller Flyer" referrerpolicy="no-referrer" style="max-width: 300px; max-height: 400px; border-radius: 10px;"></p>
|
||||
<p><img src="{{ concert.flyer_url or concert.flyer_path }}" alt="{{ _('Aktueller Flyer') }}" referrerpolicy="no-referrer" style="max-width: 300px; max-height: 400px; border-radius: 10px;"></p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<div>
|
||||
<button type="submit" class="button">💾 Änderungen speichern</button>
|
||||
<a href="/concerts/{{ concert.id }}" class="button button-secondary">Abbrechen</a>
|
||||
<button type="submit" class="button">{{ _('💾 Änderungen speichern') }}</button>
|
||||
<a href="/concerts/{{ concert.id }}" class="button button-secondary">{{ _('Abbrechen') }}</a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<!DOCTYPE html><html lang="de"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Gefolgt · MetalCircle</title><link rel="stylesheet" href="/static/css/style.css"><style>
|
||||
<!DOCTYPE html><html lang="{{ language() }}"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>{{ _('Gefolgt · MetalCircle') }}</title><link rel="stylesheet" href="/static/css/style.css"><style>
|
||||
.follow-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:16px}.follow-card{padding:18px;background:var(--surface);border:1px solid var(--border);border-radius:12px}.follow-list{display:flex;flex-wrap:wrap;gap:8px;padding:0;list-style:none}.follow-list li{display:flex;align-items:center;gap:6px;padding:7px 10px;background:#171212;border:1px solid var(--border);border-radius:999px}.follow-list form{margin:0}.follow-remove{padding:0;color:#fca5a5;background:none;border:0;cursor:pointer;font-size:1rem}.follow-events{display:grid;gap:10px}.follow-event{display:block;padding:14px;background:#0b0b0b;border:1px solid var(--border);border-radius:10px}.follow-event:hover{border-color:#ef4444}.follow-reason{color:var(--muted);font-size:.82rem}
|
||||
</style></head><body><header><div class="header-inner"><a href="/" class="logo"><img class="brand-logo" src="/static/images/metalcircle-full.png" alt="MetalCircle"></a>{% include '_user_menu.html' %}</div></header><main>
|
||||
<div class="page-title"><h1>⭐ Gefolgt</h1><p>Bands, Locations und passende kommende Veranstaltungen.</p></div>
|
||||
<div class="follow-grid"><section class="follow-card"><h2>Bands</h2><ul class="follow-list">{% for band in followed_bands %}<li>{{ band.name }}<form method="post" action="/following/bands/remove"><input type="hidden" name="band_key" value="{{ band.key }}"><button class="follow-remove" type="submit" title="Nicht mehr folgen" aria-label="{{ band.name }} nicht mehr folgen">×</button></form></li>{% else %}<li>Noch keine Band gefolgt.</li>{% endfor %}</ul></section>
|
||||
<section class="follow-card"><h2>Locations</h2><ul class="follow-list">{% for venue in followed_venues %}<li>{{ venue.name }}{% if venue.city %}, {{ venue.city }}{% endif %}<form method="post" action="/following/venues/remove"><input type="hidden" name="venue_id" value="{{ venue.id }}"><button class="follow-remove" type="submit" title="Nicht mehr folgen" aria-label="{{ venue.name }} nicht mehr folgen">×</button></form></li>{% else %}<li>Noch keiner Location gefolgt.</li>{% endfor %}</ul></section></div>
|
||||
<section class="follow-card" style="margin-top:18px"><h2>Kommende Treffer</h2><div class="follow-events">{% for event in events %}<a class="follow-event" href="/concerts/{{ event.id }}"><strong>{{ event.artist }}</strong><br>{{ event.date }} · {{ event.time }}{% if event.venue %} · {{ event.venue }}{% endif %}<div class="follow-reason">{% if event.matched_band %}Band{% endif %}{% if event.matched_band and event.matched_venue %} und {% endif %}{% if event.matched_venue %}Location{% endif %} gefolgt</div></a>{% else %}<p>Momentan gibt es keine kommenden Treffer.</p>{% endfor %}</div></section>
|
||||
</style><link rel="stylesheet" href="/static/css/language.css">
|
||||
</head><body><header><div class="header-inner"><a href="/" class="logo"><img class="brand-logo" src="/static/images/metalcircle-full.png" alt="MetalCircle"></a>{% include '_user_menu.html' %}</div> {% include '_language_switch.html' %}
|
||||
</header><main>
|
||||
<div class="page-title"><h1>{{ _('⭐ Gefolgt') }}</h1><p>{{ _('Bands, Locations und passende kommende Veranstaltungen.') }}</p></div>
|
||||
<div class="follow-grid"><section class="follow-card"><h2>Bands</h2><ul class="follow-list">{% for band in followed_bands %}<li>{{ band.name }}<form method="post" action="/following/bands/remove"><input type="hidden" name="band_key" value="{{ band.key }}"><button class="follow-remove" type="submit" title="{{ _('Nicht mehr folgen') }}" aria-label="{{ band.name }} {{ _('nicht mehr folgen') }}">×</button></form></li>{% else %}<li>{{ _('Noch keine Band gefolgt.') }}</li>{% endfor %}</ul></section>
|
||||
<section class="follow-card"><h2>{{ _('Locations') }}</h2><ul class="follow-list">{% for venue in followed_venues %}<li>{{ venue.name }}{% if venue.city %}, {{ venue.city }}{% endif %}<form method="post" action="/following/venues/remove"><input type="hidden" name="venue_id" value="{{ venue.id }}"><button class="follow-remove" type="submit" title="{{ _('Nicht mehr folgen') }}" aria-label="{{ venue.name }} {{ _('nicht mehr folgen') }}">×</button></form></li>{% else %}<li>{{ _('Noch keiner Location gefolgt.') }}</li>{% endfor %}</ul></section></div>
|
||||
<section class="follow-card" style="margin-top:18px"><h2>{{ _('Kommende Treffer') }}</h2><div class="follow-events">{% for event in events %}<a class="follow-event" href="/concerts/{{ event.id }}"><strong>{{ event.artist }}</strong><br>{{ event.date }} · {{ event.time }}{% if event.venue %} · {{ event.venue }}{% endif %}<div class="follow-reason">{% if event.matched_band %}Band{% endif %}{% if event.matched_band and event.matched_venue %} {{ _('und') }} {% endif %}{% if event.matched_venue %}{{ _('Location') }}{% endif %} {{ _('gefolgt') }}</div></a>{% else %}<p>{{ _('Momentan gibt es keine kommenden Treffer.') }}</p>{% endfor %}</div></section>
|
||||
</main></body></html>
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Impressum · MetalCircle</title><link rel="icon" type="image/png" href="/static/images/metalcircle-circle.png"><link rel="stylesheet" href="/static/css/style.css"></head>
|
||||
<html lang="{{ language() }}">
|
||||
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>{{ _('Impressum · MetalCircle') }}</title><link rel="icon" type="image/png" href="/static/images/metalcircle-circle.png"><link rel="stylesheet" href="/static/css/style.css"><link rel="stylesheet" href="/static/css/language.css">
|
||||
</head>
|
||||
<body>
|
||||
<header><div class="header-inner"><a href="/" class="logo"><img class="brand-logo" src="/static/images/metalcircle-full.png" alt="MetalCircle"></a></div></header>
|
||||
<header><div class="header-inner"><a href="/" class="logo"><img class="brand-logo" src="/static/images/metalcircle-full.png" alt="MetalCircle"></a></div> {% include '_language_switch.html' %}
|
||||
</header>
|
||||
<main>
|
||||
<div class="page-title"><h1>Impressum</h1><p>Anbieterkennzeichnung</p></div>
|
||||
<div class="page-title"><h1>{{ _('Impressum') }}</h1><p>{{ _('Anbieterkennzeichnung') }}</p></div>
|
||||
<section class="admin-section legal-copy">
|
||||
<h2>MetalCircle</h2>
|
||||
<p><strong>Verantwortlich für den Inhalt</strong></p>
|
||||
<p>Kai Piekny<br>Fleyerstr. 33<br>58097 Hagen<br>Deutschland</p>
|
||||
<p>E-Mail: <a href="mailto:konzert@pinguholic.de">konzert@pinguholic.de</a></p>
|
||||
<p class="legal-note">Dieses private Projekt befindet sich derzeit im geschlossenen Betrieb. Vor einer Veröffentlichung werden die Angaben und rechtlichen Anforderungen nochmals geprüft.</p>
|
||||
<p><strong>{{ _('Verantwortlich für den Inhalt') }}</strong></p>
|
||||
<p>Kai Piekny<br>Fleyerstr. 33<br>58097 Hagen<br>{{ _('Deutschland') }}</p>
|
||||
<p>{{ _('E-Mail:') }} <a href="mailto:konzert@pinguholic.de">konzert@pinguholic.de</a></p>
|
||||
<p class="legal-note">{{ _('Dieses private Projekt befindet sich derzeit im geschlossenen Betrieb. Vor einer Veröffentlichung werden die Angaben und rechtlichen Anforderungen nochmals geprüft.') }}</p>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
+25
-23
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<html lang="{{ language() }}">
|
||||
|
||||
<head>
|
||||
|
||||
@@ -204,19 +204,20 @@
|
||||
|
||||
</style>
|
||||
|
||||
<link rel="stylesheet" href="/static/css/language.css">
|
||||
</head>
|
||||
|
||||
{% macro event_card(concert, linked=false) %}
|
||||
<a href="/concerts/{{ concert.id }}" class="concert-card{% if linked %} linked-event{% endif %}">
|
||||
<span class="event-category">{{ concert.event_type_label }}</span>
|
||||
{% if concert.is_invited %}<span class="event-category"> · ✉ Eingeladen</span>{% elif concert.visibility == 'friends' %}<span class="event-category"> · 🔒 Nur Freunde</span>{% elif concert.visibility == 'private' %}<span class="event-category"> · 🔐 Privat</span>{% endif %}
|
||||
<span class="event-category">{{ concert.event_type_label | t }}</span>
|
||||
{% if concert.is_invited %}<span class="event-category"> {{ _('· ✉ Eingeladen') }}</span>{% elif concert.visibility == 'friends' %}<span class="event-category"> {{ _('· 🔒 Nur Freunde') }}</span>{% elif concert.visibility == 'private' %}<span class="event-category"> {{ _('· 🔐 Privat') }}</span>{% endif %}
|
||||
<div class="concert-artist">
|
||||
{% if concert.event_type == 'festival' %}🎪{% elif concert.event_type == 'other' %}🥂{% else %}🎸{% endif %}
|
||||
{{ concert.artist }}
|
||||
</div>
|
||||
<div class="concert-meta">
|
||||
<div class="concert-date">
|
||||
📅 {{ concert.date }}{% if concert.end_date %} – {{ concert.end_date }}{% else %} um {{ concert.time }} Uhr{% endif %}
|
||||
📅 {{ concert.date }}{% if concert.end_date %} – {{ concert.end_date }}{% else %} {{ _('um') }} {{ concert.time }} {{ _('Uhr') }}{% endif %}
|
||||
</div>
|
||||
<div>📍 {{ concert.venue }}</div>
|
||||
</div>
|
||||
@@ -233,7 +234,7 @@
|
||||
<div>
|
||||
|
||||
<h1>
|
||||
{% if archive %}☠ Vergangene Veranstaltungen{% else %}<img class="brand-logo" src="/static/images/metalcircle-full.png" alt="MetalCircle">{% endif %}
|
||||
{% if archive %}{{ _('☠ Vergangene Veranstaltungen') }}{% else %}<img class="brand-logo" src="/static/images/metalcircle-full.png" alt="MetalCircle">{% endif %}
|
||||
</h1>
|
||||
|
||||
<div class="subtitle">
|
||||
@@ -246,42 +247,43 @@
|
||||
<div class="header-actions">
|
||||
|
||||
<a href="{% if archive %}/{% else %}/events/past{% endif %}" class="admin-button">
|
||||
{% if archive %}⚡ Bevorstehend{% else %}☠ Vergangene{% endif %}
|
||||
{% if archive %}{{ _('⚡ Bevorstehend') }}{% else %}{{ _('☠ Vergangene') }}{% endif %}
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="/concerts/new"
|
||||
class="new-concert-button"
|
||||
>
|
||||
+ Veranstaltung hinzufügen
|
||||
{{ _('+ Veranstaltung hinzufügen') }}
|
||||
</a>
|
||||
|
||||
{% include '_language_switch.html' %}
|
||||
{% include '_user_menu.html' %}
|
||||
|
||||
</div>
|
||||
|
||||
</header>
|
||||
|
||||
<section class="search-panel" aria-label="Suche">
|
||||
<section class="search-panel" aria-label="{{ _('Suche') }}">
|
||||
<form class="search-form" method="get" action="{% if archive %}/events/past{% else %}/{% endif %}">
|
||||
<input type="search" name="q" value="{{ search_query }}" placeholder="Veranstaltungen, Orte oder User suchen …" aria-label="Veranstaltungen oder User suchen">
|
||||
<button class="new-concert-button" type="submit">Suchen</button>
|
||||
<input type="search" name="q" value="{{ search_query }}" placeholder="{{ _('Veranstaltungen, Orte oder User suchen …') }}" aria-label="{{ _('Veranstaltungen oder User suchen') }}">
|
||||
<button class="new-concert-button" type="submit">{{ _('Suchen') }}</button>
|
||||
<details class="advanced-filters" {% if filters.date_from or filters.date_to or filters.venue or filters.country or filters.category %}open{% endif %}>
|
||||
<summary>Weitere Filter{% if filters.date_from or filters.date_to or filters.venue or filters.country or filters.category %} · aktiv{% endif %}</summary>
|
||||
<summary>{{ _('Weitere Filter') }}{% if filters.date_from or filters.date_to or filters.venue or filters.country or filters.category %} {{ _('· aktiv') }}{% endif %}</summary>
|
||||
<div class="advanced-filter-fields">
|
||||
<label>Von Datum<input type="date" name="date_from" value="{{ filters.date_from }}"></label>
|
||||
<label>Bis Datum<input type="date" name="date_to" value="{{ filters.date_to }}"></label>
|
||||
<label>Ort / Location<input type="search" name="venue" value="{{ filters.venue }}" placeholder="z. B. Düsseldorf"></label>
|
||||
<label>Land<input type="search" name="country" value="{{ filters.country }}" placeholder="z. B. Deutschland"></label>
|
||||
<label>Kategorie<select name="category"><option value="">Alle Kategorien</option><option value="concert" {% if filters.category == 'concert' %}selected{% endif %}>Konzert</option><option value="festival" {% if filters.category == 'festival' %}selected{% endif %}>Festival</option><option value="other" {% if filters.category == 'other' %}selected{% endif %}>Sonstiges</option></select></label>
|
||||
<label>{{ _('Von Datum') }}<input type="date" name="date_from" value="{{ filters.date_from }}"></label>
|
||||
<label>{{ _('Bis Datum') }}<input type="date" name="date_to" value="{{ filters.date_to }}"></label>
|
||||
<label>{{ _('Ort / Location') }}<input type="search" name="venue" value="{{ filters.venue }}" placeholder="{{ _('z. B. Düsseldorf') }}"></label>
|
||||
<label>{{ _('Land') }}<input type="search" name="country" value="{{ filters.country }}" placeholder="{{ _('z. B. Deutschland') }}"></label>
|
||||
<label>{{ _('Kategorie') }}<select name="category"><option value="">{{ _('Alle Kategorien') }}</option><option value="concert" {% if filters.category == 'concert' %}selected{% endif %}>{{ _('Konzert') }}</option><option value="festival" {% if filters.category == 'festival' %}selected{% endif %}>Festival</option><option value="other" {% if filters.category == 'other' %}selected{% endif %}>{{ _('Sonstiges') }}</option></select></label>
|
||||
</div>
|
||||
{% if filters.date_from or filters.date_to or filters.venue or filters.country or filters.category %}<a class="clear-filters" href="{% if archive %}/events/past{% else %}/{% endif %}{% if search_query %}?q={{ search_query|urlencode }}{% endif %}">Filter löschen</a>{% endif %}
|
||||
{% if filters.date_from or filters.date_to or filters.venue or filters.country or filters.category %}<a class="clear-filters" href="{% if archive %}/events/past{% else %}/{% endif %}{% if search_query %}?q={{ search_query|urlencode }}{% endif %}">{{ _('Filter löschen') }}</a>{% endif %}
|
||||
</details>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
{% if search_query %}
|
||||
<h2 class="search-results-title">User zu „{{ search_query }}“</h2>
|
||||
<h2 class="search-results-title">{{ _('User zu „') }}{{ search_query }}“</h2>
|
||||
{% if user_results %}
|
||||
<div class="user-results">
|
||||
{% for result in user_results %}
|
||||
@@ -291,8 +293,8 @@
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}<p class="subtitle">Keine passenden User gefunden.</p>{% endif %}
|
||||
<h2 class="search-results-title">Veranstaltungen zu „{{ search_query }}“</h2>
|
||||
{% else %}<p class="subtitle">{{ _('Keine passenden User gefunden.') }}</p>{% endif %}
|
||||
<h2 class="search-results-title">{{ _('Veranstaltungen zu „') }}{{ search_query }}“</h2>
|
||||
{% endif %}
|
||||
|
||||
|
||||
@@ -315,7 +317,7 @@
|
||||
{% if archive and past_concerts %}
|
||||
|
||||
<h2 class="past-concerts-title">
|
||||
Archiv
|
||||
{{ _('Archiv') }}
|
||||
</h2>
|
||||
|
||||
{% for concert in past_concerts %}
|
||||
@@ -339,11 +341,11 @@
|
||||
<div class="empty-state">
|
||||
|
||||
<h2>
|
||||
{% if archive %}Noch keine vergangenen Veranstaltungen ☠{% else %}Noch keine Veranstaltungen 🎸{% endif %}
|
||||
{% if archive %}{{ _('Noch keine vergangenen Veranstaltungen ☠') }}{% else %}{{ _('Noch keine Veranstaltungen 🎸') }}{% endif %}
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
{% if archive %}Hier erscheinen abgeschlossene Veranstaltungen.{% else %}Leg die erste Veranstaltung an!{% endif %}
|
||||
{% if archive %}{{ _('Hier erscheinen abgeschlossene Veranstaltungen.') }}{% else %}{{ _('Leg die erste Veranstaltung an!') }}{% endif %}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,39 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<html lang="{{ language() }}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Anmelden · MetalCircle</title>
|
||||
<title>{{ _('Anmelden · MetalCircle') }}</title>
|
||||
<link rel="icon" type="image/png" href="/static/images/metalcircle-circle.png">
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
<link rel="stylesheet" href="/static/css/language.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="header-inner">
|
||||
<a href="/" class="logo"><img class="brand-logo" src="/static/images/metalcircle-full.png" alt="MetalCircle"></a>
|
||||
</div>
|
||||
{% include '_language_switch.html' %}
|
||||
</header>
|
||||
<main>
|
||||
<div class="page-title">
|
||||
<h1>Anmelden</h1>
|
||||
<p>Melde dich an, um Konzerte hinzuzufügen oder zu bearbeiten.</p>
|
||||
<h1>{{ _('Anmelden') }}</h1>
|
||||
<p>{{ _('Melde dich an, um Konzerte hinzuzufügen oder zu bearbeiten.') }}</p>
|
||||
</div>
|
||||
<section class="empty">
|
||||
<form method="post" action="/login">
|
||||
<input type="hidden" name="next" value="{{ next_path }}">
|
||||
{% if error %}<p role="alert">{{ error }}</p>{% endif %}
|
||||
{% if error %}<p role="alert">{{ error | t }}</p>{% endif %}
|
||||
<p>
|
||||
<label>Benutzername oder E-Mail<br>
|
||||
<label>{{ _('Benutzername oder E-Mail') }}<br>
|
||||
<input type="text" name="username" required autocomplete="username">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label>Passwort<br>
|
||||
<label>{{ _('Passwort') }}<br>
|
||||
<input type="password" name="password" required autocomplete="current-password">
|
||||
</label>
|
||||
</p>
|
||||
<button type="submit" class="button">Anmelden</button>
|
||||
<p><small>Passwort vergessen? Bitte einen Admin um einen zeitlich begrenzten Reset-Link.</small></p>
|
||||
<button type="submit" class="button">{{ _('Anmelden') }}</button>
|
||||
<p><small>{{ _('Passwort vergessen? Bitte einen Admin um einen zeitlich begrenzten Reset-Link.') }}</small></p>
|
||||
</form>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
+18
-16
@@ -1,9 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<html lang="{{ language() }}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nachrichten · MetalCircle</title>
|
||||
<title>{{ _('Nachrichten · MetalCircle') }}</title>
|
||||
<link rel="icon" type="image/png" href="/static/images/metalcircle-circle.png">
|
||||
<link rel="stylesheet" href="/static/css/style.css">
|
||||
<style>
|
||||
@@ -38,9 +38,11 @@
|
||||
.conversation-heading { margin-top: 20px; }
|
||||
@media (max-width: 600px) { .chat-form { grid-template-columns: 1fr; } .chat-bubble { max-width: 90%; } }
|
||||
</style>
|
||||
<link rel="stylesheet" href="/static/css/language.css">
|
||||
</head>
|
||||
<body>
|
||||
<header><div class="header-inner"><a href="/" class="logo"><img class="brand-logo" src="/static/images/metalcircle-full.png" alt="MetalCircle"></a>{% include '_user_menu.html' %}</div></header>
|
||||
<header><div class="header-inner"><a href="/" class="logo"><img class="brand-logo" src="/static/images/metalcircle-full.png" alt="MetalCircle"></a>{% include '_user_menu.html' %}</div> {% include '_language_switch.html' %}
|
||||
</header>
|
||||
<main>
|
||||
{% if partner %}
|
||||
<div class="chat-header">
|
||||
@@ -49,43 +51,43 @@
|
||||
</div>
|
||||
<div class="chat-log">
|
||||
{% for message in messages %}<div class="chat-bubble{% if message.from_me %} mine{% endif %}"{% if loop.last %} id="latest"{% endif %}>{{ message.body }}<span class="chat-time">{{ message.created_at }}</span></div>
|
||||
{% else %}<p class="empty-chat">Noch keine Nachrichten. Schreib die erste!</p>{% endfor %}
|
||||
{% else %}<p class="empty-chat">{{ _('Noch keine Nachrichten. Schreib die erste!') }}</p>{% endfor %}
|
||||
</div>
|
||||
<form class="chat-form" method="post" action="/messages/{{ partner.username }}">
|
||||
<textarea name="body" maxlength="2000" required placeholder="Nachricht schreiben …" aria-label="Nachricht"></textarea>
|
||||
<button class="button" type="submit">Senden</button>
|
||||
<textarea name="body" maxlength="2000" required placeholder="{{ _('Nachricht schreiben …') }}" aria-label="{{ _('Nachricht') }}"></textarea>
|
||||
<button class="button" type="submit">{{ _('Senden') }}</button>
|
||||
</form>
|
||||
<p><a class="button button-secondary" href="/messages">← Alle Nachrichten</a></p>
|
||||
<p><a class="button button-secondary" href="/messages">{{ _('← Alle Nachrichten') }}</a></p>
|
||||
{% else %}
|
||||
<div class="page-title"><h1>🤘 Nachrichten</h1><p>Private Chats mit deinen Freunden.</p></div>
|
||||
<div class="page-title"><h1>{{ _('🤘 Nachrichten') }}</h1><p>{{ _('Private Chats mit deinen Freunden.') }}</p></div>
|
||||
{% if friend_requests or event_invitations %}
|
||||
<section class="message-notifications">
|
||||
<h2>Neue Anfragen</h2>
|
||||
<h2>{{ _('Neue Anfragen') }}</h2>
|
||||
{% for request in friend_requests %}
|
||||
<div class="notification-row">
|
||||
<span class="notification-icon">🤝</span>
|
||||
<span><strong>{{ request.display_name }}</strong> möchte sich mit dir befreunden.<small>{{ request.created_at }}</small></span>
|
||||
<span class="notification-actions"><form method="post" action="/friendships/{{ request.id }}/accept"><input type="hidden" name="return_to" value="/messages"><button class="button" type="submit">Annehmen</button></form><form method="post" action="/friendships/{{ request.id }}/decline"><input type="hidden" name="return_to" value="/messages"><button class="button button-secondary" type="submit">Ablehnen</button></form></span>
|
||||
<span><strong>{{ request.display_name }}</strong> {{ _('möchte sich mit dir befreunden.') }}<small>{{ request.created_at }}</small></span>
|
||||
<span class="notification-actions"><form method="post" action="/friendships/{{ request.id }}/accept"><input type="hidden" name="return_to" value="/messages"><button class="button" type="submit">{{ _('Annehmen') }}</button></form><form method="post" action="/friendships/{{ request.id }}/decline"><input type="hidden" name="return_to" value="/messages"><button class="button button-secondary" type="submit">{{ _('Ablehnen') }}</button></form></span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% for invitation in event_invitations %}
|
||||
<a class="notification-row" href="/concerts/{{ invitation.concert_id }}">
|
||||
<span class="notification-icon">✉️</span>
|
||||
<span><strong>{{ invitation.artist }}</strong> – Einladung von {{ invitation.invited_by }}<small>{{ invitation.date }}</small></span>
|
||||
<span class="notification-link">Ansehen →</span>
|
||||
<span><strong>{{ invitation.artist }}</strong> {{ _('– Einladung von') }} {{ invitation.invited_by }}<small>{{ invitation.date }}</small></span>
|
||||
<span class="notification-link">{{ _('Ansehen →') }}</span>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</section>
|
||||
{% endif %}
|
||||
<h2 class="conversation-heading">Unterhaltungen</h2>
|
||||
<h2 class="conversation-heading">{{ _('Unterhaltungen') }}</h2>
|
||||
<div class="conversation-list">
|
||||
{% for conversation in conversations %}
|
||||
<a class="conversation-card" href="/messages/{{ conversation.username }}">
|
||||
{% if conversation.avatar_path %}<img class="chat-avatar" src="{{ conversation.avatar_path }}" alt="">{% else %}<span class="chat-avatar">{{ conversation.display_name[:1] }}</span>{% endif %}
|
||||
<span class="conversation-copy"><strong>{{ conversation.display_name }}</strong><span>{% if conversation.last_message %}{% if conversation.last_from_me %}Du: {% endif %}{{ conversation.last_message }} · {{ conversation.last_at }}{% else %}Chat beginnen{% endif %}</span></span>
|
||||
<span class="conversation-copy"><strong>{{ conversation.display_name }}</strong><span>{% if conversation.last_message %}{% if conversation.last_from_me %}{{ _('Du:') }} {% endif %}{{ conversation.last_message }} · {{ conversation.last_at }}{% else %}{{ _('Chat beginnen') }}{% endif %}</span></span>
|
||||
{% if conversation.unread_count %}<span class="unread-badge">{{ conversation.unread_count }}</span>{% endif %}
|
||||
</a>
|
||||
{% else %}<div class="empty"><h2>Noch keine Chats</h2><p>Chats sind mit bestätigten Freunden und mit Admins verfügbar.</p></div>{% endfor %}
|
||||
{% else %}<div class="empty"><h2>{{ _('Noch keine Chats') }}</h2><p>{{ _('Chats sind mit bestätigten Freunden und mit Admins verfügbar.') }}</p></div>{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</main>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<html lang="{{ language() }}">
|
||||
|
||||
<head>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
content="width=device-width, initial-scale=1.0"
|
||||
>
|
||||
|
||||
<title>Veranstaltung hinzufügen · MetalCircle</title>
|
||||
<title>{{ _('Veranstaltung hinzufügen · MetalCircle') }}</title>
|
||||
<link rel="icon" type="image/png" href="/static/images/metalcircle-circle.png">
|
||||
|
||||
<link
|
||||
@@ -71,6 +71,7 @@
|
||||
|
||||
</style>
|
||||
|
||||
<link rel="stylesheet" href="/static/css/language.css">
|
||||
</head>
|
||||
|
||||
|
||||
@@ -88,6 +89,7 @@
|
||||
|
||||
</div>
|
||||
|
||||
{% include '_language_switch.html' %}
|
||||
</header>
|
||||
|
||||
|
||||
@@ -95,10 +97,10 @@
|
||||
|
||||
<div class="page-title">
|
||||
|
||||
<h1>🎤 Veranstaltung hinzufügen</h1>
|
||||
<h1>{{ _('🎤 Veranstaltung hinzufügen') }}</h1>
|
||||
|
||||
<p>
|
||||
Trag ein, worauf wir uns freuen.
|
||||
{{ _('Trag ein, worauf wir uns freuen.') }}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
@@ -113,38 +115,38 @@
|
||||
>
|
||||
|
||||
<p>
|
||||
<label for="event-type">Kategorie</label><br>
|
||||
<label for="event-type">{{ _('Kategorie') }}</label><br>
|
||||
<select name="event_type" id="event-type" required>
|
||||
<option value="concert">Konzert</option>
|
||||
<option value="concert">{{ _('Konzert') }}</option>
|
||||
<option value="festival">Festival</option>
|
||||
<option value="other">Sonstiges</option>
|
||||
<option value="other">{{ _('Sonstiges') }}</option>
|
||||
</select>
|
||||
</p>
|
||||
|
||||
<p id="parent-event-field" hidden>
|
||||
<label for="parent-event-id">Gehört zu</label><br>
|
||||
<label for="parent-event-id">{{ _('Gehört zu') }}</label><br>
|
||||
<select name="parent_event_id" id="parent-event-id">
|
||||
<option value="">Keine Verknüpfung – eigenständige Karte</option>
|
||||
<option value="">{{ _('Keine Verknüpfung – eigenständige Karte') }}</option>
|
||||
{% for event in linkable_events %}
|
||||
<option value="{{ event.id }}">{{ event.event_type_label }} · {{ event.title }} · {{ event.date }}</option>
|
||||
<option value="{{ event.id }}">{{ event.event_type_label | t }} · {{ event.title }} · {{ event.date }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<small>Zum Beispiel ein gemeinsames Vortrinken vor einem Konzert.</small>
|
||||
<small>{{ _('Zum Beispiel ein gemeinsames Vortrinken vor einem Konzert.') }}</small>
|
||||
</p>
|
||||
|
||||
<fieldset id="event-visibility-field" hidden>
|
||||
<legend>Sichtbarkeit</legend>
|
||||
<label><input type="radio" name="visibility" value="public" checked> Öffentlich – alle können die Veranstaltung sehen und teilnehmen</label><br>
|
||||
<label><input type="radio" name="visibility" value="friends"> Nur Freunde – nur bestätigte Freunde können sie sehen und teilnehmen</label><br>
|
||||
<label><input type="radio" name="visibility" value="private"> Privat – nur gezielt eingeladene Mitglieder</label>
|
||||
<legend>{{ _('Sichtbarkeit') }}</legend>
|
||||
<label><input type="radio" name="visibility" value="public" checked> {{ _('Öffentlich – alle können die Veranstaltung sehen und teilnehmen') }}</label><br>
|
||||
<label><input type="radio" name="visibility" value="friends"> {{ _('Nur Freunde – nur bestätigte Freunde können sie sehen und teilnehmen') }}</label><br>
|
||||
<label><input type="radio" name="visibility" value="private"> {{ _('Privat – nur gezielt eingeladene Mitglieder') }}</label>
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="event-invitations-field" hidden>
|
||||
<legend>Mitglieder einladen</legend>
|
||||
<p class="flyer-help">Eingeladene erhalten eine Benachrichtigung und sehen die Veranstaltung in ihrer Übersicht.</p>
|
||||
<legend>{{ _('Mitglieder einladen') }}</legend>
|
||||
<p class="flyer-help">{{ _('Eingeladene erhalten eine Benachrichtigung und sehen die Veranstaltung in ihrer Übersicht.') }}</p>
|
||||
{% for member in invitable_users %}
|
||||
<label style="display:block;margin:7px 0"><input type="checkbox" name="invited_user_ids" value="{{ member.id }}"> {{ member.display_name }} <small>@{{ member.username }}</small></label>
|
||||
{% else %}<p>Noch keine weiteren Mitglieder vorhanden.</p>{% endfor %}
|
||||
{% else %}<p>{{ _('Noch keine weiteren Mitglieder vorhanden.') }}</p>{% endfor %}
|
||||
</fieldset>
|
||||
|
||||
<!-- ================================================= -->
|
||||
@@ -154,7 +156,7 @@
|
||||
<p>
|
||||
|
||||
<label>
|
||||
Titel / Künstler / Band<br>
|
||||
{{ _('Titel / Künstler / Band') }}<br>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
@@ -163,7 +165,7 @@
|
||||
required
|
||||
minlength="2"
|
||||
maxlength="255"
|
||||
placeholder="z. B. Iron Maiden"
|
||||
placeholder="{{ _('z. B. Iron Maiden') }}"
|
||||
>
|
||||
|
||||
</label>
|
||||
@@ -171,10 +173,10 @@
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="band-names">Bands / Line-up <small>(eine Band pro Zeile)</small><br>
|
||||
<textarea id="band-names" name="band_names" rows="3" maxlength="4000" placeholder="dArtagnan Supportband"></textarea>
|
||||
<label for="band-names">{{ _('Bands / Line-up') }} <small>{{ _('(eine Band pro Zeile)') }}</small><br>
|
||||
<textarea id="band-names" name="band_names" rows="3" maxlength="4000" placeholder="{{ _('dArtagnan\nSupportband') }}"></textarea>
|
||||
</label>
|
||||
<small>Der Veranstaltungstitel bleibt frei. Diese Namen werden einzeln für „Band folgen“ verwendet.</small>
|
||||
<small>{{ _('Der Veranstaltungstitel bleibt frei. Diese Namen werden einzeln für „Band folgen“ verwendet.') }}</small>
|
||||
</p>
|
||||
|
||||
<!-- ================================================= -->
|
||||
@@ -184,7 +186,7 @@
|
||||
<div class="venue-search">
|
||||
|
||||
<label for="venue-search">
|
||||
Veranstaltungsort
|
||||
{{ _('Veranstaltungsort') }}
|
||||
</label>
|
||||
|
||||
<div class="venue-search-control">
|
||||
@@ -193,7 +195,7 @@
|
||||
id="venue-search"
|
||||
name="venue_name"
|
||||
autocomplete="off"
|
||||
placeholder="z. B. Turbinenhalle"
|
||||
placeholder="{{ _('z. B. Turbinenhalle') }}"
|
||||
required
|
||||
>
|
||||
|
||||
@@ -210,12 +212,12 @@
|
||||
></div>
|
||||
|
||||
<div class="venue-manual-details">
|
||||
<small>Optional für eine bessere Zuordnung:</small>
|
||||
<small>{{ _('Optional für eine bessere Zuordnung:') }}</small>
|
||||
<div class="venue-address-grid">
|
||||
<input type="text" name="street" id="venue-street" placeholder="Straße">
|
||||
<input type="text" name="postal_code" id="venue-postal-code" placeholder="PLZ">
|
||||
<input type="text" name="city" id="venue-city" placeholder="Ort / Stadt">
|
||||
<input type="text" name="country" id="venue-country" placeholder="Land">
|
||||
<input type="text" name="street" id="venue-street" placeholder="{{ _('Straße') }}">
|
||||
<input type="text" name="postal_code" id="venue-postal-code" placeholder="{{ _('PLZ') }}">
|
||||
<input type="text" name="city" id="venue-city" placeholder="{{ _('Ort / Stadt') }}">
|
||||
<input type="text" name="country" id="venue-country" placeholder="{{ _('Land') }}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -248,7 +250,7 @@
|
||||
<p>
|
||||
|
||||
<label>
|
||||
Datum und Uhrzeit<br>
|
||||
{{ _('Datum und Uhrzeit') }}<br>
|
||||
|
||||
<input
|
||||
type="datetime-local"
|
||||
@@ -266,7 +268,7 @@
|
||||
|
||||
<p id="festival-end-field" hidden>
|
||||
<label>
|
||||
Enddatum und Uhrzeit<br>
|
||||
{{ _('Enddatum und Uhrzeit') }}<br>
|
||||
<input type="datetime-local" name="end_datetime" id="festival-end-datetime">
|
||||
</label>
|
||||
</p>
|
||||
@@ -279,12 +281,12 @@
|
||||
<p>
|
||||
|
||||
<label>
|
||||
Beschreibung<br>
|
||||
{{ _('Beschreibung') }}<br>
|
||||
|
||||
<textarea
|
||||
name="description"
|
||||
rows="5"
|
||||
placeholder="Optional: Infos zur Veranstaltung, Tour, Supportbands usw."
|
||||
placeholder="{{ _('Optional: Infos zur Veranstaltung, Tour, Supportbands usw.') }}"
|
||||
></textarea>
|
||||
|
||||
</label>
|
||||
@@ -299,7 +301,7 @@
|
||||
<p>
|
||||
|
||||
<label>
|
||||
Ticket-Link<br>
|
||||
{{ _('Ticket-Link') }}<br>
|
||||
|
||||
<input
|
||||
type="url"
|
||||
@@ -319,14 +321,14 @@
|
||||
<p>
|
||||
|
||||
<label>
|
||||
Ticketpreis<br>
|
||||
{{ _('Ticketpreis') }}<br>
|
||||
|
||||
<input
|
||||
type="number"
|
||||
name="ticket_price"
|
||||
step="0.01"
|
||||
min="0"
|
||||
placeholder="z. B. 59.90"
|
||||
placeholder="{{ _('z. B. 59.90') }}"
|
||||
>
|
||||
|
||||
</label>
|
||||
@@ -340,13 +342,13 @@
|
||||
|
||||
<div class="flyer-upload">
|
||||
|
||||
<label for="flyer-url">🔗 Flyer extern verlinken <small>(bevorzugt)</small></label>
|
||||
<label for="flyer-url">{{ _('🔗 Flyer extern verlinken') }} <small>{{ _('(bevorzugt)') }}</small></label>
|
||||
<input type="url" id="flyer-url" name="flyer_url" placeholder="https://veranstalter.example/flyer.jpg">
|
||||
<div class="flyer-help">Bitte möglichst den offiziellen Flyer des Veranstalters oder der Band verlinken. Das Bild wird nicht auf MetalCircle kopiert; der Link wird als Quelle ausgewiesen.</div>
|
||||
<div class="flyer-help">{{ _('Bitte möglichst den offiziellen Flyer des Veranstalters oder der Band verlinken. Das Bild wird nicht auf MetalCircle kopiert; der Link wird als Quelle ausgewiesen.') }}</div>
|
||||
|
||||
<label for="flyer">
|
||||
|
||||
🎨 Veranstaltungs-Flyer
|
||||
{{ _('🎨 Veranstaltungs-Flyer') }}
|
||||
|
||||
</label>
|
||||
|
||||
@@ -361,7 +363,7 @@
|
||||
|
||||
<div class="flyer-help">
|
||||
|
||||
Alternative, falls kein offizieller externer Link verfügbar ist: JPG, PNG oder WEBP · maximal 10 MB. Ist ein Link angegeben, wird er bevorzugt dargestellt.
|
||||
{{ _('Alternative, falls kein offizieller externer Link verfügbar ist: JPG, PNG oder WEBP · maximal 10 MB. Ist ein Link angegeben, wird er bevorzugt dargestellt.') }}
|
||||
|
||||
</div>
|
||||
|
||||
@@ -374,7 +376,7 @@
|
||||
<img
|
||||
id="flyer-preview"
|
||||
class="flyer-preview"
|
||||
alt="Flyer Vorschau"
|
||||
alt="{{ _('Flyer Vorschau') }}"
|
||||
>
|
||||
|
||||
<div
|
||||
@@ -397,7 +399,7 @@
|
||||
type="submit"
|
||||
class="button"
|
||||
>
|
||||
💾 Veranstaltung speichern
|
||||
{{ _('💾 Veranstaltung speichern') }}
|
||||
</button>
|
||||
|
||||
|
||||
@@ -405,7 +407,7 @@
|
||||
href="/"
|
||||
class="button button-secondary"
|
||||
>
|
||||
Abbrechen
|
||||
{{ _('Abbrechen') }}
|
||||
</a>
|
||||
|
||||
</div>
|
||||
@@ -663,8 +665,8 @@ function appendManualVenueResult(query, isOnlyResult) {
|
||||
result.type = "button";
|
||||
result.className = "venue-result venue-result-manual";
|
||||
result.innerHTML = `
|
||||
<strong>✏️ „${escapeHtml(query)}“ verwenden</strong>
|
||||
<span>${isOnlyResult ? "Keine passenden Treffer – Eingabe trotzdem speichern" : "Als eigenen Veranstaltungsort anlegen"}</span>
|
||||
<strong>${escapeHtml({{ _('✏️ „{name}“ verwenden') | tojson }}.replace('{name}', query))}</strong>
|
||||
<span>${isOnlyResult ? {{ _('Keine passenden Treffer – Eingabe trotzdem speichern') | tojson }} : {{ _('Als eigenen Veranstaltungsort anlegen') | tojson }}}</span>
|
||||
`;
|
||||
result.addEventListener("click", () => {
|
||||
selectVenue({
|
||||
@@ -839,7 +841,7 @@ flyerInput.addEventListener(
|
||||
) {
|
||||
|
||||
alert(
|
||||
"Bitte JPG, PNG oder WEBP auswählen."
|
||||
{{ _('Bitte JPG, PNG oder WEBP auswählen.') | tojson }}
|
||||
);
|
||||
|
||||
|
||||
@@ -857,7 +859,7 @@ flyerInput.addEventListener(
|
||||
) {
|
||||
|
||||
alert(
|
||||
"Der Flyer darf maximal 10 MB groß sein."
|
||||
{{ _('Der Flyer darf maximal 10 MB groß sein.') | tojson }}
|
||||
);
|
||||
|
||||
|
||||
@@ -916,7 +918,7 @@ function showDuplicateMatches(matches) {
|
||||
return;
|
||||
}
|
||||
const heading = document.createElement("strong");
|
||||
heading.textContent = "⚠️ Möglicherweise existiert diese Veranstaltung bereits:";
|
||||
heading.textContent = {{ _('⚠️ Möglicherweise existiert diese Veranstaltung bereits:') | tojson }};
|
||||
const list = document.createElement("ul");
|
||||
matches.forEach(match => {
|
||||
const item = document.createElement("li");
|
||||
@@ -990,7 +992,7 @@ concertForm.addEventListener("submit", async event => {
|
||||
const matches = await checkForDuplicates(true);
|
||||
if (matches.length && duplicateConfirmed.value !== "true") {
|
||||
const proceed = window.confirm(
|
||||
"Am selben Tag gibt es bereits eine Veranstaltung mit einem sehr ähnlichen Bandnamen. Trotzdem speichern?"
|
||||
{{ _('Am selben Tag gibt es bereits eine Veranstaltung mit einem sehr ähnlichen Bandnamen. Trotzdem speichern?') | tojson }}
|
||||
);
|
||||
if (!proceed) return;
|
||||
duplicateConfirmed.value = "true";
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<!DOCTYPE html><html lang="de"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Passwort zurücksetzen · MetalCircle</title><link rel="icon" type="image/png" href="/static/images/metalcircle-circle.png"><link rel="stylesheet" href="/static/css/style.css"></head><body>
|
||||
<header><div class="header-inner"><a href="/" class="logo"><img class="brand-logo" src="/static/images/metalcircle-full.png" alt="MetalCircle"></a></div></header><main><div class="page-title"><h1>Passwort zurücksetzen</h1></div><section class="empty">
|
||||
{% if error %}<p role="alert">{{ error }}</p>{% endif %}<form method="post" action="/password-reset/{{ token }}"><label>Neues Passwort<br><input type="password" name="password" minlength="10" required autocomplete="new-password"></label><br><label>Passwort wiederholen<br><input type="password" name="password_repeat" minlength="10" required autocomplete="new-password"></label><br><button class="button" type="submit">Passwort speichern</button></form>
|
||||
<!DOCTYPE html><html lang="{{ language() }}"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>{{ _('Passwort zurücksetzen · MetalCircle') }}</title><link rel="icon" type="image/png" href="/static/images/metalcircle-circle.png"><link rel="stylesheet" href="/static/css/style.css"><link rel="stylesheet" href="/static/css/language.css">
|
||||
</head><body>
|
||||
<header><div class="header-inner"><a href="/" class="logo"><img class="brand-logo" src="/static/images/metalcircle-full.png" alt="MetalCircle"></a></div> {% include '_language_switch.html' %}
|
||||
</header><main><div class="page-title"><h1>{{ _('Passwort zurücksetzen') }}</h1></div><section class="empty">
|
||||
{% if error %}<p role="alert">{{ error | t }}</p>{% endif %}<form method="post" action="/password-reset/{{ token }}"><label>{{ _('Neues Passwort') }}<br><input type="password" name="password" minlength="10" required autocomplete="new-password"></label><br><label>{{ _('Passwort wiederholen') }}<br><input type="password" name="password_repeat" minlength="10" required autocomplete="new-password"></label><br><button class="button" type="submit">{{ _('Passwort speichern') }}</button></form>
|
||||
</section></main></body></html>
|
||||
|
||||
+61
-59
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<html lang="{{ language() }}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
@@ -70,6 +70,7 @@
|
||||
.connection-actions .button { padding: 7px 10px; font-size: .85rem; }
|
||||
@media (max-width: 720px) { .profile-layout { grid-template-columns: 1fr; } .kutte { padding-left: 20px; padding-right: 20px; } .account-actions { flex-direction:column; } .data-export { text-align:left; } }
|
||||
</style>
|
||||
<link rel="stylesheet" href="/static/css/language.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
@@ -77,81 +78,82 @@
|
||||
<a href="/" class="logo"><img class="brand-logo" src="/static/images/metalcircle-full.png" alt="MetalCircle"></a>
|
||||
{% include '_user_menu.html' %}
|
||||
</div>
|
||||
{% include '_language_switch.html' %}
|
||||
</header>
|
||||
<main>
|
||||
<div class="page-title">
|
||||
<h1>Profil</h1>
|
||||
<p>Deine Konzertgeschichte und deine Kutte.</p>
|
||||
<h1>{{ _('Profil') }}</h1>
|
||||
<p>{{ _('Deine Konzertgeschichte und deine Kutte.') }}</p>
|
||||
</div>
|
||||
<div class="profile-layout">
|
||||
<section class="profile-card">
|
||||
{% if profile.avatar_path %}
|
||||
<img class="avatar" src="{{ profile.avatar_path }}" alt="Profilbild von {{ profile.display_name }}">
|
||||
<img class="avatar" src="{{ profile.avatar_path }}" alt="{{ _('Profilbild von') }} {{ profile.display_name }}">
|
||||
{% else %}
|
||||
<div class="avatar-fallback" aria-label="Kein Profilbild">{{ profile.display_name[:1] }}</div>
|
||||
<div class="avatar-fallback" aria-label="{{ _('Kein Profilbild') }}">{{ profile.display_name[:1] }}</div>
|
||||
{% endif %}
|
||||
<h2 class="profile-name">{{ profile.display_name }}</h2>
|
||||
<p class="profile-handle">@{{ profile.username }}</p>
|
||||
{% if can_view_details %}<p class="profile-since">Dabei seit {{ profile.created_at }}</p>{% endif %}
|
||||
{% if can_view_details %}<p class="profile-since">{{ _('Dabei seit') }} {{ profile.created_at }}</p>{% endif %}
|
||||
{% if can_view_details and profile.instagram_url %}
|
||||
<a class="instagram-link" href="{{ profile.instagram_url }}" target="_blank" rel="noopener noreferrer">◎ Instagram · @{{ profile.instagram_handle }}</a>
|
||||
{% endif %}
|
||||
<div class="stat"><strong>{{ attended_count }}</strong>besuchte Konzerte</div>
|
||||
<div class="stat"><strong>{{ attended_count }}</strong>{{ _('besuchte Konzerte') }}</div>
|
||||
{% if not is_own_profile %}
|
||||
<div class="friend-actions">
|
||||
{% if not block_status.blocked_by_viewer and not block_status.blocked_viewer %}
|
||||
{% if (user.is_admin or profile.is_admin) and (not friendship or friendship.status != 'accepted') %}
|
||||
<a class="button" href="/messages/{{ profile.username }}">Nachricht senden</a>
|
||||
<a class="button" href="/messages/{{ profile.username }}">{{ _('Nachricht senden') }}</a>
|
||||
{% endif %}
|
||||
{% if not friendship %}
|
||||
<form method="post" action="/users/{{ profile.username }}/friend-request"><button class="button" type="submit">Freundschaft anfragen</button></form>
|
||||
<form method="post" action="/users/{{ profile.username }}/friend-request"><button class="button" type="submit">{{ _('Freundschaft anfragen') }}</button></form>
|
||||
{% elif friendship.status == 'accepted' %}
|
||||
<a class="button" href="/messages/{{ profile.username }}">Nachricht senden</a>
|
||||
<form method="post" action="/friendships/{{ friendship.id }}/remove"><button class="button button-secondary" type="submit">Freundschaft beenden</button></form>
|
||||
<a class="button" href="/messages/{{ profile.username }}">{{ _('Nachricht senden') }}</a>
|
||||
<form method="post" action="/friendships/{{ friendship.id }}/remove"><button class="button button-secondary" type="submit">{{ _('Freundschaft beenden') }}</button></form>
|
||||
{% elif friendship.addressee_id == user.id %}
|
||||
<form method="post" action="/friendships/{{ friendship.id }}/accept"><button class="button" type="submit">Annehmen</button></form>
|
||||
<form method="post" action="/friendships/{{ friendship.id }}/decline"><button class="button button-secondary" type="submit">Ablehnen</button></form>
|
||||
<form method="post" action="/friendships/{{ friendship.id }}/accept"><button class="button" type="submit">{{ _('Annehmen') }}</button></form>
|
||||
<form method="post" action="/friendships/{{ friendship.id }}/decline"><button class="button button-secondary" type="submit">{{ _('Ablehnen') }}</button></form>
|
||||
{% else %}
|
||||
<form method="post" action="/friendships/{{ friendship.id }}/remove"><button class="button button-secondary" type="submit">Anfrage zurückziehen</button></form>
|
||||
<form method="post" action="/friendships/{{ friendship.id }}/remove"><button class="button button-secondary" type="submit">{{ _('Anfrage zurückziehen') }}</button></form>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if block_status.blocked_by_viewer %}
|
||||
<form method="post" action="/users/{{ profile.username }}/unblock"><button class="button button-secondary" type="submit">Blockierung aufheben</button></form>
|
||||
<form method="post" action="/users/{{ profile.username }}/unblock"><button class="button button-secondary" type="submit">{{ _('Blockierung aufheben') }}</button></form>
|
||||
{% else %}
|
||||
<form method="post" action="/users/{{ profile.username }}/block" onsubmit="return confirm('Diesen Nutzer wirklich blockieren?');"><button class="button button-secondary" type="submit">Nutzer blockieren</button></form>
|
||||
<form method="post" action="/users/{{ profile.username }}/block" onsubmit="return confirm({{ _('Diesen Nutzer wirklich blockieren?') | tojson | forceescape }});"><button class="button button-secondary" type="submit">{{ _('Nutzer blockieren') }}</button></form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if block_status.blocked_viewer and not block_status.blocked_by_viewer %}<p class="private-note">Interaktionen mit diesem Nutzer sind nicht verfügbar.</p>{% endif %}
|
||||
{% if not can_view_details %}<p class="private-note">Weitere Profildetails sind nur für Freunde sichtbar.</p>{% endif %}
|
||||
{% if block_status.blocked_viewer and not block_status.blocked_by_viewer %}<p class="private-note">{{ _('Interaktionen mit diesem Nutzer sind nicht verfügbar.') }}</p>{% endif %}
|
||||
{% if not can_view_details %}<p class="private-note">{{ _('Weitere Profildetails sind nur für Freunde sichtbar.') }}</p>{% endif %}
|
||||
{% endif %}
|
||||
</section>
|
||||
<section class="badges-card">
|
||||
<h2>🥋 Virtuelle Kutte</h2>
|
||||
<p>Deine freigeschalteten Patches. Besuchs-Patches werden jeweils durch die höchste Stufe ersetzt.</p>
|
||||
<h2>{{ _('🥋 Virtuelle Kutte') }}</h2>
|
||||
<p>{{ _('Deine freigeschalteten Patches. Besuchs-Patches werden jeweils durch die höchste Stufe ersetzt.') }}</p>
|
||||
<div class="kutte">
|
||||
<div class="patch-grid">
|
||||
{% for badge in badges %}
|
||||
{% if badge.earned %}
|
||||
<button class="patch-button" type="button" onclick="document.getElementById('patch-{{ badge.code }}').showModal()" aria-label="Details zu {{ badge.name }} anzeigen">
|
||||
<span class="patch earned {% if badge.image_path %}patch-image-frame{% else %}patch-icon-frame{% endif %}" title="{{ badge.name }} – {{ badge.description }}">
|
||||
<button class="patch-button" type="button" onclick="document.getElementById('patch-{{ badge.code }}').showModal()" aria-label="{{ _('Details zu') }} {{ badge.name | t }} {{ _('anzeigen') }}">
|
||||
<span class="patch earned {% if badge.image_path %}patch-image-frame{% else %}patch-icon-frame{% endif %}" title="{{ badge.name | t }} – {{ badge.description | t }}">
|
||||
{% if badge.image_path %}
|
||||
<img class="patch-image" src="{{ badge.image_path }}" alt="Patch {{ badge.name }}">
|
||||
<img class="patch-image" src="{{ badge.image_path }}" alt="Patch {{ badge.name | t }}">
|
||||
{% else %}
|
||||
<span class="patch-icon">{{ badge.icon }}</span>
|
||||
{% endif %}
|
||||
</span></button>
|
||||
<dialog class="patch-dialog" id="patch-{{ badge.code }}">
|
||||
<form method="dialog"><button class="button button-secondary patch-dialog-close" aria-label="Schließen">×</button></form>
|
||||
{% if badge.image_path %}<img class="patch-dialog-preview" src="{{ badge.image_path }}" alt="Patch {{ badge.name }}">{% else %}<div class="patch-icon">{{ badge.icon }}</div>{% endif %}
|
||||
<h3>{{ badge.name }}</h3><p>{{ badge.description }}</p>
|
||||
<p><strong>Erhalten am:</strong> {{ badge.awarded_at }}</p>
|
||||
{% if badge.trigger_concert %}<p><strong>Ausgelöst durch:</strong> <a href="/concerts/{{ badge.trigger_concert.id }}">{{ badge.trigger_concert.artist }} · {{ badge.trigger_concert.date }}</a></p>{% else %}<p><strong>Grund:</strong> {{ badge.description }}</p>{% endif %}
|
||||
<form method="dialog"><button class="button button-secondary patch-dialog-close" aria-label="{{ _('Schließen') }}">×</button></form>
|
||||
{% if badge.image_path %}<img class="patch-dialog-preview" src="{{ badge.image_path }}" alt="Patch {{ badge.name | t }}">{% else %}<div class="patch-icon">{{ badge.icon }}</div>{% endif %}
|
||||
<h3>{{ badge.name | t }}</h3><p>{{ badge.description | t }}</p>
|
||||
<p><strong>{{ _('Erhalten am:') }}</strong> {{ badge.awarded_at }}</p>
|
||||
{% if badge.trigger_concert %}<p><strong>{{ _('Ausgelöst durch:') }}</strong> <a href="/concerts/{{ badge.trigger_concert.id }}">{{ badge.trigger_concert.artist }} · {{ badge.trigger_concert.date }}</a></p>{% else %}<p><strong>{{ _('Grund:') }}</strong> {{ badge.description | t }}</p>{% endif %}
|
||||
</dialog>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if not profile.earned_codes %}
|
||||
<p>Noch keine Patches – dein erstes Konzert wartet!</p>
|
||||
<p>{{ _('Noch keine Patches – dein erstes Konzert wartet!') }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -160,10 +162,10 @@
|
||||
{% if is_own_profile %}
|
||||
<section class="connections">
|
||||
<details class="connections-details" {% if connections.incoming %}open{% endif %}>
|
||||
<summary>🤝 Freunde{% if connections.friends %} · {{ connections.friends|length }}{% endif %}</summary>
|
||||
<summary>{{ _('🤝 Freunde') }}{% if connections.friends %} · {{ connections.friends|length }}{% endif %}</summary>
|
||||
<div class="connections-content">
|
||||
{% if connections.incoming %}
|
||||
<h3>Offene Anfragen</h3>
|
||||
<h3>{{ _('Offene Anfragen') }}</h3>
|
||||
<div class="connection-list">
|
||||
{% for connection in connections.incoming %}
|
||||
<div class="connection-row">
|
||||
@@ -172,15 +174,15 @@
|
||||
<strong>{{ connection.display_name }} <small>@{{ connection.username }}</small></strong>
|
||||
</a>
|
||||
<div class="connection-actions">
|
||||
<form method="post" action="/friendships/{{ connection.id }}/accept"><button class="button" type="submit">Annehmen</button></form>
|
||||
<form method="post" action="/friendships/{{ connection.id }}/decline"><button class="button button-secondary" type="submit">Ablehnen</button></form>
|
||||
<form method="post" action="/friendships/{{ connection.id }}/accept"><button class="button" type="submit">{{ _('Annehmen') }}</button></form>
|
||||
<form method="post" action="/friendships/{{ connection.id }}/decline"><button class="button button-secondary" type="submit">{{ _('Ablehnen') }}</button></form>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if connections.friends %}
|
||||
<h3>Deine Freunde</h3>
|
||||
<h3>{{ _('Deine Freunde') }}</h3>
|
||||
<div class="connection-list">
|
||||
{% for connection in connections.friends %}
|
||||
<div class="connection-row">
|
||||
@@ -188,15 +190,15 @@
|
||||
{% if connection.avatar_path %}<img class="connection-avatar" src="{{ connection.avatar_path }}" alt="">{% else %}<span class="connection-avatar">{{ connection.display_name[:1] }}</span>{% endif %}
|
||||
<strong>{{ connection.display_name }} <small>@{{ connection.username }}</small></strong>
|
||||
</a>
|
||||
<div class="connection-actions"><form method="post" action="/friendships/{{ connection.id }}/remove"><button class="button button-secondary" type="submit">Entfernen</button></form></div>
|
||||
<div class="connection-actions"><form method="post" action="/friendships/{{ connection.id }}/remove"><button class="button button-secondary" type="submit">{{ _('Entfernen') }}</button></form></div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if connections.outgoing %}<p class="friend-note">{{ connections.outgoing|length }} gesendete Anfrage(n) warten noch auf Antwort.</p>{% endif %}
|
||||
{% if not connections.incoming and not connections.friends and not connections.outgoing %}<p class="friend-note">Noch keine Verbindungen. Finde andere User über die Suche auf der Startseite.</p>{% endif %}
|
||||
{% if connections.outgoing %}<p class="friend-note">{{ connections.outgoing|length }} {{ _('gesendete Anfrage(n) warten noch auf Antwort.') }}</p>{% endif %}
|
||||
{% if not connections.incoming and not connections.friends and not connections.outgoing %}<p class="friend-note">{{ _('Noch keine Verbindungen. Finde andere User über die Suche auf der Startseite.') }}</p>{% endif %}
|
||||
<details class="blocked-users">
|
||||
<summary>Blockierte Nutzer{% if connections.blocked %} ({{ connections.blocked|length }}){% endif %}</summary>
|
||||
<summary>{{ _('Blockierte Nutzer') }}{% if connections.blocked %} ({{ connections.blocked|length }}){% endif %}</summary>
|
||||
{% if connections.blocked %}
|
||||
<div class="connection-list">
|
||||
{% for connection in connections.blocked %}
|
||||
@@ -205,12 +207,12 @@
|
||||
{% if connection.avatar_path %}<img class="connection-avatar" src="{{ connection.avatar_path }}" alt="">{% else %}<span class="connection-avatar">{{ connection.display_name[:1] }}</span>{% endif %}
|
||||
<strong>{{ connection.display_name }} <small>@{{ connection.username }}</small></strong>
|
||||
</a>
|
||||
<div class="connection-actions"><form method="post" action="/users/{{ connection.username }}/unblock"><button class="button button-secondary" type="submit">Freigeben</button></form></div>
|
||||
<div class="connection-actions"><form method="post" action="/users/{{ connection.username }}/unblock"><button class="button button-secondary" type="submit">{{ _('Freigeben') }}</button></form></div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="friend-note">Keine blockierten Nutzer.</p>
|
||||
<p class="friend-note">{{ _('Keine blockierten Nutzer.') }}</p>
|
||||
{% endif %}
|
||||
</details>
|
||||
</div>
|
||||
@@ -219,37 +221,37 @@
|
||||
{% endif %}
|
||||
{% if is_own_profile %}
|
||||
<section class="profile-edit">
|
||||
<h2>Profil bearbeiten</h2>
|
||||
{% if form_error %}<p class="profile-error" role="alert">{{ form_error }}</p>{% endif %}
|
||||
<h2>{{ _('Profil bearbeiten') }}</h2>
|
||||
{% if form_error %}<p class="profile-error" role="alert">{{ form_error | t }}</p>{% endif %}
|
||||
{% if form_success %}<p class="profile-success" role="status">{{ form_success }}</p>{% endif %}
|
||||
<form method="post" action="/profile" enctype="multipart/form-data" id="profile-form">
|
||||
<label for="display-name">Anzeigename</label>
|
||||
<label for="display-name">{{ _('Anzeigename') }}</label>
|
||||
<input id="display-name" name="display_name" type="text" value="{{ profile.display_name }}" maxlength="100">
|
||||
<label for="instagram-url">Instagram-Profil <small>(optional)</small></label>
|
||||
<input id="instagram-url" name="instagram_url" type="text" value="{{ instagram_input if instagram_input is not none else (profile.instagram_url or '') }}" placeholder="@name oder https://instagram.com/name" autocomplete="url">
|
||||
{% if profile.instagram_url %}<a class="instagram-current" href="{{ profile.instagram_url }}" target="_blank" rel="noopener noreferrer">Aktuell verknüpft: @{{ profile.instagram_handle }}</a>{% endif %}
|
||||
<label for="instagram-url">{{ _('Instagram-Profil') }} <small>(optional)</small></label>
|
||||
<input id="instagram-url" name="instagram_url" type="text" value="{{ instagram_input if instagram_input is not none else (profile.instagram_url or '') }}" placeholder="{{ _('@name oder https://instagram.com/name') }}" autocomplete="url">
|
||||
{% if profile.instagram_url %}<a class="instagram-current" href="{{ profile.instagram_url }}" target="_blank" rel="noopener noreferrer">{{ _('Aktuell verknüpft: @') }}{{ profile.instagram_handle }}</a>{% endif %}
|
||||
<fieldset>
|
||||
<legend>Wer darf alle Profildetails sehen?</legend>
|
||||
<legend>{{ _('Wer darf alle Profildetails sehen?') }}</legend>
|
||||
<div class="visibility-options">
|
||||
<label><input type="radio" name="profile_visibility" value="public" {% if profile.profile_visibility == 'public' %}checked{% endif %}> <span><strong>Alle User</strong><br><small>Jeder angemeldete User sieht auch Instagram und Beitrittsdatum.</small></span></label>
|
||||
<label><input type="radio" name="profile_visibility" value="friends" {% if profile.profile_visibility == 'friends' %}checked{% endif %}> <span><strong>Nur Freunde</strong><br><small>Username, Profilbild, Konzertanzahl und Kutte bleiben immer sichtbar.</small></span></label>
|
||||
<label><input type="radio" name="profile_visibility" value="nobody" {% if profile.profile_visibility == 'nobody' %}checked{% endif %}> <span><strong>Niemand</strong><br><small>Zusätzliche Details siehst nur du; Admins behalten administrativen Zugriff.</small></span></label>
|
||||
<label><input type="radio" name="profile_visibility" value="public" {% if profile.profile_visibility == 'public' %}checked{% endif %}> <span><strong>{{ _('Alle User') }}</strong><br><small>{{ _('Jeder angemeldete User sieht auch Instagram und Beitrittsdatum.') }}</small></span></label>
|
||||
<label><input type="radio" name="profile_visibility" value="friends" {% if profile.profile_visibility == 'friends' %}checked{% endif %}> <span><strong>{{ _('Nur Freunde') }}</strong><br><small>{{ _('Username, Profilbild, Konzertanzahl und Kutte bleiben immer sichtbar.') }}</small></span></label>
|
||||
<label><input type="radio" name="profile_visibility" value="nobody" {% if profile.profile_visibility == 'nobody' %}checked{% endif %}> <span><strong>{{ _('Niemand') }}</strong><br><small>{{ _('Zusätzliche Details siehst nur du; Admins behalten administrativen Zugriff.') }}</small></span></label>
|
||||
</div>
|
||||
</fieldset>
|
||||
<label for="avatar">Profilbild</label>
|
||||
<label for="avatar">{{ _('Profilbild') }}</label>
|
||||
<input id="avatar" name="avatar" type="file" accept="image/jpeg,image/png,image/webp">
|
||||
<span class="avatar-upload-status" id="avatar-upload-status">Große Bilder werden vor dem Upload automatisch optimiert.</span>
|
||||
<button class="button" type="submit">Profil speichern</button>
|
||||
<span class="avatar-upload-status" id="avatar-upload-status">{{ _('Große Bilder werden vor dem Upload automatisch optimiert.') }}</span>
|
||||
<button class="button" type="submit">{{ _('Profil speichern') }}</button>
|
||||
</form>
|
||||
<div class="account-actions">
|
||||
<details class="account-delete">
|
||||
<summary>Account löschen</summary>
|
||||
<p>Diese Aktion löscht dein Konto dauerhaft und kann nicht rückgängig gemacht werden.</p>
|
||||
<form method="post" action="/profile/delete" onsubmit="return confirm('Account wirklich dauerhaft löschen?');">
|
||||
<button type="submit" class="admin-danger">Account dauerhaft löschen</button>
|
||||
<summary>{{ _('Account löschen') }}</summary>
|
||||
<p>{{ _('Diese Aktion löscht dein Konto dauerhaft und kann nicht rückgängig gemacht werden.') }}</p>
|
||||
<form method="post" action="/profile/delete" onsubmit="return confirm({{ _('Account wirklich dauerhaft löschen?') | tojson | forceescape }});">
|
||||
<button type="submit" class="admin-danger">{{ _('Account dauerhaft löschen') }}</button>
|
||||
</form>
|
||||
</details>
|
||||
<p class="data-export"><a href="/profile/export">Meine gespeicherten Daten exportieren (JSON)</a></p>
|
||||
<p class="data-export"><a href="/profile/export">{{ _('Meine gespeicherten Daten exportieren (JSON)') }}</a></p>
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
@@ -288,7 +290,7 @@ if (profileForm) {
|
||||
profileForm.requestSubmit();
|
||||
} catch (error) {
|
||||
console.error("Avatar optimization failed:", error);
|
||||
status.textContent = "Das Bild konnte nicht verarbeitet werden. Bitte ein kleineres JPG, PNG oder WebP wählen.";
|
||||
status.textContent = {{ _('Das Bild konnte nicht verarbeitet werden. Bitte ein kleineres JPG, PNG oder WebP wählen.') | tojson }};
|
||||
button.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<html lang="{{ language() }}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<title>MetalCircle - Registrierung</title>
|
||||
<title>{{ _('MetalCircle - Registrierung') }}</title>
|
||||
<link rel="icon" type="image/png" href="/static/images/metalcircle-circle.png">
|
||||
|
||||
<style>
|
||||
@@ -28,16 +28,18 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="/static/css/language.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{% include '_language_switch.html' %}
|
||||
|
||||
<p><img src="/static/images/metalcircle-full.png" alt="MetalCircle" style="display:block;max-width:100%;height:auto;margin:0 auto 24px"></p>
|
||||
|
||||
<h2>Einladung angenommen</h2>
|
||||
<h2>{{ _('Einladung angenommen') }}</h2>
|
||||
|
||||
<p>
|
||||
Erstelle deinen Account für MetalCircle.
|
||||
{{ _('Erstelle deinen Account für MetalCircle.') }}
|
||||
</p>
|
||||
|
||||
<form method="post" action="/register">
|
||||
@@ -48,7 +50,7 @@
|
||||
value="{{ token }}"
|
||||
>
|
||||
|
||||
<label>Benutzername</label>
|
||||
<label>{{ _('Benutzername') }}</label>
|
||||
<input
|
||||
type="text"
|
||||
name="username"
|
||||
@@ -56,14 +58,14 @@
|
||||
maxlength="50"
|
||||
>
|
||||
|
||||
<label>Anzeigename</label>
|
||||
<label>{{ _('Anzeigename') }}</label>
|
||||
<input
|
||||
type="text"
|
||||
name="display_name"
|
||||
maxlength="100"
|
||||
>
|
||||
|
||||
<label>E-Mail</label>
|
||||
<label>{{ _('E-Mail') }}</label>
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
@@ -71,7 +73,7 @@
|
||||
maxlength="255"
|
||||
>
|
||||
|
||||
<label>Passwort</label>
|
||||
<label>{{ _('Passwort') }}</label>
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
@@ -81,7 +83,7 @@
|
||||
>
|
||||
|
||||
<button type="submit">
|
||||
Account erstellen
|
||||
{{ _('Account erstellen') }}
|
||||
</button>
|
||||
|
||||
</form>
|
||||
|
||||
Reference in New Issue
Block a user