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:
+20
-2
@@ -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>📅 {{.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 ↗</a>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="claim-form-wrapper">
|
||||
<div class="form-title">I'm coming →</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 ↗</button>
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .IsAdmin}}
|
||||
<div class="section-label" style="margin-top:40px">Admin: Location</div>
|
||||
|
||||
@@ -33,6 +33,12 @@
|
||||
<label>Description (optional, markdown supported)</label>
|
||||
<textarea name="description" rows="4" placeholder="e.g. Bring your own blanket! We'll have a grill set up near the big oak tree." style="border:var(--border-w) solid var(--border);background:var(--cream);padding:10px 14px;font-family:'Bricolage Grotesque',sans-serif;font-size:0.95rem;resize:vertical;outline:none;"></textarea>
|
||||
</div>
|
||||
{{if .AuthEnabled}}
|
||||
<div class="form-row">
|
||||
<label>Attendee cap (optional — guests must log in to RSVP)</label>
|
||||
<input type="number" name="attendee_cap" min="0" max="1000" placeholder="e.g. 30 — leave blank for unlimited">
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="section-label" style="margin:24px 0 16px">Slots</div>
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<div class="claim-form-wrapper">
|
||||
{{if eq .Step "identify"}}
|
||||
<form method="POST" action="/login">
|
||||
{{if .Next}}<input type="hidden" name="next" value="{{.Next}}">{{end}}
|
||||
<div class="form-row">
|
||||
<label>Email</label>
|
||||
<input type="email" name="identifier" placeholder="you@example.com" required autofocus>
|
||||
@@ -23,6 +24,7 @@
|
||||
{{else}}
|
||||
<form method="POST" action="/login/verify">
|
||||
<input type="hidden" name="identifier" value="{{.Identifier}}">
|
||||
{{if .Next}}<input type="hidden" name="next" value="{{.Next}}">{{end}}
|
||||
<p style="font-family:'DM Mono',monospace;font-size:0.78rem;color:#555;margin-bottom:16px;">
|
||||
Code sent to <strong>{{.Identifier}}</strong>
|
||||
</p>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
<div class="claim-form-wrapper">
|
||||
<form method="POST" action="/account/name">
|
||||
{{if .Next}}<input type="hidden" name="next" value="{{.Next}}">{{end}}
|
||||
<div class="form-row">
|
||||
<label>Name</label>
|
||||
<input type="text" name="name" placeholder="Your name" required autofocus
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<div class="section-label">Going ({{.TotalGoing}})</div>
|
||||
<div class="section-label">Going ({{.TotalGoing}}{{if .Capped}} / {{.Event.AttendeeCap}}{{end}})</div>
|
||||
<div class="rsvp-list">
|
||||
{{if .GoingList}}
|
||||
{{range .GoingList}}
|
||||
|
||||
Reference in New Issue
Block a user