feat(members): implement /members page — Kachel-Ansicht (E2, issue #48) #58

Merged
marcel merged 10 commits from feat/issue-48-members-kachel into master 2026-04-10 20:34:24 +02:00

10 Commits

Author SHA1 Message Date
60d84c0c94 fix(members): add error toasts for invite failures + Content-Type header
- handleInviteClick: show toast and bail early when POST /invites fails
- handleRegenerate: show toast when regeneration POST fails
- handleRoleChange: add Content-Type: application/json header on PATCH

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 20:31:19 +02:00
9d3be84a0c fix(members): guard against removing the last planner from household
removeMember now checks the planner count before deleting a planner
member. Throws ConflictException("Cannot remove the last planner")
when only one planner remains, matching the spec requirement in S4.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 20:30:55 +02:00
eb5699577b feat(backend): make invite share URL base configurable via app.base-url
Replaces hardcoded \"https://yourapp.com\" with a Spring property.

- application.yml: app.base-url defaults to http://localhost:5173
- application-docker.yml: reads APP_BASE_URL env var, same default
- HouseholdService: injects @Value("${app.base-url}") and uses it in
  toInviteResponse() to build shareUrl
- HouseholdServiceTest: sets field via ReflectionTestUtils in @BeforeEach;
  adds test asserting shareUrl starts with configured base URL

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 20:07:53 +02:00
05476ecaab fix(members/invites): unwrap ApiResponse before returning to client
POST /members/invites was returning the full ApiResponseInviteResponse
wrapper. The client set activeInvite directly from the response body,
so shareUrl/inviteCode/expiresAt were missing (nested under .data).
Fixed to return data?.data — the inner InviteResponse — matching the
shape that InvitePanel and page.server.ts already expect.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 19:57:13 +02:00
c40b0fe095 fix(members): create invite on first click when no active invite exists
When activeInvite is null and the user clicks the invite card, POST to
/members/invites first to generate a code, then toggle the panel open.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 19:55:00 +02:00
4e67ff4258 feat(members): align grid UI to spec — avatar colors, badges, join date, invite panel
- MemberCard: white bg, 1px border + shadow-card, centered column layout,
  avatar color by role (green-dark/blue), role badge with role-specific colors,
  join date "seit DD.MM.YYYY", Du-badge below join date, ⋯ kebab with icons
  and divider, inline role-control with Abbrechen, blue editing border #B5D4F4
- InviteCard: white bg, 1.5px dashed border, min-height 180px, plus circle,
  label "Mitglied einladen", full hover state (green border/bg/icon/label)
- InvitePanel: white bg, title "Einladelink teilen", description, mono link
  box, yellow expiry pill when ≤ 24h, text-link "Neuen Link generieren"
- RemoveDialog: white bg, padding 28px 32px, "?" in title, updated body text
- +page.server.ts: expose householdName from locals.haushalt
- +page.svelte: subtitle "{n} Mitglieder · {householdName}"
- Tests: add join date format test, Abbrechen test, InvitePanel title test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 19:54:26 +02:00
df3b774f0c fix(members): match settings page padding and h1 typography
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 19:45:32 +02:00
1b5704c8b5 feat(members): implement /members page — Kachel-Ansicht (E2, issue #48)
Backend:
- Rename V006 migration to V026 (avoid conflict with existing V006)
- Migration adds invalidated_at + partial unique index on household_invite

Frontend:
- Toast.svelte — new system component (message + dismiss)
- SegmentedControl.svelte — new system component (options, value, onchange)
- members/+page.server.ts — loads members + active invite
- members/[userId]/+server.ts — DELETE/PATCH proxy
- members/invites/+server.ts — POST (regenerate) proxy
- MemberCard.svelte — tile with avatar, kebab, inline role edit
- RemoveDialog.svelte — confirmation dialog (desktop modal + BottomSheet mobile)
- InviteCard.svelte + InvitePanel.svelte — invite management UI
- MemberGrid.svelte — responsive 4/2-col grid with sorted members
- members/+page.svelte — page composing all components with optimistic updates

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 19:01:08 +02:00
b04f2c51d2 feat(members): update schema.d.ts with GET invites, DELETE/PATCH member types
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 18:42:52 +02:00
d1e4b6c49e feat(members): implement DELETE/PATCH member + GET invites backend endpoints
- Add V006 migration: invalidated_at column + partial unique index on household_invite
- Add findByHouseholdIdAndInvalidatedAtIsNull, findByHouseholdIdAndUserId, countByHouseholdIdAndRole
- Add ChangeRoleRequest DTO
- HouseholdService: getActiveInvite, createInvite (regenerate), removeMember, changeMemberRole
- HouseholdController: GET /v1/households/mine/invites, DELETE/PATCH /v1/households/mine/members/{userId}

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 18:41:38 +02:00