CRISIS FIX: login flow + mobile responsive + token expiry handling
ROOT CAUSE ISOLATED:
Backend POST /api/auth/login, GET/PUT /api/auth/me, POST avatar, POST /logout
all return 200 OK (verified curl). Damirov problem is browser-side:
stale localStorage tokens that don't match current backend → 401 cascade
→ avatar upload appears as 'failed: 401' → profile changes 'lost'.
FIXES:
1. apiAuth() in app.html now:
- Pre-checks JWT exp claim before request
- On 401 response: clears localStorage (pgz_access/refresh/user) +
redirects to /login?reason=unauthorized
- On JWT expired: redirects to /login?reason=expired
2. login.html displays toast for ?reason=expired/unauthorized
3. Mobile responsive CSS (max-width: 768px):
- app.html: hamburger menu, sidebar slide-in, full-width drill-down panel
- sport2.html: KPI grid 2-col, klubovi 1-col, tables horizontal scroll
- Both: viewport meta + media queries + touch-friendly buttons
4. Mobile menu toggle button + backdrop overlay added
VERIFIED E2E (curl):
- POST /auth/login → 200 + JWT
- GET /auth/me → 200 + telefon persisted
- PUT /auth/me → 200, DB row updated
- POST /auth/me/avatar → 200, file saved + avatar_url returned
- POST /auth/logout → 200, token revoked (next /me returns 401)
This commit is contained in:
@@ -44,6 +44,7 @@ button{cursor:pointer;font-weight:600}
|
||||
.loader{display:inline-block;width:14px;height:14px;border:2px solid #00f0ff;border-top-color:transparent;border-radius:50%;animation:sp 0.8s linear infinite;vertical-align:middle;margin-right:6px}
|
||||
@keyframes sp{to{transform:rotate(360deg)}}
|
||||
</style>
|
||||
<script src="/static/oib_format.js" defer></script>
|
||||
</head><body>
|
||||
|
||||
<div id="g"></div>
|
||||
@@ -223,7 +224,7 @@ async function openDetail(node) {
|
||||
let html = `<h2>🏆 ${escape(k.naziv || node.name)}</h2>`;
|
||||
html += `<div class="kv"><span>Sport</span><b>${escape(k.sport || '—')}</b></div>`;
|
||||
html += `<div class="kv"><span>Grad</span><b>${escape(k.grad || '—')}</b></div>`;
|
||||
html += `<div class="kv"><span>OIB</span><b>${escape(k.oib || '—')}</b></div>`;
|
||||
html += `<div class="kv"><span>OIB</span><b>${escape(k.oib?formatOib(k.oib,{klub_id:k.id,savez_id:k.savez_id}):'—')}</b></div>`;
|
||||
html += `<div class="kv"><span>Predsjednik</span><b>${escape(k.predsjednik || '—')}</b></div>`;
|
||||
html += `<div class="kv"><span>Tajnik</span><b>${escape(k.tajnik || '—')}</b></div>`;
|
||||
html += `<div class="kv"><span>Email</span><b>${escape(k.email || '—')}</b></div>`;
|
||||
@@ -257,7 +258,7 @@ async function openDetail(node) {
|
||||
const s = await r.json();
|
||||
let html = `<h2>🏛 ${escape(s.naziv || node.name)}</h2>`;
|
||||
html += `<div class="kv"><span>Sport</span><b>${escape(s.sport || '—')}</b></div>`;
|
||||
html += `<div class="kv"><span>OIB</span><b>${escape(s.oib || '—')}</b></div>`;
|
||||
html += `<div class="kv"><span>OIB</span><b>${escape(s.oib?formatOib(s.oib,{savez_id:s.id}):'—')}</b></div>`;
|
||||
html += `<div class="kv"><span>Predsjednik</span><b>${escape(s.predsjednik || '—')}</b></div>`;
|
||||
html += `<div class="kv"><span>Tajnik</span><b>${escape(s.tajnik || '—')}</b></div>`;
|
||||
html += `<div class="kv"><span>Godina osnutka</span><b>${escape(s.godina_osnutka || '—')}</b></div>`;
|
||||
|
||||
Reference in New Issue
Block a user