b2af866cf0
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.
454 lines
17 KiB
HTML
454 lines
17 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="{{ language() }}">
|
|
|
|
<head>
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
<meta
|
|
name="viewport"
|
|
content="width=device-width, initial-scale=1.0"
|
|
>
|
|
|
|
<title>{{ concert.artist }} | MetalCircle</title>
|
|
<link rel="icon" type="image/png" href="/static/images/metalcircle-circle.png">
|
|
|
|
<link
|
|
rel="stylesheet"
|
|
href="/static/css/style.css"
|
|
>
|
|
|
|
<style>
|
|
.detail-topbar { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; }
|
|
.event-type-badge { display: inline-block; margin-bottom: 8px; padding: 4px 9px; border: 1px solid #4b5563; border-radius: 999px; color: #c4b5fd; font-size: .8rem; font-weight: 700; text-transform: uppercase; }
|
|
.parent-event-link { color: #c4b5fd; }
|
|
.flyer-source { display: block; width: 100%; flex: 0 0 100%; margin: 10px 0 0; color: #a8a29e; font-size: .85rem; text-align: center; }
|
|
.flyer-source a { color: #f87171; text-decoration: underline; }
|
|
.flyer-disclaimer { display: block; margin-top: 4px; font-size: .75rem; }
|
|
.follow-actions { display:flex; flex-wrap:wrap; justify-content:center; gap:8px; margin:0 0 20px; }
|
|
.follow-actions form { margin:0; }
|
|
.follow-button { padding:8px 11px; color:#fca5a5; background:#171212; border:1px solid var(--border); border-radius:9px; cursor:pointer; }
|
|
.follow-button.is-following { color:#fff; border-color:#dc2626; background:#7f1d1d; }
|
|
.diary-section { margin-top:26px; padding-top:22px; border-top:1px solid var(--border); }
|
|
.diary-form { max-width:none; }
|
|
.diary-form select, .diary-form input, .diary-form textarea { width:100%; }
|
|
.diary-form textarea { min-height:130px; resize:vertical; }
|
|
</style>
|
|
|
|
<link rel="stylesheet" href="/static/css/language.css">
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<div class="container">
|
|
|
|
<div class="detail-topbar">
|
|
<a href="/" class="back-link">{{ _('← Zurück zum Kalender') }}</a>
|
|
{% include '_language_switch.html' %}
|
|
{% include '_user_menu.html' %}
|
|
</div>
|
|
|
|
<article class="concert-detail">
|
|
|
|
{% if concert.flyer_url or concert.flyer_path %}
|
|
|
|
<div class="flyer-container">
|
|
|
|
<img
|
|
src="{{ concert.flyer_url or concert.flyer_path }}"
|
|
alt="Flyer {{ concert.artist }}"
|
|
class="concert-flyer"
|
|
referrerpolicy="no-referrer"
|
|
{% if concert.flyer_url and concert.flyer_path %}onerror="this.onerror=null;this.src='{{ concert.flyer_path }}'"{% endif %}
|
|
>
|
|
|
|
{% 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>
|
|
{% endif %}
|
|
|
|
</div>
|
|
|
|
{% endif %}
|
|
|
|
|
|
<div class="concert-content">
|
|
|
|
<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 }}
|
|
</h1>
|
|
|
|
<div class="follow-actions" id="following">
|
|
{% 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>
|
|
</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>
|
|
</form>{% endif %}
|
|
<a class="follow-button" href="/following">{{ _('Meine gefolgten Inhalte') }}</a>
|
|
</div>
|
|
|
|
|
|
<div class="concert-info">
|
|
|
|
<div class="info-item">
|
|
|
|
<span class="info-icon">
|
|
📅
|
|
</span>
|
|
|
|
<div>
|
|
|
|
<strong>
|
|
{{ concert.date }}
|
|
</strong>
|
|
|
|
<br>
|
|
|
|
{{ 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 %}
|
|
{% endif %}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
<div class="info-item">
|
|
|
|
<span class="info-icon">
|
|
📍
|
|
</span>
|
|
|
|
<div>
|
|
|
|
<strong>
|
|
{{ concert.venue.name }}
|
|
</strong>
|
|
|
|
{% if concert.venue.street %}
|
|
|
|
<br>
|
|
|
|
{{ concert.venue.street }}
|
|
|
|
{% endif %}
|
|
|
|
|
|
{% if concert.venue.postal_code
|
|
or concert.venue.city
|
|
or (concert.venue.country
|
|
and concert.venue.country not in ('Deutschland', 'Germany')) %}
|
|
|
|
<br>
|
|
|
|
{{ concert.venue.postal_code }}
|
|
{{ concert.venue.city }}
|
|
{% if concert.venue.country
|
|
and concert.venue.country not in ('Deutschland', 'Germany') %}
|
|
{% if concert.venue.postal_code or concert.venue.city %} · {% endif %}
|
|
{{ concert.venue.country }}
|
|
{% endif %}
|
|
|
|
{% endif %}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
{% if concert.ticket_price %}
|
|
|
|
<div class="info-item">
|
|
|
|
<span class="info-icon">
|
|
🎟️
|
|
</span>
|
|
|
|
<div>
|
|
|
|
<strong>
|
|
Ticket
|
|
</strong>
|
|
|
|
<br>
|
|
|
|
{{ concert.ticket_price }} €
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{% endif %}
|
|
|
|
</div>
|
|
|
|
{% if concert.parent_event %}
|
|
<p>
|
|
{{ _('Gehört zu:') }}
|
|
<a class="parent-event-link" href="/concerts/{{ concert.parent_event.id }}">
|
|
{{ concert.parent_event.event_type_label | t }} · {{ concert.parent_event.title }}
|
|
</a>
|
|
</p>
|
|
{% endif %}
|
|
|
|
|
|
{% if concert.description %}
|
|
|
|
<section class="description">
|
|
|
|
<h2>
|
|
{{ _('Über die Veranstaltung') }}
|
|
</h2>
|
|
|
|
<p>
|
|
{{ concert.description }}
|
|
</p>
|
|
|
|
</section>
|
|
|
|
{% endif %}
|
|
|
|
|
|
<div class="event-actions">
|
|
{% if concert.ticket_url %}
|
|
|
|
<div class="ticket-button-container">
|
|
|
|
<a
|
|
href="{{ concert.ticket_url }}"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
class="ticket-button"
|
|
>
|
|
🎟️ Tickets
|
|
</a>
|
|
|
|
</div>
|
|
|
|
{% endif %}
|
|
|
|
<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') }}">
|
|
<a href="/concerts/{{ concert.id }}/edit" class="concert-action concert-action-edit">
|
|
{{ _('✏️ Veranstaltung bearbeiten') }}
|
|
</a>
|
|
|
|
{% if user.is_admin and can_delete %}
|
|
|
|
<form
|
|
method="post"
|
|
action="/concerts/{{ concert.id }}/delete"
|
|
onsubmit="return confirm({{ _('Diese Veranstaltung wirklich löschen?') | tojson | forceescape }});"
|
|
>
|
|
<button type="submit" class="concert-action concert-action-delete">
|
|
{{ _('🗑️ Veranstaltung löschen') }}
|
|
</button>
|
|
</form>
|
|
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% endif %}
|
|
|
|
|
|
</div>
|
|
|
|
{% if not concert.is_past %}
|
|
|
|
<section class="attendance-section" id="attendance">
|
|
|
|
<h2>
|
|
{{ _('🐧 Wer ist dabei?') }}
|
|
</h2>
|
|
|
|
<p>{{ _('Wähle, wie es bei dir aussieht. Deine Auswahl kann jederzeit geändert werden.') }}</p>
|
|
|
|
<div class="attendance-actions">
|
|
|
|
<form method="post" action="/concerts/{{ concert.id }}/attendance">
|
|
<input type="hidden" name="status" value="attending">
|
|
<button
|
|
type="submit"
|
|
class="attendance-option {% if current_attendance == 'attending' %}is-selected{% endif %}"
|
|
>
|
|
{{ _('✓ Zugesagt') }}
|
|
</button>
|
|
</form>
|
|
|
|
<form method="post" action="/concerts/{{ concert.id }}/attendance">
|
|
<input type="hidden" name="status" value="maybe">
|
|
<button
|
|
type="submit"
|
|
class="attendance-option {% if current_attendance == 'maybe' %}is-selected{% endif %}"
|
|
>
|
|
{{ _('? Vielleicht') }}
|
|
</button>
|
|
</form>
|
|
|
|
<form method="post" action="/concerts/{{ concert.id }}/attendance">
|
|
<input type="hidden" name="status" value="ticket_search">
|
|
<button
|
|
type="submit"
|
|
class="attendance-option {% if current_attendance == 'ticket_search' %}is-selected{% endif %}"
|
|
>
|
|
{{ _('🔎 Suche Ticket') }}
|
|
</button>
|
|
</form>
|
|
|
|
<form method="post" action="/concerts/{{ concert.id }}/attendance">
|
|
<input type="hidden" name="status" value="ticket_offer">
|
|
<button
|
|
type="submit"
|
|
class="attendance-option {% if current_attendance == 'ticket_offer' %}is-selected{% endif %}"
|
|
>
|
|
{{ _('🎟️ Biete Ticket') }}
|
|
</button>
|
|
</form>
|
|
|
|
</div>
|
|
|
|
<details class="attendance-list">
|
|
<summary>
|
|
{{ 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>
|
|
|
|
{% if attending_users %}
|
|
<ul>
|
|
{% for attendee in attending_users %}
|
|
<li><a href="/users/{{ attendee.username }}">{{ attendee.name }}</a></li>
|
|
{% endfor %}
|
|
</ul>
|
|
{% else %}
|
|
<p>{{ _('Noch niemand hat zugesagt.') }}</p>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div>
|
|
<h3>{{ _('? Vielleicht') }}</h3>
|
|
|
|
{% if maybe_users %}
|
|
<ul>
|
|
{% for maybe_user in maybe_users %}
|
|
<li><a href="/users/{{ maybe_user.username }}">{{ maybe_user.name }}</a></li>
|
|
{% endfor %}
|
|
</ul>
|
|
{% else %}
|
|
<p>{{ _('Noch keine Vielleicht-Angaben.') }}</p>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div>
|
|
<h3>{{ _('🔎 Sucht ein Ticket') }}</h3>
|
|
|
|
{% if ticket_seekers %}
|
|
<ul>
|
|
{% for seeker in ticket_seekers %}
|
|
<li><a href="/users/{{ seeker.username }}">{{ seeker.name }}</a></li>
|
|
{% endfor %}
|
|
</ul>
|
|
{% else %}
|
|
<p>{{ _('Aktuell sucht niemand ein Ticket.') }}</p>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div>
|
|
<h3>{{ _('🎟️ Bietet ein Ticket') }}</h3>
|
|
|
|
{% if ticket_offers %}
|
|
<ul>
|
|
{% for offer in ticket_offers %}
|
|
<li><a href="/users/{{ offer.username }}">{{ offer.name }}</a></li>
|
|
{% endfor %}
|
|
</ul>
|
|
{% else %}
|
|
<p>{{ _('Aktuell bietet niemand ein Ticket an.') }}</p>
|
|
{% endif %}
|
|
</div>
|
|
|
|
</div>
|
|
</details>
|
|
|
|
</section>
|
|
|
|
{% endif %}
|
|
|
|
{% if concert.is_past %}
|
|
<section class="diary-section" id="diary">
|
|
<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>
|
|
{% else %}
|
|
<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>
|
|
|
|
<form method="post" action="/concerts/{{ concert.id }}/comments" class="comment-form">
|
|
<label for="comment-body">{{ _('Mit anderen Konzertbesuchern austauschen') }}</label>
|
|
<textarea
|
|
id="comment-body"
|
|
name="body"
|
|
rows="4"
|
|
maxlength="2000"
|
|
required
|
|
placeholder="{{ _('Schreib einen Kommentar …') }}"
|
|
></textarea>
|
|
<button type="submit" class="attendance-option comment-submit">
|
|
{{ _('Kommentar senden') }}
|
|
</button>
|
|
</form>
|
|
|
|
{% if comments %}
|
|
|
|
<div class="comment-list">
|
|
{% for comment in comments %}
|
|
<article class="comment">
|
|
<div class="comment-meta">
|
|
<strong><a href="/users/{{ comment.username }}">{{ comment.author }}</a></strong>
|
|
<span>{{ comment.created_at }}</span>
|
|
</div>
|
|
<p>{{ comment.body }}</p>
|
|
</article>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
{% else %}
|
|
|
|
<p class="comments-empty">{{ _('Noch keine Kommentare. Mach den Anfang!') }}</p>
|
|
|
|
{% endif %}
|
|
|
|
</section>
|
|
|
|
</div>
|
|
|
|
</article>
|
|
|
|
</div>
|
|
|
|
</body>
|
|
|
|
</html>
|