Add API loading indicator and session-expiry auto-redirect
- Thin animated bar at page top shows while any fetch is in-flight - api() centrally redirects to login on 401 (except login endpoint) - Logo click triggers manual goal refresh - cursor:pointer on logo Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
bd0190cfde
commit
7ea6aeb98c
3 changed files with 15 additions and 1 deletions
|
|
@ -253,14 +253,23 @@ function dlbl(g,i){
|
|||
|
||||
// ── API ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
var _apiPending = 0;
|
||||
function _apiBar(){ document.getElementById('api-bar').classList.toggle('loading', _apiPending > 0); }
|
||||
|
||||
function api(method, path, body){
|
||||
var opts = {method:method, credentials:'include', headers:{'Content-Type':'application/json'}};
|
||||
if(body) opts.body = JSON.stringify(body);
|
||||
_apiPending++; _apiBar();
|
||||
return fetch('api/' + path, opts).then(function(res){
|
||||
return res.json().then(function(data){
|
||||
if(!res.ok){ var e=new Error(data.error||'Fehler'); e.status=res.status; throw e; }
|
||||
return data;
|
||||
});
|
||||
}).catch(function(e){
|
||||
if(e.status===401 && path!=='login'){ showLogin(); }
|
||||
throw e;
|
||||
}).finally(function(){
|
||||
_apiPending--; _apiBar();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -903,6 +912,7 @@ function updateHeader(){
|
|||
|
||||
document.getElementById('btnNew').onclick=openNew;
|
||||
document.getElementById('btnData').onclick=openData;
|
||||
document.querySelector('.hdr-logo').onclick=function(){ loadGoals().then(function(g){goals=g;render();}).catch(function(){}); };
|
||||
updateHeader();
|
||||
|
||||
var _qs=new URLSearchParams(window.location.search);
|
||||
|
|
|
|||
|
|
@ -2,10 +2,13 @@
|
|||
:root{--bg:#f5f4f0;--bg2:#fff;--bg3:#f0eeea;--border:rgba(0,0,0,.07);--border2:rgba(0,0,0,.12);--text:#1a1a1a;--text2:#666;--text3:#aaa;--green:#16a34a;--green-bg:rgba(22,163,74,.08);--blue:#2563eb;--blue-bg:rgba(37,99,235,.08);--amber:#d97706;--amber-bg:rgba(217,119,6,.08);--red:#dc2626;--red-bg:rgba(220,38,38,.08);--r:14px;--rs:8px}
|
||||
*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}
|
||||
body{font-family:'DM Sans',sans-serif;background:var(--bg);color:var(--text);min-height:100dvh;padding-bottom:80px}
|
||||
#api-bar{position:fixed;top:0;left:0;right:0;height:2px;z-index:200;pointer-events:none;overflow:hidden}
|
||||
#api-bar.loading::after{content:'';position:absolute;top:0;height:100%;width:40%;background:var(--blue);animation:api-bar-slide 1s linear infinite}
|
||||
@keyframes api-bar-slide{0%{left:-40%}100%{left:140%}}
|
||||
.main-wrap{max-width:480px;margin:0 auto}
|
||||
.hdr{position:sticky;top:0;z-index:100;background:rgba(245,244,240,.92);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-bottom:1px solid var(--border);padding:16px 20px 14px;display:flex;align-items:center;justify-content:space-between}
|
||||
.hdr-title{font-size:18px;font-weight:600;letter-spacing:-.3px}
|
||||
.hdr-logo{height:100px;width:auto;mix-blend-mode:multiply;display:block}
|
||||
.hdr-logo{height:100px;width:auto;mix-blend-mode:multiply;display:block;cursor:pointer}
|
||||
.hdr-sub{font-size:12px;color:var(--text3);margin-top:1px;font-family:'DM Mono',monospace}
|
||||
.hdr-btns{display:flex;gap:8px;align-items:center}
|
||||
.sw{font-family:'DM Mono',monospace;font-size:15px;color:var(--text3);cursor:pointer;user-select:none;letter-spacing:-.5px;min-width:52px;text-align:center}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
<link rel="stylesheet" href="style.css?v={{ cssv }}"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="api-bar"></div>
|
||||
<div class="main-wrap">
|
||||
<div class="hdr">
|
||||
<div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue