Compare commits

...

2 Commits

Author SHA1 Message Date
Teajey b38e707ae2
feat: can delete your ideas on other's lists 2024-05-23 21:33:56 +12:00
Teajey 7d46722053
feat: add gifts to other's wishlists 2024-05-23 14:07:37 +12:00
6 changed files with 152 additions and 44 deletions

View File

@ -49,7 +49,7 @@ func (ctx *Context) UpdateForeignWishlist(w http.ResponseWriter, r *http.Request
return return
} }
userReference := r.PathValue("userReference") userReference := r.PathValue("userReference")
switch r.Form.Get("mode") { switch r.Form.Get("intent") {
case "claim": case "claim":
claims := r.Form["unclaimed"] claims := r.Form["unclaimed"]
unclaims := r.Form["claimed"] unclaims := r.Form["claimed"]
@ -66,8 +66,30 @@ func (ctx *Context) UpdateForeignWishlist(w http.ResponseWriter, r *http.Request
http.Error(w, "Failed to complete gifts...", http.StatusInternalServerError) http.Error(w, "Failed to complete gifts...", http.StatusInternalServerError)
return return
} }
case "add":
giftName := r.Form.Get("gift_name")
if giftName == "" {
http.Error(w, "Gift name not provided", http.StatusBadRequest)
return
}
err := user.AddGiftToUser(userReference, giftName)
if err != nil {
log.Printf("Failed to add gift idea to other user: %s\n", err)
http.Error(w, "Failed to add gift idea to other user...", http.StatusInternalServerError)
return
}
case "delete":
claims := r.Form["unclaimed"]
unclaims := r.Form["claimed"]
gifts := append(claims, unclaims...)
err := user.RemoveGifts(gifts...)
if err != nil {
log.Printf("Failed to remove gift idea for other user: %s\n", err)
http.Error(w, "Failed to remove gift idea for other user...", http.StatusInternalServerError)
return
}
default: default:
http.Error(w, "Invalid mode", http.StatusBadRequest) http.Error(w, "Invalid intent", http.StatusBadRequest)
} }
http.Redirect(w, r, "/"+userReference, http.StatusSeeOther) http.Redirect(w, r, "/"+userReference, http.StatusSeeOther)
} }

View File

