Add password reset flow, attendee profile enhancements, and spec

- Add forgot_password and reset_password templates and routes
- Enhance profile.html with photo crop/zoom functionality
- Add attendee type management to edit_attendee.html
- Update templates styling and layout consistency
- Document database schema in SPEC.md
- Add CLAUDE.md project guidance

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-19 19:52:42 +00:00
parent 335658f2bf
commit 934848b7f5
19 changed files with 758 additions and 124 deletions
+68 -40
View File
@@ -4,47 +4,75 @@
{% block content %}
<div class="attendees-page">
<h1>{{ 'event_attendees'|t }}</h1>
<p>{{ 'connect_with_attendees'|t }}</p>
{% if attendees %}
<div class="attendees-grid">
{% for att in attendees %}
<div class="attendee-card">
<div class="attendee-photo">
{% if att.profile_picture %}
<img src="{{ url_for('static', filename='uploads/' + att.profile_picture) }}" alt="Photo">
{% else %}
<div class="no-photo">{{ att.first_name[0] }}{{ att.last_name[0] }}</div>
{% endif %}
</div>
<div class="attendee-info">
<h3>{{ att.first_name }} {{ att.last_name }}</h3>
<p class="attendee-org">{{ (att.organisation)|spacify if att.organisation else '' }}</p>
<p class="attendee-role">{{ (att.role)|spacify if att.role else '' }}</p>
{% if att.introduction %}
<p class="attendee-intro">{{ att.introduction[:100] }}{% if att.introduction|length > 100 %}...{% endif %}</p>
{% endif %}
</div>
<div class="attendee-actions">
{% if att.my_status == 'accepted' %}
<span class="badge badge-success">{{ 'connected'|t }}</span>
{% elif att.my_status == 'pending' %}
<span class="badge badge-pending">{{ 'request_pending'|t }}</span>
{% elif att.my_status == 'rejected' %}
<span class="badge badge-rejected">{{ 'rejected'|t }}</span>
{% else %}
<form method="POST" action="{{ url_for('create_connection') }}" class="connect-form">
<input type="hidden" name="connected_attendee_id" value="{{ att.id }}">
<button type="submit" class="btn btn-sm btn-primary">{{ 'connect'|t }}</button>
</form>
{% endif %}
</div>
</div>
{% endfor %}
{% if event %}
<div class="event-info-header">
<h1>{{ event.name }}</h1>
<div class="event-meta">
{% if event.location %}
<span class="event-location">{{ event.location }}</span>
{% endif %}
{% if event.start_date %}
<span class="event-date">{{ event.start_date|format_date }}</span>
{% endif %}
</div>
{% else %}
<p class="no-attendees">{{ 'no_other_attendees'|t }}</p>
{% if event.description %}
<p class="event-description">{{ event.description }}</p>
{% endif %}
</div>
{% endif %}
<p class="page-subtitle">{{ 'connect_with_attendees'|t }}</p>
<div class="attendee-search">
<form method="GET" action="{{ url_for('list_attendees') }}">
<input type="text" name="q" value="{{ search_query }}" placeholder="{{ 'search_attendees'|t }}">
<button type="submit" class="btn btn-primary">{{ 'search'|t }}</button>
</form>
<p class="search-hint">{{ 'search_min_chars'|t|format(min_chars) }}</p>
</div>
{% if search_query and search_query|length >= min_chars %}
{% if too_broad %}
<p class="search-too-broad">{{ 'search_too_broad'|t }}</p>
{% elif attendees %}
<div class="attendees-grid">
{% for att in attendees %}
<div class="attendee-card">
<div class="attendee-photo">
{% if att.profile_picture %}
<img src="{{ url_for('static', filename='uploads/' + att.profile_picture) }}" alt="Photo">
{% else %}
<div class="no-photo">{{ att.first_name[0] }}{{ att.last_name[0] }}</div>
{% endif %}
</div>
<div class="attendee-info">
<h3>{{ att.first_name }} {{ att.last_name }}</h3>
<p class="attendee-org">{{ att.organisation if att.organisation else '' }}</p>
<p class="attendee-role">{{ att.role if att.role else '' }}</p>
{% if att.introduction %}
<p class="attendee-intro">{{ att.introduction[:100] }}{% if att.introduction|length > 100 %}...{% endif %}</p>
{% endif %}
</div>
<div class="attendee-actions">
{% if att.my_status == 'accepted' %}
<span class="badge badge-success">{{ 'connected'|t }}</span>
{% elif att.my_status == 'pending' %}
<span class="badge badge-pending">{{ 'request_pending'|t }}</span>
{% elif att.my_status == 'rejected' %}
<span class="badge badge-rejected">{{ 'rejected'|t }}</span>
{% else %}
<form method="POST" action="{{ url_for('create_connection') }}" class="connect-form">
<input type="hidden" name="connected_attendee_id" value="{{ att.id }}">
<button type="submit" class="btn btn-sm btn-primary">{{ 'connect'|t }}</button>
</form>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% else %}
<p class="no-attendees">{{ 'no_attendees_found'|t }}</p>
{% endif %}
{% endif %}
</div>
{% endblock %}
+1 -1
View File
@@ -31,7 +31,7 @@
<div class="request-info">
<h3>{{ req.first_name }} {{ req.last_name }}</h3>
<p class="request-email">{{ req.email }}</p>
<p class="request-org">{{ (req.organisation)|spacify if req.organisation else '' }}{% if req.role %} - {{ (req.role)|spacify }}{% endif %}</p>
<p class="request-org">{{ req.organisation if req.organisation else '' }}{% if req.role %} - {{ req.role }}{% endif %}</p>
{% if req.introduction %}
<p class="request-intro">"{{ req.introduction }}"</p>
{% endif %}
+2 -2
View File
@@ -54,8 +54,8 @@
{% for conn in connections %}
<div class="connection-card">
<h4 style="margin-bottom: 12px;">{{ conn.first_name }} {{ conn.last_name }}</h4>
<p class="connection-org" style="margin-bottom: 12px;">{{ (conn.organisation)|spacify if conn.organisation else '' }}</p>
<p class="connection-role">{{ (conn.role)|spacify if conn.role else '' }}</p>
<p class="connection-org" style="margin-bottom: 12px;">{{ conn.organisation if conn.organisation else '' }}</p>
<p class="connection-role">{{ conn.role if conn.role else '' }}</p>
</div>
{% endfor %}
</div>
+55
View File
@@ -102,6 +102,14 @@
<textarea id="introduction" name="introduction" rows="4">{{ attendee.introduction or '' }}</textarea>
</div>
<div class="form-group switch-group">
<label for="visible_to_others">{{ 'visible_to_attendees'|t }}</label>
<label class="switch">
<input type="checkbox" id="visible_to_others" name="visible_to_others" value="1" {% if attendee.visible_to_others %}checked{% endif %}>
<span class="slider"></span>
</label>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">{{ 'update_profile'|t }}</button>
</div>
@@ -110,6 +118,53 @@
</div>
<style>
.switch-group {
display: flex;
align-items: center;
gap: 1rem;
}
.switch-group label {
margin-bottom: 0;
}
.switch {
position: relative;
display: inline-block;
width: 50px;
height: 26px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: 0.3s;
border-radius: 26px;
}
.slider:before {
position: absolute;
content: "";
height: 20px;
width: 20px;
left: 3px;
bottom: 3px;
background-color: white;
transition: 0.3s;
border-radius: 50%;
}
input:checked + .slider {
background-color: #2ecc71;
}
input:checked + .slider:before {
transform: translateX(24px);
}
.current-photo {
width: 150px;
height: 150px;