Gitfed
bastien-mrq/gitfed / cmd / gitfed-web / theme.go
package main

import (
	"net/http"
	"strings"
	"time"
)

const themeCookieName = "gitfed_theme"

// theme resolves the UI theme for a request from its cookie (set via the
// nav switcher) — "light" or "dark" force that theme regardless of the
// browser's own preference, "" (unset, or any other value) means "system":
// no data-theme attribute is rendered at all, so the page's
// prefers-color-scheme media query decides instead. There's no
// Accept-Language-style fallback here on purpose — unlike language, "system"
// is a legitimate, common default, not a missing signal to guess around.
func (s *server) theme(r *http.Request) string {
	if c, err := r.Cookie(themeCookieName); err == nil && (c.Value == "light" || c.Value == "dark") {
		return c.Value
	}
	return ""
}

// themeToggleNext returns the icon to show for the nav's compact theme
// button given the current theme value ("" meaning system), and the theme
// clicking it switches to — a three-state cycle (system -> light -> dark
// -> system) exposed as one small icon rather than the full spelled-out
// choice, which lives in Settings instead for anyone who wants it there.
func themeToggleNext(current string) (icon, next string) {
	switch current {
	case "light":
		return "sun", "dark"
	case "dark":
		return "moon", "system"
	default:
		return "auto", "light"
	}
}

// handleSetTheme stores the chosen theme in a cookie (or clears it for
// "system") and bounces back to wherever the switcher was clicked from —
// same shape as handleSetLang.
func (s *server) handleSetTheme(w http.ResponseWriter, r *http.Request) {
	choice := r.PathValue("theme")
	cookie := &http.Cookie{
		Name:     themeCookieName,
		Path:     "/",
		HttpOnly: true,
		Secure:   true,
		SameSite: http.SameSiteLaxMode,
	}
	if choice == "light" || choice == "dark" {
		cookie.Value = choice
		cookie.Expires = time.Now().Add(365 * 24 * time.Hour)
	} else {
		cookie.MaxAge = -1 // "system" — clear any previous explicit choice
	}
	http.SetCookie(w, cookie)

	next := r.URL.Query().Get("next")
	if next == "" || !strings.HasPrefix(next, "/") || strings.HasPrefix(next, "//") {
		next = "/"
	}
	http.Redirect(w, r, next, http.StatusSeeOther)
}