Crisis V7 MEGA: sufinanciranje_sport + panel + CRM auth

DB:
- pgz_sport.sufinanciranje_sport.je_klub flag (RSS programi/totals false)
- pgz_sport.sufinanciranje_sport.klub_id matched

Endpoints:
- /v2/potpore/by-year: samo_klubovi=True default + davatelj filter

Frontend:
- sport2.html PANEL FORCE HIDE CSS (right:-100vw default)
- crm_v2.html: redirect to /login only on actual 401, not on page load
This commit is contained in:
2026-05-05 15:02:47 +02:00
parent 007825acee
commit f07fdad919
18 changed files with 1235 additions and 65 deletions
+44 -9
View File
@@ -487,7 +487,18 @@ footer { height:36px; background:var(--bg2); border-top:1px solid var(--rim);
<div id="toast"></div>
<script>
const TOKEN = localStorage.getItem('token') || localStorage.getItem('access_token') || '';
// ━━━ AUTH: model after app.html (pgz_access primary, fallbacks for legacy keys) ━━━
function getToken(){
try {
return localStorage.getItem('pgz_access')
|| sessionStorage.getItem('pgz_access')
|| localStorage.getItem('jwt')
|| localStorage.getItem('access_token')
|| localStorage.getItem('token')
|| '';
} catch(e){ return ''; }
}
let TOKEN = getToken();
const API = '/sport/api/v2/crm';
const STAGE_LABEL = {
prospecting:'Prospecting', qualification:'Qualification', proposal:'Proposal',
@@ -495,14 +506,34 @@ const STAGE_LABEL = {
};
const STAGES = ['prospecting','qualification','proposal','negotiation','closed_won','closed_lost'];
if (!TOKEN) {
location.href = '/sport/login?next=' + encodeURIComponent(location.pathname);
}
// JWT expiry pre-check + redirect only when truly missing/expired
(function checkAuth(){
if(!TOKEN){
if(!window.__pgz_redirecting && window.__pgz_made_api_call){ window.__pgz_redirecting = true; location.href = '/login?next=' + encodeURIComponent(location.pathname); } else { console.warn('[CRM] no token — login optional'); }
return;
}
try {
const payload = JSON.parse(atob(TOKEN.split('.')[1]));
if(payload.exp && payload.exp * 1000 < Date.now()){
['pgz_access','pgz_refresh','pgz_user','jwt','access_token','token'].forEach(k => {
try{localStorage.removeItem(k); sessionStorage.removeItem(k);}catch(e){}
});
if(!window.__pgz_redirecting){ window.__pgz_redirecting = true; location.href = '/login?reason=expired'; }
}
} catch(e){ /* not parseable, let server respond */ }
})();
async function api(path, opts={}) {
TOKEN = getToken(); // refresh in case of token rotation
const headers = {'Authorization':'Bearer '+TOKEN, 'Content-Type':'application/json', ...(opts.headers||{})};
const res = await fetch(API+path, {...opts, headers});
if (res.status === 401) { location.href='/sport/login'; throw new Error('401'); }
if (res.status === 401) {
['pgz_access','pgz_refresh','pgz_user','jwt','access_token','token'].forEach(k => {
try{localStorage.removeItem(k); sessionStorage.removeItem(k);}catch(e){}
});
if(!window.__pgz_redirecting){ window.__pgz_redirecting = true; location.href='/login?reason=unauthorized'; }
throw new Error('401');
}
const txt = await res.text();
let data; try { data = JSON.parse(txt); } catch { data = txt; }
if (!res.ok) {
@@ -548,16 +579,20 @@ function switchTab(name) {
// ────── /me ──────
async function loadMe() {
try {
const me = await fetch('/sport/api/v2/me', {headers:{'Authorization':'Bearer '+TOKEN}}).then(r=>r.json());
const tok = getToken();
const me = await fetch('/sport/api/v2/auth/me', {headers:{'Authorization':'Bearer '+tok}}).then(r=>r.json());
document.getElementById('me').textContent = (me.email || me.full_name || 'user');
} catch { document.getElementById('me').textContent='?'; }
}
document.getElementById('logout').addEventListener('click', async (e) => {
e.preventDefault();
try { await fetch('/sport/api/v2/auth/logout', {method:'POST', headers:{'Authorization':'Bearer '+TOKEN}}); } catch {}
localStorage.removeItem('token'); localStorage.removeItem('access_token');
location.href='/sport/login';
const tok = getToken();
try { await fetch('/sport/api/v2/auth/logout', {method:'POST', headers:{'Authorization':'Bearer '+tok}}); } catch {}
['pgz_access','pgz_refresh','pgz_user','app-role','jwt','access_token','refresh_token','pgz_session_id','token'].forEach(k => {
try{localStorage.removeItem(k); sessionStorage.removeItem(k);}catch(e){}
});
location.href='/login';
});
// ────── Pipeline & Dashboard ──────