feat(events): guest accounts for public event registration

Non-members who register for an event now get a persistent identity:
with consent, a status:"guest" Member is upserted and an auth cookie is
set so the "You're Registered" state survives a page refresh.

Tiered auto-login matches passwordless-auth norms — auto-login is only
safe when the account holds no privileges:
- New email → create guest + cookie
- Returning guest → cookie
- Existing non-guest (active/pending/etc.) → attach ticket only, no
  cookie, confirmation email includes a sign-in link

Guests are gated on status === "guest", so admin/middleware code that
keys on status === "active" naturally excludes them. Guests are also
treated as non-members for ticket pricing/validation to prevent picking
up member-only pricing on their second registration.
This commit is contained in:
Jennie Robinson Faber 2026-04-16 21:23:31 +01:00
parent 7e7672d52b
commit 6f9e6a3d98
7 changed files with 162 additions and 10 deletions

View file

@ -154,6 +154,18 @@
securely
</p>
<label class="consent-field">
<input
v-model="form.createAccount"
type="checkbox"
:disabled="processing"
>
<span>Create a free guest account so I can manage my registration</span>
</label>
<p class="field-hint consent-hint">
Guest accounts let you view your tickets and register faster next time. We won't add you to member communications.
</p>
<button
type="submit"
class="btn btn-primary"
@ -241,6 +253,7 @@ const ticketInfo = ref(null);
const form = ref({
name: props.userName || "",
email: props.userEmail || "",
createAccount: true,
});
const isLoggedIn = computed(() => !!props.userEmail);
@ -337,6 +350,7 @@ const handleSubmit = async () => {
const body = {
name: form.value.name,
email: form.value.email,
createAccount: form.value.createAccount,
};
if (transactionId) body.transactionId = transactionId;
@ -357,6 +371,13 @@ const handleSubmit = async () => {
});
emit("success", response);
if (response?.signedIn) {
// New guest account or returning guest refresh client auth state so the
// rest of the app sees them as logged in.
await useAuth().checkMemberStatus();
}
await fetchTicketInfo(form.value.email);
} catch (err) {
console.error("Error purchasing ticket:", err);
@ -429,4 +450,22 @@ const formatEventDate = (date) => {
color: var(--text-faint);
margin-top: 2px;
}
.consent-field {
display: flex;
align-items: flex-start;
gap: 8px;
font-size: 12px;
color: var(--text);
margin-bottom: 4px;
cursor: pointer;
}
.consent-field input[type="checkbox"] {
margin-top: 3px;
flex-shrink: 0;
}
.consent-hint {
margin-bottom: 14px;
padding-left: 24px;
}
</style>