Files
conference/templates/organizer/breakout_session_detail.html
T
paul a3546b4c01 Security hardening: env-based config, CSRF protection, secure code generation, plus presenter management and email communications system
- Move all credentials from hardcoded to .env with python-dotenv (add .env.example template and .gitignore)
- Add CSRF token generation and validation on all state-changing requests
- Replace random.choices with secrets.choice for all secure code generation
- Add session cookie security headers (HttpOnly, Secure, SameSite)
- Add presenter management: assign/remove presenters to breakout sessions, presenter QR scanning
- Add email communications system: templates per event, custom email sending, sent email tracking
- Add staff/organizer profile pages with staff code display
- New DB tables: event_email_templates, sent_emails, user_communications
- New DB columns: staff.reminder_count/reminder_dates, breakout_session_organizers.presenter_code
- Expand English translation strings across all new features

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-26 20:34:48 +00:00

192 lines
8.1 KiB
HTML

{% extends "base.html" %}
{% block title %}{{ session.name }} - {{ 'breakout_session'|t }}{% endblock %}
{% block content %}
<div class="session-detail-page">
<div class="page-header">
<div class="header-content">
<h1>{{ session.name }}</h1>
<p class="page-subtitle">{{ session.event_name }}</p>
</div>
<div class="header-actions">
<a href="{{ url_for('edit_breakout_session', session_id=session.id) }}" class="btn btn-outline">{{ 'edit_session'|t }}</a>
<a href="{{ url_for('list_breakout_sessions', event_id=session.event_id) }}" class="btn btn-ghost">{{ 'back_to_sessions'|t }}</a>
</div>
</div>
<div class="session-meta-grid">
<div class="meta-card">
<div class="meta-icon">
<i data-lucide="calendar"></i>
</div>
<div class="meta-content">
<span class="meta-label">{{ 'time'|t }}</span>
<span class="meta-value">
{% if session.start_time %}
{{ session.start_time|localized_date('%H:%M') }} - {{ session.end_time|localized_date('%H:%M') }}
{% else %}
TBD
{% endif %}
</span>
</div>
</div>
<div class="meta-card">
<div class="meta-icon">
<i data-lucide="map-pin"></i>
</div>
<div class="meta-content">
<span class="meta-label">{{ 'location'|t }}</span>
<span class="meta-value">{{ session.location or 'TBD' }}</span>
</div>
</div>
<div class="meta-card">
<div class="meta-icon">
<i data-lucide="users"></i>
</div>
<div class="meta-content">
<span class="meta-label">{{ 'capacity'|t }}</span>
<span class="meta-value">
{% if session.max_attendees %}
{{ rsvps|length }} / {{ session.max_attendees }}
{% else %}
{{ rsvps|length }} ({{ 'unlimited'|t }})
{% endif %}
</span>
</div>
</div>
</div>
{% if session.description %}
<div class="session-description-card">
<h3>{{ 'description'|t }}</h3>
<p>{{ session.description }}</p>
</div>
{% endif %}
<div class="session-sections">
<!-- Presenters Section -->
<section class="section-card presenters-section">
<div class="section-header">
<h2>{{ 'presenters'|t }}</h2>
<button type="button" class="btn btn-sm btn-primary" onclick="toggleAddPresenter()">
<i data-lucide="plus"></i> {{ 'add_presenter'|t }}
</button>
</div>
<!-- Add Presenter Form -->
<div id="add-presenter-form" class="add-presenter-form hidden">
<form method="POST" action="{{ url_for('add_presenter_to_session', session_id=session.id) }}">
<input type="hidden" name="code" value="{{ session.code }}">
<div class="form-row">
<div class="form-group">
<label for="organizer_id">{{ 'select_organizer'|t }}</label>
<select id="organizer_id" name="organizer_id" required>
<option value="">{{ 'select_organizer'|t }}...</option>
{% for org in available_organizers %}
<option value="{{ org.id }}">{{ org.first_name }} {{ org.last_name }} ({{ org.email }})</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">{{ 'add_presenter'|t }}</button>
<button type="button" class="btn btn-ghost" onclick="toggleAddPresenter()">{{ 'cancel'|t }}</button>
</div>
</form>
</div>
{% if presenters %}
<div class="presenters-grid">
{% for presenter in presenters %}
<div class="presenter-card">
<div class="presenter-info">
<div class="presenter-avatar">
{{ presenter.first_name[0] }}{{ presenter.last_name[0] }}
</div>
<div class="presenter-details">
<h4>{{ presenter.first_name }} {{ presenter.last_name }}</h4>
<p>{{ presenter.email }}</p>
</div>
</div>
<div class="presenter-qr">
<div class="qr-code-display" data-code="{{ presenter.presenter_code }}">
<span class="qr-label">{{ 'scan_here'|t }}</span>
<span class="qr-code">{{ presenter.presenter_code }}</span>
</div>
</div>
<div class="presenter-actions">
<form method="POST" action="{{ url_for('remove_presenter_from_session', session_id=session.id, presenter_id=presenter.id) }}" class="inline-form">
<input type="hidden" name="code" value="{{ session.code }}">
<button type="submit" class="btn btn-sm btn-ghost btn-remove" data-confirm="{{ 'confirm_remove_presenter'|t }}">
<i data-lucide="trash-2"></i>
</button>
</form>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="empty-state">
<i data-lucide="user-x"></i>
<p>{{ 'no_presenters_yet'|t }}</p>
</div>
{% endif %}
</section>
<!-- Registered Attendees Section -->
<section class="section-card attendees-section">
<div class="section-header">
<h2>{{ 'registered_attendees'|t }}</h2>
<span class="badge badge-info">{{ rsvps|length }}</span>
</div>
{% if rsvps %}
<div class="table-container">
<table class="attendees-table">
<thead>
<tr>
<th>{{ 'name'|t }}</th>
<th>{{ 'email'|t }}</th>
<th>{{ 'organisation'|t }}</th>
<th>{{ 'role'|t }}</th>
</tr>
</thead>
<tbody>
{% for rsvp in rsvps %}
<tr>
<td>
<div class="attendee-name-cell">
<span class="attendee-avatar-sm">{{ rsvp.first_name[0] }}{{ rsvp.last_name[0] }}</span>
{{ rsvp.first_name }} {{ rsvp.last_name }}
</div>
</td>
<td>{{ rsvp.email }}</td>
<td>{{ rsvp.organisation if rsvp.organisation else '-' }}</td>
<td>{{ rsvp.role if rsvp.role else '-' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="empty-state">
<i data-lucide="users"></i>
<p>{{ 'no_attendees_registered'|t }}</p>
</div>
{% endif %}
</section>
</div>
</div>
<script>
function toggleAddPresenter() {
const form = document.getElementById('add-presenter-form');
form.classList.toggle('hidden');
}
document.addEventListener('DOMContentLoaded', function() {
lucide.createIcons();
});
</script>
{% endblock %}