Implement user settings #19
@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.32bit.cafe/32bitcafe/guestbook/internal/forms"
|
||||
"git.32bit.cafe/32bitcafe/guestbook/internal/models"
|
||||
@ -39,7 +40,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 +131,39 @@ 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) {
|
||||
user := app.getCurrentUser(r)
|
||||
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() {
|
||||
// TODO: rerender template with errors
|
||||
app.clientError(w, http.StatusUnprocessableEntity)
|
||||
}
|
||||
user.Settings.LocalTimezone, err = time.LoadLocation(form.LocalTimezone)
|
||||
if err != nil {
|
||||
app.serverError(w, r, err)
|
||||
return
|
||||
}
|
||||
err = app.users.UpdateUserSettings(user.ID, user.Settings)
|
||||
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() {
|
||||
@ -56,10 +59,17 @@ func main() {
|
||||
sessionManager: sessionManager,
|
||||
websites: &models.WebsiteModel{DB: db},
|
||||
guestbooks: &models.GuestbookModel{DB: db},
|
||||
users: &models.UserModel{DB: db},
|
||||
users: &models.UserModel{DB: db, Settings: make(map[string]models.Setting)},
|
||||
guestbookComments: &models.GuestbookCommentModel{DB: db},
|
||||
formDecoder: formDecoder,
|
||||
debug: *debug,
|
||||
timezones: getAvailableTimezones(),
|
||||
}
|
||||
|
||||
err = app.users.InitializeSettingsMap()
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
@ -97,3 +107,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,
|
||||
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,
|
||||
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:"-"`
|
||||
}
|
||||
|
@ -3,9 +3,11 @@ package models
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrNoRecord = errors.New("models: no matching record found")
|
||||
ErrNoRecord = errors.New("models: no matching record found")
|
||||
|
||||
ErrInvalidCredentials = errors.New("models: invalid credentials")
|
||||
ErrInvalidCredentials = errors.New("models: invalid credentials")
|
||||
|
||||
ErrDuplicateEmail = errors.New("models: duplicate email")
|
||||
ErrDuplicateEmail = errors.New("models: duplicate email")
|
||||
|
||||
ErrInvalidSettingValue = errors.New("models: invalid setting value")
|
||||
)
|
||||
|
@ -5,6 +5,14 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type GuestbookSettings struct {
|
||||
IsCommentingEnabled bool
|
||||
ReenableCommenting time.Time
|
||||
IsVisible bool
|
||||
FilteredWords []string
|
||||
AllowRemoteHostAccess bool
|
||||
}
|
||||
|
||||
type Guestbook struct {
|
||||
ID int64
|
||||
ShortId uint64
|
||||
@ -13,6 +21,7 @@ type Guestbook struct {
|
||||
Created time.Time
|
||||
Deleted time.Time
|
||||
IsActive bool
|
||||
Settings GuestbookSettings
|
||||
}
|
||||
|
||||
type GuestbookModel struct {
|
||||
@ -70,3 +79,47 @@ func (m *GuestbookModel) GetAll(userId int64) ([]Guestbook, error) {
|
||||
}
|
||||
return guestbooks, nil
|
||||
}
|
||||
|
||||
func (m *GuestbookModel) initializeGuestbookSettings(guestbookId int64, settings UserSettings) error {
|
||||
stmt := `INSERT INTO guestbook_settings (GuestbookId, SettingId, AllowedSettingValueId, UnconstrainedValue) VALUES
|
||||
(?, ?, ?, ?),
|
||||
(?, ?, ?, ?),
|
||||
(?, ?, ?, ?),
|
||||
(?, ?, ?, ?),
|
||||
(?, ?, ?, ?)`
|
||||
_ = len(stmt)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *GuestbookModel) UpdateSetting(guestbookId int64, value string) {
|
||||
stmt := `UPDATE guestbook_settings SET
|
||||
AllowedSettingValueId=IFNULL((SELECT Id FROM allowed_setting_values WHERE SettingId = guestbook_settings.SettingId AND ItemValue = ?), AllowedSettingValueId),
|
||||
UnconstrainedValue=(SELECT ? FROM settings WHERE settings.Id = guestbook_settings.SettingId AND settings.Constrained=0)
|
||||
WHERE GuestbookId = ?
|
||||
AND SettingId = (SELECT Id from Settings WHERE Description=?);`
|
||||
_ = len(stmt)
|
||||
}
|
||||
|
||||
func (m *GuestbookModel) SetCommentingEnabled(guestbookId int64, enabled bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *GuestbookModel) SetReenableCommentingDate(guestbookId int64, reenableTime time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *GuestbookModel) SetVisible(guestbookId int64, visible bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *GuestbookModel) AddFilteredWord(guestbookId int64, word string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *GuestbookModel) RemoveFilteredWord(guestbookId int64, word string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *GuestbookModel) SetRemoteHostAccess(guestbookId int64, allowed bool) error {
|
||||
return nil
|
||||
}
|
||||
|
154
internal/models/settings.go
Normal file
154
internal/models/settings.go
Normal file
@ -0,0 +1,154 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SettingGroup struct {
|
||||
id int
|
||||
description string
|
||||
}
|
||||
|
||||
func (g *SettingGroup) Id() int {
|
||||
return g.id
|
||||
}
|
||||
|
||||
func (g *SettingGroup) Description() string {
|
||||
return g.description
|
||||
}
|
||||
|
||||
const (
|
||||
SETTING_GROUP_USER = "user"
|
||||
SETTING_GROUP_GUESTBOOK = "guestbook"
|
||||
)
|
||||
|
||||
type SettingDataType struct {
|
||||
id int
|
||||
description string
|
||||
}
|
||||
|
||||
func (d *SettingDataType) Id() int {
|
||||
return d.id
|
||||
}
|
||||
|
||||
func (d *SettingDataType) Description() string {
|
||||
return d.description
|
||||
}
|
||||
|
||||
const (
|
||||
SETTING_TYPE_INTEGER = "integer"
|
||||
SETTING_TYPE_STRING = "alphanumeric"
|
||||
SETTING_TYPE_DATE = "datetime"
|
||||
)
|
||||
|
||||
type Setting struct {
|
||||
id int
|
||||
description string
|
||||
constrained bool
|
||||
dataType SettingDataType
|
||||
settingGroup SettingGroup
|
||||
minValue string
|
||||
maxValue string
|
||||
}
|
||||
|
||||
func (s *Setting) Id() int {
|
||||
return s.id
|
||||
}
|
||||
|
||||
func (s *Setting) Description() string {
|
||||
return s.description
|
||||
}
|
||||
|
||||
func (s *Setting) Constrained() bool {
|
||||
return s.constrained
|
||||
}
|
||||
|
||||
func (s *Setting) DataType() SettingDataType {
|
||||
return s.dataType
|
||||
}
|
||||
|
||||
func (s *Setting) SettingGroup() SettingGroup {
|
||||
return s.settingGroup
|
||||
}
|
||||
|
||||
func (s *Setting) MinValue() string {
|
||||
return s.minValue
|
||||
}
|
||||
|
||||
func (s *Setting) MaxValue() string {
|
||||
return s.maxValue
|
||||
}
|
||||
|
||||
func (s *Setting) Validate(value string) bool {
|
||||
switch s.dataType.description {
|
||||
case SETTING_TYPE_INTEGER:
|
||||
return s.validateInt(value)
|
||||
case SETTING_TYPE_STRING:
|
||||
return s.validateAlphanum(value)
|
||||
case SETTING_TYPE_DATE:
|
||||
return s.validateDatetime(value)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Setting) validateInt(value string) bool {
|
||||
v, err := strconv.ParseInt(value, 10, 0)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
var min int64
|
||||
var max int64
|
||||
if len(s.minValue) > 0 {
|
||||
min, err = strconv.ParseInt(s.minValue, 10, 0)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if v < min {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if len(s.maxValue) > 0 {
|
||||
max, err = strconv.ParseInt(s.maxValue, 10, 0)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if v < max {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *Setting) validateDatetime(value string) bool {
|
||||
v, err := time.Parse(time.DateTime, value)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
var min time.Time
|
||||
var max time.Time
|
||||
|
||||
if len(s.minValue) > 0 {
|
||||
min, err = time.Parse(time.DateTime, s.minValue)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if v.Before(min) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if len(s.maxValue) > 0 {
|
||||
max, err = time.Parse(time.DateTime, s.maxValue)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if v.After(max) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Setting) validateAlphanum(value string) bool {
|
||||
return len(value) >= 0
|
||||
}
|
@ -10,6 +10,14 @@ import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type UserSettings struct {
|
||||
LocalTimezone *time.Location
|
||||
}
|
||||
|
||||
const (
|
||||
SettingUserTimezone = "local_timezone"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID int64
|
||||
ShortId uint64
|
||||
@ -19,20 +27,54 @@ type User struct {
|
||||
IsBanned bool
|
||||
HashedPassword []byte
|
||||
Created time.Time
|
||||
Settings UserSettings
|
||||
}
|
||||
|
||||
type UserModel struct {
|
||||
DB *sql.DB
|
||||
DB *sql.DB
|
||||
Settings map[string]Setting
|
||||
}
|
||||
|
||||
func (m *UserModel) Insert(shortId uint64, username string, email string, password string) error {
|
||||
func (m *UserModel) InitializeSettingsMap() error {
|
||||
if m.Settings == nil {
|
||||
m.Settings = make(map[string]Setting)
|
||||
}
|
||||
stmt := `SELECT settings.Id, settings.Description, Constrained, d.Id, d.Description, g.Id, g.Description, MinValue, MaxValue
|
||||
FROM settings
|
||||
LEFT JOIN setting_data_types d ON settings.DataType = d.Id
|
||||
LEFT JOIN setting_groups g ON settings.SettingGroup = g.Id
|
||||
WHERE SettingGroup = (SELECT Id FROM setting_groups WHERE Description = 'user' LIMIT 1)`
|
||||
result, err := m.DB.Query(stmt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for result.Next() {
|
||||
var s Setting
|
||||
var mn sql.NullString
|
||||
var mx sql.NullString
|
||||
err := result.Scan(&s.id, &s.description, &s.constrained, &s.dataType.id, &s.dataType.description, &s.settingGroup.id, &s.settingGroup.description, &mn, &mx)
|
||||
if mn.Valid {
|
||||
s.minValue = mn.String
|
||||
}
|
||||
if mx.Valid {
|
||||
s.maxValue = mx.String
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.Settings[s.description] = s
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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 +83,14 @@ func (m *UserModel) Insert(shortId uint64, username string, email string, passwo
|
||||
}
|
||||
return err
|
||||
}
|
||||
id, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = m.initializeUserSettings(id, settings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -55,6 +105,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 +124,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 +185,75 @@ 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 m.Settings[SettingUserTimezone].id:
|
||||
settings.LocalTimezone, err = time.LoadLocation(unconstrainedValue.String)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return settings, err
|
||||
}
|
||||
|
||||
func (m *UserModel) initializeUserSettings(userId int64, settings UserSettings) error {
|
||||
stmt := `INSERT INTO user_settings (UserId, SettingId, AllowedSettingValueId, UnconstrainedValue)
|
||||
VALUES (?, ?, ?, ?)`
|
||||
_, err := m.DB.Exec(stmt, userId, m.Settings[SettingUserTimezone].id, nil, settings.LocalTimezone.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *UserModel) UpdateUserSettings(userId int64, settings UserSettings) error {
|
||||
err := m.UpdateSetting(userId, m.Settings[SettingUserTimezone], settings.LocalTimezone.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *UserModel) UpdateSetting(userId int64, setting Setting, value string) error {
|
||||
valid := setting.Validate(value)
|
||||
if !valid {
|
||||
return ErrInvalidSettingValue
|
||||
}
|
||||
stmt := `UPDATE user_settings SET
|
||||
AllowedSettingValueId=IFNULL(
|
||||
(SELECT Id FROM allowed_setting_values WHERE SettingId = user_settings.SettingId AND ItemValue = ?), AllowedSettingValueId
|
||||
),
|
||||
UnconstrainedValue=(SELECT ? FROM settings WHERE settings.Id = user_settings.SettingId AND settings.Constrained=0)
|
||||
WHERE userId = ?
|
||||
AND SettingId = (SELECT Id from Settings WHERE Description=?);`
|
||||
result, err := m.DB.Exec(stmt, value, value, userId, setting.description)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rows, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rows != 1 {
|
||||
return ErrInvalidSettingValue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -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>
|
||||
@ -167,4 +167,4 @@ templ AllGuestbooksView(data CommonData, websites []models.Website) {
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -5,79 +5,99 @@ import (
|
||||
"git.32bit.cafe/32bitcafe/guestbook/internal/models"
|
||||
)
|
||||
|
||||
templ UserLogin (title string, data CommonData, form forms.UserLoginForm) {
|
||||
@base(title, data) {
|
||||
<h1>Login</h1>
|
||||
<form action="/users/login" method="POST" novalidate>
|
||||
<input type="hidden" name="csrf_token" value={ data.CSRFToken }>
|
||||
for _, error := range form.NonFieldErrors {
|
||||
<div class="error">{ error }</div>
|
||||
}
|
||||
<div>
|
||||
<label>Email: </label>
|
||||
{{ error, exists := form.FieldErrors["email"] }}
|
||||
if exists {
|
||||
<label class="error">{ error }</label>
|
||||
}
|
||||
<input type="email" name="email" value={form.Email}>
|
||||
</div>
|
||||
<div>
|
||||
<label>Password: </label>
|
||||
{{ error, exists = form.FieldErrors["password"] }}
|
||||
if exists {
|
||||
<label class="error">{ error }</label>
|
||||
}
|
||||
<input type="password" name="password">
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" value="login">
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
templ UserLogin(title string, data CommonData, form forms.UserLoginForm) {
|
||||
@base(title, data) {
|
||||
<h1>Login</h1>
|
||||
<form action="/users/login" method="POST" novalidate>
|
||||
<input type="hidden" name="csrf_token" value={ data.CSRFToken }/>
|
||||
for _, error := range form.NonFieldErrors {
|
||||
<div class="error">{ error }</div>
|
||||
}
|
||||
<div>
|
||||
<label>Email: </label>
|
||||
{{ error, exists := form.FieldErrors["email"] }}
|
||||
if exists {
|
||||
<label class="error">{ error }</label>
|
||||
}
|
||||
<input type="email" name="email" value={ form.Email }/>
|
||||
</div>
|
||||
<div>
|
||||
<label>Password: </label>
|
||||
{{ error, exists = form.FieldErrors["password"] }}
|
||||
if exists {
|
||||
<label class="error">{ error }</label>
|
||||
}
|
||||
<input type="password" name="password"/>
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" value="login"/>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
|
||||
templ UserRegistration (title string, data CommonData, form forms.UserRegistrationForm) {
|
||||
@base(title, data) {
|
||||
<h1>User Registration</h1>
|
||||
<form action="/users/register" method="post">
|
||||
<input type="hidden" name="csrf_token" value={ data.CSRFToken }>
|
||||
<div>
|
||||
{{ error, exists := form.FieldErrors["name"] }}
|
||||
<label for="username">Username: </label>
|
||||
if exists {
|
||||
<label class="error">{ error }</label>
|
||||
}
|
||||
<input type="text" name="username" id="username" value={form.Name} required />
|
||||
</div>
|
||||
<div>
|
||||
{{ error, exists = form.FieldErrors["email"] }}
|
||||
<label for="email">Email: </label>
|
||||
if exists {
|
||||
<label class="error">{ error }</label>
|
||||
}
|
||||
<input type="text" name="email" id="email" value={form.Email} required />
|
||||
</div>
|
||||
<div>
|
||||
{{ error, exists = form.FieldErrors["password"] }}
|
||||
<label for="password">Password: </label>
|
||||
if exists {
|
||||
<label class="error">{ error }</label>
|
||||
}
|
||||
<input type="password" name="password" id="password" />
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" value="Register" />
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
templ UserRegistration(title string, data CommonData, form forms.UserRegistrationForm) {
|
||||
@base(title, data) {
|
||||
<h1>User Registration</h1>
|
||||
<form action="/users/register" method="post">
|
||||
<input type="hidden" name="csrf_token" value={ data.CSRFToken }/>
|
||||
<div>
|
||||
{{ error, exists := form.FieldErrors["name"] }}
|
||||
<label for="username">Username: </label>
|
||||
if exists {
|
||||
<label class="error">{ error }</label>
|
||||
}
|
||||
<input type="text" name="username" id="username" value={ form.Name } required/>
|
||||
</div>
|
||||
<div>
|
||||
{{ error, exists = form.FieldErrors["email"] }}
|
||||
<label for="email">Email: </label>
|
||||
if exists {
|
||||
<label class="error">{ error }</label>
|
||||
}
|
||||
<input type="text" name="email" id="email" value={ form.Email } required/>
|
||||
</div>
|
||||
<div>
|
||||
{{ error, exists = form.FieldErrors["password"] }}
|
||||
<label for="password">Password: </label>
|
||||
if exists {
|
||||
<label class="error">{ error }</label>
|
||||
}
|
||||
<input type="password" name="password" id="password"/>
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" value="Register"/>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
|
||||
templ UserProfile (title string, data CommonData, user models.User) {
|
||||
@base(title, data) {
|
||||
<h1>{ user.Username }</h1>
|
||||
<p>{ user.Email }</p>
|
||||
}
|
||||
templ UserProfile(title string, data CommonData, user models.User) {
|
||||
@base(title, data) {
|
||||
<h1>{ user.Username }</h1>
|
||||
<p>{ user.Email }</p>
|
||||
}
|
||||
}
|
||||
|
||||
templ UserSettings () {
|
||||
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 }/>
|
||||
<label>Local Timezone</label>
|
||||
<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>
|
||||
}
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ func UserLogin(title string, data CommonData, form forms.UserLoginForm) templ.Co
|
||||
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.templ`, Line: 12, Col: 73}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 12, Col: 64}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@ -71,7 +71,7 @@ func UserLogin(title string, data CommonData, form forms.UserLoginForm) templ.Co
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(error)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 14, Col: 42}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 14, Col: 30}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@ -95,7 +95,7 @@ func UserLogin(title string, data CommonData, form forms.UserLoginForm) templ.Co
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(error)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 20, Col: 48}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 20, Col: 33}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@ -113,7 +113,7 @@ func UserLogin(title string, data CommonData, form forms.UserLoginForm) templ.Co
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(form.Email)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 22, Col: 66}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 22, Col: 55}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@ -132,7 +132,7 @@ func UserLogin(title string, data CommonData, form forms.UserLoginForm) templ.Co
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(error)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 28, Col: 48}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 28, Col: 33}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@ -197,7 +197,7 @@ func UserRegistration(title string, data CommonData, form forms.UserRegistration
|
||||
var templ_7745c5c3_Var10 string
|
||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(data.CSRFToken)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 43, Col: 73}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 43, Col: 64}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@ -220,7 +220,7 @@ func UserRegistration(title string, data CommonData, form forms.UserRegistration
|
||||
var templ_7745c5c3_Var11 string
|
||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(error)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 48, Col: 48}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 48, Col: 33}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@ -238,7 +238,7 @@ func UserRegistration(title string, data CommonData, form forms.UserRegistration
|
||||
var templ_7745c5c3_Var12 string
|
||||
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(form.Name)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 50, Col: 81}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 50, Col: 70}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@ -261,7 +261,7 @@ func UserRegistration(title string, data CommonData, form forms.UserRegistration
|
||||
var templ_7745c5c3_Var13 string
|
||||
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(error)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 56, Col: 48}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 56, Col: 33}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@ -279,7 +279,7 @@ func UserRegistration(title string, data CommonData, form forms.UserRegistration
|
||||
var templ_7745c5c3_Var14 string
|
||||
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(form.Email)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 58, Col: 76}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 58, Col: 65}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@ -302,7 +302,7 @@ func UserRegistration(title string, data CommonData, form forms.UserRegistration
|
||||
var templ_7745c5c3_Var15 string
|
||||
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(error)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 64, Col: 48}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 64, Col: 33}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@ -367,7 +367,7 @@ func UserProfile(title string, data CommonData, user models.User) templ.Componen
|
||||
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: 77, Col: 27}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 77, Col: 21}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@ -380,7 +380,7 @@ func UserProfile(title string, data CommonData, user models.User) templ.Componen
|
||||
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: 78, Col: 23}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 78, Col: 17}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@ -400,7 +400,7 @@ func UserProfile(title string, data CommonData, user models.User) templ.Componen
|
||||
})
|
||||
}
|
||||
|
||||
func UserSettings() templ.Component {
|
||||
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 {
|
||||
@ -421,6 +421,111 @@ func UserSettings() templ.Component {
|
||||
templ_7745c5c3_Var20 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
user := data.CurrentUser
|
||||
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, "<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_Var22 string
|
||||
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(data.CSRFToken)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 88, Col: 65}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "\"> <label>Local Timezone</label> <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, 34, "<option value=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var23 string
|
||||
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(tz)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 93, Col: 25}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "\" selected=\"true\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var24 string
|
||||
templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(tz)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 93, Col: 48}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "</option>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "<option value=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var25 string
|
||||
templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(tz)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 95, Col: 25}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var26 string
|
||||
templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(tz)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 95, Col: 32}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "</option>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "</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_Var21), templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
@ -4,151 +4,151 @@ import "fmt"
|
||||
import "git.32bit.cafe/32bitcafe/guestbook/internal/models"
|
||||
import "git.32bit.cafe/32bitcafe/guestbook/internal/forms"
|
||||
|
||||
func wUrl (w models.Website) string {
|
||||
return fmt.Sprintf("/websites/%s", shortIdToSlug(w.ShortId))
|
||||
func wUrl(w models.Website) string {
|
||||
return fmt.Sprintf("/websites/%s", shortIdToSlug(w.ShortId))
|
||||
}
|
||||
|
||||
templ wSidebar(website models.Website) {
|
||||
{{ dashUrl := wUrl(website) + "/dashboard" }}
|
||||
{{ gbUrl := wUrl(website) + "/guestbook" }}
|
||||
<nav>
|
||||
<div>
|
||||
<h2>{ website.Name}</h2>
|
||||
<ul>
|
||||
<li><a href={ templ.URL(dashUrl) }>Dashboard</a></li>
|
||||
<li><a href={ templ.URL(externalUrl(website.SiteUrl)) } target="_blank">View Website</a></li>
|
||||
</ul>
|
||||
<h3>Guestbook</h3>
|
||||
<ul>
|
||||
<li><a href={ templ.URL(gbUrl) } target="_blank">View Guestbook</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><a href={ templ.URL(dashUrl + "/guestbook/comments") }>Manage messages</a></li>
|
||||
<li><a href={ templ.URL(dashUrl + "/guestbook/comments/queue") }>Review message queue</a></li>
|
||||
<li><a href={ templ.URL(dashUrl + "/guestbook/blocklist") }>Block users</a></li>
|
||||
<li><a href={ templ.URL(dashUrl + "/guestbook/comments/trash") }>Trash</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><a href={ templ.URL(dashUrl + "/guestbook/themes") }>Themes</a></li>
|
||||
<li><a href={ templ.URL(dashUrl + "/guestbook/customize") }>Custom CSS</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Feeds</h3>
|
||||
<p>Coming Soon</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Account</h3>
|
||||
<ul>
|
||||
<li><a href="/users/settings">Settings</a></li>
|
||||
<li><a href="/users/privacy">Privacy</a></li>
|
||||
<li><a href="/help">Help</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
{{ dashUrl := wUrl(website) + "/dashboard" }}
|
||||
{{ gbUrl := wUrl(website) + "/guestbook" }}
|
||||
<nav>
|
||||
<div>
|
||||
<h2>{ website.Name }</h2>
|
||||
<ul>
|
||||
<li><a href={ templ.URL(dashUrl) }>Dashboard</a></li>
|
||||
<li><a href={ templ.URL(externalUrl(website.SiteUrl)) } target="_blank">View Website</a></li>
|
||||
</ul>
|
||||
<h3>Guestbook</h3>
|
||||
<ul>
|
||||
<li><a href={ templ.URL(gbUrl) } target="_blank">View Guestbook</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><a href={ templ.URL(dashUrl + "/guestbook/comments") }>Manage messages</a></li>
|
||||
<li><a href={ templ.URL(dashUrl + "/guestbook/comments/queue") }>Review message queue</a></li>
|
||||
<li><a href={ templ.URL(dashUrl + "/guestbook/blocklist") }>Block users</a></li>
|
||||
<li><a href={ templ.URL(dashUrl + "/guestbook/comments/trash") }>Trash</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><a href={ templ.URL(dashUrl + "/guestbook/themes") }>Themes</a></li>
|
||||
<li><a href={ templ.URL(dashUrl + "/guestbook/customize") }>Custom CSS</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Feeds</h3>
|
||||
<p>Coming Soon</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Account</h3>
|
||||
<ul>
|
||||
<li><a href="/users/settings">Settings</a></li>
|
||||
<li><a href="/users/privacy">Privacy</a></li>
|
||||
<li><a href="/help">Help</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
}
|
||||
|
||||
templ displayWebsites (websites []models.Website) {
|
||||
if len(websites) == 0 {
|
||||
<p>No Websites yet. <a href="">Register a website.</a></p>
|
||||
} else {
|
||||
<ul id="websites" hx-get="/websites" hx-trigger="newWebsite from:body" hx-swap="outerHTML">
|
||||
for _, w := range websites {
|
||||
<li>
|
||||
<a href={ templ.URL(wUrl(w) + "/dashboard")}>{ w.Name }</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
templ displayWebsites(websites []models.Website) {
|
||||
if len(websites) == 0 {
|
||||
<p>No Websites yet. <a href="">Register a website.</a></p>
|
||||
} else {
|
||||
<ul id="websites" hx-get="/websites" hx-trigger="newWebsite from:body" hx-swap="outerHTML">
|
||||
for _, w := range websites {
|
||||
<li>
|
||||
<a href={ templ.URL(wUrl(w) + "/dashboard") }>{ w.Name }</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
}
|
||||
|
||||
templ websiteCreateForm(csrfToken string, form forms.WebsiteCreateForm) {
|
||||
<input type="hidden" name="csrf_token" value={csrfToken}>
|
||||
<div>
|
||||
{{ err, exists := form.FieldErrors["sitename"]}}
|
||||
<label for="sitename">Site Name: </label>
|
||||
if exists {
|
||||
<label class="error">{ err }</label>
|
||||
}
|
||||
<input type="text" name="sitename" id="sitename" required />
|
||||
</div>
|
||||
<div>
|
||||
{{ err, exists = form.FieldErrors["siteurl"] }}
|
||||
<label for="siteurl">Site URL: </label>
|
||||
if exists {
|
||||
<label class="error">{ err }</label>
|
||||
}
|
||||
<input type="text" name="siteurl" id="siteurl" required />
|
||||
</div>
|
||||
<div>
|
||||
{{ err, exists = form.FieldErrors["authorname"] }}
|
||||
<label for="authorname">Site Author: </label>
|
||||
if exists {
|
||||
<label class="error">{ err }</label>
|
||||
}
|
||||
<input type="text" name="authorname" id="authorname" required />
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit">Submit</button>
|
||||
</div>
|
||||
<input type="hidden" name="csrf_token" value={ csrfToken }/>
|
||||
<div>
|
||||
{{ err, exists := form.FieldErrors["sitename"] }}
|
||||
<label for="sitename">Site Name: </label>
|
||||
if exists {
|
||||
<label class="error">{ err }</label>
|
||||
}
|
||||
<input type="text" name="sitename" id="sitename" required/>
|
||||
</div>
|
||||
<div>
|
||||
{{ err, exists = form.FieldErrors["siteurl"] }}
|
||||
<label for="siteurl">Site URL: </label>
|
||||
if exists {
|
||||
<label class="error">{ err }</label>
|
||||
}
|
||||
<input type="text" name="siteurl" id="siteurl" required/>
|
||||
</div>
|
||||
<div>
|
||||
{{ err, exists = form.FieldErrors["authorname"] }}
|
||||
<label for="authorname">Site Author: </label>
|
||||
if exists {
|
||||
<label class="error">{ err }</label>
|
||||
}
|
||||
<input type="text" name="authorname" id="authorname" required/>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit">Submit</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
templ WebsiteCreateButton() {
|
||||
<button hx-get="/websites/create" hx-target="closest div">Add Website</button>
|
||||
<button hx-get="/websites/create" hx-target="closest div">Add Website</button>
|
||||
}
|
||||
|
||||
templ WebsiteList(title string, data CommonData, websites []models.Website) {
|
||||
if data.IsHtmx {
|
||||
@displayWebsites(websites)
|
||||
} else {
|
||||
@base(title, data) {
|
||||
<h1>My Websites</h1>
|
||||
<div>
|
||||
@WebsiteCreateButton()
|
||||
</div>
|
||||
<div>
|
||||
@displayWebsites(websites)
|
||||
</div>
|
||||
}
|
||||
}
|
||||
if data.IsHtmx {
|
||||
@displayWebsites(websites)
|
||||
} else {
|
||||
@base(title, data) {
|
||||
<h1>My Websites</h1>
|
||||
<p>
|
||||
@WebsiteCreateButton()
|
||||
</p>
|
||||
<div>
|
||||
@displayWebsites(websites)
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
templ WebsiteDashboard(title string, data CommonData, website models.Website) {
|
||||
@base(title, data) {
|
||||
<div id="dashboard">
|
||||
@wSidebar(website)
|
||||
<div>
|
||||
<h1>{ website.Name }</h1>
|
||||
<p>
|
||||
Stats and stuff will go here.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@base(title, data) {
|
||||
<div id="dashboard">
|
||||
@wSidebar(website)
|
||||
<div>
|
||||
<h1>{ website.Name }</h1>
|
||||
<p>
|
||||
Stats and stuff will go here.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
templ WebsiteDashboardComingSoon(title string, data CommonData, website models.Website) {
|
||||
@base(title, data) {
|
||||
<div id="dashboard">
|
||||
@wSidebar(website)
|
||||
<div>
|
||||
<h1>{ website.Name }</h1>
|
||||
<p>
|
||||
Coming Soon
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@base(title, data) {
|
||||
<div id="dashboard">
|
||||
@wSidebar(website)
|
||||
<div>
|
||||
<h1>{ website.Name }</h1>
|
||||
<p>
|
||||
Coming Soon
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
templ WebsiteCreate(title string, data CommonData, form forms.WebsiteCreateForm) {
|
||||
if data.IsHtmx {
|
||||
<form hx-post="/websites/create" hx-target="closest div">
|
||||
@websiteCreateForm(data.CSRFToken, form)
|
||||
</form>
|
||||
} else {
|
||||
<form action="/websites/create" method="post">
|
||||
@websiteCreateForm(data.CSRFToken, form)
|
||||
</form>
|
||||
}
|
||||
}
|
||||
if data.IsHtmx {
|
||||
<form hx-post="/websites/create" hx-target="closest div">
|
||||
@websiteCreateForm(data.CSRFToken, form)
|
||||
</form>
|
||||
} else {
|
||||
<form action="/websites/create" method="post">
|
||||
@websiteCreateForm(data.CSRFToken, form)
|
||||
</form>
|
||||
}
|
||||
}
|
||||
|
@ -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