Add attendee cap with required login for capped events

Hosts can optionally cap total attendance (RSVPs + plus-ones) when
creating an event. Capped events require guests to log in via the
existing email code flow; RSVPs are tied to accounts and deduped per
user. The event page shows spots left and replaces the form with a
full notice when the cap is reached; the server rejects over-cap
RSVPs and edits with 409 as a race backstop.

Also threads a safeNext-validated ?next= param through the login and
name-setup flow so guests land back on the event after signing in.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 19:36:57 -04:00
parent 2aa3abf035
commit d005080aca
13 changed files with 368 additions and 44 deletions
+20 -2
View File
@@ -19,7 +19,7 @@
{{define "content"}}
<div class="event-header">
<div class="event-tag">Open · {{.TotalGoing}} going</div>
<div class="event-tag">Open · {{.TotalGoing}} going{{if .Capped}} · {{if .CapFull}}FULL{{else}}{{.SpotsLeft}} spot{{if ne .SpotsLeft 1}}s{{end}} left{{end}}{{end}}</div>
<h1 class="event-title">{{.Event.Title}}</h1>
<div class="event-meta">
{{if .Event.Date}}<span>&#128197; {{.Event.Date}}</span>{{end}}
@@ -41,6 +41,23 @@
<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)}}
<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.
</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 &#8599;</a>
</div>
{{else}}
<div class="claim-form-wrapper">
<div class="form-title">I'm coming &#8594;</div>
<form hx-post="/e/{{.Event.Slug}}/rsvp"
@@ -64,7 +81,7 @@
{{end}}
<div class="form-row">
<label>Bringing anyone?</label>
<input type="number" name="plus_one" value="0" min="0" max="10">
<input type="number" name="plus_one" value="0" min="0" max="{{if .Capped}}{{.PlusOneMax}}{{else}}10{{end}}">
</div>
<div class="form-row" id="claim-note">
<label>Note (optional)</label>
@@ -73,6 +90,7 @@
<button class="btn-submit" type="submit">Count me in &#8599;</button>
</form>
</div>
{{end}}
{{if .IsAdmin}}
<div class="section-label" style="margin-top:40px">Admin: Location</div>