add edit page, action buttons
Marco Andronaco andronacomarco@gmail.com
Thu, 17 Oct 2024 23:54:44 +0200
13 files changed,
187 insertions(+),
23 deletions(-)
jump to
M
src/app/functions.go
→
src/app/functions.go
@@ -8,6 +8,7 @@ "log"
"net/http" "os" "regexp" + "strconv" "strings" "time"@@ -15,6 +16,7 @@ "github.com/birabittoh/auth-boilerplate/src/email"
) type HabitDisplay struct { + ID uint Class string Name string LastAck string@@ -141,13 +143,42 @@ }
func getLoggedUser(r *http.Request) (user User, ok bool) { userID, ok := r.Context().Value(userContextKey).(uint) - db.Find(&user, userID) + if !ok { + return + } + + if db.Find(&user, userID).Error != nil { + ok = true + } + return user, ok } -func formatDuration(d time.Duration) string { - // TODO: 48h1m13s --> 2.01 days - return d.String() +func formatDuration(t time.Time) string { + days := int(time.Since(t).Hours()) / 24 + + switch { + case days == 0: + return "Today" + case days == 1: + return "Yesterday" + case days <= 7: + return fmt.Sprintf("%d day(s) ago", days) + case days <= 30: + weeks := days / 7 + remainingDays := days % 7 + if remainingDays == 0 { + return fmt.Sprintf("%d week(s) ago", weeks) + } + return fmt.Sprintf("%d wee(k), %d day(s) ago", weeks, remainingDays) + default: + months := days / 30 + remainingDays := days % 30 + if remainingDays == 0 { + return fmt.Sprintf("%d month(s) ago", months) + } + return fmt.Sprintf("%d month(s), %d day(s) ago", months, remainingDays) + } } func toHabitDisplay(habit Habit) HabitDisplay {@@ -155,9 +186,11 @@ var lastAck string
if habit.LastAck == nil { lastAck = "-" } else { - lastAck = formatDuration(time.Since(*habit.LastAck)) + lastAck = formatDuration(*habit.LastAck) } + return HabitDisplay{ + ID: habit.ID, Name: habit.Name, LastAck: lastAck, Disabled: habit.Disabled,@@ -165,6 +198,39 @@ Class: classGood,
} } +func getHabit(id uint) (habit Habit, err error) { + err = db.Model(&Habit{}).Find(&habit, id).Error + return +} + +func getHabitHelper(w http.ResponseWriter, r *http.Request) (habit Habit, err error) { + id := getID(r) + if id == 0 { + err = errors.New("no id") + http.Error(w, "bad request", http.StatusBadRequest) + return + } + + user, ok := getLoggedUser(r) + if !ok { + err = errors.New("no logged user") + http.Error(w, "unauthorized", http.StatusUnauthorized) + return + } + + habit, err = getHabit(id) + if err != nil { + http.Error(w, "not found", http.StatusNotFound) + return + } + + if habit.UserID != user.ID { + err = errors.New("forbidden") + http.Error(w, "forbidden", http.StatusForbidden) + } + return +} + func getAllHabits(userID uint) (positives []HabitDisplay, negatives []HabitDisplay, err error) { var habits []Habit err = db.Model(&Habit{}).Where(&Habit{UserID: userID}).Find(&habits).Error@@ -182,3 +248,11 @@ }
} return } + +func getID(r *http.Request) uint { + res, err := strconv.ParseUint(r.PathValue("id"), 10, 64) + if err != nil { + return 0 + } + return uint(res) +}
M
src/app/handlers.go
→
src/app/handlers.go
@@ -32,6 +32,15 @@
xt.ExecuteTemplate(w, "habits.tmpl", data) } +func getHabitsIDHandler(w http.ResponseWriter, r *http.Request) { + habit, err := getHabitHelper(w, r) + if err != nil { + return + } + + xt.ExecuteTemplate(w, "habits-id.tmpl", habit) +} + func getNewPositiveHandler(w http.ResponseWriter, r *http.Request) { data := map[string]interface{}{"Negative": false} xt.ExecuteTemplate(w, "new.tmpl", data)@@ -71,6 +80,28 @@ Name: name,
Days: days, Negative: negative, }) + + http.Redirect(w, r, "/habits", http.StatusFound) +} + +func postAckIDHandler(w http.ResponseWriter, r *http.Request) { + habit, err := getHabitHelper(w, r) + if err != nil { + return + } + + if habit.LastAck != nil { + if time.Since(*habit.LastAck) < 6*time.Hour { + http.Redirect(w, r, "/habits", http.StatusFound) // TODO: redirect to an error page instead + return + } + } + + db.Create(Ack{HabitID: habit.ID}) + + now := time.Now() + habit.LastAck = &now + db.Save(&habit) http.Redirect(w, r, "/habits", http.StatusFound) }
M
src/app/init.go
→
src/app/init.go
@@ -118,9 +118,11 @@
// App http.HandleFunc("GET /", getIndexHandler) http.HandleFunc("GET /habits", loginRequired(getHabitsHandler)) + http.HandleFunc("GET /habits/{id}", loginRequired(getHabitsIDHandler)) http.HandleFunc("GET /new/positive", loginRequired(getNewPositiveHandler)) http.HandleFunc("GET /new/negative", loginRequired(getNewNegativeHandler)) http.HandleFunc("POST /new", loginRequired(postNewHandler)) + http.HandleFunc("POST /ack/{id}", loginRequired(postAckIDHandler)) // Auth http.HandleFunc("GET /register", getRegisterHandler)
M
static/style.css
→
static/style.css
@@ -18,3 +18,14 @@ grid-template-columns: auto auto;
justify-content: space-between; align-items: center } + +.actions { + display: grid; + grid-template-columns: auto auto; + justify-items: center; + justify-content: center; +} + +.actions > form { + padding-inline: 5px; +}
M
templates/auth-login.tmpl
→
templates/auth-login.tmpl
@@ -1,6 +1,6 @@
{{ extends "auth.tmpl" }} -{{define "title" -}}Login{{end}} +{{define "title" -}}Login - {{end}} {{define "auth" -}} <h1>Login</h1>
M
templates/auth-new_password.tmpl
→
templates/auth-new_password.tmpl
@@ -1,6 +1,6 @@
{{ extends "auth.tmpl" }} -{{define "title" -}}Reset password{{end}} +{{define "title" -}}Reset password - {{end}} {{define "auth" -}} <h1>Reset password</h1>
M
templates/auth-register.tmpl
→
templates/auth-register.tmpl
@@ -1,6 +1,6 @@
{{ extends "auth.tmpl" }} -{{define "title" -}}Sign up{{end}} +{{define "title" -}}Sign up - {{end}} {{define "auth" -}} <h1>Sign up</h1>
M
templates/auth-reset_password.tmpl
→
templates/auth-reset_password.tmpl
@@ -1,6 +1,6 @@
{{ extends "auth.tmpl" }} -{{define "title" -}}Reset password{{end}} +{{define "title" -}}Reset password - {{end}} {{define "auth" -}} <h1>Reset password</h1>
M
templates/base.tmpl
→
templates/base.tmpl
@@ -4,7 +4,7 @@
<head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>{{block "title" .}}Page title{{end}}</title> + <title>{{block "title" .}}{{end}}WellBinge</title> <link rel="icon" type="image/png" href="/static/favicon/favicon-48x48.png" sizes="48x48" /> <link rel="icon" type="image/svg+xml" href="/static/favicon/favicon.svg" />
A
templates/habits-id.tmpl
@@ -0,0 +1,28 @@
+{{ extends "base.tmpl" }} + +{{define "title" -}}Edit - {{end}} + +{{define "content" -}} + <h1>Edit habit</h1> + + <form method="post" action="/edit"> + <label> + <span>Name:</span> + <input type="text" name="name" autocomplete="off" placeholder="Name" value="{{ .Name }}" required /> + </label> + {{ if not .Negative }} + <label> + <span>Days:</span> + <input type="number" name="days" autocomplete="off" placeholder="Days" min="1" max="60" value="{{ .Days }}" required /> + </label> + <label> + <span>Enabled:</span> + <input type="checkbox" name="enabled"{{ if not .Disabled }} checked{{ end }} /> + </label> + {{ end }} + <input type="submit" value="Edit" /> + </form> + <form method="post" action="/edit"> + <input type="submit" value="Delete" /> + </form> +{{end}}
M
templates/habits.tmpl
→
templates/habits.tmpl
@@ -1,12 +1,12 @@
{{ extends "base.tmpl" }} -{{define "title" -}}Habits{{end}} +{{define "title" -}}Habits - {{end}} {{define "content" -}} <h1>Welcome, <i>{{.User.Username}}</i>!</h1> <a href="/logout">Logout</a><br /> <div class="habits-title"> - <h4>Positive habits</h4> + <h3>Positive habits</h3> <a href="/new/positive" class="button">+ Add</a> </div> <table>@@ -14,23 +14,33 @@ <thead>
<tr> <td>Name</td> <td>Last time</td> - <td>Enabled</td> + <td>Actions</td> </tr> </thead> <tbody> - {{range .Positive}} - <tr class="{{.Class}}"> - <td>{{.Name}}</td> - <td>{{.LastAck}}</td> - <td><input type="checkbox" disabled{{ if not .Disabled }} checked{{end}} /></td> - </tr> + {{ range .Positive }} + <a href="/habits/{{ .ID }}"> + <tr class="{{.Class}}"> + <td>{{ .Name }}</td> + <td><i>{{ .LastAck }}</i></td> + <td class="actions"> + <form action="/ack/{{ .ID }}" method="post"> + <input type="submit" value="Ack" /> + </form> + + <form action="/habits/{{ .ID }}" method="get"> + <input type="submit" value="Edit" /> + </form> + </td> + </tr> + </a> {{end}} </tbody> <tfoot></tfoot> </table> <div class="habits-title"> - <h4>Negative habits</h4> + <h3>Negative habits</h3> <a href="/new/negative" class="button">+ Add</a> </div> <table>@@ -38,6 +48,7 @@ <thead>
<tr> <td>Name</td> <td>Last time</td> + <td>Actions</td> </tr> </thead> <tbody>@@ -45,6 +56,15 @@ {{range .Negative}}
<tr class="{{.Class}}"> <td>{{.Name}}</td> <td>{{.LastAck}}</td> + <td class="actions"> + <form action="/ack/{{ .ID }}" method="post"> + <input type="submit" value="Ack" /> + </form> + + <form action="/habits/{{ .ID }}" method="get"> + <input type="submit" value="Edit" /> + </form> + </td> </tr> {{end}} </tbody>
M
templates/index.tmpl
→
templates/index.tmpl
@@ -1,7 +1,5 @@
{{ extends "base.tmpl" }} -{{define "title" -}}WellBinge{{end}} - {{define "content" -}} <h1>WellBinge</h1> <h4>Create positive habits, get reminders, quit addictions.</h4>
M
templates/new.tmpl
→
templates/new.tmpl
@@ -1,6 +1,6 @@
{{ extends "base.tmpl" }} -{{define "title" -}}New habit{{end}} +{{define "title" -}}New - {{end}} {{define "content" -}} <h1>New habit</h1>