CRISIS V3: definitive apiAuth + mobile hamburger + Playwright E2E test

apiAuth in app.html:
- Pre-checks JWT exp client-side BEFORE making request
- On expired: clears localStorage + redirects /login?reason=expired
- On 401 from server: clears + redirects /login?reason=unauthorized
- Single-flight redirect via window.__pgz_redirecting flag

login.html:
- Toast for ?reason=expired (red) / ?reason=unauthorized (orange)

app.html mobile:
- Hamburger button injected into topbar (.tb)
- Mobile CSS: sidebar slide-in -280→0, backdrop overlay, full-width drill-down
- toggleMobileSidebar() global function
- @media (max-width:768px) display:inline-flex, sidebar fixed pos

scripts/playwright_e2e.py:
- Desktop test (1280x800): login, JWT persist, profile, logo, logout
- Mobile test (375x812 iPhone X): viewport, login flow, hamburger, no h-scroll
- Output: _audit/playwright_<TS>/results.json + screenshots/*.png

Reproducible: TS=YYYYmmdd_HHMM python3 scripts/playwright_e2e.py
This commit is contained in:
2026-05-05 09:21:39 +02:00
parent 8e136351f9
commit dd2f7daaf8
25 changed files with 523 additions and 41 deletions
+17
View File
@@ -582,5 +582,22 @@ $('#cookieMore').addEventListener('click', e => { e.preventDefault(); $('#privac
}, 100);
})();
</script>
<script>
// Auto-toast za reason=expired / unauthorized (CRISIS V3)
(function(){
const params = new URLSearchParams(window.location.search);
const reason = params.get('reason');
if(!reason) return;
setTimeout(() => {
const div = document.createElement('div');
div.id = 'pgz-reason-toast';
div.style.cssText = 'position:fixed;top:20px;left:50%;transform:translateX(-50%);background:'+(reason==='expired'?'#c0392b':'#e67e22')+';color:#fff;padding:12px 20px;border-radius:6px;z-index:9999;font-size:14px;box-shadow:0 4px 12px rgba(0,0,0,.3);font-family:system-ui,sans-serif';
div.textContent = reason==='expired' ? 'Sesija je istekla. Molim prijavi se ponovno.' : (reason==='unauthorized' ? 'Sesija je nevažeća. Prijavi se opet.' : 'Potrebna prijava.');
document.body.appendChild(div);
setTimeout(() => div.remove(), 6000);
}, 100);
})();
</script>
</body>
</html>