User feedback + expanded privacy-respecting admin stats
Feedback: - feedback table; POST /api/feedback (anonymous-ok, optional category/email, honeypot + per-day flood cap) stores + emails the admin; GET /api/admin/feedback. - Shared feedback store + FeedbackModal; a speech-bubble opens it from the desktop header, the mobile top bar (logo moves left), the footer, and /account. Feedback section in /admin. Stats (additive, same privacy model — no IP/UA/referrer/raw terms): - Event vocab: summary_viewed (fired on /a load), full_story (card → source), not_today/less_like_this/hide_topic, replace_used/replace_none, paywall_replace, paywalled_source_open. Card title/image opens /a (no double-count); history records via keepalive so it survives the nav. - Dashboard: Accounts card (counts only), reading funnel (summary→source rate), emotional-mix & friction, paywall, returning-visitor buckets. (Health metrics deferred to a future monitoring dashboard.) 131 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -50,6 +50,19 @@ def send_email(to: str, subject: str, text: str, html: str | None = None) -> Non
|
||||
server.send_message(msg)
|
||||
|
||||
|
||||
def send_feedback(to: str, category: str, message: str, contact: str | None, who: str) -> None:
|
||||
"""Notify the admin of new user feedback (plain text is plenty here)."""
|
||||
subject = f"Upbeat Bytes feedback · {category}"
|
||||
reply = contact or "(none given)"
|
||||
text = (
|
||||
f"New feedback ({category})\n"
|
||||
f"From: {who}\n"
|
||||
f"Reply to: {reply}\n\n"
|
||||
f"{message}\n"
|
||||
)
|
||||
send_email(to, subject, text)
|
||||
|
||||
|
||||
def send_magic_link(to: str, link: str) -> None:
|
||||
"""Send a calm, single-purpose sign-in email."""
|
||||
subject = "Your Upbeat Bytes sign-in link"
|
||||
|
||||
Reference in New Issue
Block a user