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:
@@ -1,5 +1,6 @@
|
||||
<script>
|
||||
import Avatar from './Avatar.svelte';
|
||||
import { openFeedback } from '$lib/feedback.svelte.js';
|
||||
let { onSaved, onaccount, user = null, boundariesActive = false } = $props();
|
||||
</script>
|
||||
|
||||
@@ -11,24 +12,29 @@
|
||||
|
||||
<nav class="utils" aria-label="Your controls">
|
||||
{#if user}
|
||||
<button class="util" onclick={onSaved} title="Saved articles">
|
||||
<button class="util desk" onclick={onSaved} title="Saved articles">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 3h12v18l-6-4-6 4z"
|
||||
fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round" /></svg>
|
||||
<span>Saved</span>
|
||||
</button>
|
||||
{/if}
|
||||
<a class="util shield" class:on={boundariesActive} href="/account?section=boundaries"
|
||||
<a class="util shield desk" class:on={boundariesActive} href="/account?section=boundaries"
|
||||
title={boundariesActive ? 'Boundaries are on' : 'Your boundaries'}>
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3l7 3v5c0 4.4-3 7.6-7 9-4-1.4-7-4.6-7-9V6l7-3z"
|
||||
fill={boundariesActive ? 'currentColor' : 'none'} stroke="currentColor" stroke-width="1.8"
|
||||
stroke-linejoin="round" /></svg>
|
||||
</a>
|
||||
<!-- Feedback bubble: the one control kept on the mobile bar too. -->
|
||||
<button class="util" onclick={openFeedback} title="Share feedback" aria-label="Share feedback">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 5h16v11H8l-4 3z"
|
||||
fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round" /></svg>
|
||||
</button>
|
||||
{#if user}
|
||||
<button class="acct" onclick={onaccount} title={user.email} aria-label="Your account">
|
||||
<button class="acct desk" onclick={onaccount} title={user.email} aria-label="Your account">
|
||||
<Avatar {user} size={30} />
|
||||
</button>
|
||||
{:else}
|
||||
<button class="signin" onclick={onaccount}>Sign in</button>
|
||||
<button class="signin desk" onclick={onaccount}>Sign in</button>
|
||||
{/if}
|
||||
</nav>
|
||||
</div>
|
||||
@@ -64,10 +70,11 @@
|
||||
}
|
||||
.signin:hover { background: var(--accent-soft); }
|
||||
|
||||
/* On phones the bottom tab bar handles navigation; keep the bar to the logo. */
|
||||
/* On phones the bottom tab bar handles navigation; the top bar keeps just the
|
||||
logo (left) and the feedback bubble (right). Other utils move to bottom nav. */
|
||||
@media (max-width: 720px) {
|
||||
.bar { height: 66px; justify-content: center; }
|
||||
.utils { display: none; }
|
||||
.bar { height: 66px; }
|
||||
.utils .desk { display: none; }
|
||||
.logo { height: 46px; }
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user