Name des Mieters bei Einladung erfassen und anzeigen

This commit is contained in:
Giuseppe Lombardo 2026-08-13 17:24:01 +00:00
parent 29ebb2e6b7
commit e36a65e63c
3 changed files with 15 additions and 4 deletions

View File

@ -20,13 +20,13 @@ function inviteLinkFor(req: Request, token: string): string {
return `${baseUrl.replace(/\/$/, '')}/?invite=${token}`; 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( invitationsRouter.post(
'/invitations', '/invitations',
requireAuth, requireAuth,
requireRole('LANDLORD', 'ADMIN'), requireRole('LANDLORD', 'ADMIN'),
async (req: AuthedRequest, res: Response) => { async (req: AuthedRequest, res: Response) => {
const { email, roomId, role } = req.body || {}; const { email, fullName, roomId, role } = req.body || {};
if (!email || !String(email).trim()) { if (!email || !String(email).trim()) {
return res.status(400).json({ error: 'E-Mail-Adresse erforderlich' }); return res.status(400).json({ error: 'E-Mail-Adresse erforderlich' });
} }
@ -64,6 +64,7 @@ invitationsRouter.post(
const invitation = await prisma.invitation.create({ const invitation = await prisma.invitation.create({
data: { data: {
email: normalizedEmail, email: normalizedEmail,
fullName: typeof fullName === 'string' && fullName.trim() ? fullName.trim() : null,
token, token,
role: invitedRole, role: invitedRole,
roomId: roomId || null, roomId: roomId || null,
@ -124,6 +125,7 @@ invitationsRouter.get('/invitations/:token/preview', async (req: Request, res: R
} }
res.status(200).json({ res.status(200).json({
email: invitation.email, email: invitation.email,
fullName: invitation.fullName,
role: invitation.role, role: invitation.role,
room: invitation.room, room: invitation.room,
expiresAt: invitation.expiresAt, expiresAt: invitation.expiresAt,

View File

@ -237,6 +237,7 @@ model Room {
model Invitation { model Invitation {
id String @id @default(uuid()) id String @id @default(uuid())
email String email String
fullName String? @map("full_name")
token String @unique token String @unique
role UserRole @default(TENANT) role UserRole @default(TENANT)
roomId String? @map("room_id") roomId String? @map("room_id")

View File

@ -656,6 +656,10 @@
<div class="invite-card"> <div class="invite-card">
<div id="inviteFormError" style="display:none" class="form-error"></div> <div id="inviteFormError" style="display:none" class="form-error"></div>
<form id="inviteForm" class="invite-form-row"> <form id="inviteForm" class="invite-form-row">
<div class="field">
<label for="inviteName">Name des Mieters (optional)</label>
<input type="text" id="inviteName" placeholder="Max Mustermann" />
</div>
<div class="field"> <div class="field">
<label for="inviteEmail">E-Mail-Adresse des Mieters</label> <label for="inviteEmail">E-Mail-Adresse des Mieters</label>
<input type="email" id="inviteEmail" required placeholder="mieter@beispiel.de" /> <input type="email" id="inviteEmail" required placeholder="mieter@beispiel.de" />
@ -674,7 +678,7 @@
</div> </div>
<table class="invite-table" id="inviteTable" style="display:none"> <table class="invite-table" id="inviteTable" style="display:none">
<thead> <thead>
<tr><th>E-Mail</th><th>Zimmer</th><th>Status</th><th>Eingeladen am</th><th></th></tr> <tr><th>Name</th><th>E-Mail</th><th>Zimmer</th><th>Status</th><th>Eingeladen am</th><th></th></tr>
</thead> </thead>
<tbody id="inviteTableBody"></tbody> <tbody id="inviteTableBody"></tbody>
</table> </table>
@ -1305,6 +1309,7 @@
sub.textContent = `Einladung als ${ROLE_LABEL[preview.role] || preview.role}` + sub.textContent = `Einladung als ${ROLE_LABEL[preview.role] || preview.role}` +
(preview.room ? ` für ${preview.room.roomNumber}` : '') + '. Bitte Passwort festlegen.'; (preview.room ? ` für ${preview.room.roomNumber}` : '') + '. Bitte Passwort festlegen.';
document.getElementById('acceptEmail').value = preview.email; document.getElementById('acceptEmail').value = preview.email;
if (preview.fullName) document.getElementById('acceptFullName').value = preview.fullName;
form.style.display = 'flex'; form.style.display = 'flex';
form.style.flexDirection = 'column'; form.style.flexDirection = 'column';
@ -1511,14 +1516,16 @@
submitBtn.disabled = true; submitBtn.disabled = true;
submitBtn.textContent = 'Lädt ein…'; submitBtn.textContent = 'Lädt ein…';
try { try {
const fullName = document.getElementById('inviteName').value.trim() || undefined;
const email = document.getElementById('inviteEmail').value.trim(); const email = document.getElementById('inviteEmail').value.trim();
const roomId = document.getElementById('inviteRoom').value || undefined; const roomId = document.getElementById('inviteRoom').value || undefined;
const data = await apiFetch('/invitations', { const data = await apiFetch('/invitations', {
method: 'POST', method: 'POST',
body: JSON.stringify({ email, roomId }), body: JSON.stringify({ email, fullName, roomId }),
}); });
document.getElementById('inviteLinkText').textContent = data.inviteLink; document.getElementById('inviteLinkText').textContent = data.inviteLink;
document.getElementById('inviteLinkResult').style.display = 'flex'; document.getElementById('inviteLinkResult').style.display = 'flex';
document.getElementById('inviteName').value = '';
document.getElementById('inviteEmail').value = ''; document.getElementById('inviteEmail').value = '';
loadInvitations(); loadInvitations();
} catch (err) { } catch (err) {
@ -1551,6 +1558,7 @@
table.style.display = 'table'; table.style.display = 'table';
data.invitations.forEach(inv => { data.invitations.forEach(inv => {
const tr = document.createElement('tr'); const tr = document.createElement('tr');
tr.appendChild(el('td', '', inv.fullName || ''));
tr.appendChild(el('td', '', inv.email)); tr.appendChild(el('td', '', inv.email));
tr.appendChild(el('td', '', inv.room ? inv.room.roomNumber : '')); tr.appendChild(el('td', '', inv.room ? inv.room.roomNumber : ''));
tr.appendChild(el('td', '', `<span class="invite-status-chip ${inv.status}">${INVITE_STATUS_LABEL[inv.status] || inv.status}</span>`)); tr.appendChild(el('td', '', `<span class="invite-status-chip ${inv.status}">${INVITE_STATUS_LABEL[inv.status] || inv.status}</span>`));