settings: add nav visibility prefs and per-user Strava credentials

- user_prefs table in db.py with get/set helpers
- GET/PUT /api/me/prefs endpoints for bulk pref management
- GET/PUT/DELETE /api/me/strava-credentials; PUT preserves existing
  secret when client_secret field is left blank
- _strava_creds() helper resolves per-user → instance fallback across
  all five Strava endpoints
- Settings page: Navigation card (hide Feed/Community/About toggles)
  and Strava credentials card
- Base.astro: ids on feed/community/about nav links; applies
  nav_hide_* prefs after login
This commit is contained in:
Davide Scaini
2026-04-15 20:37:42 +02:00
parent 4fd5ba428e
commit 87a69bcc8b
4 changed files with 358 additions and 13 deletions
+22 -3
View File
@@ -166,7 +166,7 @@ try {
<div class="nav-links flex items-center gap-5 overflow-x-auto flex-1 min-w-0">
<!-- Feed tab: only shown for multi-user (more than one shard) -->
{!singleHandle && (
<a href={baseUrl} class="text-sm text-zinc-400 hover:text-white transition-colors shrink-0">Feed</a>
<a id="nav-feed" href={baseUrl} class="text-sm text-zinc-400 hover:text-white transition-colors shrink-0">Feed</a>
)}
<!-- Single-user: static handle link. Multi-user: populated by user-widget script. -->
{singleHandle
@@ -177,7 +177,7 @@ try {
<a id="nav-stats" href={singleHandle ? `${baseUrl}u/${singleHandle}/stats/` : `${baseUrl}stats/`} data-user-path="stats/" class="text-sm text-zinc-400 hover:text-white transition-colors shrink-0">Stats</a>
<a id="nav-athlete" href={singleHandle ? `${baseUrl}u/${singleHandle}/athlete/` : `${baseUrl}athlete/`} data-user-path="athlete/" class="text-sm text-zinc-400 hover:text-white transition-colors shrink-0">Athlete</a>
{!singleHandle && (
<a href={`${baseUrl}community/`} class="text-sm text-zinc-400 hover:text-white transition-colors shrink-0">Community</a>
<a id="nav-community" href={`${baseUrl}community/`} class="text-sm text-zinc-400 hover:text-white transition-colors shrink-0">Community</a>
)}
{mobileApp && (
<a id="nav-record" href={singleHandle ? `${baseUrl}u/${singleHandle}/record/` : `${baseUrl}record/`} data-user-path="record/" class="text-sm text-zinc-400 hover:text-white transition-colors shrink-0">Record</a>
@@ -185,7 +185,7 @@ try {
{mobileApp && (
<a href={`${baseUrl}convert/`} class="text-sm text-zinc-400 hover:text-white transition-colors shrink-0">Convert</a>
)}
<a href={`${baseUrl}about/`} class="text-sm text-zinc-400 hover:text-white transition-colors shrink-0">About</a>
<a id="nav-about" href={`${baseUrl}about/`} class="text-sm text-zinc-400 hover:text-white transition-colors shrink-0">About</a>
</div>
)}
@@ -511,6 +511,25 @@ try {
const chk = document.getElementById('upload-keep-original');
if (chk && user.store_originals_default) chk.checked = true;
// Apply nav visibility prefs
try {
const pr = await fetch('/api/me/prefs', { credentials: 'include' });
if (pr.ok) {
const prefs = await pr.json();
const navHideMap: Record<string, string> = {
'nav_hide_feed': 'nav-feed',
'nav_hide_community': 'nav-community',
'nav_hide_about': 'nav-about',
};
for (const [key, elId] of Object.entries(navHideMap)) {
if (prefs[key] === 'true') {
const el = document.getElementById(elId);
if (el) el.style.display = 'none';
}
}
}
} catch (_) {}
// Admin: show admin link and poll for active jobs
if (user.is_admin) {
const adminLink = document.getElementById('nav-admin');