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:
+4
-3
@@ -82,6 +82,7 @@ tr.clickable:hover { background:var(--bg-3); box-shadow:inset 3px 0 0 var(--acce
|
||||
</style>
|
||||
<link rel="stylesheet" href="/static/shared/sidebar.css">
|
||||
<script src="/static/shared/sidebar.js" defer data-active="racuni"></script>
|
||||
<script src="/static/oib_format.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
@@ -606,7 +607,7 @@ async function loadInvoices() {
|
||||
<td onclick="openInvoice(${i.id})">${i.invoice_kind||'—'}</td>
|
||||
<td onclick="openInvoice(${i.id})">${i.invoice_no||'—'}</td>
|
||||
<td onclick="openInvoice(${i.id})">${i.vendor_name||'—'}</td>
|
||||
<td onclick="openInvoice(${i.id})" style="font-family:'JetBrains Mono'">${i.vendor_oib||'—'}</td>
|
||||
<td onclick="openInvoice(${i.id})" style="font-family:'JetBrains Mono'">${i.vendor_oib?formatOib(i.vendor_oib,{klub_id:i.klub_id}):'—'}</td>
|
||||
<td onclick="openInvoice(${i.id})">${i.klub_naziv||'—'}</td>
|
||||
<td class="num" onclick="openInvoice(${i.id})">${fmtEur(i.amount_gross)}</td>
|
||||
<td onclick="openInvoice(${i.id})">${sBadge(i.payment_status)}</td>
|
||||
@@ -752,7 +753,7 @@ async function openInvoice(id) {
|
||||
// KV polja
|
||||
$('#inv_kv').innerHTML = `
|
||||
<div>Izdavatelj</div><div>${escHtml(i.vendor_name||'—')}</div>
|
||||
<div>OIB izdavatelja</div><div>${escHtml(i.vendor_oib||'—')}</div>
|
||||
<div>OIB izdavatelja</div><div>${i.vendor_oib?escHtml(formatOib(i.vendor_oib,{klub_id:i.klub_id})):'—'}</div>
|
||||
<div>Broj računa</div><div>${escHtml(i.invoice_no||'—')}</div>
|
||||
<div>Datum</div><div>${fmtDate(i.invoice_date)}</div>
|
||||
<div>Klub</div><div>${escHtml(i.klub_naziv||'—')}</div>
|
||||
@@ -914,7 +915,7 @@ async function openPutni(id) {
|
||||
$('#pn_invoices_table tbody').innerHTML = invs.length ? invs.map(i => `
|
||||
<tr class="clickable" onclick="closeModal('pnModal'); setTimeout(()=>openInvoice(${i.id}), 100)">
|
||||
<td>${i.id}</td><td>${escHtml(i.invoice_kind||'—')}</td><td>${escHtml(i.vendor_name||'—')}</td>
|
||||
<td style="font-family:'JetBrains Mono'">${escHtml(i.vendor_oib||'—')}</td>
|
||||
<td style="font-family:'JetBrains Mono'">${i.vendor_oib?escHtml(formatOib(i.vendor_oib,{klub_id:i.klub_id})):'—'}</td>
|
||||
<td>${fmtDate(i.invoice_date)}</td>
|
||||
<td class="num">${fmtEur(i.amount_gross)}</td>
|
||||
<td>${sBadge(i.payment_status)}</td>
|
||||
|
||||
Reference in New Issue
Block a user