Files
TimeTracker/app/config/support_ui.py
T
Dries Peeters b0dde80ba9 feat(web): high-visibility support modal, prompts, and supporter UX
Add a support modal with usage stats, tier and license links, share control, and offline-safe outbound CTAs. Surface support from the header, sidebar, user menu, dashboard card, and settings "Support & Community" section without hiding entry points when a supporter license is active.

Introduce UsageStatsService and a persisted users.support_stats_reports_generated counter incremented on key report exports and custom report views. Add SupportPromptService for session-scoped soft toasts (after export, dashboard milestones, long session via POST /donate/request-soft-prompt).

Wire consent-aware track_event names support.* and mirror funnel rows in DonationInteraction; fix has_recent_donation_click to treat link_clicked as a recent click. Document events and SUPPORT_* / migration notes in docs.

Tests: tests/test_support_services.py for prompt and usage stats behavior.
2026-04-15 10:55:37 +02:00

60 lines
1.8 KiB
Python

"""Non-translated support/checkout configuration (URLs, numeric defaults)."""
from __future__ import annotations
import os
from typing import Any, Dict
def get_support_portal_base(config: Dict[str, Any] | Any) -> str:
"""Marketing site base; defaults to drytrix TimeTracker domain."""
if hasattr(config, "get"):
raw = (config.get("SUPPORT_PORTAL_BASE") or "").strip()
else:
raw = ""
if not raw:
raw = os.getenv("SUPPORT_PORTAL_BASE", "https://timetracker.drytrix.com").strip()
return raw.rstrip("/")
def build_support_checkout_urls(config: Dict[str, Any] | Any) -> Dict[str, str]:
"""
Per-tier outbound URLs. Unset env vars fall back to SUPPORT_PURCHASE_URL so checkout stays one hop.
"""
if hasattr(config, "get"):
purchase = (config.get("SUPPORT_PURCHASE_URL") or "").strip()
else:
purchase = ""
if not purchase:
purchase = os.getenv(
"SUPPORT_PURCHASE_URL", "https://timetracker.drytrix.com/support.html"
).strip()
def _tier(env_name: str) -> str:
v = os.getenv(env_name, "").strip()
return v or purchase
return {
"eur5": _tier("SUPPORT_DONATE_EUR5_URL"),
"eur10": _tier("SUPPORT_DONATE_EUR10_URL"),
"eur25": _tier("SUPPORT_DONATE_EUR25_URL"),
"license": purchase,
}
def get_long_session_minutes() -> int:
try:
return max(30, int(os.getenv("SUPPORT_LONG_SESSION_MINUTES", "120")))
except ValueError:
return 120
def get_social_proof_text(config: Dict[str, Any] | Any) -> str:
if hasattr(config, "get"):
t = (config.get("SUPPORT_SOCIAL_PROOF_TEXT") or "").strip()
else:
t = ""
if not t:
t = os.getenv("SUPPORT_SOCIAL_PROOF_TEXT", "").strip()
return t