Implement guestbook settings #20
| @ -39,7 +39,8 @@ func (app *application) postUserRegister(w http.ResponseWriter, r *http.Request) | ||||
| 		return | ||||
| 	} | ||||
| 	shortId := app.createShortId() | ||||
| 	err = app.users.Insert(shortId, form.Name, form.Email, form.Password) | ||||
| 	settings := DefaultUserSettings() | ||||
| 	err = app.users.Insert(shortId, form.Name, form.Email, form.Password, settings) | ||||
| 	if err != nil { | ||||
| 		if errors.Is(err, models.ErrDuplicateEmail) { | ||||
| 			form.AddFieldError("email", "Email address is already in use") | ||||
| @ -129,3 +130,34 @@ func (app *application) getUser(w http.ResponseWriter, r *http.Request) { | ||||
| 	data := app.newCommonData(r) | ||||
| 	views.UserProfile(user.Username, data, user).Render(r.Context(), w) | ||||
| } | ||||
| 
 | ||||
| func (app *application) getUserSettings(w http.ResponseWriter, r *http.Request) { | ||||
| 	data := app.newCommonData(r) | ||||
| 	views.UserSettingsView(data, app.timezones).Render(r.Context(), w) | ||||
| } | ||||
| 
 | ||||
| func (app *application) putUserSettings(w http.ResponseWriter, r *http.Request) { | ||||
| 	userId := app.getCurrentUser(r).ID | ||||
| 	var form forms.UserSettingsForm | ||||
| 	err := app.decodePostForm(r, &form) | ||||
| 	if err != nil { | ||||
| 		app.clientError(w, http.StatusBadRequest) | ||||
| 		app.serverError(w, r, err) | ||||
| 		return | ||||
| 	} | ||||
| 	form.CheckField(validator.PermittedValue(form.LocalTimezone, app.timezones...), "timezone", "Invalid value") | ||||
| 	if !form.Valid() { | ||||
| 		// rerender template with errors | ||||
| 		app.clientError(w, http.StatusUnprocessableEntity) | ||||
| 	} | ||||
| 	err = app.users.SetLocalTimezone(userId, form.LocalTimezone) | ||||
| 	if err != nil { | ||||
| 		app.serverError(w, r, err) | ||||
| 		return | ||||
| 	} | ||||
| 	app.sessionManager.Put(r.Context(), "flash", "Settings changed successfully") | ||||
| 	data := app.newCommonData(r) | ||||
| 	w.Header().Add("HX-Refresh", "true") | ||||
| 	views.UserSettingsView(data, app.timezones).Render(r.Context(), w) | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -112,3 +112,9 @@ func (app *application) newCommonData(r *http.Request) views.CommonData { | ||||
| 		IsHtmx:          r.Header.Get("Hx-Request") == "true", | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func DefaultUserSettings() models.UserSettings { | ||||
| 	return models.UserSettings{ | ||||
| 		LocalTimezone: time.Now().UTC().Location(), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -7,7 +7,9 @@ import ( | ||||
| 	"log/slog" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 	"unicode" | ||||
| 
 | ||||
| 	"git.32bit.cafe/32bitcafe/guestbook/internal/models" | ||||
| 	"github.com/alexedwards/scs/sqlite3store" | ||||
| @ -26,6 +28,7 @@ type application struct { | ||||
| 	sessionManager    *scs.SessionManager | ||||
| 	formDecoder       *schema.Decoder | ||||
| 	debug             bool | ||||
| 	timezones         []string | ||||
| } | ||||
| 
 | ||||
| func main() { | ||||
| @ -60,6 +63,7 @@ func main() { | ||||
| 		guestbookComments: &models.GuestbookCommentModel{DB: db}, | ||||
| 		formDecoder:       formDecoder, | ||||
| 		debug:             *debug, | ||||
| 		timezones:         getAvailableTimezones(), | ||||
| 	} | ||||
| 
 | ||||
| 	tlsConfig := &tls.Config{ | ||||
| @ -97,3 +101,50 @@ func openDB(dsn string) (*sql.DB, error) { | ||||
| 	} | ||||
| 	return db, nil | ||||
| } | ||||
| 
 | ||||
| func getAvailableTimezones() []string { | ||||
| 	var zones []string | ||||
| 	var zoneDirs = []string{ | ||||
| 		"/usr/share/zoneinfo/", | ||||
| 		"/usr/share/lib/zoneinfo/", | ||||
| 		"/usr/lib/locale/TZ/", | ||||
| 	} | ||||
| 	for _, zd := range zoneDirs { | ||||
| 		zones = walkTzDir(zd, zones) | ||||
| 		for idx, zone := range zones { | ||||
| 			zones[idx] = strings.ReplaceAll(zone, zd+"/", "") | ||||
| 		} | ||||
| 	} | ||||
| 	return zones | ||||
| } | ||||
| 
 | ||||
| func walkTzDir(path string, zones []string) []string { | ||||
| 	fileInfos, err := os.ReadDir(path) | ||||
| 	if err != nil { | ||||
| 		return zones | ||||
| 	} | ||||
| 	isAlpha := func(s string) bool { | ||||
| 		for _, r := range s { | ||||
| 			if !unicode.IsLetter(r) { | ||||
| 				return false | ||||
| 			} | ||||
| 		} | ||||
| 		return true | ||||
| 	} | ||||
| 	for _, info := range fileInfos { | ||||
| 		if info.Name() != strings.ToUpper(info.Name()[:1])+info.Name()[1:] { | ||||
| 			continue | ||||
| 		} | ||||
| 		if !isAlpha(info.Name()[:1]) { | ||||
| 			continue | ||||
| 		} | ||||
| 		newPath := path + "/" + info.Name() | ||||
| 		if info.IsDir() { | ||||
| 			zones = walkTzDir(newPath, zones) | ||||
| 		} else { | ||||
| 			zones = append(zones, newPath) | ||||
| 		} | ||||
| 	} | ||||
| 	return zones | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -28,7 +28,8 @@ func (app *application) routes() http.Handler { | ||||
| 	// mux.Handle("GET /users", protected.ThenFunc(app.getUsersList)) | ||||
| 	mux.Handle("GET /users/{id}", protected.ThenFunc(app.getUser)) | ||||
| 	mux.Handle("POST /users/logout", protected.ThenFunc(app.postUserLogout)) | ||||
| 	mux.Handle("GET /users/settings", protected.ThenFunc(app.notImplemented)) | ||||
| 	mux.Handle("GET /users/settings", protected.ThenFunc(app.getUserSettings)) | ||||
| 	mux.Handle("PUT /users/settings", protected.ThenFunc(app.putUserSettings)) | ||||
| 	mux.Handle("GET /users/privacy", protected.ThenFunc(app.notImplemented)) | ||||
| 	mux.Handle("GET /guestbooks", protected.ThenFunc(app.getAllGuestbooks)) | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										76
									
								
								db/create-settings-tables.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								db/create-settings-tables.sql
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,76 @@ | ||||
| CREATE TABLE setting_groups ( | ||||
|     Id integer primary key autoincrement, | ||||
|     Description varchar(256) NOT NULL | ||||
| ); | ||||
| 
 | ||||
| CREATE TABLE setting_data_types ( | ||||
|     Id integer primary key autoincrement, | ||||
|     Description varchar(64) NOT NULL | ||||
| ); | ||||
| 
 | ||||
| CREATE TABLE settings ( | ||||
|     Id integer primary key autoincrement, | ||||
|     Description varchar(256) NOT NULL, | ||||
|     Constrained boolean NOT NULL, | ||||
|     DataType integer NOT NULL, | ||||
|     SettingGroup int NOT NULL, | ||||
|     MinValue varchar(6), | ||||
|     MaxValue varchar(6), | ||||
|     FOREIGN KEY (DataType) REFERENCES setting_data_types(Id) | ||||
|         ON DELETE RESTRICT | ||||
|         ON UPDATE RESTRICT, | ||||
|     FOREIGN KEY (SettingGroup) REFERENCES setting_groups(Id) | ||||
|         ON DELETE RESTRICT | ||||
|         ON UPDATE RESTRICT | ||||
| ); | ||||
| 
 | ||||
| CREATE TABLE allowed_setting_values ( | ||||
|     Id integer primary key autoincrement, | ||||
|     SettingId integer NOT NULL, | ||||
|     ItemValue varchar(256), | ||||
|     Caption varchar(256), | ||||
|     FOREIGN KEY (SettingId) REFERENCES settings(Id) | ||||
|         ON DELETE RESTRICT | ||||
|         ON UPDATE RESTRICT | ||||
| ); | ||||
| 
 | ||||
| CREATE TABLE user_settings ( | ||||
|     Id integer primary key autoincrement, | ||||
|     UserId integer NOT NULL, | ||||
|     SettingId integer NOT NULL, | ||||
|     AllowedSettingValueId integer NOT NULL, | ||||
|     UnconstrainedValue varchar(256), | ||||
|     FOREIGN KEY (UserId) REFERENCES users(Id) | ||||
|         ON DELETE RESTRICT | ||||
|         ON UPDATE RESTRICT, | ||||
|     FOREIGN KEY (SettingId) REFERENCES settings(Id) | ||||
|         ON DELETE RESTRICT | ||||
|         ON UPDATE RESTRICT, | ||||
|     FOREIGN KEY (AllowedSettingValueId) REFERENCES allowed_setting_values(Id) | ||||
|         ON DELETE RESTRICT | ||||
|         ON UPDATE RESTRICT | ||||
| ); | ||||
| 
 | ||||
| CREATE TABLE guestbook_settings ( | ||||
|     Id integer primary key autoincrement, | ||||
|     GuestbookId integer NOT NULL, | ||||
|     SettingId integer NOT NULL, | ||||
|     AllowedSettingValueId integer NOT NULL, | ||||
|     UnconstrainedValue varchar(256), | ||||
|     FOREIGN KEY (GuestbookId) REFERENCES guestbooks(Id) | ||||
|         ON DELETE RESTRICT | ||||
|         ON UPDATE RESTRICT, | ||||
|     FOREIGN KEY (SettingId) REFERENCES settings(Id) | ||||
|         ON DELETE RESTRICT | ||||
|         ON UPDATE RESTRICT, | ||||
|     FOREIGN KEY (AllowedSettingValueId) REFERENCES allowed_setting_values(Id) | ||||
|         ON DELETE RESTRICT | ||||
|         ON UPDATE RESTRICT | ||||
| ); | ||||
| 
 | ||||
| INSERT INTO setting_groups (Description) VALUES ('guestbook'); | ||||
| INSERT INTO setting_groups (Description) VALUES ('user'); | ||||
| 
 | ||||
| INSERT INTO setting_data_types (Description) VALUES ('alphanumeric'); | ||||
| INSERT INTO setting_data_types (Description) VALUES ('integer'); | ||||
| INSERT INTO setting_data_types (Description) VALUES ('datetime'); | ||||
							
								
								
									
										9
									
								
								db/create-settings.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								db/create-settings.sql
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | ||||
| INSERT INTO setting_groups (Description) VALUES ("guestbook"); | ||||
| INSERT INTO setting_groups (Description) VALUES ("user"); | ||||
| 
 | ||||
| INSERT INTO setting_data_types (Description) VALUES ("alphanumeric") | ||||
| INSERT INTO setting_data_types (Description) VALUES ("integer") | ||||
| INSERT INTO setting_data_types (Description) VALUES ("datetime") | ||||
| 
 | ||||
| INSERT INTO settings (Description, Constrained, DataType, SettingGroup) | ||||
| VALUES ("Local Timezone", 0, 1, 1); | ||||
| @ -29,3 +29,8 @@ type WebsiteCreateForm struct { | ||||
| 	AuthorName          string `schema:"authorname"` | ||||
| 	validator.Validator `schema:"-"` | ||||
| } | ||||
| 
 | ||||
| type UserSettingsForm struct { | ||||
| 	LocalTimezone       string `schema:"timezones"` | ||||
| 	validator.Validator `schema:"-"` | ||||
| } | ||||
|  | ||||
| @ -10,6 +10,14 @@ import ( | ||||
| 	"golang.org/x/crypto/bcrypt" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	u_timezone = 1 | ||||
| ) | ||||
| 
 | ||||
| type UserSettings struct { | ||||
| 	LocalTimezone *time.Location | ||||
| } | ||||
| 
 | ||||
| type User struct { | ||||
| 	ID             int64 | ||||
| 	ShortId        uint64 | ||||
| @ -19,20 +27,21 @@ type User struct { | ||||
| 	IsBanned       bool | ||||
| 	HashedPassword []byte | ||||
| 	Created        time.Time | ||||
| 	Settings       UserSettings | ||||
| } | ||||
| 
 | ||||
| type UserModel struct { | ||||
| 	DB *sql.DB | ||||
| } | ||||
| 
 | ||||
| func (m *UserModel) Insert(shortId uint64, username string, email string, password string) error { | ||||
| func (m *UserModel) Insert(shortId uint64, username string, email string, password string, settings UserSettings) error { | ||||
| 	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 12) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	stmt := `INSERT INTO users (ShortId, Username, Email, IsBanned, HashedPassword, Created) | ||||
|     VALUES (?, ?, ?, FALSE, ?, ?)` | ||||
| 	_, err = m.DB.Exec(stmt, shortId, username, email, hashedPassword, time.Now().UTC()) | ||||
| 	result, err := m.DB.Exec(stmt, shortId, username, email, hashedPassword, time.Now().UTC()) | ||||
| 	if err != nil { | ||||
| 		if sqliteError, ok := err.(sqlite3.Error); ok { | ||||
| 			if sqliteError.ExtendedCode == 2067 && strings.Contains(sqliteError.Error(), "Email") { | ||||
| @ -41,6 +50,17 @@ func (m *UserModel) Insert(shortId uint64, username string, email string, passwo | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
| 	id, err := result.LastInsertId() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	settingsStmt := `INSERT INTO user_settings  | ||||
| 		(UserId, SettingId, AllowedSettingValueId, UnconstrainedValue) | ||||
| 		VALUES (?, ?, ?, ?)` | ||||
| 	_, err = m.DB.Exec(settingsStmt, id, u_timezone, nil, settings.LocalTimezone.String()) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| @ -55,6 +75,11 @@ func (m *UserModel) Get(id uint64) (User, error) { | ||||
| 		} | ||||
| 		return User{}, err | ||||
| 	} | ||||
| 	settings, err := m.GetSettings(u.ID) | ||||
| 	if err != nil { | ||||
| 		return u, err | ||||
| 	} | ||||
| 	u.Settings = settings | ||||
| 	return u, nil | ||||
| } | ||||
| 
 | ||||
| @ -69,6 +94,11 @@ func (m *UserModel) GetById(id int64) (User, error) { | ||||
| 		} | ||||
| 		return User{}, err | ||||
| 	} | ||||
| 	settings, err := m.GetSettings(u.ID) | ||||
| 	if err != nil { | ||||
| 		return u, err | ||||
| 	} | ||||
| 	u.Settings = settings | ||||
| 	return u, nil | ||||
| } | ||||
| 
 | ||||
| @ -125,3 +155,40 @@ func (m *UserModel) Exists(id int64) (bool, error) { | ||||
| 	err := m.DB.QueryRow(stmt, id).Scan(&exists) | ||||
| 	return exists, err | ||||
| } | ||||
| 
 | ||||
| func (m *UserModel) GetSettings(userId int64) (UserSettings, error) { | ||||
| 	stmt := `SELECT u.SettingId, a.ItemValue, u.UnconstrainedValue FROM user_settings AS u | ||||
| 			LEFT JOIN allowed_setting_values AS a ON u.SettingId = a.SettingId | ||||
| 			WHERE UserId = ?` | ||||
| 	var settings UserSettings | ||||
| 	rows, err := m.DB.Query(stmt, userId) | ||||
| 	if err != nil { | ||||
| 		return settings, err | ||||
| 	} | ||||
| 	for rows.Next() { | ||||
| 		var id int | ||||
| 		var itemValue sql.NullString | ||||
| 		var unconstrainedValue sql.NullString | ||||
| 		err = rows.Scan(&id, &itemValue, &unconstrainedValue) | ||||
| 		if err != nil { | ||||
| 			return settings, err | ||||
| 		} | ||||
| 		switch id { | ||||
| 		case u_timezone: | ||||
| 			settings.LocalTimezone, err = time.LoadLocation(unconstrainedValue.String) | ||||
| 			if err != nil { | ||||
| 				panic(err) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return settings, err | ||||
| } | ||||
| 
 | ||||
| func (m *UserModel) SetLocalTimezone(userId int64, timezone string) error { | ||||
| 	stmt := `UPDATE user_settings SET UnconstrainedValue = ? WHERE UserId = ?` | ||||
| 	_, err := m.DB.Exec(stmt, timezone, userId) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| // Code generated by templ - DO NOT EDIT. | ||||
| 
 | ||||
| // templ: version: v0.3.833 | ||||
| // templ: version: v0.3.857 | ||||
| package views | ||||
| 
 | ||||
| //lint:file-ignore SA4006 This context is only used if a nested component is present. | ||||
|  | ||||
| @ -48,7 +48,7 @@ templ GuestbookDashboardCommentView(data CommonData, w models.Website, c models. | ||||
|                 | <a href={ templ.URL(externalUrl(c.AuthorSite))} target="_blank">{ c.AuthorSite }</a> | ||||
|             } | ||||
|             <p> | ||||
|                 { c.Created.Format("01-02-2006 03:04PM") } | ||||
|                 { c.Created.In(data.CurrentUser.Settings.LocalTimezone).Format("01-02-2006 03:04PM") } | ||||
|             </p> | ||||
|         </div> | ||||
|         <p> | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| // Code generated by templ - DO NOT EDIT. | ||||
| 
 | ||||
| // templ: version: v0.3.833 | ||||
| // templ: version: v0.3.857 | ||||
| package views | ||||
| 
 | ||||
| //lint:file-ignore SA4006 This context is only used if a nested component is present. | ||||
| @ -275,9 +275,9 @@ func GuestbookDashboardCommentView(data CommonData, w models.Website, c models.G | ||||
| 			return templ_7745c5c3_Err | ||||
| 		} | ||||
| 		var templ_7745c5c3_Var14 string | ||||
| 		templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(c.Created.Format("01-02-2006 03:04PM")) | ||||
| 		templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(c.Created.In(data.CurrentUser.Settings.LocalTimezone).Format("01-02-2006 03:04PM")) | ||||
| 		if templ_7745c5c3_Err != nil { | ||||
| 			return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 51, Col: 56} | ||||
| 			return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 51, Col: 100} | ||||
| 		} | ||||
| 		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) | ||||
| 		if templ_7745c5c3_Err != nil { | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| // Code generated by templ - DO NOT EDIT. | ||||
| 
 | ||||
| // templ: version: v0.3.833 | ||||
| // templ: version: v0.3.857 | ||||
| package views | ||||
| 
 | ||||
| //lint:file-ignore SA4006 This context is only used if a nested component is present. | ||||
|  | ||||
| @ -79,5 +79,11 @@ templ UserProfile (title string, data CommonData, user models.User) { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| templ UserSettings () { | ||||
| templ UserSettings (title string, data CommonData, user models.User) { | ||||
|     @base(title, data) { | ||||
|         <form hx-put="/users/settings"> | ||||
|             <input type="text"> | ||||
|             <button type="submit">Submit</button> | ||||
|         </form> | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										24
									
								
								ui/views/users_settings.templ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								ui/views/users_settings.templ
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | ||||
| package views | ||||
| 
 | ||||
| templ UserSettingsView(data CommonData, timezones []string) { | ||||
| 	{{ user := data.CurrentUser }} | ||||
| 	@base("User Settings", data) { | ||||
| 		<div> | ||||
| 			<h1>User Settings</h1> | ||||
| 			<form hx-put="/users/settings"> | ||||
| 				<input type="hidden" name="csrf_token" value={ data.CSRFToken }/> | ||||
| 				<h3>Timezone</h3> | ||||
| 				<select name="timezones" id="timezone-select"> | ||||
| 					for _, tz := range timezones { | ||||
| 						if tz == user.Settings.LocalTimezone.String() { | ||||
| 							<option value={ tz } selected="true">{ tz }</option> | ||||
| 						} else { | ||||
| 							<option value={ tz }>{ tz }</option> | ||||
| 						} | ||||
| 					} | ||||
| 				</select> | ||||
| 				<input type="submit" value="Submit"/> | ||||
| 			</form> | ||||
| 		</div> | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										141
									
								
								ui/views/users_settings_templ.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								ui/views/users_settings_templ.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,141 @@ | ||||
| // Code generated by templ - DO NOT EDIT. | ||||
| 
 | ||||
| // templ: version: v0.3.857 | ||||
| package views | ||||
| 
 | ||||
| //lint:file-ignore SA4006 This context is only used if a nested component is present. | ||||
| 
 | ||||
| import "github.com/a-h/templ" | ||||
| import templruntime "github.com/a-h/templ/runtime" | ||||
| 
 | ||||
| func UserSettingsView(data CommonData, timezones []string) templ.Component { | ||||
| 	return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { | ||||
| 		templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context | ||||
| 		if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { | ||||
| 			return templ_7745c5c3_CtxErr | ||||
| 		} | ||||
| 		templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) | ||||
| 		if !templ_7745c5c3_IsBuffer { | ||||
| 			defer func() { | ||||
| 				templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) | ||||
| 				if templ_7745c5c3_Err == nil { | ||||
| 					templ_7745c5c3_Err = templ_7745c5c3_BufErr | ||||
| 				} | ||||
| 			}() | ||||
| 		} | ||||
| 		ctx = templ.InitializeContext(ctx) | ||||
| 		templ_7745c5c3_Var1 := templ.GetChildren(ctx) | ||||
| 		if templ_7745c5c3_Var1 == nil { | ||||
| 			templ_7745c5c3_Var1 = templ.NopComponent | ||||
| 		} | ||||
| 		ctx = templ.ClearChildren(ctx) | ||||
| 		user := data.CurrentUser | ||||
| 		templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { | ||||
| 			templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context | ||||
| 			templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) | ||||
| 			if !templ_7745c5c3_IsBuffer { | ||||
| 				defer func() { | ||||
| 					templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) | ||||
| 					if templ_7745c5c3_Err == nil { | ||||
| 						templ_7745c5c3_Err = templ_7745c5c3_BufErr | ||||
| 					} | ||||
| 				}() | ||||
| 			} | ||||
| 			ctx = templ.InitializeContext(ctx) | ||||
| 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div><h1>User Settings</h1><form hx-put=\"/users/settings\"><input type=\"hidden\" name=\"csrf_token\" value=\"") | ||||
| 			if templ_7745c5c3_Err != nil { | ||||
| 				return templ_7745c5c3_Err | ||||
| 			} | ||||
| 			var templ_7745c5c3_Var3 string | ||||
| 			templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.CSRFToken) | ||||
| 			if templ_7745c5c3_Err != nil { | ||||
| 				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users_settings.templ`, Line: 9, Col: 65} | ||||
| 			} | ||||
| 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) | ||||
| 			if templ_7745c5c3_Err != nil { | ||||
| 				return templ_7745c5c3_Err | ||||
| 			} | ||||
| 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\"><h3>Timezone</h3><select name=\"timezones\" id=\"timezone-select\">") | ||||
| 			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, 3, "<option value=\"") | ||||
| 					if templ_7745c5c3_Err != nil { | ||||
| 						return templ_7745c5c3_Err | ||||
| 					} | ||||
| 					var templ_7745c5c3_Var4 string | ||||
| 					templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(tz) | ||||
| 					if templ_7745c5c3_Err != nil { | ||||
| 						return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users_settings.templ`, Line: 14, Col: 25} | ||||
| 					} | ||||
| 					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) | ||||
| 					if templ_7745c5c3_Err != nil { | ||||
| 						return templ_7745c5c3_Err | ||||
| 					} | ||||
| 					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\" selected=\"true\">") | ||||
| 					if templ_7745c5c3_Err != nil { | ||||
| 						return templ_7745c5c3_Err | ||||
| 					} | ||||
| 					var templ_7745c5c3_Var5 string | ||||
| 					templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(tz) | ||||
| 					if templ_7745c5c3_Err != nil { | ||||
| 						return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users_settings.templ`, Line: 14, Col: 48} | ||||
| 					} | ||||
| 					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) | ||||
| 					if templ_7745c5c3_Err != nil { | ||||
| 						return templ_7745c5c3_Err | ||||
| 					} | ||||
| 					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</option>") | ||||
| 					if templ_7745c5c3_Err != nil { | ||||
| 						return templ_7745c5c3_Err | ||||
| 					} | ||||
| 				} else { | ||||
| 					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<option value=\"") | ||||
| 					if templ_7745c5c3_Err != nil { | ||||
| 						return templ_7745c5c3_Err | ||||
| 					} | ||||
| 					var templ_7745c5c3_Var6 string | ||||
| 					templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(tz) | ||||
| 					if templ_7745c5c3_Err != nil { | ||||
| 						return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users_settings.templ`, Line: 16, Col: 25} | ||||
| 					} | ||||
| 					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) | ||||
| 					if templ_7745c5c3_Err != nil { | ||||
| 						return templ_7745c5c3_Err | ||||
| 					} | ||||
| 					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\">") | ||||
| 					if templ_7745c5c3_Err != nil { | ||||
| 						return templ_7745c5c3_Err | ||||
| 					} | ||||
| 					var templ_7745c5c3_Var7 string | ||||
| 					templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(tz) | ||||
| 					if templ_7745c5c3_Err != nil { | ||||
| 						return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users_settings.templ`, Line: 16, Col: 32} | ||||
| 					} | ||||
| 					_, 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, 8, "</option>") | ||||
| 					if templ_7745c5c3_Err != nil { | ||||
| 						return templ_7745c5c3_Err | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</select> <input type=\"submit\" value=\"Submit\"></form></div>") | ||||
| 			if templ_7745c5c3_Err != nil { | ||||
| 				return templ_7745c5c3_Err | ||||
| 			} | ||||
| 			return nil | ||||
| 		}) | ||||
| 		templ_7745c5c3_Err = base("User Settings", data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) | ||||
| 		if templ_7745c5c3_Err != nil { | ||||
| 			return templ_7745c5c3_Err | ||||
| 		} | ||||
| 		return nil | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| var _ = templruntime.GeneratedTemplate | ||||
| @ -1,6 +1,6 @@ | ||||
| // Code generated by templ - DO NOT EDIT. | ||||
| 
 | ||||
| // templ: version: v0.3.833 | ||||
| // templ: version: v0.3.857 | ||||
| package views | ||||
| 
 | ||||
| //lint:file-ignore SA4006 This context is only used if a nested component is present. | ||||
| @ -400,7 +400,7 @@ func UserProfile(title string, data CommonData, user models.User) templ.Componen | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| func UserSettings() templ.Component { | ||||
| func UserSettings(title string, data CommonData, user models.User) templ.Component { | ||||
| 	return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { | ||||
| 		templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context | ||||
| 		if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { | ||||
| @ -421,6 +421,28 @@ func UserSettings() templ.Component { | ||||
| 			templ_7745c5c3_Var20 = templ.NopComponent | ||||
| 		} | ||||
| 		ctx = templ.ClearChildren(ctx) | ||||
| 		templ_7745c5c3_Var21 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { | ||||
| 			templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context | ||||
| 			templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) | ||||
| 			if !templ_7745c5c3_IsBuffer { | ||||
| 				defer func() { | ||||
| 					templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) | ||||
| 					if templ_7745c5c3_Err == nil { | ||||
| 						templ_7745c5c3_Err = templ_7745c5c3_BufErr | ||||
| 					} | ||||
| 				}() | ||||
| 			} | ||||
| 			ctx = templ.InitializeContext(ctx) | ||||
| 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<form hx-put=\"/users/settings\"><input type=\"text\"> <button type=\"submit\">Submit</button></form>") | ||||
| 			if templ_7745c5c3_Err != nil { | ||||
| 				return templ_7745c5c3_Err | ||||
| 			} | ||||
| 			return nil | ||||
| 		}) | ||||
| 		templ_7745c5c3_Err = base(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var21), templ_7745c5c3_Buffer) | ||||
| 		if templ_7745c5c3_Err != nil { | ||||
| 			return templ_7745c5c3_Err | ||||
| 		} | ||||
| 		return nil | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| @ -63,7 +63,7 @@ templ displayWebsites (websites []models.Website) { | ||||
| } | ||||
| 
 | ||||
| templ websiteCreateForm(csrfToken string, form forms.WebsiteCreateForm) { | ||||
|     <input type="hidden" name="csrf_token" value={csrfToken}> | ||||
| 	<input type="hidden" name="csrf_token" value={ csrfToken }/> | ||||
| 	<div> | ||||
| 		{{ err, exists := form.FieldErrors["sitename"] }} | ||||
| 		<label for="sitename">Site Name: </label> | ||||
| @ -103,9 +103,9 @@ templ WebsiteList(title string, data CommonData, websites []models.Website) { | ||||
| 	} else { | ||||
| 		@base(title, data) { | ||||
| 			<h1>My Websites</h1> | ||||
|             <div> | ||||
| 			<p> | ||||
| 				@WebsiteCreateButton() | ||||
|             </div> | ||||
| 			</p> | ||||
| 			<div> | ||||
| 				@displayWebsites(websites) | ||||
| 			</div> | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| // Code generated by templ - DO NOT EDIT. | ||||
| 
 | ||||
| // templ: version: v0.3.833 | ||||
| // templ: version: v0.3.857 | ||||
| package views | ||||
| 
 | ||||
| //lint:file-ignore SA4006 This context is only used if a nested component is present. | ||||
| @ -46,7 +46,7 @@ func wSidebar(website models.Website) templ.Component { | ||||
| 		var templ_7745c5c3_Var2 string | ||||
| 		templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(website.Name) | ||||
| 		if templ_7745c5c3_Err != nil { | ||||
| 			return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 16, Col: 30} | ||||
| 			return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 16, Col: 21} | ||||
| 		} | ||||
| 		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) | ||||
| 		if templ_7745c5c3_Err != nil { | ||||
| @ -189,7 +189,7 @@ func displayWebsites(websites []models.Website) templ.Component { | ||||
| 				var templ_7745c5c3_Var14 string | ||||
| 				templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(w.Name) | ||||
| 				if templ_7745c5c3_Err != nil { | ||||
| 					return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 58, Col: 73} | ||||
| 					return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 58, Col: 59} | ||||
| 				} | ||||
| 				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) | ||||
| 				if templ_7745c5c3_Err != nil { | ||||
| @ -237,7 +237,7 @@ func websiteCreateForm(csrfToken string, form forms.WebsiteCreateForm) templ.Com | ||||
| 		var templ_7745c5c3_Var16 string | ||||
| 		templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(csrfToken) | ||||
| 		if templ_7745c5c3_Err != nil { | ||||
| 			return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 66, Col: 59} | ||||
| 			return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 66, Col: 57} | ||||
| 		} | ||||
| 		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) | ||||
| 		if templ_7745c5c3_Err != nil { | ||||
| @ -260,7 +260,7 @@ func websiteCreateForm(csrfToken string, form forms.WebsiteCreateForm) templ.Com | ||||
| 			var templ_7745c5c3_Var17 string | ||||
| 			templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(err) | ||||
| 			if templ_7745c5c3_Err != nil { | ||||
| 				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 71, Col: 38} | ||||
| 				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 71, Col: 29} | ||||
| 			} | ||||
| 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) | ||||
| 			if templ_7745c5c3_Err != nil { | ||||
| @ -288,7 +288,7 @@ func websiteCreateForm(csrfToken string, form forms.WebsiteCreateForm) templ.Com | ||||
| 			var templ_7745c5c3_Var18 string | ||||
| 			templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(err) | ||||
| 			if templ_7745c5c3_Err != nil { | ||||
| 				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 79, Col: 38} | ||||
| 				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 79, Col: 29} | ||||
| 			} | ||||
| 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) | ||||
| 			if templ_7745c5c3_Err != nil { | ||||
| @ -316,7 +316,7 @@ func websiteCreateForm(csrfToken string, form forms.WebsiteCreateForm) templ.Com | ||||
| 			var templ_7745c5c3_Var19 string | ||||
| 			templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(err) | ||||
| 			if templ_7745c5c3_Err != nil { | ||||
| 				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 87, Col: 38} | ||||
| 				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 87, Col: 29} | ||||
| 			} | ||||
| 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) | ||||
| 			if templ_7745c5c3_Err != nil { | ||||
| @ -403,7 +403,7 @@ func WebsiteList(title string, data CommonData, websites []models.Website) templ | ||||
| 					}() | ||||
| 				} | ||||
| 				ctx = templ.InitializeContext(ctx) | ||||
| 				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<h1>My Websites</h1><div>") | ||||
| 				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<h1>My Websites</h1><p>") | ||||
| 				if templ_7745c5c3_Err != nil { | ||||
| 					return templ_7745c5c3_Err | ||||
| 				} | ||||
| @ -411,7 +411,7 @@ func WebsiteList(title string, data CommonData, websites []models.Website) templ | ||||
| 				if templ_7745c5c3_Err != nil { | ||||
| 					return templ_7745c5c3_Err | ||||
| 				} | ||||
| 				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "</div><div>") | ||||
| 				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "</p><div>") | ||||
| 				if templ_7745c5c3_Err != nil { | ||||
| 					return templ_7745c5c3_Err | ||||
| 				} | ||||
| @ -482,7 +482,7 @@ func WebsiteDashboard(title string, data CommonData, website models.Website) tem | ||||
| 			var templ_7745c5c3_Var25 string | ||||
| 			templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(website.Name) | ||||
| 			if templ_7745c5c3_Err != nil { | ||||
| 				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 121, Col: 34} | ||||
| 				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 121, Col: 22} | ||||
| 			} | ||||
| 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) | ||||
| 			if templ_7745c5c3_Err != nil { | ||||
| @ -550,7 +550,7 @@ func WebsiteDashboardComingSoon(title string, data CommonData, website models.We | ||||
| 			var templ_7745c5c3_Var28 string | ||||
| 			templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(website.Name) | ||||
| 			if templ_7745c5c3_Err != nil { | ||||
| 				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 135, Col: 34} | ||||
| 				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 135, Col: 22} | ||||
| 			} | ||||
| 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) | ||||
| 			if templ_7745c5c3_Err != nil { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user