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');