Topbar: logo firme + naslov; avatar upload; uklanjanje logo zone

- Topbar: logo slika firme (toggle on/off) pa naslov stranice; bez teksta firme
- Sidebar: samo naziv firme i podnaslov (tekst), bez slike loga
- Avatar: korisnik uploaduje ličnu sliku u Profil > Tema > Avatar;
  prikazuje se kao dugme za meni (desno u topbaru); fallback inicijali
- Logo firme kartica: dugme "Ukloni sliku" + ruta /podesavanja/logo/ukloni
- Logo zona iz podešavanja uklonjena; jedan iOS toggle za prikaz loga u topbaru
- Migracije 049 (topbar_logo_slika/tekst) i 050 (avatar_putanja na korisnicima)
- iOS-style .toggl switch u main.css
This commit is contained in:
2026-06-16 02:46:48 +02:00
parent 3c5c8060c1
commit 85cb1e25c7
15 changed files with 320 additions and 60 deletions
+115
View File
@@ -289,3 +289,118 @@ func (h *Handler) SacuvajLokalnuTemu(w http.ResponseWriter, r *http.Request) {
}
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
}
// ProfilOtpremiAvatar prima upload lične avatar slike korisnika
func (h *Handler) ProfilOtpremiAvatar(w http.ResponseWriter, r *http.Request) {
k := middleware.KorisnikIzKonteksta(r.Context())
if k == nil {
http.Redirect(w, r, "/prijava", http.StatusSeeOther)
return
}
r.Body = http.MaxBytesReader(w, r.Body, 2<<20+4096)
if err := r.ParseMultipartForm(2 << 20); err != nil {
middleware.SetFlash(w, r, h.DB, "greska", "Fajl je prevelik (maksimum 2 MB).")
http.Redirect(w, r, "/profil/tema", http.StatusSeeOther)
return
}
fajl, zaglavlje, err := r.FormFile("avatar")
if err != nil {
middleware.SetFlash(w, r, h.DB, "greska", "Nije odabran fajl.")
http.Redirect(w, r, "/profil/tema", http.StatusSeeOther)
return
}
defer fajl.Close()
ext := strings.ToLower(filepath.Ext(zaglavlje.Filename))
dozvoljenoExt := map[string]string{
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".png": "image/png",
".webp": "image/webp",
}
ocekivaniMime, ok := dozvoljenoExt[ext]
if !ok {
middleware.SetFlash(w, r, h.DB, "greska", "Dozvoljeni formati su JPG, PNG i WebP.")
http.Redirect(w, r, "/profil/tema", http.StatusSeeOther)
return
}
buf := make([]byte, 512)
n, _ := fajl.Read(buf)
stvarniMime := http.DetectContentType(buf[:n])
if !strings.HasPrefix(stvarniMime, ocekivaniMime) {
middleware.SetFlash(w, r, h.DB, "greska", "Sadržaj fajla ne odgovara odabranoj ekstenziji.")
http.Redirect(w, r, "/profil/tema", http.StatusSeeOther)
return
}
if _, err := fajl.Seek(0, io.SeekStart); err != nil {
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri obradi fajla.")
http.Redirect(w, r, "/profil/tema", http.StatusSeeOther)
return
}
// briše stari avatar sa diska
svezi, _ := h.KorisniciRepo.DohvatiPoID(r.Context(), k.ID)
if svezi != nil && svezi.AvatarPutanja != "" {
deo, _, _ := strings.Cut(svezi.AvatarPutanja, "?")
os.Remove(filepath.Join("web/static/uploads", filepath.Base(deo)))
}
novoIme := fmt.Sprintf("korisnik_%d_avatar%s", k.ID, ext)
odrediste := filepath.Join("web/static/uploads", novoIme)
dst, err := os.Create(odrediste)
if err != nil {
slog.Error("ProfilOtpremiAvatar: ne mogu kreirati fajl", "error", err)
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju fajla.")
http.Redirect(w, r, "/profil/tema", http.StatusSeeOther)
return
}
defer dst.Close()
if _, err := io.Copy(dst, fajl); err != nil {
slog.Error("ProfilOtpremiAvatar: greška pri kopiranju", "error", err)
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju fajla.")
http.Redirect(w, r, "/profil/tema", http.StatusSeeOther)
return
}
// cache-buster verzija u URL-u
v := fmt.Sprintf("%d", time.Now().Unix())
putanja := fmt.Sprintf("/static/uploads/%s?v=%s", novoIme, v)
if err := h.KorisniciRepo.SacuvajAvatar(r.Context(), k.ID, putanja); err != nil {
slog.Error("ProfilOtpremiAvatar: greška pri čuvanju u bazi", "error", err)
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju.")
http.Redirect(w, r, "/profil/tema", http.StatusSeeOther)
return
}
middleware.SetFlash(w, r, h.DB, "uspeh", "Avatar je sačuvan.")
http.Redirect(w, r, "/profil/tema", http.StatusSeeOther)
}
// ProfilUkloniAvatar briše ličnu avatar sliku korisnika
func (h *Handler) ProfilUkloniAvatar(w http.ResponseWriter, r *http.Request) {
k := middleware.KorisnikIzKonteksta(r.Context())
if k == nil {
http.Redirect(w, r, "/prijava", http.StatusSeeOther)
return
}
svezi, _ := h.KorisniciRepo.DohvatiPoID(r.Context(), k.ID)
if svezi != nil && svezi.AvatarPutanja != "" {
deo, _, _ := strings.Cut(svezi.AvatarPutanja, "?")
os.Remove(filepath.Join("web/static/uploads", filepath.Base(deo)))
}
if err := h.KorisniciRepo.SacuvajAvatar(r.Context(), k.ID, ""); err != nil {
slog.Error("ProfilUkloniAvatar", "error", err)
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri uklanjanju avatara.")
http.Redirect(w, r, "/profil/tema", http.StatusSeeOther)
return
}
middleware.SetFlash(w, r, h.DB, "uspeh", "Avatar je uklonjen.")
http.Redirect(w, r, "/profil/tema", http.StatusSeeOther)
}