Implement user settings #19

Open
yequari wants to merge 7 commits from feature-usersettings-db into dev
17 changed files with 889 additions and 231 deletions

View File

@ -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)
}

View File

@ -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(),
}
}

View File

@ -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
}

View File

@ -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))

View 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
View 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);

View File

@ -29,3 +29,8 @@ type WebsiteCreateForm struct {
AuthorName string `schema:"authorname"`
validator.Validator `schema:"-"`
}
type UserSettingsForm struct {
LocalTimezone string `schema:"timezones"`
validator.Validator `schema:"-"`
}

View File

@ -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")
)

View File

@ -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
View 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
}

View File

@ -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
}

View File

@ -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>
}
}
}

View File

@ -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 {

View File

@ -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>
}
}

View File

@ -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
})
}

View File

@ -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>
}
}

View File

@ -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 {