Add yes/no RSVP answers with required login
RSVPs now carry a status ('yes'/'no'). When auth is enabled, answering
requires login for all events, not just capped ones. Answers are deduped
one per user and re-answering updates in place. A 'no' zeroes plus_one,
is excluded from Going counts and cap math, frees the person's slot
claims, and shows in a separate "Can't make it" list.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,13 @@
|
||||
<label>Your name</label>
|
||||
<input type="text" name="name" value="{{.Rsvp.Name}}" required>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label>Coming?</label>
|
||||
<select name="status">
|
||||
<option value="yes" {{if ne .Rsvp.Status "no"}}selected{{end}}>Yes, I'm going</option>
|
||||
<option value="no" {{if eq .Rsvp.Status "no"}}selected{{end}}>Can't make it</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label>Bringing anyone?</label>
|
||||
<input type="number" name="plus_one" value="{{.Rsvp.PlusOne}}" min="0" max="10">
|
||||
|
||||
+18
-11
@@ -41,25 +41,25 @@
|
||||
|
||||
<div class="section-label" style="margin-top:40px">Sign up</div>
|
||||
|
||||
{{if and .Capped .CapFull}}
|
||||
<div class="claim-form-wrapper">
|
||||
<div class="form-title">Event is full</div>
|
||||
<p style="font-family:'DM Mono',monospace;font-size:0.8rem;color:#555;">
|
||||
All {{.Event.AttendeeCap}} spots are taken.
|
||||
</p>
|
||||
</div>
|
||||
{{else if and .Capped (not .User)}}
|
||||
{{if and .AuthEnabled (not .User)}}
|
||||
<div class="claim-form-wrapper">
|
||||
<div class="form-title">Log in to RSVP</div>
|
||||
<p style="font-family:'DM Mono',monospace;font-size:0.8rem;color:#555;margin-bottom:16px;">
|
||||
This event has limited spots ({{.SpotsLeft}} left), so RSVPs require an account.
|
||||
{{if .Capped}}This event has limited spots ({{.SpotsLeft}} left), so RSVPs{{else}}RSVPs{{end}} require an account.
|
||||
</p>
|
||||
<a class="btn-submit" style="display:inline-block;text-decoration:none;text-align:center;"
|
||||
href="/login?next=/e/{{.Event.Slug}}">Log in to RSVP ↗</a>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="claim-form-wrapper">
|
||||
<div class="form-title">I'm coming →</div>
|
||||
{{if .CapFull}}
|
||||
<div class="form-title">Event is full</div>
|
||||
<p style="font-family:'DM Mono',monospace;font-size:0.8rem;color:#555;margin-bottom:16px;">
|
||||
All {{.Event.AttendeeCap}} spots are taken. You can still let them know you can't make it.
|
||||
</p>
|
||||
{{else}}
|
||||
<div class="form-title">Are you coming?</div>
|
||||
{{end}}
|
||||
<form hx-post="/e/{{.Event.Slug}}/rsvp"
|
||||
hx-target="#slots-container"
|
||||
hx-swap="innerHTML settle:0.1s"
|
||||
@@ -68,6 +68,7 @@
|
||||
<label>Your name</label>
|
||||
<input type="text" name="name" placeholder="e.g. Sam" required value="{{if .User}}{{.User.Name}}{{end}}">
|
||||
</div>
|
||||
{{if not .CapFull}}
|
||||
{{if .Slots}}
|
||||
<div class="form-row">
|
||||
<label>Bringing something?</label>
|
||||
@@ -83,11 +84,17 @@
|
||||
<label>Bringing anyone?</label>
|
||||
<input type="number" name="plus_one" value="0" min="0" max="{{if .Capped}}{{.PlusOneMax}}{{else}}10{{end}}">
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="form-row" id="claim-note">
|
||||
<label>Note (optional)</label>
|
||||
<input type="text" id="claim-note-input" name="note" placeholder="e.g. arriving late, dietary restrictions, etc.">
|
||||
</div>
|
||||
<button class="btn-submit" type="submit">Count me in ↗</button>
|
||||
<div style="display:flex;gap:10px">
|
||||
{{if not .CapFull}}
|
||||
<button class="btn-submit" type="submit" name="status" value="yes">Count me in ↗</button>
|
||||
{{end}}
|
||||
<button class="btn-submit btn-no" type="submit" name="status" value="no">Can't make it</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
@@ -286,6 +286,13 @@
|
||||
box-shadow: 4px 4px 0 var(--sage);
|
||||
transform: translate(-2px, -2px);
|
||||
}
|
||||
.btn-no {
|
||||
background: var(--cream);
|
||||
color: var(--ink);
|
||||
}
|
||||
.btn-no:hover {
|
||||
box-shadow: 4px 4px 0 var(--peach);
|
||||
}
|
||||
.progress-bar {
|
||||
height: 4px;
|
||||
background: #e8e8e8;
|
||||
|
||||
+23
-2
@@ -37,11 +37,11 @@
|
||||
{{if .GoingList}}
|
||||
{{range .GoingList}}
|
||||
<span class="claim-chip">
|
||||
{{if gt .RsvpID 0}}<span class="rsvp-name-link"
|
||||
{{if and (gt .RsvpID 0) $.CanEditRsvps}}<span class="rsvp-name-link"
|
||||
hx-get="/e/{{$.Event.Slug}}/rsvp/{{.RsvpID}}/edit"
|
||||
hx-target="#edit-modal"
|
||||
hx-swap="innerHTML">{{.Name}}</span>{{else}}{{.Name}}{{end}}{{if gt .PlusOne 0}} +{{.PlusOne}}{{end}}{{if .Note}} <small style="color:#888">({{.Note}})</small>{{end}}
|
||||
{{if gt .RsvpID 0}}
|
||||
{{if and (gt .RsvpID 0) $.CanEditRsvps}}
|
||||
<button hx-delete="/e/{{$.Event.Slug}}/rsvp/{{.RsvpID}}"
|
||||
hx-target="#slots-container"
|
||||
hx-swap="innerHTML settle:0.1s"
|
||||
@@ -54,6 +54,27 @@
|
||||
<span class="nobody">no one yet</span>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
{{if .NotGoingList}}
|
||||
<div class="section-label">Can't make it ({{len .NotGoingList}})</div>
|
||||
<div class="rsvp-list">
|
||||
{{range .NotGoingList}}
|
||||
<span class="claim-chip" style="opacity:0.6">
|
||||
{{if $.CanEditRsvps}}<span class="rsvp-name-link"
|
||||
hx-get="/e/{{$.Event.Slug}}/rsvp/{{.RsvpID}}/edit"
|
||||
hx-target="#edit-modal"
|
||||
hx-swap="innerHTML">{{.Name}}</span>{{else}}{{.Name}}{{end}}{{if .Note}} <small style="color:#888">({{.Note}})</small>{{end}}
|
||||
{{if $.CanEditRsvps}}
|
||||
<button hx-delete="/e/{{$.Event.Slug}}/rsvp/{{.RsvpID}}"
|
||||
hx-target="#slots-container"
|
||||
hx-swap="innerHTML settle:0.1s"
|
||||
hx-confirm="Remove {{.Name}}?"
|
||||
title="Remove">×</button>
|
||||
{{end}}
|
||||
</span>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{define "slots.html"}}{{template "slots-inner" .}}{{end}}
|
||||
|
||||
Reference in New Issue
Block a user