From 2c69e75842b0e602dafe1306b816ca5c62493de3 Mon Sep 17 00:00:00 2001 From: Davide Scaini Date: Sat, 16 May 2026 20:27:43 +0200 Subject: [PATCH] Show orange upload button when Strava/Garmin auth fails GET /api/me/sync-status reads _strava_sync_status.json and _garmin_sync_status.json for the logged-in user. On page load the nav script checks this endpoint and, if either service has status=auth_error, turns the upload arrow orange with a tooltip naming the disconnected service(s). --- bincio/serve/routers/me.py | 23 +++++++++++++++++++++++ site/src/layouts/Base.astro | 19 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/bincio/serve/routers/me.py b/bincio/serve/routers/me.py index 4809b52..bd6c76c 100644 --- a/bincio/serve/routers/me.py +++ b/bincio/serve/routers/me.py @@ -317,3 +317,26 @@ async def me_change_password( raise HTTPException(400, "New password must be at least 8 characters") _change_password(deps._get_db(), user.handle, new_pw) return JSONResponse({"ok": True}) + + +@router.get("/api/me/sync-status") +async def get_sync_status( + bincio_session: str | None = Cookie(default=None), +) -> JSONResponse: + """Return the last sync status for Strava and Garmin for the logged-in user.""" + user = deps._require_user(bincio_session) + user_dir = deps._get_data_dir() / user.handle + + def _read_status(filename: str) -> dict | None: + p = user_dir / filename + try: + return json.loads(p.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError): + return None + + strava = _read_status("_strava_sync_status.json") + garmin = _read_status("_garmin_sync_status.json") + return JSONResponse({ + "strava": strava, + "garmin": garmin, + }) diff --git a/site/src/layouts/Base.astro b/site/src/layouts/Base.astro index 0a62e14..c5f1c93 100644 --- a/site/src/layouts/Base.astro +++ b/site/src/layouts/Base.astro @@ -663,6 +663,25 @@ try { if (wikiElM) wikiElM.style.display = ''; } + // Sync error: turn upload button orange if Strava or Garmin auth failed + try { + const sr = await fetch('/api/me/sync-status', { credentials: 'include' }); + if (sr.ok) { + const ss = await sr.json(); + const errServices = [ + ss.strava?.status === 'auth_error' ? 'Strava' : null, + ss.garmin?.status === 'auth_error' ? 'Garmin' : null, + ].filter(Boolean); + if (errServices.length > 0) { + const btn = document.getElementById('upload-btn'); + if (btn) { + btn.style.color = '#f97316'; + btn.title = `${errServices.join(' & ')} connection lost — open to reconnect`; + } + } + } + } catch (_) {} + // Admin: show admin link and poll for active jobs if (user.is_admin) { const adminLink = document.getElementById('nav-admin');