8e136351f9
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)
170 lines
11 KiB
HTML
170 lines
11 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="hr">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<title>PGŽ Sport · Politika privatnosti</title>
|
||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><rect width='32' height='32' rx='6' fill='%2306080d'/><text x='16' y='23' text-anchor='middle' font-size='18' font-family='monospace' fill='%2300f0ff'>P</text></svg>">
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||
<style>
|
||
:root {
|
||
--bg: #06080d;
|
||
--bg-2: #0d1117;
|
||
--bg-3: #161b22;
|
||
--border: #1f2937;
|
||
--text: #e6edf3;
|
||
--text-2: #8b949e;
|
||
--text-3: #6e7681;
|
||
--accent: #00f0ff;
|
||
--accent-2: #00b8d4;
|
||
--green: #56d364;
|
||
--yellow: #d29922;
|
||
}
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
html, body { font-family: 'Inter', system-ui, sans-serif; background: var(--bg); color: var(--text); min-height: 100vh; font-size: 14px; line-height: 1.65; }
|
||
.wrap { max-width: 880px; margin: 0 auto; padding: 56px 28px 96px; }
|
||
header { border-bottom: 1px solid var(--border); padding-bottom: 24px; margin-bottom: 32px; }
|
||
h1 { font-size: 28px; font-weight: 700; letter-spacing: -0.5px; margin-bottom: 6px; }
|
||
.kicker { color: var(--accent); font-family: 'JetBrains Mono', monospace; font-size: 11px; letter-spacing: 2px; text-transform: uppercase; margin-bottom: 12px; }
|
||
.meta { color: var(--text-2); font-size: 12px; font-family: 'JetBrains Mono', monospace; }
|
||
h2 { font-size: 18px; font-weight: 600; margin: 36px 0 12px; color: var(--text); border-left: 3px solid var(--accent); padding-left: 12px; }
|
||
h3 { font-size: 14px; font-weight: 600; margin: 20px 0 8px; color: var(--text); }
|
||
p, li { color: var(--text-2); margin-bottom: 10px; }
|
||
strong { color: var(--text); font-weight: 600; }
|
||
ul { padding-left: 22px; margin-bottom: 12px; }
|
||
a { color: var(--accent); text-decoration: none; }
|
||
a:hover { text-decoration: underline; }
|
||
.box { background: var(--bg-2); border: 1px solid var(--border); border-radius: 8px; padding: 18px 22px; margin: 16px 0; }
|
||
.box.warn { border-color: var(--yellow); }
|
||
.box.ok { border-color: var(--green); }
|
||
table { width: 100%; border-collapse: collapse; margin: 12px 0 24px; font-size: 13px; }
|
||
th, td { text-align: left; padding: 10px 12px; border-bottom: 1px solid var(--border); }
|
||
th { color: var(--accent); font-weight: 600; font-size: 11px; letter-spacing: 1px; text-transform: uppercase; }
|
||
td { color: var(--text-2); }
|
||
td strong { color: var(--text); }
|
||
code { font-family: 'JetBrains Mono', monospace; font-size: 12px; background: var(--bg-3); padding: 1px 6px; border-radius: 3px; color: var(--accent); }
|
||
.footer-back { margin-top: 48px; padding-top: 24px; border-top: 1px solid var(--border); display: flex; justify-content: space-between; flex-wrap: wrap; gap: 12px; font-size: 12px; color: var(--text-3); }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="wrap">
|
||
<header>
|
||
<div class="kicker">PGŽ Sport ERP/CRM · v1 · 2026</div>
|
||
<h1>Politika privatnosti i zaštite osobnih podataka</h1>
|
||
<div class="meta">Verzija dokumenta: <strong>v1</strong> · Stupa na snagu: 2026-05-04 · Posljednja izmjena: 2026-05-05</div>
|
||
</header>
|
||
|
||
<div class="box">
|
||
<p><strong>Voditelj obrade:</strong> Primorsko-goranska županija — Odjel za sport, Slogin kula 2/IV, Rijeka</p>
|
||
<p><strong>Kontakt za GDPR:</strong> <a href="mailto:gdpr@pgz.hr">gdpr@pgz.hr</a></p>
|
||
<p><strong>Službenik za zaštitu podataka (DPO):</strong> Damir Radulić — <a href="mailto:damir@rinet.one">damir@rinet.one</a></p>
|
||
</div>
|
||
|
||
<h2>1. Koje podatke prikupljamo</h2>
|
||
<p>Platforma PGŽ Sport prikuplja i obrađuje sljedeće kategorije osobnih podataka, sukladno Općoj uredbi o zaštiti podataka (GDPR — Uredba (EU) 2016/679) i Zakonu o provedbi Opće uredbe o zaštiti podataka (NN 42/18):</p>
|
||
<ul>
|
||
<li><strong>Identifikacijski podaci:</strong> ime, prezime, OIB, datum rođenja, spol</li>
|
||
<li><strong>Kontakt podaci:</strong> e-pošta, broj telefona, adresa kluba/saveza</li>
|
||
<li><strong>Funkcijski podaci:</strong> uloga (predsjednik, tajnik, član, trener), klub/savez, kategorija</li>
|
||
<li><strong>Tehnički podaci:</strong> IP adresa prilikom prijave, identifikator sesije, podaci o uređaju (User-Agent), vrijeme prijave</li>
|
||
<li><strong>Sigurnosni podaci:</strong> lozinka (hash), 2FA tajna (kriptirana), revocirani tokeni</li>
|
||
<li><strong>Sportski podaci:</strong> licence, kategorizacija, liječnički pregledi, članarine, transferi</li>
|
||
</ul>
|
||
|
||
<h2>2. Pravna osnova obrade (čl. 6 GDPR)</h2>
|
||
<table>
|
||
<thead><tr><th>Kategorija obrade</th><th>Pravna osnova</th><th>Članak</th></tr></thead>
|
||
<tbody>
|
||
<tr><td><strong>Prijava, sigurnost sesije, audit log</strong></td><td>Legitimni interes voditelja obrade</td><td>čl. 6(1)(f)</td></tr>
|
||
<tr><td><strong>Vođenje registra sportskih klubova</strong></td><td>Pravna obveza (Zakon o sportu, NN 141/22)</td><td>čl. 6(1)(c)</td></tr>
|
||
<tr><td><strong>Obrada zahtjeva za sufinanciranje</strong></td><td>Izvršavanje zadaće u javnom interesu</td><td>čl. 6(1)(e)</td></tr>
|
||
<tr><td><strong>Analitički kolačići</strong></td><td>Privola (opt-in)</td><td>čl. 6(1)(a)</td></tr>
|
||
<tr><td><strong>Marketinške komunikacije</strong></td><td>Privola (opt-in)</td><td>čl. 6(1)(a)</td></tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<h2>3. Vaša prava (čl. 15–22 GDPR)</h2>
|
||
|
||
<h3>Članak 15 — Pravo na pristup</h3>
|
||
<p>Imate pravo dobiti potvrdu obrađuju li se Vaši osobni podaci te pristup tim podacima. Implementirano kroz: <code>GET /api/users/me/gdpr-export</code> (vraća JSON s kompletnim profilom, sesijama, audit logom, povijesti privola, vezama na klub/savez).</p>
|
||
|
||
<h3>Članak 16 — Pravo na ispravak</h3>
|
||
<p>Imate pravo zatražiti ispravak netočnih podataka. Implementirano kroz: <code>PUT /api/auth/me</code> (ime, prezime, OIB, telefon, jezik) i sučelje "Moj profil".</p>
|
||
|
||
<h3>Članak 17 — Pravo na brisanje ("pravo na zaborav")</h3>
|
||
<p>Imate pravo zatražiti brisanje Vaših osobnih podataka kada osnova za obradu prestane. Implementirano kroz: <code>POST /api/users/me/gdpr-erase</code> ili <code>POST /api/gdpr/erase</code>. Zahtjev se obrađuje u roku od 30 dana. Nakon odobrenja, identifikacijski podaci se anonimiziraju (e-pošta postaje <code>erased-{id}@anonymous.gdpr</code>, ime postaje "Erased", OIB i telefon se brišu).</p>
|
||
<div class="box warn">
|
||
<p><strong>Napomena:</strong> Pojedini podaci moraju ostati zbog pravne obveze (npr. revizijski trag financijskih transakcija — Zakon o računovodstvu, 11 godina). U tom slučaju podaci se pseudonimiziraju, ali ne brišu u potpunosti.</p>
|
||
</div>
|
||
|
||
<h3>Članak 18 — Pravo na ograničenje obrade</h3>
|
||
<p>Imate pravo zatražiti privremeno ograničenje obrade dok se ne riješi spor o točnosti podataka. Kontaktirajte <a href="mailto:gdpr@pgz.hr">gdpr@pgz.hr</a>.</p>
|
||
|
||
<h3>Članak 20 — Pravo na prenosivost podataka</h3>
|
||
<p>Imate pravo dobiti svoje podatke u strukturiranom, uobičajeno korištenom i strojno čitljivom formatu (JSON). Implementirano kroz: <code>GET /api/users/me/gdpr-export</code>.</p>
|
||
|
||
<h3>Članak 21 — Pravo na prigovor</h3>
|
||
<p>Imate pravo prigovoriti obradi temeljenoj na legitimnom interesu. Kontaktirajte <a href="mailto:gdpr@pgz.hr">gdpr@pgz.hr</a>.</p>
|
||
|
||
<h3>Članak 7(3) — Povlačenje privole</h3>
|
||
<p>Privola za neobvezne kolačiće (analitika, marketing) može se povući u bilo kojem trenutku, jednako jednostavno kao što je dana. Implementirano kroz: <code>POST /api/users/me/withdraw-consent</code> ili <code>DELETE /api/users/me/gdpr-consent</code>.</p>
|
||
|
||
<h2>4. Kolačići</h2>
|
||
<table>
|
||
<thead><tr><th>Tip</th><th>Svrha</th><th>Trajanje</th><th>Pravna osnova</th></tr></thead>
|
||
<tbody>
|
||
<tr><td><strong>Nužni</strong></td><td>Sesija, CSRF, sigurnost prijave</td><td>Sesija</td><td>Legitimni interes</td></tr>
|
||
<tr><td><strong>Funkcionalni</strong></td><td>Postavke jezika, tema, sidebar stanje</td><td>30 dana</td><td>Privola</td></tr>
|
||
<tr><td><strong>Analitički</strong></td><td>Anonimne statistike korištenja</td><td>365 dana</td><td>Privola</td></tr>
|
||
<tr><td><strong>Marketinški</strong></td><td>Trenutno se ne koriste</td><td>—</td><td>Privola</td></tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<h2>5. Razdoblja čuvanja</h2>
|
||
<ul>
|
||
<li><strong>Audit log</strong> (prijave, izmjene): 5 godina</li>
|
||
<li><strong>Sesijski tokeni:</strong> max 90 dana, a po odjavi se opozivaju</li>
|
||
<li><strong>Korisnički profili:</strong> dok je račun aktivan + 1 godina nakon deaktivacije</li>
|
||
<li><strong>Financijski podaci:</strong> 11 godina (Zakon o računovodstvu, čl. 8)</li>
|
||
<li><strong>Podaci o članovima klubova:</strong> dok je član registriran u klubu + 5 godina</li>
|
||
</ul>
|
||
|
||
<h2>6. Sigurnosne mjere</h2>
|
||
<ul>
|
||
<li>HTTPS (TLS 1.3) za sav promet</li>
|
||
<li>Lozinke pohranjene kao Argon2/bcrypt hash</li>
|
||
<li>Dvofaktorska autentikacija (TOTP) dostupna svim korisnicima</li>
|
||
<li>Audit log svih akcija sa IP adresom i User-Agentom</li>
|
||
<li>OIB se prikazuje samo administratorima; za ostale korisnike se maskira (<code>•••XXX••</code>)</li>
|
||
<li>Pristup po načelu najmanjih ovlasti (RBAC) — uloge: super_admin, pgz_admin, savez_admin, klub_admin, klub_user, klub_clan, viewer</li>
|
||
</ul>
|
||
|
||
<h2>7. Dijeljenje podataka s trećim stranama</h2>
|
||
<p>Vaši podaci se <strong>ne prodaju</strong> i <strong>ne ustupaju</strong> trećim stranama u marketinške svrhe. Podaci se mogu razmjenjivati isključivo s:</p>
|
||
<ul>
|
||
<li>Hrvatskim sportskim savezom — kada je to pravna obveza za registraciju kluba/člana</li>
|
||
<li>Ministarstvom turizma i sporta — pri prijavi za sufinanciranje</li>
|
||
<li>Nadležnim tijelima (sud, policija) — na temelju pravomoćnog naloga</li>
|
||
</ul>
|
||
|
||
<h2>8. Pritužbe</h2>
|
||
<p>Pritužbu na obradu osobnih podataka možete podnijeti:</p>
|
||
<ul>
|
||
<li>Voditelju obrade: <a href="mailto:gdpr@pgz.hr">gdpr@pgz.hr</a></li>
|
||
<li>Službeniku za zaštitu podataka: <a href="mailto:damir@rinet.one">damir@rinet.one</a></li>
|
||
<li>Agenciji za zaštitu osobnih podataka (AZOP), Selska cesta 136, Zagreb — <a href="https://azop.hr" target="_blank" rel="noopener">azop.hr</a></li>
|
||
</ul>
|
||
|
||
<div class="box ok">
|
||
<p><strong>Strojno čitljiva verzija ove politike:</strong> dostupna na <code>GET /api/gdpr/policy</code> u JSON formatu (verzija, URL, popis prava, kontakti).</p>
|
||
</div>
|
||
|
||
<div class="footer-back">
|
||
<span>© 2026 Primorsko-goranska županija · Odjel za sport</span>
|
||
<span><a href="/sport/static/login.html">← Povratak na prijavu</a></span>
|
||
</div>
|
||
</div>
|
||
</body>
|
||
</html>
|