feat(join): A4 — Join household (accept invite) #61

Merged
marcel merged 24 commits from feat/issue-21-join-household into master 2026-04-19 14:29:14 +02:00
4 changed files with 23 additions and 13 deletions
Showing only changes of commit 6aed303627 - Show all commits

View File

@@ -24,7 +24,7 @@ public class SecurityConfig {
.authorizeHttpRequests(auth -> auth
.requestMatchers("/v1/auth/signup", "/v1/auth/login").permitAll()
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
.requestMatchers("/v1/invites/*").permitAll()
.requestMatchers("/v1/invites/**").permitAll()
.requestMatchers("/v1/admin/**").hasAuthority("ROLE_ADMIN")
.anyRequest().authenticated())
.exceptionHandling(ex -> ex

View File

@@ -10,6 +10,7 @@ import org.springframework.web.context.WebApplicationContext;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
class SecurityConfigTest extends AbstractIntegrationTest {
@@ -33,6 +34,15 @@ class SecurityConfigTest extends AbstractIntegrationTest {
.andExpect(status().isNotFound());
}
@Test
void inviteAcceptEndpointIsAccessibleWithoutAuthentication() throws Exception {
// 400 = validation error (empty body), but NOT 401 — proves the path is permitted
mockMvc.perform(post("/v1/invites/ANYCODE/accept")
.contentType(org.springframework.http.MediaType.APPLICATION_JSON)
.content("{}"))
.andExpect(status().isBadRequest());
}
@Test
void protectedEndpointRequiresAuthentication() throws Exception {
mockMvc.perform(get("/v1/households/mine"))

View File

@@ -26,7 +26,7 @@
<!-- Desktop layout (≥ 1024px): two-column side by side -->
<div class="flex min-h-screen flex-col lg:flex-row">
<!-- Left / top: green-tint panel -->
<div class="bg-[var(--green-tint)] p-6 lg:flex lg:w-[400px] lg:flex-shrink-0 lg:items-center lg:justify-center lg:p-12">
<div class="bg-[var(--green-dark)] p-6 lg:flex lg:w-[400px] lg:flex-shrink-0 lg:items-center lg:justify-center lg:p-12">
<HouseholdIdentityPanel
householdName={data.householdName ?? ''}
inviterName={data.inviterName ?? ''}

View File

@@ -2,38 +2,38 @@
let { householdName, inviterName }: { householdName: string; inviterName: string } = $props();
</script>
<div class="flex flex-col items-center gap-4 rounded-2xl bg-[var(--green-tint)] p-6 text-center">
<div class="flex flex-col items-center gap-4 rounded-2xl bg-[var(--green-dark)] p-6 text-center">
<!-- App logo -->
<span class="text-[48px]" aria-hidden="true">🥗</span>
<!-- Household name -->
<div>
<h2
class="font-[var(--font-display)] text-[22px] font-semibold tracking-[-0.02em] text-[var(--color-text)]"
class="font-[var(--font-display)] text-[22px] font-semibold tracking-[-0.02em] text-white"
>
{householdName}
</h2>
<p class="mt-1 font-[var(--font-sans)] text-[12px] text-[var(--color-text-muted)]">
<p class="mt-1 font-[var(--font-sans)] text-[12px] text-[var(--green-light)]">
Eingeladen von {inviterName}
</p>
</div>
<!-- Permissions info box -->
<div class="w-full rounded-xl bg-white/20 px-4 py-3 text-left">
<p class="mb-2 font-[var(--font-sans)] text-[11px] font-medium uppercase tracking-wide text-[var(--color-text-muted)]">
<div class="w-full rounded-xl bg-white/10 px-4 py-3 text-left">
<p class="mb-2 font-[var(--font-sans)] text-[11px] font-medium uppercase tracking-wide text-[var(--green-light)]">
Als Mitglied kannst du
</p>
<ul class="flex flex-col gap-1.5">
<li class="flex items-center gap-2 font-[var(--font-sans)] text-[13px] text-[var(--color-text)]">
<span class="text-[var(--green)] font-semibold" aria-hidden="true"></span>
<li class="flex items-center gap-2 font-[var(--font-sans)] text-[13px] text-white">
<span class="font-semibold text-[var(--green-light)]" aria-hidden="true"></span>
Wochenplan einsehen
</li>
<li class="flex items-center gap-2 font-[var(--font-sans)] text-[13px] text-[var(--color-text)]">
<span class="text-[var(--green)] font-semibold" aria-hidden="true"></span>
<li class="flex items-center gap-2 font-[var(--font-sans)] text-[13px] text-white">
<span class="font-semibold text-[var(--green-light)]" aria-hidden="true"></span>
Einkaufsliste abhaken
</li>
<li class="flex items-center gap-2 font-[var(--font-sans)] text-[13px] text-[var(--color-text)]">
<span class="text-[var(--green)] font-semibold" aria-hidden="true"></span>
<li class="flex items-center gap-2 font-[var(--font-sans)] text-[13px] text-white">
<span class="font-semibold text-[var(--green-light)]" aria-hidden="true"></span>
Artikel zur Liste hinzufügen
</li>
</ul>