From 26a723649411881582fae8783b5d9b3a964310fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Rodrigues?= <95761178+GoncaloRodri@users.noreply.github.com> Date: Sun, 14 Jun 2026 16:08:29 +0100 Subject: [PATCH] style: global dark form inputs + team emoji avatars (#23) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Form inputs: - Add a catch-all CSS rule targeting all text-type inputs, selects, and textareas not already covered by .form-group or .form-input โ€” sets background:--bg2, color:--text, focus ring. Fixes white-box appearance in transactions, goals, settings, portfolio, import, people, and tax templates without touching any HTML. Team avatars: - OrgTeam.Avatar string (emoji) โ€” persisted in MongoDB, defaults to ๐Ÿ‘ฅ - OrgTeamCreate handler reads "avatar" form field - org_teams.html: emoji picker (30 options) in new-team modal; preview updates live; selected emoji highlighted with accent border - avatarEmojis() and teamAvatar() template functions registered in FuncMap - Team badges in org_members, org_events, org_event_detail, org_report all show the emoji inline with the team name Co-authored-by: Gonรงalo Rodrigues Co-authored-by: Claude Sonnet 4.6 --- apps/finance/services/api/main/handler.go | 13 +++++ apps/finance/services/api/main/handler_org.go | 5 ++ apps/finance/services/api/main/models_org.go | 1 + .../services/api/main/templates/base.html | 58 ++++++++++++++++++- .../api/main/templates/org_event_detail.html | 2 +- .../api/main/templates/org_events.html | 2 +- .../api/main/templates/org_members.html | 12 +++- .../api/main/templates/org_report.html | 2 +- .../api/main/templates/org_teams.html | 35 +++++++++-- 9 files changed, 119 insertions(+), 11 deletions(-) diff --git a/apps/finance/services/api/main/handler.go b/apps/finance/services/api/main/handler.go index 8d64a9d..082788c 100644 --- a/apps/finance/services/api/main/handler.go +++ b/apps/finance/services/api/main/handler.go @@ -118,6 +118,19 @@ func parseTmpl(files ...string) *template.Template { return "var(--bg3); color:var(--text3)" } }, + "avatarEmojis": func() []string { + return []string{ + "๐Ÿ‘ฅ", "๐Ÿš€", "โšก", "๐ŸŽฏ", "๐Ÿ†", "๐Ÿ’ก", "๐Ÿ”ฅ", "๐ŸŒŠ", "๐ŸŽจ", "๐Ÿ› ๏ธ", + "๐Ÿ“Š", "๐ŸŽช", "๐ŸŒฟ", "๐Ÿฆ‹", "๐Ÿ„", "๐ŸŽธ", "๐Ÿ”ฌ", "๐Ÿ“ก", "๐Ÿ—๏ธ", "๐ŸŽญ", + "๐ŸŒ", "๐Ÿฆ", "๐Ÿ‰", "๐Ÿฆ…", "๐Ÿ‹", "๐ŸŒ™", "โญ", "๐Ÿ€", "๐Ÿ”ฎ", "๐ŸŽฒ", + } + }, + "teamAvatar": func(t OrgTeam) string { + if t.Avatar != "" { + return t.Avatar + } + return "๐Ÿ‘ฅ" + }, "varColor": func(planned, actual int64) string { if planned == 0 { return "var(--text2)" diff --git a/apps/finance/services/api/main/handler_org.go b/apps/finance/services/api/main/handler_org.go index 4a138d7..14118a7 100644 --- a/apps/finance/services/api/main/handler_org.go +++ b/apps/finance/services/api/main/handler_org.go @@ -229,11 +229,16 @@ func (h *Handler) OrgTeamCreate(w http.ResponseWriter, r *http.Request) { http.Error(w, "name required", http.StatusBadRequest) return } + avatar := r.FormValue("avatar") + if avatar == "" { + avatar = "๐Ÿ‘ฅ" + } team := &OrgTeam{ ID: bson.NewObjectID().Hex(), OrgID: org.ID, Name: name, Type: teamType, + Avatar: avatar, CreatedAt: time.Now(), } if err := h.store.createTeam(ctx, team); err != nil { diff --git a/apps/finance/services/api/main/models_org.go b/apps/finance/services/api/main/models_org.go index 21daf46..d321898 100644 --- a/apps/finance/services/api/main/models_org.go +++ b/apps/finance/services/api/main/models_org.go @@ -35,6 +35,7 @@ type OrgTeam struct { OrgID string `bson:"org_id" json:"org_id"` Name string `bson:"name" json:"name"` Type TeamType `bson:"type" json:"type"` + Avatar string `bson:"avatar" json:"avatar"` // single emoji CreatedAt time.Time `bson:"created_at" json:"created_at"` } diff --git a/apps/finance/services/api/main/templates/base.html b/apps/finance/services/api/main/templates/base.html index 85d0eff..e8ecec2 100644 --- a/apps/finance/services/api/main/templates/base.html +++ b/apps/finance/services/api/main/templates/base.html @@ -396,11 +396,67 @@ cursor: pointer; font-family: inherit; } + /* Global catch-all: any bare input/select/textarea gets dark theme. + .form-group and .form-input rules above still win for their elements + due to specificity; this only fixes uncovered stragglers. */ + input:not([type=checkbox]):not([type=radio]):not([type=range]):not([type=submit]):not([type=button]):not([type=reset]):not([type=hidden]):not([type=color]), + select, + textarea { + background: var(--bg2); + color: var(--text); + border-color: var(--border2); + font-family: inherit; + } + input:not([type=checkbox]):not([type=radio]):not([type=range]):not([type=submit]):not([type=button]):not([type=reset]):not([type=hidden]):not([type=color]):focus, + select:focus, + textarea:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-glow); + } + input::placeholder, textarea::placeholder { color: var(--text3); opacity: 0.7; } + select option { background: var(--bg2); color: var(--text); } input[type="color"] { padding: 4px; height: 38px; cursor: pointer; } - select option, .form-input option { background: var(--bg2); color: var(--text); } textarea.form-input { resize: vertical; min-height: 72px; line-height: 1.5; } + /* Team / org avatars */ + .team-avatar { + display: inline-flex; align-items: center; justify-content: center; + width: 32px; height: 32px; + border-radius: 8px; + font-size: 17px; + line-height: 1; + flex-shrink: 0; + background: var(--bg3); + border: 1px solid var(--border2); + user-select: none; + } + .team-avatar-sm { + width: 22px; height: 22px; + border-radius: 5px; + font-size: 12px; + } + .team-avatar-lg { + width: 48px; height: 48px; + border-radius: 12px; + font-size: 26px; + } + /* Emoji picker row */ + .emoji-picker { display: flex; flex-wrap: wrap; gap: 6px; } + .emoji-opt { + width: 36px; height: 36px; + border-radius: 8px; + border: 2px solid transparent; + background: var(--bg3); + font-size: 18px; + cursor: pointer; + display: flex; align-items: center; justify-content: center; + transition: border-color 0.12s, background 0.12s; + } + .emoji-opt:hover { background: var(--surface2); } + .emoji-opt.selected { border-color: var(--accent); background: var(--accent-glow); } + /* โ”€โ”€ Badges โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ .badge { display: inline-flex; align-items: center; gap: 5px; diff --git a/apps/finance/services/api/main/templates/org_event_detail.html b/apps/finance/services/api/main/templates/org_event_detail.html index a1de7bd..6366987 100644 --- a/apps/finance/services/api/main/templates/org_event_detail.html +++ b/apps/finance/services/api/main/templates/org_event_detail.html @@ -118,7 +118,7 @@
TEAMS
-
{{if $d.EventTeams}}{{range $d.EventTeams}}{{.Name}}{{end}}{{else}}โ€”{{end}}
+
{{if $d.EventTeams}}{{range $d.EventTeams}}{{if .Avatar}}{{.Avatar}}{{else}}๐Ÿ‘ฅ{{end}}{{.Name}}{{end}}{{else}}โ€”{{end}}
{{if $d.Event.Description}} diff --git a/apps/finance/services/api/main/templates/org_events.html b/apps/finance/services/api/main/templates/org_events.html index 258a839..89b8e4f 100644 --- a/apps/finance/services/api/main/templates/org_events.html +++ b/apps/finance/services/api/main/templates/org_events.html @@ -50,7 +50,7 @@ {{.Event.Name}} - {{if .Teams}}{{range .Teams}}{{.Name}}{{end}}{{else}}โ€”{{end}} + {{if .Teams}}{{range .Teams}}{{if .Avatar}}{{.Avatar}}{{else}}๐Ÿ‘ฅ{{end}}{{.Name}}{{end}}{{else}}โ€”{{end}} {{dateShort .Event.DateStart}} โ€” {{dateShort .Event.DateEnd}} {{cents .TotalIncome}} diff --git a/apps/finance/services/api/main/templates/org_members.html b/apps/finance/services/api/main/templates/org_members.html index f335468..0ad72d7 100644 --- a/apps/finance/services/api/main/templates/org_members.html +++ b/apps/finance/services/api/main/templates/org_members.html @@ -43,11 +43,17 @@ {{$m.Role}} {{end}} - + {{if $m.TeamIDs}} - {{range $i, $tid := $m.TeamIDs}} - {{range $d.Teams}}{{if eq .ID $tid}}{{if $i}}, {{end}}{{.Name}}{{end}}{{end}} +
+ {{range $tid := $m.TeamIDs}} + {{range $d.Teams}}{{if eq .ID $tid}} + + {{if .Avatar}}{{.Avatar}}{{else}}๐Ÿ‘ฅ{{end}}{{.Name}} + + {{end}}{{end}} {{end}} +
{{else}} all teams {{end}} diff --git a/apps/finance/services/api/main/templates/org_report.html b/apps/finance/services/api/main/templates/org_report.html index 180f784..295226d 100644 --- a/apps/finance/services/api/main/templates/org_report.html +++ b/apps/finance/services/api/main/templates/org_report.html @@ -78,7 +78,7 @@
{{dateShort $ev.DateStart}} โ€” {{dateShort $ev.DateEnd}}
{{if .Teams}}
- {{range .Teams}}{{.Name}}{{end}} + {{range .Teams}}{{if .Avatar}}{{.Avatar}}{{else}}๐Ÿ‘ฅ{{end}}{{.Name}}{{end}}
{{end}} diff --git a/apps/finance/services/api/main/templates/org_teams.html b/apps/finance/services/api/main/templates/org_teams.html index cbd60cd..df19f8c 100644 --- a/apps/finance/services/api/main/templates/org_teams.html +++ b/apps/finance/services/api/main/templates/org_teams.html @@ -19,7 +19,7 @@ - + {{if eq $d.MyRole "admin"}}{{end}} @@ -30,7 +30,12 @@ {{$count := 0}} {{range $d.Members}}{{range .TeamIDs}}{{if eq . $t.ID}}{{$count = add $count 1}}{{end}}{{end}}{{end}} - +
NameTeam Type Members
{{$t.Name}} +
+ {{if $t.Avatar}}{{$t.Avatar}}{{else}}๐Ÿ‘ฅ{{end}} + {{$t.Name}} +
+
{{if eq (print $t.Type) "guest"}} guest @@ -64,13 +69,27 @@ {{end}} {{if eq $d.MyRole "admin"}} -