feat: Clock

This commit is contained in:
2026-03-03 17:38:51 +00:00
parent 225ac5e441
commit 8152072bec
7 changed files with 365 additions and 114 deletions

View File

@@ -4,6 +4,7 @@ import uuid
from datetime import datetime
from functools import wraps
from pathlib import Path
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
from flask import (
Flask,
@@ -34,6 +35,9 @@ app.config["GOOGLE_MAPS_EMBED_URL"] = os.environ.get(
"https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d0",
)
app.config["WEDDING_DATE"] = os.environ.get("WEDDING_DATE", "")
app.config["WEDDING_COUNTDOWN_ISO"] = os.environ.get("WEDDING_COUNTDOWN_ISO", "")
app.config["WEDDING_COUNTDOWN_LOCAL"] = os.environ.get("WEDDING_COUNTDOWN_LOCAL", "2026-09-04 15:00")
app.config["WEDDING_TIMEZONE"] = os.environ.get("WEDDING_TIMEZONE", "Europe/Berlin")
app.config["HERO_IMAGE_FILENAME"] = os.environ.get("HERO_IMAGE_FILENAME")
ALLOWED_EXTENSIONS = {"jpg", "jpeg", "png", "heic", "heif"}
@@ -280,6 +284,14 @@ TEXTS = {
"flash_admin_only": "Dieser Bereich ist nur für Admins verfügbar.",
"dashboard": "Dashboard",
"back": "Zurück",
"countdown_button_label": "Countdown bis zur Hochzeit",
"countdown_until": "Noch",
"countdown_started": "Die Feier hat begonnen",
"countdown_days": "Tage",
"countdown_hours": "Std",
"countdown_minutes": "Min",
"countdown_seconds": "Sek",
"countdown_subline": "bis zur Hochzeit",
},
"en": {
"brand": "Svenja & Dominic",
@@ -361,6 +373,14 @@ TEXTS = {
"flash_admin_only": "This area is available to admins only.",
"dashboard": "Dashboard",
"back": "Back",
"countdown_button_label": "Wedding countdown",
"countdown_until": "Starts in",
"countdown_started": "The celebration has started",
"countdown_days": "Days",
"countdown_hours": "Hrs",
"countdown_minutes": "Min",
"countdown_seconds": "Sec",
"countdown_subline": "until the wedding",
},
}
@@ -390,6 +410,39 @@ def get_hero_image_asset() -> str:
return "assets/hero.jpg"
def get_wedding_countdown_iso() -> str:
configured_iso = str(app.config.get("WEDDING_COUNTDOWN_ISO", "") or "").strip()
if configured_iso:
try:
datetime.fromisoformat(configured_iso.replace("Z", "+00:00"))
return configured_iso
except ValueError:
pass
local_value = str(app.config.get("WEDDING_COUNTDOWN_LOCAL", "") or "").strip()
timezone_name = str(app.config.get("WEDDING_TIMEZONE", "Europe/Berlin") or "Europe/Berlin").strip()
if not local_value:
return "2026-09-04T15:00:00+02:00"
parsed_local = None
for fmt in ("%Y-%m-%d %H:%M", "%Y-%m-%dT%H:%M", "%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S"):
try:
parsed_local = datetime.strptime(local_value, fmt)
break
except ValueError:
continue
if parsed_local is None:
return "2026-09-04T15:00:00+02:00"
try:
tz = ZoneInfo(timezone_name)
except ZoneInfoNotFoundError:
tz = ZoneInfo("Europe/Berlin")
return parsed_local.replace(tzinfo=tz).isoformat()
@app.context_processor
def inject_common() -> dict:
role = session.get("role", "guest")
@@ -408,6 +461,7 @@ def inject_common() -> dict:
"location_website_url": app.config["LOCATION_WEBSITE_URL"],
"google_maps_embed_url": app.config["GOOGLE_MAPS_EMBED_URL"],
"wedding_date": app.config["WEDDING_DATE"],
"wedding_countdown_iso": get_wedding_countdown_iso(),
"hero_image_url": url_for("static", filename=get_hero_image_asset()),
}