diff --git a/backend/src/routes/invitations.ts b/backend/src/routes/invitations.ts index 7fc82a2..5ff5862 100644 --- a/backend/src/routes/invitations.ts +++ b/backend/src/routes/invitations.ts @@ -20,13 +20,13 @@ function inviteLinkFor(req: Request, token: string): string { return `${baseUrl.replace(/\/$/, '')}/?invite=${token}`; } -// POST /v1/invitations (nur Vermieter/Admin) — { email, roomId?, role? } -> { invitation, inviteLink } +// POST /v1/invitations (nur Vermieter/Admin) — { email, fullName?, roomId?, role? } -> { invitation, inviteLink } invitationsRouter.post( '/invitations', requireAuth, requireRole('LANDLORD', 'ADMIN'), async (req: AuthedRequest, res: Response) => { - const { email, roomId, role } = req.body || {}; + const { email, fullName, roomId, role } = req.body || {}; if (!email || !String(email).trim()) { return res.status(400).json({ error: 'E-Mail-Adresse erforderlich' }); } @@ -64,6 +64,7 @@ invitationsRouter.post( const invitation = await prisma.invitation.create({ data: { email: normalizedEmail, + fullName: typeof fullName === 'string' && fullName.trim() ? fullName.trim() : null, token, role: invitedRole, roomId: roomId || null, @@ -124,6 +125,7 @@ invitationsRouter.get('/invitations/:token/preview', async (req: Request, res: R } res.status(200).json({ email: invitation.email, + fullName: invitation.fullName, role: invitation.role, room: invitation.room, expiresAt: invitation.expiresAt, diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 6536325..1bc324e 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -237,6 +237,7 @@ model Room { model Invitation { id String @id @default(uuid()) email String + fullName String? @map("full_name") token String @unique role UserRole @default(TENANT) roomId String? @map("room_id") diff --git a/web-dashboard/index.html b/web-dashboard/index.html index a4c62f3..ff67827 100644 --- a/web-dashboard/index.html +++ b/web-dashboard/index.html @@ -656,6 +656,10 @@
+
+ + +
@@ -674,7 +678,7 @@
- + @@ -1305,6 +1309,7 @@ sub.textContent = `Einladung als ${ROLE_LABEL[preview.role] || preview.role}` + (preview.room ? ` für ${preview.room.roomNumber}` : '') + '. Bitte Passwort festlegen.'; document.getElementById('acceptEmail').value = preview.email; + if (preview.fullName) document.getElementById('acceptFullName').value = preview.fullName; form.style.display = 'flex'; form.style.flexDirection = 'column'; @@ -1511,14 +1516,16 @@ submitBtn.disabled = true; submitBtn.textContent = 'Lädt ein…'; try { + const fullName = document.getElementById('inviteName').value.trim() || undefined; const email = document.getElementById('inviteEmail').value.trim(); const roomId = document.getElementById('inviteRoom').value || undefined; const data = await apiFetch('/invitations', { method: 'POST', - body: JSON.stringify({ email, roomId }), + body: JSON.stringify({ email, fullName, roomId }), }); document.getElementById('inviteLinkText').textContent = data.inviteLink; document.getElementById('inviteLinkResult').style.display = 'flex'; + document.getElementById('inviteName').value = ''; document.getElementById('inviteEmail').value = ''; loadInvitations(); } catch (err) { @@ -1551,6 +1558,7 @@ table.style.display = 'table'; data.invitations.forEach(inv => { const tr = document.createElement('tr'); + tr.appendChild(el('td', '', inv.fullName || '–')); tr.appendChild(el('td', '', inv.email)); tr.appendChild(el('td', '', inv.room ? inv.room.roomNumber : '–')); tr.appendChild(el('td', '', `${INVITE_STATUS_LABEL[inv.status] || inv.status}`));