diff --git a/GUESTS.md b/GUESTS.md index e5b85af..483963d 100644 --- a/GUESTS.md +++ b/GUESTS.md @@ -2,7 +2,7 @@ Aktueller Stand der Login-Gruppen aus `backend/app.py` (`DEFAULT_INVITATION_GROUPS`). -Anzahl Gruppen: **41** +Anzahl Gruppen: **43** | Username | Rolle | Passwort | Members | |---|---|---|---| @@ -47,5 +47,7 @@ Anzahl Gruppen: **41** | Familie Kieffer | guest | `Kief!Fest24#` | Anny, John | | Peter | guest | `Peter!Fest26#` | Peter | | Johanna | guest | `Joha!Fest26#` | Johanna | +| Fotograf | guest | `Foto!Graf26#` | – (nur Foto-Upload) | +| Fotobox | guest | `Foto!Box26#` | – (nur Foto-Upload) | Hinweis: Diese Datei enthält Klartext-Passwörter und sollte nicht öffentlich geteilt werden. diff --git a/backend/app.py b/backend/app.py index a199f82..dfa7081 100644 --- a/backend/app.py +++ b/backend/app.py @@ -212,6 +212,18 @@ DEFAULT_INVITATION_GROUPS = [ }, {"name": "Peter", "password": "Peter!Fest26#", "role": "guest", "members": ["Peter"]}, {"name": "Johanna", "password": "Joha!Fest26#", "role": "guest", "members": ["Johanna"]}, + { + "name": "Fotograf", + "password": os.environ.get("PHOTOGRAPHER_PASSWORD", "Foto!Graf26#"), + "role": "guest", + "members": [], + }, + { + "name": "Fotobox", + "password": os.environ.get("PHOTOBOOTH_PASSWORD", "Foto!Box26#"), + "role": "guest", + "members": [], + }, ] AGE_REQUIRED_NAMES = {"Lena", "Finn", "Fin", "Bruno"} @@ -453,7 +465,15 @@ TEXTS = { "gifts_caption": "Trommelwirbel... in unserer 70-Quadratmeter-Wohnung ist leider kein Platz mehr für Materielles.", "gifts_text": "Also: Money! Ein finanzieller Beitrag zu unserer Reise nach der Hochzeit ist herzlich willkommen.", "gallery_uploaded_by": "von {name}", + "gallery_uploaded_by_prefix": "von", "gallery_empty": "Noch keine Bilder vorhanden.", + "gallery_filter_label": "Galerie nach Uploader filtern", + "gallery_filter_trigger": "Nach Uploader filtern:", + "gallery_filter_no_results": "Keine Bilder entsprechen den ausgewählten Filtern.", + "gallery_uploader_filter_label": "Uploader auswählen", + "gallery_uploader_all": "Alle", + "gallery_uploader_none": "Keine", + "gallery_uploader_selected": "{count} ausgewählt", "gallery_image_alt": "Upload von {name}", "gallery_save_hint": "Tipp: Halte das Bild länger gedrückt, um es in deiner Mediathek zu speichern.", "flash_enter_group_name": "Bitte Gruppenname eingeben.", @@ -727,7 +747,15 @@ TEXTS = { "gifts_caption": "Drum roll... in our 70-square-meter apartment, we sadly have no room left for material things.", "gifts_text": "So: Money! A financial contribution to our trip after the wedding is very welcome.", "gallery_uploaded_by": "by {name}", + "gallery_uploaded_by_prefix": "by", "gallery_empty": "No photos available yet.", + "gallery_filter_label": "Filter gallery by uploader", + "gallery_filter_trigger": "Filter by uploader:", + "gallery_filter_no_results": "No photos match the selected filters.", + "gallery_uploader_filter_label": "Select uploaders", + "gallery_uploader_all": "All", + "gallery_uploader_none": "None", + "gallery_uploader_selected": "{count} selected", "gallery_image_alt": "Uploaded by {name}", "gallery_save_hint": "Tip: Touch and hold the photo to save it to your photo library.", "flash_enter_group_name": "Please enter group name.", @@ -1081,12 +1109,15 @@ def init_db() -> None: def seed_default_groups() -> None: db = get_db() - existing = db.execute("SELECT COUNT(*) AS c FROM groups").fetchone() - if int(existing["c"] or 0) > 0: - return - now = datetime.utcnow().isoformat() for entry in DEFAULT_INVITATION_GROUPS: + existing = db.execute( + "SELECT id FROM groups WHERE LOWER(name) = LOWER(?)", + (entry["name"],), + ).fetchone() + if existing is not None: + continue + password_hash = generate_password_hash(entry["password"]) cursor = db.execute( "INSERT INTO groups (name, password_hash, role, created_at) VALUES (?, ?, ?, ?)", @@ -1104,13 +1135,14 @@ def seed_default_groups() -> None: ) ) - db.executemany( - """ - INSERT INTO group_members (group_id, name, requires_age, created_at) - VALUES (?, ?, ?, ?) - """, - member_rows, - ) + if member_rows: + db.executemany( + """ + INSERT INTO group_members (group_id, name, requires_age, created_at) + VALUES (?, ?, ?, ?) + """, + member_rows, + ) db.commit() @@ -1247,7 +1279,12 @@ def complete_onboarding(): @app.get("/gästebereich") @login_required def guest_area(): - return render_template("guest_area.html") + db = get_db() + member_row = db.execute( + "SELECT 1 FROM group_members WHERE group_id = ? LIMIT 1", + (int(session["group_id"]),), + ).fetchone() + return render_template("guest_area.html", has_rsvp_members=member_row is not None) @app.route("/gastgeberbereich", methods=["GET"]) @@ -1322,6 +1359,9 @@ def rsvp(): (current_group_id,), ).fetchall() + if not members: + return redirect(url_for("guest_area")) + if request.method == "POST": updates = [] for member in members: @@ -1430,7 +1470,11 @@ def gallery(): ORDER BY uploads.id DESC """ ).fetchall() - return render_template("gallery.html", images=images) + uploaders = sorted( + {str(image["uploaded_by_name"]) for image in images}, + key=str.casefold, + ) + return render_template("gallery.html", images=images, uploaders=uploaders) @app.post("/gallery/delete/") diff --git a/backend/static/styles.css b/backend/static/styles.css index 7614ef7..ffde7fb 100644 --- a/backend/static/styles.css +++ b/backend/static/styles.css @@ -1269,6 +1269,129 @@ input[type="file"]:focus { gap: 1rem; } +.gallery-filter-controls { + display: flex; + align-items: flex-start; + justify-content: center; + flex-wrap: wrap; + gap: 0.65rem; + margin: 0 0 1rem; +} + +.gallery-item.is-filtered-out { + display: none; +} + +.gallery-filter-empty { + margin: 1.25rem 0 0; + color: rgba(31, 26, 23, 0.7); +} + +.gallery-uploader-filter { + position: relative; + width: min(100%, 22rem); +} + +.gallery-uploader-trigger { + width: 100%; + min-height: 2.75rem; + padding: 0 0.9rem 0 1rem; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.3rem; + border: 1px solid rgba(31, 58, 47, 0.2); + border-radius: 999px; + color: var(--forest); + background: rgba(255, 253, 248, 0.92); + box-shadow: 0 5px 15px rgba(31, 58, 47, 0.08); + font: inherit; + cursor: pointer; + white-space: nowrap; + transition: border-color 0.2s ease, box-shadow 0.2s ease; +} + +.gallery-uploader-trigger:hover, +.gallery-uploader-trigger:focus-visible, +.gallery-uploader-trigger[aria-expanded="true"] { + border-color: var(--gold); + box-shadow: 0 0 0 3px rgba(178, 137, 70, 0.14); + outline: none; +} + +.gallery-uploader-trigger svg { + width: 1.1rem; + height: 1.1rem; + flex: 0 0 auto; + fill: currentColor; +} + +.gallery-uploader-chevron { + margin-left: 0.15rem; + transition: transform 0.18s ease; +} + +.gallery-uploader-trigger[aria-expanded="true"] .gallery-uploader-chevron { + transform: rotate(180deg); +} + +.gallery-uploader-search { + opacity: 0.72; +} + +.gallery-uploader-menu { + position: absolute; + z-index: 50; + top: calc(100% + 0.45rem); + left: 50%; + width: max-content; + min-width: 14rem; + max-width: min(20rem, calc(100vw - 2rem)); + max-height: min(22rem, 60vh); + padding: 0.45rem; + overflow: auto; + transform: translateX(-50%); + border: 1px solid rgba(31, 58, 47, 0.16); + border-radius: 16px; + background: #fffdf8; + box-shadow: 0 14px 32px rgba(31, 58, 47, 0.2); +} + +.gallery-uploader-menu[hidden] { + display: none; +} + +.gallery-uploader-options { + padding-top: 0.25rem; + border-top: 1px solid rgba(31, 58, 47, 0.1); +} + +.gallery-uploader-option { + min-height: 2.4rem; + padding: 0.45rem 0.55rem; + display: flex; + align-items: center; + gap: 0.55rem; + border-radius: 10px; + cursor: pointer; +} + +.gallery-uploader-option:hover { + background: rgba(232, 219, 196, 0.42); +} + +.gallery-uploader-option input { + width: 1rem; + height: 1rem; + margin: 0; + flex: 0 0 auto; + accent-color: var(--forest); +} + +.gallery-uploader-option-all { + font-weight: 700; +} + .gallery-item { margin: 0; } @@ -1298,6 +1421,14 @@ input[type="file"]:focus { .gallery-item figcaption { margin-top: 0.55rem; margin-bottom: 0.55rem; + text-align: center; + color: rgba(31, 58, 47, 0.68); + font-size: 0.94rem; +} + +.gallery-item figcaption strong { + color: var(--forest); + font-weight: 700; } .gallery-delete-form { diff --git a/backend/templates/gallery.html b/backend/templates/gallery.html index 19faf84..3f12cb3 100644 --- a/backend/templates/gallery.html +++ b/backend/templates/gallery.html @@ -3,9 +3,44 @@

{{ t('gallery') }}

{% if images %} + +