Compare commits

...

5 commits

5 changed files with 45 additions and 13 deletions

View file

@ -406,7 +406,7 @@ async def serve_media(path: str, request: _Request):
raise _HE(404, "Nicht gefunden.") raise _HE(404, "Nicht gefunden.")
return _media_response(filepath) 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") @app.get("/.well-known/assetlinks.json")
async def assetlinks(): async def assetlinks():

View file

@ -418,20 +418,36 @@ async def cancel_subscription(user=Depends(get_current_user)):
try: try:
from mailer import send_email, email_html from mailer import send_email, email_html
import html as _html import html as _html
tier_label = {"pro": "Ban Yaro Pro", "breeder": "Züchter"}.get(row["subscription_tier"], row["subscription_tier"]) tier_label = {"pro": "Ban Yaro Pro", "breeder": "Züchter"}.get(row["subscription_tier"], row["subscription_tier"])
expires_fmt = expires[:10] if expires else "" 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"<p>Dein Abo ist weiterhin aktiv bis zum <strong>{expires_de}</strong>. "
f"Ab diesem Datum wirst du automatisch auf den kostenlosen Tarif gesetzt.</p>"
if expires_de else
"<p>Dein Abo bleibt bis zum Ende des bezahlten Zeitraums aktiv.</p>"
)
body_html = f""" body_html = f"""
<p>Hallo {_html.escape(user['name'])},</p> <p>Hallo {_html.escape(user['name'])},</p>
<p>deine Kündigung für <strong>{tier_label}</strong> wurde bestätigt.</p> <p>deine Kündigung für <strong>{tier_label}</strong> wurde bestätigt.</p>
<p>Dein Abo ist weiterhin aktiv bis zum <strong>{expires_fmt}</strong>. {expiry_line}
Ab diesem Datum wirst du automatisch auf den kostenlosen Tarif gesetzt.</p>
<p>Deine Daten (Tagebuch, Gesundheit, Notizen) bleiben vollständig erhalten. <p>Deine Daten (Tagebuch, Gesundheit, Notizen) bleiben vollständig erhalten.
Wenn du mehrere Hunde hast, kannst du vor dem Ablauf einen als Haupthund festlegen.</p> Wenn du mehrere Hunde hast, kannst du vor dem Ablauf einen als Haupthund festlegen.</p>
<p>Wir hoffen, dich bald wieder begrüßen zu dürfen!</p> <p>Wir hoffen, dich bald wieder begrüßen zu dürfen!</p>
<p>Viele Grüße<br>René &amp; das Ban Yaro Team</p>""" <p>Viele Grüße<br>René &amp; das Ban Yaro Team</p>"""
html = email_html(body_html, cta_url="https://banyaro.app", cta_label="Ban Yaro öffnen") 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" 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) await send_email(user["email"], f"Kündigung bestätigt — {tier_label}", html, plain)
except Exception: except Exception:
pass pass

View file

@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung. 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 APP_VERSION = '1.5.1'; // ← semantische Version, wird bei make release gesetzt
const IS_STAGING = location.hostname === 'staging.banyaro.app'; const IS_STAGING = location.hostname === 'staging.banyaro.app';
// Cache-Bust-Parameter nach Update-Reload sofort entfernen // Cache-Bust-Parameter nach Update-Reload sofort entfernen

View file

@ -110,7 +110,13 @@ window.Page_settings = (() => {
const isPaid = (isPro || isBreeder) && !tier.endsWith('_test') && !isAdmin; const isPaid = (isPro || isBreeder) && !tier.endsWith('_test') && !isAdmin;
const _expiryInfo = () => { const _expiryInfo = () => {
if (!isPaid || !expiresDate) return ''; if (!isPaid) return '';
if (cancelled && !expiresDate) {
return `<div style="font-size:var(--text-xs);color:#e65100;margin-top:var(--space-1)">
Gekündigt läuft bis Ablauf des bezahlten Zeitraums
</div>`;
}
if (!expiresDate) return '';
const color = cancelled ? '#e65100' : 'var(--c-text-secondary)'; const color = cancelled ? '#e65100' : 'var(--c-text-secondary)';
const text = cancelled const text = cancelled
? `Gekündigt — läuft bis ${expiresDate}` ? `Gekündigt — läuft bis ${expiresDate}`
@ -422,7 +428,17 @@ window.Page_settings = (() => {
const fresh = await API.auth.me(); const fresh = await API.auth.me();
Object.assign(_appState.user, fresh); Object.assign(_appState.user, fresh);
UI.modal.close(); 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(); _render();
} catch (e) { } catch (e) {
btn.disabled = false; btn.disabled = false;
@ -1298,10 +1314,10 @@ window.Page_settings = (() => {
document.getElementById('settings-delete-account-btn')?.addEventListener('click', async () => { document.getElementById('settings-delete-account-btn')?.addEventListener('click', async () => {
const ok = await UI.modal.confirm({ const ok = await UI.modal.confirm({
title: 'Konto unwiderruflich löschen?', 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.', 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', confirmText: 'Ja, Konto löschen',
danger: true, danger: true,
}); });
if (!ok) return; if (!ok) return;
try { try {

View file

@ -3,7 +3,7 @@
Offline-Cache + Push Notifications + Tile-Cache Offline-Cache + Push Notifications + Tile-Cache
============================================================ */ ============================================================ */
const CACHE_VERSION = 'by-v948'; const CACHE_VERSION = 'by-v951';
const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_STATIC = `${CACHE_VERSION}-static`;
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache