diff --git a/frontend/src/routes/persons/[id]/edit/+page.server.ts b/frontend/src/routes/persons/[id]/edit/+page.server.ts
index 13e39509..e814737a 100644
--- a/frontend/src/routes/persons/[id]/edit/+page.server.ts
+++ b/frontend/src/routes/persons/[id]/edit/+page.server.ts
@@ -86,5 +86,53 @@ export const actions = {
}
throw redirect(303, `/persons/${targetPersonId}`);
+ },
+
+ addAlias: async ({ request, params, fetch }) => {
+ const formData = await request.formData();
+ const lastName = formData.get('lastName')?.toString().trim();
+ const firstName = formData.get('firstName')?.toString().trim() || undefined;
+ const type = formData.get('type')?.toString();
+
+ if (!lastName) {
+ return fail(400, { aliasError: 'Nachname ist ein Pflichtfeld.' });
+ }
+ if (!type) {
+ return fail(400, { aliasError: 'Art ist ein Pflichtfeld.' });
+ }
+
+ const api = createApiClient(fetch);
+ const result = await api.POST('/api/persons/{id}/aliases', {
+ params: { path: { id: params.id } },
+ body: { lastName, firstName, type: type as 'BIRTH' | 'WIDOWED' | 'DIVORCED' | 'OTHER' }
+ });
+
+ if (!result.response.ok) {
+ const code = (result.error as unknown as { code?: string })?.code;
+ return fail(result.response.status, { aliasError: getErrorMessage(code) });
+ }
+
+ return { aliasSuccess: true };
+ },
+
+ removeAlias: async ({ request, params, fetch }) => {
+ const formData = await request.formData();
+ const aliasId = formData.get('aliasId')?.toString();
+
+ if (!aliasId) {
+ return fail(400, { aliasError: 'Alias ID fehlt.' });
+ }
+
+ const api = createApiClient(fetch);
+ const result = await api.DELETE('/api/persons/{id}/aliases/{aliasId}', {
+ params: { path: { id: params.id, aliasId } }
+ });
+
+ if (!result.response.ok) {
+ const code = (result.error as unknown as { code?: string })?.code;
+ return fail(result.response.status, { aliasError: getErrorMessage(code) });
+ }
+
+ return { aliasSuccess: true };
}
};
diff --git a/frontend/src/routes/persons/[id]/edit/+page.svelte b/frontend/src/routes/persons/[id]/edit/+page.svelte
index 4e641553..dd0f539e 100644
--- a/frontend/src/routes/persons/[id]/edit/+page.svelte
+++ b/frontend/src/routes/persons/[id]/edit/+page.svelte
@@ -51,12 +51,7 @@ const person = $derived(data.person);
-
+
diff --git a/frontend/src/routes/persons/[id]/edit/NameHistoryEditCard.svelte b/frontend/src/routes/persons/[id]/edit/NameHistoryEditCard.svelte
index f602c87a..a7e6c185 100644
--- a/frontend/src/routes/persons/[id]/edit/NameHistoryEditCard.svelte
+++ b/frontend/src/routes/persons/[id]/edit/NameHistoryEditCard.svelte
@@ -1,10 +1,8 @@
@@ -108,6 +45,10 @@ async function addAlias() {
{m.person_alias_heading()}
+ {#if aliasError}
+
{aliasError}
+ {/if}
+
{#if sorted.length === 0}
{m.person_alias_empty()}
{:else}
@@ -117,7 +58,7 @@ async function addAlias() {
{typeLabel(alias.type)}
- {alias.firstName ?? personFirstName}
+ {#if alias.firstName}{alias.firstName}{/if}
{alias.lastName}
@@ -155,52 +96,50 @@ async function addAlias() {
{m.person_alias_add_heading()}
- {#if addError}
-
{addError}
- {/if}
+
@@ -213,18 +152,33 @@ async function addAlias() {
-
+
+
+
diff --git a/frontend/src/routes/persons/[id]/edit/NameHistoryEditCard.svelte.test.ts b/frontend/src/routes/persons/[id]/edit/NameHistoryEditCard.svelte.test.ts
new file mode 100644
index 00000000..ce5510cd
--- /dev/null
+++ b/frontend/src/routes/persons/[id]/edit/NameHistoryEditCard.svelte.test.ts
@@ -0,0 +1,86 @@
+import { describe, it, expect } from 'vitest';
+import { render } from 'vitest-browser-svelte';
+import { page } from 'vitest/browser';
+import NameHistoryEditCard from './NameHistoryEditCard.svelte';
+
+const aliases = [
+ { id: 'a1', lastName: 'de Gruyter', firstName: null, type: 'BIRTH', sortOrder: 0 },
+ { id: 'a2', lastName: 'Schmidt', firstName: 'Maria', type: 'WIDOWED', sortOrder: 1 }
+];
+
+describe('NameHistoryEditCard', () => {
+ it('should render alias rows when aliases exist', async () => {
+ render(NameHistoryEditCard, { aliases, canWrite: true });
+
+ await expect.element(page.getByText('de Gruyter')).toBeInTheDocument();
+ await expect.element(page.getByText('Schmidt')).toBeInTheDocument();
+ });
+
+ it('should show empty state when no aliases', async () => {
+ render(NameHistoryEditCard, { aliases: [], canWrite: true });
+
+ const emptyText = document.querySelector('.italic');
+ expect(emptyText).not.toBeNull();
+ });
+
+ it('should show add form when canWrite is true', async () => {
+ render(NameHistoryEditCard, { aliases: [], canWrite: true });
+
+ const form = document.querySelector('form[action="?/addAlias"]');
+ expect(form).not.toBeNull();
+ });
+
+ it('should hide add form when canWrite is false', async () => {
+ render(NameHistoryEditCard, { aliases: [], canWrite: false });
+
+ const form = document.querySelector('form[action="?/addAlias"]');
+ expect(form).toBeNull();
+ });
+
+ it('should hide delete buttons when canWrite is false', async () => {
+ render(NameHistoryEditCard, { aliases, canWrite: false });
+
+ const deleteButtons = document.querySelectorAll('button[aria-label*="Entfernen"]');
+ expect(deleteButtons.length).toBe(0);
+ });
+
+ it('should show delete buttons when canWrite is true', async () => {
+ render(NameHistoryEditCard, { aliases, canWrite: true });
+
+ const deleteButtons = document.querySelectorAll('button[aria-label*="Entfernen"]');
+ expect(deleteButtons.length).toBe(2);
+ });
+
+ it('should include alias name in delete button aria-label', async () => {
+ render(NameHistoryEditCard, { aliases: [aliases[0]], canWrite: true });
+
+ const btn = document.querySelector('button[aria-label*="de Gruyter"]');
+ expect(btn).not.toBeNull();
+ });
+
+ it('should show delete modal when delete button is clicked', async () => {
+ render(NameHistoryEditCard, { aliases: [aliases[0]], canWrite: true });
+
+ const deleteBtn = document.querySelector('button[aria-label*="de Gruyter"]')!;
+ deleteBtn.dispatchEvent(new MouseEvent('click', { bubbles: true }));
+
+ await expect.element(page.getByText('Alias entfernen?')).toBeInTheDocument();
+ });
+
+ it('should show alias error when provided', async () => {
+ render(NameHistoryEditCard, {
+ aliases: [],
+ canWrite: true,
+ aliasError: 'Something went wrong'
+ });
+
+ await expect.element(page.getByText('Something went wrong')).toBeInTheDocument();
+ });
+
+ it('should have required attribute on lastName input', async () => {
+ render(NameHistoryEditCard, { aliases: [], canWrite: true });
+
+ const input = document.querySelector('input[name="lastName"]') as HTMLInputElement;
+ expect(input.required).toBe(true);
+ });
+});