Add polished answer notifications (#1)
Sends a branded Resend email when a question receives its first answer, records accepted and failed sends, and adds collapsed answer editing with cancel behavior. Co-authored-by: codegirl-007 <s.raide@gmail.com>
This commit was merged in pull request #1.
This commit is contained in:
+27
-9
@@ -2,6 +2,7 @@ package web
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/jpeg"
|
||||
@@ -27,6 +28,7 @@ type profilePage struct {
|
||||
UploadsEnabled bool
|
||||
Error string
|
||||
StateVal string
|
||||
EmailVal string
|
||||
}
|
||||
|
||||
func (s *Server) handleProfileForm(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -35,7 +37,7 @@ func (s *Server) handleProfileForm(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/login?next=/profile", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
s.renderProfile(w, r, u, "", u.State)
|
||||
s.renderProfile(w, r, u, "", u.State, u.Email)
|
||||
}
|
||||
|
||||
func (s *Server) handleProfile(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -45,7 +47,7 @@ func (s *Server) handleProfile(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if err := r.ParseMultipartForm(3 << 20); err != nil {
|
||||
s.renderProfile(w, r, u, "Could not read form (max 2MB for images).", u.State)
|
||||
s.renderProfile(w, r, u, "Could not read form (max 2MB for images).", u.State, u.Email)
|
||||
return
|
||||
}
|
||||
want := s.sessions.GetString(r.Context(), "csrf")
|
||||
@@ -57,7 +59,12 @@ func (s *Server) handleProfile(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
state := geo.NormalizeState(r.FormValue("state"))
|
||||
if !geo.ValidState(state) {
|
||||
s.renderProfile(w, r, u, "Choose a valid US state or leave it blank.", state)
|
||||
s.renderProfile(w, r, u, "Choose a valid US state or leave it blank.", state, r.FormValue("email"))
|
||||
return
|
||||
}
|
||||
email, emailErr := store.ValidateEmail(r.FormValue("email"))
|
||||
if emailErr != "" {
|
||||
s.renderProfile(w, r, u, emailErr, state, r.FormValue("email"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -67,16 +74,16 @@ func (s *Server) handleProfile(w http.ResponseWriter, r *http.Request) {
|
||||
if err == nil {
|
||||
defer file.Close()
|
||||
if !s.cfg.Blob.Enabled() {
|
||||
s.renderProfile(w, r, u, "Avatar uploads are not configured on this server.", state)
|
||||
s.renderProfile(w, r, u, "Avatar uploads are not configured on this server.", state, email)
|
||||
return
|
||||
}
|
||||
if hdr.Size > 2<<20 {
|
||||
s.renderProfile(w, r, u, "Avatar must be 2MB or smaller.", state)
|
||||
s.renderProfile(w, r, u, "Avatar must be 2MB or smaller.", state, email)
|
||||
return
|
||||
}
|
||||
body, ext, contentType, prepErr := prepareAvatar(file, 2<<20)
|
||||
if prepErr != nil {
|
||||
s.renderProfile(w, r, u, "Avatar must be a JPEG, PNG, or WebP image.", state)
|
||||
s.renderProfile(w, r, u, "Avatar must be a JPEG, PNG, or WebP image.", state, email)
|
||||
return
|
||||
}
|
||||
prevURL := u.AvatarURL
|
||||
@@ -88,14 +95,19 @@ func (s *Server) handleProfile(w http.ResponseWriter, r *http.Request) {
|
||||
Size: int64(len(body)),
|
||||
})
|
||||
if upErr != nil {
|
||||
s.renderProfile(w, r, u, "Could not upload avatar. Try again later.", state)
|
||||
s.renderProfile(w, r, u, "Could not upload avatar. Try again later.", state, email)
|
||||
return
|
||||
}
|
||||
avatarURL = url
|
||||
u.State = state
|
||||
u.Email = email
|
||||
u.AvatarURL = avatarURL
|
||||
if err := s.store.SaveUserProfile(r.Context(), u); err != nil {
|
||||
_ = s.cfg.Blob.Delete(r.Context(), avatarKey)
|
||||
if errors.Is(err, store.ErrDuplicateEmail) {
|
||||
s.renderProfile(w, r, u, "That email is already registered.", state, email)
|
||||
return
|
||||
}
|
||||
http.Error(w, "could not save profile", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -106,12 +118,17 @@ func (s *Server) handleProfile(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/profile", http.StatusSeeOther)
|
||||
return
|
||||
} else if err != http.ErrMissingFile {
|
||||
s.renderProfile(w, r, u, "Could not read avatar file.", state)
|
||||
s.renderProfile(w, r, u, "Could not read avatar file.", state, email)
|
||||
return
|
||||
}
|
||||
|
||||
u.State = state
|
||||
u.Email = email
|
||||
if err := s.store.SaveUserProfile(r.Context(), u); err != nil {
|
||||
if errors.Is(err, store.ErrDuplicateEmail) {
|
||||
s.renderProfile(w, r, u, "That email is already registered.", state, email)
|
||||
return
|
||||
}
|
||||
http.Error(w, "could not save profile", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -236,7 +253,7 @@ func fitAvatar(img image.Image, maxDim int) image.Image {
|
||||
return dst
|
||||
}
|
||||
|
||||
func (s *Server) renderProfile(w http.ResponseWriter, r *http.Request, u *store.User, errMsg, stateVal string) {
|
||||
func (s *Server) renderProfile(w http.ResponseWriter, r *http.Request, u *store.User, errMsg, stateVal, emailVal string) {
|
||||
var (
|
||||
questions []store.RankedQuestion
|
||||
label string
|
||||
@@ -263,5 +280,6 @@ func (s *Server) renderProfile(w http.ResponseWriter, r *http.Request, u *store.
|
||||
UploadsEnabled: s.cfg.Blob.Enabled(),
|
||||
Error: errMsg,
|
||||
StateVal: stateVal,
|
||||
EmailVal: emailVal,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user