From c56a445c6a0641485591bb0ed59d97f3cf9ccadb Mon Sep 17 00:00:00 2001 From: yequari Date: Sun, 29 Jun 2025 18:12:32 -0700 Subject: [PATCH] add app configuration --- cmd/web/handlers_user.go | 15 +++++-- cmd/web/helpers.go | 16 ++++--- cmd/web/main.go | 96 +++++++++++++++++++++++++++++----------- ui/views/common.templ | 20 +++++---- ui/views/common_templ.go | 50 ++++++++++++--------- ui/views/users.templ | 4 +- ui/views/users_templ.go | 94 +++++++++++++++++++++------------------ 7 files changed, 186 insertions(+), 109 deletions(-) diff --git a/cmd/web/handlers_user.go b/cmd/web/handlers_user.go index 3c21fde..141d459 100644 --- a/cmd/web/handlers_user.go +++ b/cmd/web/handlers_user.go @@ -13,12 +13,18 @@ import ( ) func (app *application) getUserRegister(w http.ResponseWriter, r *http.Request) { + if !app.config.localAuthEnabled { + http.Redirect(w, r, "/users/login/oidc", http.StatusFound) + } form := forms.UserRegistrationForm{} data := app.newCommonData(r) views.UserRegistration("User Registration", data, form).Render(r.Context(), w) } func (app *application) getUserLogin(w http.ResponseWriter, r *http.Request) { + if !app.config.localAuthEnabled { + http.Redirect(w, r, "/users/login/oidc", http.StatusFound) + } views.UserLogin("Login", app.newCommonData(r), forms.UserLoginForm{}).Render(r.Context(), w) } @@ -94,6 +100,9 @@ func (app *application) postUserLogin(w http.ResponseWriter, r *http.Request) { } func (app *application) userLoginOIDC(w http.ResponseWriter, r *http.Request) { + if !app.config.oauthEnabled { + http.Redirect(w, r, "/users/login", http.StatusFound) + } state, err := randString(16) if err != nil { app.serverError(w, r, err) @@ -108,7 +117,7 @@ func (app *application) userLoginOIDC(w http.ResponseWriter, r *http.Request) { setCallbackCookie(w, r, "state", state) setCallbackCookie(w, r, "nonce", nonce) - http.Redirect(w, r, app.oauth.config.AuthCodeURL(state, oidc.Nonce(nonce)), http.StatusFound) + http.Redirect(w, r, app.config.oauth.config.AuthCodeURL(state, oidc.Nonce(nonce)), http.StatusFound) } func (app *application) userLoginOIDCCallback(w http.ResponseWriter, r *http.Request) { @@ -122,7 +131,7 @@ func (app *application) userLoginOIDCCallback(w http.ResponseWriter, r *http.Req return } - oauth2Token, err := app.oauth.config.Exchange(r.Context(), r.URL.Query().Get("code")) + oauth2Token, err := app.config.oauth.config.Exchange(r.Context(), r.URL.Query().Get("code")) if err != nil { app.logger.Error("Failed to exchange token") app.serverError(w, r, err) @@ -133,7 +142,7 @@ func (app *application) userLoginOIDCCallback(w http.ResponseWriter, r *http.Req app.serverError(w, r, errors.New("No id_token field in oauth2 token")) return } - idToken, err := app.oauth.verifier.Verify(r.Context(), rawIDToken) + idToken, err := app.config.oauth.verifier.Verify(r.Context(), rawIDToken) if err != nil { app.logger.Error("Failed to verify ID token") app.serverError(w, r, err) diff --git a/cmd/web/helpers.go b/cmd/web/helpers.go index 239aea2..8e66c3c 100644 --- a/cmd/web/helpers.go +++ b/cmd/web/helpers.go @@ -107,13 +107,15 @@ func (app *application) getCurrentUser(r *http.Request) *models.User { func (app *application) newCommonData(r *http.Request) views.CommonData { return views.CommonData{ - CurrentYear: time.Now().Year(), - Flash: app.sessionManager.PopString(r.Context(), "flash"), - IsAuthenticated: app.isAuthenticated(r), - CSRFToken: nosurf.Token(r), - CurrentUser: app.getCurrentUser(r), - IsHtmx: r.Header.Get("Hx-Request") == "true", - RootUrl: app.rootUrl, + CurrentYear: time.Now().Year(), + Flash: app.sessionManager.PopString(r.Context(), "flash"), + IsAuthenticated: app.isAuthenticated(r), + CSRFToken: nosurf.Token(r), + CurrentUser: app.getCurrentUser(r), + IsHtmx: r.Header.Get("Hx-Request") == "true", + RootUrl: app.config.rootUrl, + LocalAuthEnabled: app.config.localAuthEnabled, + OIDCEnabled: app.config.oauthEnabled, } } diff --git a/cmd/web/main.go b/cmd/web/main.go index e999e92..7df1d2b 100644 --- a/cmd/web/main.go +++ b/cmd/web/main.go @@ -9,7 +9,9 @@ import ( "fmt" "log/slog" "net/http" + "net/url" "os" + "strconv" "strings" "time" "unicode" @@ -32,6 +34,13 @@ type applicationOauthConfig struct { verifier *oidc.IDTokenVerifier } +type applicationConfig struct { + oauthEnabled bool + localAuthEnabled bool + oauth applicationOauthConfig + rootUrl string +} + type application struct { sequence uint16 logger *slog.Logger @@ -40,21 +49,24 @@ type application struct { guestbookComments models.GuestbookCommentModelInterface sessionManager *scs.SessionManager formDecoder *schema.Decoder - oauth applicationOauthConfig + config applicationConfig debug bool timezones []string - rootUrl string } func main() { addr := flag.String("addr", ":3000", "HTTP network address") dsn := flag.String("dsn", "guestbook.db", "data source name") debug := flag.Bool("debug", false, "enable debug mode") - root := flag.String("root", "https://localhost:3000", "root URL of application") flag.Parse() logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})) godotenv.Load(".env.dev") + cfg, err := setupConfig(*addr) + if err != nil { + logger.Error(err.Error()) + os.Exit(1) + } db, err := openDB(*dsn) if err != nil { @@ -78,18 +90,11 @@ func main() { users: &models.UserModel{DB: db, Settings: make(map[string]models.Setting)}, guestbookComments: &models.GuestbookCommentModel{DB: db}, formDecoder: formDecoder, + config: cfg, debug: *debug, timezones: getAvailableTimezones(), - rootUrl: *root, } - o, err := setupOauth(app.rootUrl) - if err != nil { - logger.Error(err.Error()) - os.Exit(1) - } - app.oauth = o - err = app.users.InitializeSettingsMap() if err != nil { logger.Error(err.Error()) @@ -137,36 +142,73 @@ func openDB(dsn string) (*sql.DB, error) { return db, nil } -func setupOauth(rootUrl string) (applicationOauthConfig, error) { - var c applicationOauthConfig +func setupConfig(addr string) (applicationConfig, error) { + var c applicationConfig + var ( - oauth2Provider = os.Getenv("OAUTH2_PROVIDER") - clientID = os.Getenv("OAUTH2_CLIENT_ID") - clientSecret = os.Getenv("OAUTH2_CLIENT_SECRET") + rootUrl = os.Getenv("ROOT_URL") + oidcEnabled = os.Getenv("ENABLE_OIDC") + localLoginEnabled = os.Getenv("ENABLE_LOCAL_LOGIN") + oauth2Provider = os.Getenv("OAUTH2_PROVIDER") + clientID = os.Getenv("OAUTH2_CLIENT_ID") + clientSecret = os.Getenv("OAUTH2_CLIENT_SECRET") ) - if oauth2Provider == "" || clientID == "" || clientSecret == "" { - return applicationOauthConfig{}, errors.New("OAUTH2_PROVIDER, OAUTH2_CLIENT_ID, and OAUTH2_CLIENT_SECRET must be specified as environment variables.") + if rootUrl != "" { + c.rootUrl = rootUrl + } else { + u, err := url.Parse(fmt.Sprintf("https://localhost%s", addr)) + if err != nil { + return c, err + } + c.rootUrl = u.String() } - c.ctx = context.Background() - provider, err := oidc.NewProvider(c.ctx, oauth2Provider) + oauthEnabled, err := strconv.ParseBool(oidcEnabled) if err != nil { - return applicationOauthConfig{}, err + c.oauthEnabled = false } - c.provider = provider - c.oidcConfig = &oidc.Config{ + c.oauthEnabled = oauthEnabled + + localAuthEnabled, err := strconv.ParseBool(localLoginEnabled) + if err != nil { + c.localAuthEnabled = true + } + c.localAuthEnabled = localAuthEnabled + + if !c.oauthEnabled && !c.localAuthEnabled { + return c, errors.New("Either ENABLE_OIDC or ENABLE_LOCAL_LOGIN must be set to true") + } + + // if OIDC is disabled, no more configuration needs to be read + if !oauthEnabled { + return c, nil + } + + var o applicationOauthConfig + if oauth2Provider == "" || clientID == "" || clientSecret == "" { + return c, errors.New("OAUTH2_PROVIDER, OAUTH2_CLIENT_ID, and OAUTH2_CLIENT_SECRET must be specified as environment variables.") + } + + o.ctx = context.Background() + provider, err := oidc.NewProvider(o.ctx, oauth2Provider) + if err != nil { + return c, err + } + o.provider = provider + o.oidcConfig = &oidc.Config{ ClientID: clientID, } - c.verifier = provider.Verifier(c.oidcConfig) - c.config = oauth2.Config{ + o.verifier = provider.Verifier(o.oidcConfig) + o.config = oauth2.Config{ ClientID: clientID, ClientSecret: clientSecret, Endpoint: provider.Endpoint(), - RedirectURL: fmt.Sprintf("%s/users/login/oidc/callback", rootUrl), + RedirectURL: fmt.Sprintf("%s/users/login/oidc/callback", c.rootUrl), Scopes: []string{oidc.ScopeOpenID, "profile", "email"}, } - return c, nil + c.oauth = o + return c, nil } func getAvailableTimezones() []string { diff --git a/ui/views/common.templ b/ui/views/common.templ index 5df9f50..bafe90f 100644 --- a/ui/views/common.templ +++ b/ui/views/common.templ @@ -6,13 +6,15 @@ import "fmt" import "strings" type CommonData struct { - CurrentYear int - Flash string - IsAuthenticated bool - CSRFToken string - CurrentUser *models.User - IsHtmx bool - RootUrl string + CurrentYear int + Flash string + IsAuthenticated bool + CSRFToken string + CurrentUser *models.User + IsHtmx bool + RootUrl string + LocalAuthEnabled bool + OIDCEnabled bool } func shortIdToSlug(shortId uint64) string { @@ -52,7 +54,9 @@ templ topNav(data CommonData) { Settings | Logout } else { - Create an Account | + if data.LocalAuthEnabled { + Create an Account | + } Login } diff --git a/ui/views/common_templ.go b/ui/views/common_templ.go index 6a7d8aa..e4131f2 100644 --- a/ui/views/common_templ.go +++ b/ui/views/common_templ.go @@ -14,13 +14,15 @@ import "fmt" import "strings" type CommonData struct { - CurrentYear int - Flash string - IsAuthenticated bool - CSRFToken string - CurrentUser *models.User - IsHtmx bool - RootUrl string + CurrentYear int + Flash string + IsAuthenticated bool + CSRFToken string + CurrentUser *models.User + IsHtmx bool + RootUrl string + LocalAuthEnabled bool + OIDCEnabled bool } func shortIdToSlug(shortId uint64) string { @@ -102,7 +104,7 @@ func topNav(data CommonData) templ.Component { var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.CurrentUser.Username) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 45, Col: 40} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 47, Col: 40} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -121,7 +123,7 @@ func topNav(data CommonData) templ.Component { var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(hxHeaders) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 53, Col: 62} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 55, Col: 62} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -132,12 +134,18 @@ func topNav(data CommonData) templ.Component { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "Create an Account | Login") + if data.LocalAuthEnabled { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "Create an Account | ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, " Login") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -166,7 +174,7 @@ func commonFooter() templ.Component { templ_7745c5c3_Var5 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -195,20 +203,20 @@ func base(title string, data CommonData) templ.Component { templ_7745c5c3_Var6 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<!doctype html><html lang=\"en\"><head><title>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 72, Col: 17} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 76, Col: 17} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, " - webweav.ing") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, " - webweav.ing") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -220,25 +228,25 @@ func base(title string, data CommonData) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if data.Flash != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var8 string templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(data.Flash) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 84, Col: 36} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 88, Col: 36} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -247,7 +255,7 @@ func base(title string, data CommonData) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -255,7 +263,7 @@ func base(title string, data CommonData) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/ui/views/users.templ b/ui/views/users.templ index a0d60b3..299d282 100644 --- a/ui/views/users.templ +++ b/ui/views/users.templ @@ -31,7 +31,9 @@ templ UserLogin(title string, data CommonData, form forms.UserLoginForm) {
- Login with SSO + if data.OIDCEnabled { + Login with SSO + }
} diff --git a/ui/views/users_templ.go b/ui/views/users_templ.go index 391f030..ec96f47 100644 --- a/ui/views/users_templ.go +++ b/ui/views/users_templ.go @@ -143,7 +143,17 @@ func UserLogin(title string, data CommonData, form forms.UserLoginForm) templ.Co return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
Login with SSO
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.OIDCEnabled { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "Login with SSO") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -190,130 +200,130 @@ func UserRegistration(title string, data CommonData, form forms.UserRegistration }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "

User Registration

User Registration
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "\">
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } error, exists := form.FieldErrors["name"] - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if exists { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "\" required>
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } error, exists = form.FieldErrors["email"] - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if exists { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "\" required>
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } error, exists = form.FieldErrors["password"] - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if exists { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -360,33 +370,33 @@ func UserProfile(title string, data CommonData, user models.User) templ.Componen }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var18 string templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(user.Username) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 78, Col: 21} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 80, Col: 21} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var19 string templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(user.Email) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 79, Col: 17} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 81, Col: 17} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -434,89 +444,89 @@ func UserSettingsView(data CommonData, timezones []string) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "

User Settings

User Settings

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } for _, tz := range timezones { if tz == user.Settings.LocalTimezone.String() { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err }