diff --git a/database/sql/user.sql b/database/sql/user.sql index edb71f3..544f186 100644 --- a/database/sql/user.sql +++ b/database/sql/user.sql @@ -35,3 +35,9 @@ where kthid = $1; update users set member_to = $2 where kthid = $1; + +-- name: UpdateUser :one +update users +set year_tag = coalesce($2, year_tag) -- TODO: would be nice if we could make this function take year_tag as a pointer so it can actually be null here +where kthid = $1 +returning *; diff --git a/database/user.sql.go b/database/user.sql.go index 0b513f2..567938e 100644 --- a/database/user.sql.go +++ b/database/user.sql.go @@ -108,6 +108,34 @@ func (q *Queries) RemoveSession(ctx context.Context, id uuid.UUID) error { return err } +const updateUser = `-- name: UpdateUser :one +update users +set year_tag = coalesce($2, year_tag) -- TODO: would be nice if we could make this function take year_tag as a pointer so it can actually be null here +where kthid = $1 +returning kthid, ug_kthid, email, first_name, family_name, year_tag, member_to, webauthn_id +` + +type UpdateUserParams struct { + Kthid string + YearTag string +} + +func (q *Queries) UpdateUser(ctx context.Context, arg UpdateUserParams) (User, error) { + row := q.db.QueryRow(ctx, updateUser, arg.Kthid, arg.YearTag) + var i User + err := row.Scan( + &i.Kthid, + &i.UgKthid, + &i.Email, + &i.FirstName, + &i.FamilyName, + &i.YearTag, + &i.MemberTo, + &i.WebauthnID, + ) + return i, err +} + const userSetMemberTo = `-- name: UserSetMemberTo :exec update users set member_to = $2 diff --git a/handlers/routes.go b/handlers/routes.go index 22fdb52..96556dd 100644 --- a/handlers/routes.go +++ b/handlers/routes.go @@ -17,6 +17,7 @@ func MountRoutes(s *service.Service) { http.Handle("GET /{$}", httputil.Route(s, index)) http.Handle("GET /logout", httputil.Route(s, logout)) http.Handle("GET /account", httputil.Route(s, account)) + http.Handle("PATCH /account", httputil.Route(s, updateAccount)) http.Handle("GET /invite/{id}", httputil.Route(s, acceptInvite)) // admin.go diff --git a/handlers/user.go b/handlers/user.go index 977ca7e..ca8ffc7 100644 --- a/handlers/user.go +++ b/handlers/user.go @@ -1,6 +1,7 @@ package handlers import ( + "log/slog" "net/http" "time" @@ -91,6 +92,29 @@ func account(s *service.Service, w http.ResponseWriter, r *http.Request) httputi return templates.Account(*user, passkeys, isAdmin) } +func updateAccount(s *service.Service, w http.ResponseWriter, r *http.Request) httputil.ToResponse { + user, err := s.GetLoggedInUser(r) + if err != nil { + return err + } + if user == nil { + return httputil.Unauthorized() + } + if err := r.ParseForm(); err != nil { + return httputil.BadRequest("Invalid form body") + } + yearTagList := r.Form["year-tag"] + if len(yearTagList) > 0 { + var err error + *user, err = s.UpdateUser(r.Context(), user.KTHID, yearTagList[0]) + slog.Info("Updated user year tag", "value", user.YearTag) + if err != nil { + return err + } + } + return templates.AccountYearForm(user.YearTag) +} + func acceptInvite(s *service.Service, w http.ResponseWriter, r *http.Request) httputil.ToResponse { idString := r.PathValue("id") if idString == "-" { diff --git a/pkg/static/public/style.dist.css b/pkg/static/public/style.dist.css index 8c6e5f4..bcb935e 100644 --- a/pkg/static/public/style.dist.css +++ b/pkg/static/public/style.dist.css @@ -860,24 +860,14 @@ html { text-transform: uppercase; } -.text-neutral-100 { - --tw-text-opacity: 1; - color: rgb(245 245 245 / var(--tw-text-opacity)); -} - -.text-cerise-regular { - --tw-text-opacity: 1; - color: rgb(232 61 132 / var(--tw-text-opacity)); -} - -.text-cerise-strong { +.text-cerise-light { --tw-text-opacity: 1; - color: rgb(238 42 123 / var(--tw-text-opacity)); + color: rgb(236 95 153 / var(--tw-text-opacity)); } -.text-cerise-light { +.text-neutral-100 { --tw-text-opacity: 1; - color: rgb(236 95 153 / var(--tw-text-opacity)); + color: rgb(245 245 245 / var(--tw-text-opacity)); } .outline-none { diff --git a/service/user.go b/service/user.go index 1e918ee..1f7aaae 100644 --- a/service/user.go +++ b/service/user.go @@ -17,19 +17,12 @@ import ( "github.com/jackc/pgx/v5" ) -func (s *Service) GetUser(ctx context.Context, kthid string) (*models.User, error) { - user, err := s.DB.GetUser(ctx, kthid) - if err == pgx.ErrNoRows { - return nil, nil - } - if err != nil { - return nil, err - } +func dbUserToModel(user database.User) models.User { var memberTo time.Time if user.MemberTo.Valid { memberTo = user.MemberTo.Time } - return &models.User{ + return models.User{ KTHID: user.Kthid, UGKTHID: user.UgKthid, Email: user.Email, @@ -38,7 +31,30 @@ func (s *Service) GetUser(ctx context.Context, kthid string) (*models.User, erro YearTag: user.YearTag, MemberTo: memberTo, WebAuthnID: user.WebauthnID, - }, nil + } +} + +func (s *Service) GetUser(ctx context.Context, kthid string) (*models.User, error) { + user, err := s.DB.GetUser(ctx, kthid) + if err == pgx.ErrNoRows { + return nil, nil + } + if err != nil { + return nil, err + } + u := dbUserToModel(user) + return &u, nil +} + +func (s *Service) UpdateUser(ctx context.Context, kthid string, yearTag string) (models.User, error) { + user, err := s.DB.UpdateUser(ctx, database.UpdateUserParams{ + Kthid: kthid, + YearTag: yearTag, + }) + if err != nil { + return models.User{}, err + } + return dbUserToModel(user), nil } func (s *Service) LoginUser(ctx context.Context, kthid string) httputil.ToResponse { diff --git a/templates/user.templ b/templates/user.templ index 5418e4f..53589fa 100644 --- a/templates/user.templ +++ b/templates/user.templ @@ -40,12 +40,7 @@ templ Account(user models.User, passkeys []models.Passkey, isAdmin bool) {
Email address
{ user.Email }
- + @AccountYearForm(user.YearTag)Not a chapter member
@@ -61,6 +56,15 @@ templ Account(user models.User, passkeys []models.Passkey, isAdmin bool) { } } +templ AccountYearForm(yearTag string) { + +} + templ AcceptInvite() { @modal() {