feat(join): implement A4 join household page (/join/[token])

- schema.d.ts: add GET /v1/invites/{code}, InviteInfoResponse, AcceptInviteRequest; update acceptInvite operation
- hooks.server.ts: add /join to PUBLIC_ROUTES; redirect authenticated users on /join/* to /
- +page.server.ts: load validates token (invalid:true on 404); action creates account + joins + sets session cookie
- HouseholdIdentityPanel.svelte: logo, household name (Fraunces), inviter text, static permissions list
- JoinForm.svelte: name/email/password + show/hide toggle, "Haushalt beitreten" CTA, field errors, pre-fill
- +page.svelte: no-chrome layout, mobile (banner+form stacked) / desktop (400px panel + flex:1) split, invalid-token inline state

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-10 21:31:03 +02:00
parent 92f25e56fc
commit 6950b3d8db
10 changed files with 680 additions and 3 deletions

View File

@@ -148,6 +148,22 @@ export interface paths {
patch?: never;
trace?: never;
};
"/v1/invites/{code}": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get: operations["getInviteInfo"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/v1/invites/{code}/accept": {
parameters: {
query?: never;
@@ -739,6 +755,20 @@ export interface components {
data?: components["schemas"]["AcceptInviteResponse"];
meta?: components["schemas"]["Meta"];
};
InviteInfoResponse: {
householdName?: string;
inviterName?: string;
};
ApiResponseInviteInfoResponse: {
status?: string;
data?: components["schemas"]["InviteInfoResponse"];
meta?: components["schemas"]["Meta"];
};
AcceptInviteRequest: {
name: string;
email: string;
password: string;
};
Meta: {
pagination?: components["schemas"]["Pagination"];
};
@@ -1345,7 +1375,7 @@ export interface operations {
};
};
};
acceptInvite: {
getInviteInfo: {
parameters: {
query?: never;
header?: never;
@@ -1355,6 +1385,37 @@ export interface operations {
cookie?: never;
};
requestBody?: never;
responses: {
/** @description OK */
200: {
headers: {
[name: string]: unknown;
};
content: {
"*/*": components["schemas"]["ApiResponseInviteInfoResponse"];
};
};
/** @description Not found */
404: {
headers: { [name: string]: unknown };
content: { "*/*": components["schemas"]["ApiError"] };
};
};
};
acceptInvite: {
parameters: {
query?: never;
header?: never;
path: {
code: string;
};
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["AcceptInviteRequest"];
};
};
responses: {
/** @description OK */
200: {
@@ -1365,6 +1426,16 @@ export interface operations {
"*/*": components["schemas"]["ApiResponseAcceptInviteResponse"];
};
};
/** @description Email already registered */
409: {
headers: { [name: string]: unknown };
content: { "*/*": components["schemas"]["ApiError"] };
};
/** @description Invite not found or invalid */
404: {
headers: { [name: string]: unknown };
content: { "*/*": components["schemas"]["ApiError"] };
};
};
};
listCategories: {