@ -32,7 +32,7 @@ func (ctx *Context) ViewForeignWishlist(w http.ResponseWriter, r *http.Request)
http.Error(w, "User not found", http.StatusNotFound) http.Error(w, "User not found", http.StatusNotFound)
return return
} }
gifts, err := otherUser.GetGifts() gifts, err := user.GetOtherUserGifts(userReference)
if err != nil { if err != nil {
log.Printf("An error occurred while fetching %s's wishlist: %s\n", otherUser.Name, err) log.Printf("An error occurred while fetching %s's wishlist: %s\n", otherUser.Name, err)
http.Error(w, "An error occurred while fetching this user's wishlist :(", http.StatusInternalServerError) http.Error(w, "An error occurred while fetching this user's wishlist :(", http.StatusInternalServerError)

View File

@ -19,8 +19,11 @@ type Gift struct {
ClaimantId string ClaimantId string
ClaimantName string ClaimantName string
Sent bool Sent bool
RecipientId string
RecipientName string RecipientName string
RecipientRef string RecipientRef string
CreatorId string
CreatorName string
} }
func queryForUser(query string, args ...any) (*User, error) { func queryForUser(query string, args ...any) (*User, error) {
@ -83,7 +86,7 @@ func (u *User) GetPassHash() ([]byte, error) {
} }
func (u *User) GetGifts() ([]Gift, error) { func (u *User) GetGifts() ([]Gift, error) {
stmt := "SELECT gift.id, gift.name, claimant.id, claimant.name, gift.sent FROM gift JOIN user ON gift.recipient_id = user.id LEFT JOIN user AS claimant ON gift.claimant_id = claimant.id WHERE user.id = ?" stmt := "SELECT gift.id, gift.name, claimant.id, claimant.name, gift.sent FROM gift JOIN user ON gift.recipient_id = user.id LEFT JOIN user AS claimant ON gift.claimant_id = claimant.id WHERE gift.creator_id = user.id AND user.id = ?"
rows, err := database.Query(stmt, u.Id) rows, err := database.Query(stmt, u.Id)
if err != nil { if err != nil {
return nil, err return nil, err
@ -93,15 +96,18 @@ func (u *User) GetGifts() ([]Gift, error) {
for rows.Next() { for rows.Next() {
var id string var id string
var name string var name string
var claimantId string var claimantId sql.NullString
var claimantName string var claimantName sql.NullString
var sent bool var sent bool
rows.Scan(&id, &name, &claimantId, &claimantName, &sent) err = rows.Scan(&id, &name, &claimantId, &claimantName, &sent)
if err != nil {
return nil, err
}
gift := Gift{ gift := Gift{
Id: id, Id: id,
Name: name, Name: name,
ClaimantId: claimantId, ClaimantId: claimantId.String,
ClaimantName: claimantName, ClaimantName: claimantName.String,
Sent: sent, Sent: sent,
} }
gifts = append(gifts, gift) gifts = append(gifts, gift)
@ -113,6 +119,53 @@ func (u *User) GetGifts() ([]Gift, error) {
return gifts, nil return gifts, nil
} }
func (u *User) GetOtherUserGifts(otherUserReference string) ([]Gift, error) {
otherUser, err := GetUserByReference(otherUserReference)
if err != nil {
return nil, err
}
if otherUser.Id == u.Id {
return nil, fmt.Errorf("Not allowed to view own foreign wishlist")
}
stmt := "SELECT gift.id, gift.name, claimant.id, claimant.name, gift.sent, gift.creator_id, creator.name, gift.recipient_id FROM gift JOIN user ON gift.recipient_id = user.id LEFT JOIN user AS claimant ON gift.claimant_id = claimant.id LEFT JOIN user AS creator ON gift.creator_id = creator.id WHERE user.id = ?"
rows, err := database.Query(stmt, otherUser.Id)
if err != nil {
return nil, err
}
defer rows.Close()
gifts := []Gift{}
for rows.Next() {
var id string
var name string
var claimantId sql.NullString
var claimantName sql.NullString
var sent bool
var creatorId string
var creatorName string
var recipientId string
err = rows.Scan(&id, &name, &claimantId, &claimantName, &sent, &creatorId, &creatorName, &recipientId)
if err != nil {
return nil, err
}
gift := Gift{
Id: id,
Name: name,
ClaimantId: claimantId.String,
ClaimantName: claimantName.String,
Sent: sent,
CreatorId: creatorId,
CreatorName: creatorName,
RecipientId: recipientId,
}
gifts = append(gifts, gift)
}
err = rows.Err()
if err != nil {
return nil, err
}
return gifts, nil
}
func (u *User) GetTodo() ([]Gift, error) { func (u *User) GetTodo() ([]Gift, error) {
stmt := "SELECT gift.id, gift.name, gift.sent, recipient.name, recipient.reference FROM gift JOIN user ON gift.claimant_id = user.id JOIN user AS recipient ON gift.recipient_id = recipient.id WHERE user.id = ? ORDER BY gift.sent ASC, gift.name" stmt := "SELECT gift.id, gift.name, gift.sent, recipient.name, recipient.reference FROM gift JOIN user ON gift.claimant_id = user.id JOIN user AS recipient ON gift.recipient_id = recipient.id WHERE user.id = ? ORDER BY gift.sent ASC, gift.name"
rows, err := database.Query(stmt, u.Id) rows, err := database.Query(stmt, u.Id)
@ -289,3 +342,16 @@ func (u *User) CompleteGifts(claims []string) error {
err = tx.Commit() err = tx.Commit()
return err return err
} }
func (u *User) AddGiftToUser(otherUserReference string, giftName string) error {
otherUser, err := GetUserByReference(otherUserReference)
if err != nil {
return err
}
stmt := "INSERT INTO gift (name, recipient_id, creator_id) VALUES (?, ?, ?)"
_, err = database.Exec(stmt, giftName, otherUser.Id, u.Id)
if err != nil {
return err
}
return nil
}

View File

@ -28,6 +28,12 @@
const accepted = submissionNames.length > 0 && submissionNames.every((name) => acceptedNames.includes(name)); const accepted = submissionNames.length > 0 && submissionNames.every((name) => acceptedNames.includes(name));
submitter.disabled = !accepted; submitter.disabled = !accepted;
} }
function acceptAttribute(form, submitId, acceptedAttribute) {
const checkedInputs = Array.from(form.querySelectorAll("input")).filter((i) => i.checked);
const submitter = document.getElementById(submitId);
const accepted = checkedInputs.length > 0 && checkedInputs.every((i) => i.hasAttribute(acceptedAttribute));
submitter.disabled = !accepted;
}
</script> </script>
</head> </head>

View File

@ -33,49 +33,63 @@
<h2>{{.Username}}'s list</h2> <h2>{{.Username}}'s list</h2>
{{with .Gifts}} {{with .Gifts}}
<form method="post" action="/{{$.UserReference}}/update" autocomplete="off" <form method="post" action="/{{$.UserReference}}/update" autocomplete="off"
onchange="acceptNames(this, 'claimSubmit', 'claimed', 'unclaimed'); acceptNames(this, 'completeSubmit', 'claimed');"> onchange="acceptNames(this, 'claimSubmit', 'claimed', 'unclaimed'); acceptNames(this, 'completeSubmit', 'claimed'); acceptAttribute(this, 'deleteSubmit', 'data-deletable')">
<button id="claimSubmit" class="btn btn-warning" type="submit" name="mode" value="claim" <ul class="list-group mb-3">
disabled>Claim/Unclaim</button>
<button id="completeSubmit" class="btn btn-success" type="submit" name="mode" value="complete"
disabled>Complete</button>
<ul class="list-group mt-3">
{{range .}} {{range .}}
{{$isMine := eq .ClaimantId $.CurrentUserId}}
{{$createdByMe := eq .CreatorId $.CurrentUserId}}
{{$outsideIdea := ne .RecipientId .CreatorId}}
<li class="list-group-item{{if .Sent}} list-group-item-light{{end}}"> <li class="list-group-item{{if .Sent}} list-group-item-light{{end}}">
{{if .ClaimantId}}
{{if eq .ClaimantId $.CurrentUserId}}
<input id="foreignlist_select_{{.Id}}" class="form-check-input" type="checkbox" <input id="foreignlist_select_{{.Id}}" class="form-check-input" type="checkbox"
aria-describedby="wish_detail_{{.Id}}" name="claimed" value="{{.Id}}" {{if .Sent}}disabled{{end}}> aria-describedby="wish_detail_{{.Id}}" {{if $isMine}} name="claimed" value="{{.Id}}" {{else if
{{else}} .ClaimantId}} disabled {{else}} name="unclaimed" value="{{.Id}}" {{end}} {{if .Sent}} disabled {{end}}
<input id="foreignlist_select_{{.Id}}" class="form-check-input" type="checkbox" {{if $createdByMe}}data-deletable{{end}}>
aria-describedby="wish_detail_{{.Id}}" disabled>
{{end}} <label class="form-check-label stretched-link" for="foreignlist_select_{{.Id}}">
{{else}}
<input id="foreignlist_select_{{.Id}}" class="form-check-input" type="checkbox"
aria-describedby="wish_detail_{{.Id}}" name="unclaimed" value="{{.Id}}" {{if .Sent}}disabled{{end}}>
{{end}}
<label class="form-check-label" for="foreignlist_select_{{.Id}}">
{{if .Sent}} {{if .Sent}}
<s>{{.Name}}</s> <s>{{.Name}}</s>
{{else}} {{else}}
{{.Name}} {{.Name}}
{{end}} {{end}}
</label> </label>
{{if .ClaimantId}}
{{if eq .ClaimantId $.CurrentUserId}} {{if or .ClaimantId $outsideIdea}}
<span id="wish_detail_{{.Id}}" style="color: blue;">{{if .Sent}}Completed{{else}}Claimed{{end}} by <div class="d-inline" id="wish_detail_{{.Id}}">
you</span> {{if .ClaimantId}}
{{else}} <span style="color: {{if $isMine}}blue{{else}}red{{end}};">{{if .Sent}}Completed{{else}}Claimed{{end}}
<span id="wish_detail_{{.Id}}" style="color: red;">{{if .Sent}}Completed{{else}}Claimed{{end}} by by
{{.ClaimantName}}</span> {{if $isMine}}<em>you</em>{{else}}{{.ClaimantName}}{{end}}</span>
{{end}} {{end}}
{{if $outsideIdea}}
<span style="color: green;">Added by {{if
$createdByMe}}<em>you</em>{{else}}{{.CreatorName}}{{end}}</span>
{{end}}
</div>
{{end}} {{end}}
</li> </li>
{{end}} {{end}}
</ul> </ul>
<button id="claimSubmit" class="btn btn-warning" type="submit" name="intent" value="claim"
disabled>Claim/Unclaim</button>
<button id="completeSubmit" class="btn btn-success" type="submit" name="intent" value="complete"
disabled>Complete</button>
<button id="deleteSubmit" class="btn btn-danger" type="submit" name="intent" value="delete"
disabled>Delete</button>
</form> </form>
{{else}} {{else}}
<p>They haven't wished for anything. Ask them to think of some stuff!</p> <p>They don't have any gift ideas. Ask them to think of something, or add an idea yourself! 👇 (everyone
except them will be able to see it and claim it)</p>
{{end}} {{end}}
<form method="post" action="/{{$.UserReference}}/update">
<div class="input-group mt-3">
<input class="form-control" name="gift_name"
placeholder="This will be invisible to {{.Username}}, but everyone else will be able to see it and possibly claim it."
required>
<button class="btn btn-primary" type="submit" name="intent" value="add">Add gift idea</button>
</div>
</form>
</div> </div>
</section> </section>
</div> </div>

View File

@ -30,8 +30,6 @@
{{with .Gifts}} {{with .Gifts}}
<form method="post" action="/wishlist/delete" onchange="acceptNames(this, 'deleteSubmit', 'gift')" <form method="post" action="/wishlist/delete" onchange="acceptNames(this, 'deleteSubmit', 'gift')"
autocomplete="off"> autocomplete="off">
<button id="deleteSubmit" class="btn btn-danger mb-3" type="submit" name="mode" value="delete"
disabled>Delete</button>
<ul class="list-group mb-3"> <ul class="list-group mb-3">
{{range .}} {{range .}}
<li class="list-group-item"> <li class="list-group-item">
@ -42,6 +40,8 @@
</li> </li>
{{end}} {{end}}
</ul> </ul>
<button id="deleteSubmit" class="btn btn-danger mb-3" type="submit" name="mode" value="delete"
disabled>Delete</button>
</form> </form>
{{else}} {{else}}
<p>Your list is empty. Think of some things to add!</p> <p>Your list is empty. Think of some things to add!</p>
@ -62,11 +62,7 @@
<form method="post" action="/todo/update" <form method="post" action="/todo/update"
onchange="acceptNames(this, 'unclaimSubmit', 'gift'); acceptNames(this, 'completeSubmit', 'gift')" onchange="acceptNames(this, 'unclaimSubmit', 'gift'); acceptNames(this, 'completeSubmit', 'gift')"
autocomplete="off"> autocomplete="off">
<button id="unclaimSubmit" class="btn btn-warning" type="submit" name="mode" value="unclaim" <ul class="list-group mb-3">
disabled>Unclaim</button>
<button id="completeSubmit" class="btn btn-success" type="submit" name="mode" value="complete"
disabled>Complete</button>
<ul class="list-group mt-3">
{{range .}} {{range .}}
<li class="list-group-item{{if .Sent}} list-group-item-light{{end}}"> <li class="list-group-item{{if .Sent}} list-group-item-light{{end}}">
<input id="todo_select_{{.Id}}" class="form-check-input" type="checkbox" {{if .Sent}} <input id="todo_select_{{.Id}}" class="form-check-input" type="checkbox" {{if .Sent}}
@ -86,10 +82,14 @@
</li> </li>
{{end}} {{end}}
</ul> </ul>
<button id="unclaimSubmit" class="btn btn-warning" type="submit" name="mode" value="unclaim"
disabled>Unclaim</button>
<button id="completeSubmit" class="btn btn-success" type="submit" name="mode" value="complete"
disabled>Complete</button>
</form> </form>
</div> </div>
</section> </section>
{{end}} {{end}}
</div> </div>
</div> </div>
{{end}} {{end}}