feat(trips): trip invite links + optional trip binding on admin invites

Trip invite links (#1143): each trip can have one rotating invite link in its
Share panel. An existing, logged-in user who opens /join/<token> is added to
the trip as a member; an anonymous visitor is sent to the login page and
returned to the invite afterwards — there is no registration from this link.
Reading, rotating or disabling the link all require the share_manage permission.

Admin invite trip binding (#1402): the admin create-invite dialog can now bind
a registration invite to a trip. Someone who registers via that link is
auto-added to the trip as a member (password and OIDC paths), inside the same
atomic step that consumes the invite.

Adds a trip_invite_tokens table and a nullable invite_tokens.trip_id, a shared
owner-safe/idempotent add-by-id helper, the manage + join endpoints, the
JoinTripPage and Share-panel section, the admin trip picker, and the new i18n
keys across every locale. Wiki updated.

Discussion #1143
This commit is contained in:
Maurice
2026-07-02 13:07:46 +02:00
committed by Maurice
parent bfc857d246
commit 5f77fa5cca
71 changed files with 1294 additions and 19 deletions
+3
View File
@@ -25,6 +25,9 @@ export const adminInviteCreateRequestSchema = z.object({
max_uses: z.number().optional(),
expires_in_days: z.number().optional(),
role: z.enum(['user', 'admin']).optional(),
// Optional trip binding (#1402): a user who registers via the link is
// auto-added to this trip. Nullable/absent = a plain registration invite.
trip_id: z.number().int().positive().nullable().optional(),
});
export type AdminInviteCreateRequest = z.infer<typeof adminInviteCreateRequestSchema>;