diff --git a/backend/main.py b/backend/main.py index c1aee66..1f8dd9c 100644 --- a/backend/main.py +++ b/backend/main.py @@ -406,7 +406,7 @@ async def serve_media(path: str, request: _Request): raise _HE(404, "Nicht gefunden.") return _media_response(filepath) -APP_VER = "948" # muss mit APP_VER in app.js übereinstimmen +APP_VER = "951" # muss mit APP_VER in app.js übereinstimmen @app.get("/.well-known/assetlinks.json") async def assetlinks(): diff --git a/backend/routes/auth.py b/backend/routes/auth.py index 59ad6e8..895c94c 100644 --- a/backend/routes/auth.py +++ b/backend/routes/auth.py @@ -418,20 +418,36 @@ async def cancel_subscription(user=Depends(get_current_user)): try: from mailer import send_email, email_html import html as _html - tier_label = {"pro": "Ban Yaro Pro", "breeder": "Züchter"}.get(row["subscription_tier"], row["subscription_tier"]) - expires_fmt = expires[:10] if expires else "—" + tier_label = {"pro": "Ban Yaro Pro", "breeder": "Züchter"}.get(row["subscription_tier"], row["subscription_tier"]) + expires_de = None + if expires: + from datetime import date as _date + try: + d = _date.fromisoformat(expires[:10]) + monate = ["Januar","Februar","März","April","Mai","Juni", + "Juli","August","September","Oktober","November","Dezember"] + expires_de = f"{d.day}. {monate[d.month-1]} {d.year}" + except Exception: + expires_de = expires[:10] + + expiry_line = ( + f"

Dein Abo ist weiterhin aktiv bis zum {expires_de}. " + f"Ab diesem Datum wirst du automatisch auf den kostenlosen Tarif gesetzt.

" + if expires_de else + "

Dein Abo bleibt bis zum Ende des bezahlten Zeitraums aktiv.

" + ) body_html = f"""

Hallo {_html.escape(user['name'])},

deine Kündigung für {tier_label} wurde bestätigt.

-

Dein Abo ist weiterhin aktiv bis zum {expires_fmt}. - Ab diesem Datum wirst du automatisch auf den kostenlosen Tarif gesetzt.

+ {expiry_line}

Deine Daten (Tagebuch, Gesundheit, Notizen) bleiben vollständig erhalten. Wenn du mehrere Hunde hast, kannst du vor dem Ablauf einen als Haupthund festlegen.

Wir hoffen, dich bald wieder begrüßen zu dürfen!

Viele Grüße
René & das Ban Yaro Team

""" html = email_html(body_html, cta_url="https://banyaro.app", cta_label="Ban Yaro öffnen") plain = (f"Hallo {user['name']},\n\nKündigung bestätigt für {tier_label}.\n" - f"Aktiv bis: {expires_fmt}\n\nAlle Daten bleiben erhalten.\n\nViele Grüße\nRené") + + (f"Aktiv bis: {expires_de}\n" if expires_de else "") + + "\nAlle Daten bleiben erhalten.\n\nViele Grüße\nRené") await send_email(user["email"], f"Kündigung bestätigt — {tier_label}", html, plain) except Exception: pass diff --git a/backend/static/js/app.js b/backend/static/js/app.js index a2302f4..2282add 100644 --- a/backend/static/js/app.js +++ b/backend/static/js/app.js @@ -3,7 +3,7 @@ Router, State-Management, Navigation, Initialisierung. ============================================================ */ -const APP_VER = '948'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VER = '951'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen const APP_VERSION = '1.5.1'; // ← semantische Version, wird bei make release gesetzt const IS_STAGING = location.hostname === 'staging.banyaro.app'; // Cache-Bust-Parameter nach Update-Reload sofort entfernen diff --git a/backend/static/js/pages/settings.js b/backend/static/js/pages/settings.js index 89e527b..2d23a96 100644 --- a/backend/static/js/pages/settings.js +++ b/backend/static/js/pages/settings.js @@ -110,7 +110,13 @@ window.Page_settings = (() => { const isPaid = (isPro || isBreeder) && !tier.endsWith('_test') && !isAdmin; const _expiryInfo = () => { - if (!isPaid || !expiresDate) return ''; + if (!isPaid) return ''; + if (cancelled && !expiresDate) { + return `
+ Gekündigt — läuft bis Ablauf des bezahlten Zeitraums +
`; + } + if (!expiresDate) return ''; const color = cancelled ? '#e65100' : 'var(--c-text-secondary)'; const text = cancelled ? `Gekündigt — läuft bis ${expiresDate}` @@ -422,7 +428,17 @@ window.Page_settings = (() => { const fresh = await API.auth.me(); Object.assign(_appState.user, fresh); UI.modal.close(); - UI.toast.success('Kündigung bestätigt. Eine Bestätigungsmail wurde gesendet.'); + const tier = fresh.subscription_tier || ''; + const label = { pro: 'Pro', breeder: 'Züchter' }[tier] || tier; + const exp = fresh.subscription_expires_at; + const expFmt = exp + ? new Date(exp).toLocaleDateString('de-DE', {day:'numeric',month:'long',year:'numeric'}) + : null; + UI.toast.success( + expFmt + ? `Kündigung bestätigt — ${label} läuft noch bis ${expFmt}.` + : 'Kündigung bestätigt. Eine Bestätigungsmail wurde gesendet.' + ); _render(); } catch (e) { btn.disabled = false; @@ -1298,10 +1314,10 @@ window.Page_settings = (() => { document.getElementById('settings-delete-account-btn')?.addEventListener('click', async () => { const ok = await UI.modal.confirm({ - title: 'Konto unwiderruflich löschen?', - body: 'Alle deine Daten (Tagebuch, Gesundheit, Training, Fotos) werden dauerhaft gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.', + title: 'Konto unwiderruflich löschen?', + message: 'Alle deine Daten (Tagebuch, Gesundheit, Training, Fotos) werden dauerhaft gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.', confirmText: 'Ja, Konto löschen', - danger: true, + danger: true, }); if (!ok) return; try { diff --git a/backend/static/sw.js b/backend/static/sw.js index 0242bac..5be5d42 100644 --- a/backend/static/sw.js +++ b/backend/static/sw.js @@ -3,7 +3,7 @@ Offline-Cache + Push Notifications + Tile-Cache ============================================================ */ -const CACHE_VERSION = 'by-v948'; +const CACHE_VERSION = 'by-v951'; const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache