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)
if user.MemberTo == (time.Time{}) {

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() {
diff --git a/templates/user_templ.go b/templates/user_templ.go index a13228f..3055b2f 100644 --- a/templates/user_templ.go +++ b/templates/user_templ.go @@ -157,64 +157,15 @@ func Account(user models.User, passkeys []models.Passkey, isAdmin bool) templ.Co if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("


") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var9 = []any{input + " w-16"} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var9...) + templ_7745c5c3_Err = AccountYearForm(user.YearTag).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var12 = []any{button} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var12...) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -228,12 +179,12 @@ func Account(user models.User, passkeys []models.Passkey, isAdmin bool) templ.Co if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var14 string - templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(user.MemberTo.Format(time.DateOnly)) + var templ_7745c5c3_Var9 string + templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(user.MemberTo.Format(time.DateOnly)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `user.templ`, Line: 53, Col: 73} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `user.templ`, Line: 48, Col: 73} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -246,12 +197,12 @@ func Account(user models.User, passkeys []models.Passkey, isAdmin bool) templ.Co if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var15 string - templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(user.MemberTo.Format(time.DateOnly)) + var templ_7745c5c3_Var10 string + templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(user.MemberTo.Format(time.DateOnly)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `user.templ`, Line: 55, Col: 67} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `user.templ`, Line: 50, Col: 67} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -282,6 +233,92 @@ func Account(user models.User, passkeys []models.Passkey, isAdmin bool) templ.Co }) } +func AccountYearForm(yearTag 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 { + return templ_7745c5c3_CtxErr + } + 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_Var11 := templ.GetChildren(ctx) + if templ_7745c5c3_Var11 == nil { + templ_7745c5c3_Var11 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var12 = []any{input + " w-16"} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var12...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var15 = []any{button} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var15...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) +} + func AcceptInvite() 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 @@ -298,12 +335,12 @@ func AcceptInvite() templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var16 := templ.GetChildren(ctx) - if templ_7745c5c3_Var16 == nil { - templ_7745c5c3_Var16 = templ.NopComponent + templ_7745c5c3_Var17 := templ.GetChildren(ctx) + if templ_7745c5c3_Var17 == nil { + templ_7745c5c3_Var17 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var17 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_Var18 := 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 { @@ -321,7 +358,7 @@ func AcceptInvite() templ.Component { } return templ_7745c5c3_Err }) - templ_7745c5c3_Err = modal().Render(templ.WithChildren(ctx, templ_7745c5c3_Var17), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = modal().Render(templ.WithChildren(ctx, templ_7745c5c3_Var18), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err }