fix(invite-ui): accessibility, i18n, and load function tests
Some checks failed
CI / Unit & Component Tests (push) Failing after 3m7s
CI / OCR Service Tests (push) Successful in 37s
CI / Backend Unit Tests (push) Failing after 2m47s
CI / Unit & Component Tests (pull_request) Failing after 2m34s
CI / OCR Service Tests (pull_request) Successful in 34s
CI / Backend Unit Tests (pull_request) Failing after 2m43s

- WCAG 1.3.1: add for/id pairs to all 6 fields in the create-invite form
- WCAG 1.4.1: add status icon (●○✕⏱) to status badge alongside label
- Add aria-label to copy-link buttons in the invite table
- Replace hardcoded German strings with i18n keys (Alle, Widerrufen,
  Link kopieren, Kopiert, Abbrechen)
- Increase filter button touch targets py-1.5 → py-2
- Add 5 unit tests for register page load function (no-code, ok,
  error-with-code, error-without-code, URL-encoding)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-19 09:10:42 +02:00
parent f8f5ea634e
commit 9fc4993fca
2 changed files with 126 additions and 8 deletions

View File

@@ -62,6 +62,21 @@ function statusColor(status: string) {
return 'text-gray-500 bg-gray-100';
}
}
function statusIcon(status: string) {
switch (status) {
case 'active':
return '●';
case 'exhausted':
return '○';
case 'revoked':
return '✕';
case 'expired':
return '⏱';
default:
return '';
}
}
</script>
<svelte:head>
@@ -81,15 +96,15 @@ function statusColor(status: string) {
>
<a
href="/admin/invites"
class="px-3 py-1.5 transition-colors {data.status !== 'all' ? 'bg-primary text-primary-fg' : 'bg-surface text-ink-2 hover:bg-muted'}"
class="px-3 py-2 transition-colors {data.status !== 'all' ? 'bg-primary text-primary-fg' : 'bg-surface text-ink-2 hover:bg-muted'}"
>
{m.admin_invite_status_active()}
</a>
<a
href="/admin/invites?status=all"
class="border-l border-line px-3 py-1.5 transition-colors {data.status === 'all' ? 'bg-primary text-primary-fg' : 'bg-surface text-ink-2 hover:bg-muted'}"
class="border-l border-line px-3 py-2 transition-colors {data.status === 'all' ? 'bg-primary text-primary-fg' : 'bg-surface text-ink-2 hover:bg-muted'}"
>
Alle
{m.admin_btn_show_all()}
</a>
</div>
@@ -136,7 +151,7 @@ function statusColor(status: string) {
onclick={() => copyLink(form!.created!.id, form!.created!.shareableUrl)}
class="flex-shrink-0 rounded border border-green-300 bg-white px-3 py-1.5 font-sans text-xs font-bold text-green-700 transition-colors hover:bg-green-50"
>
{copiedId === form.created.id ? '✓' : 'Kopieren'}
{copiedId === form.created.id ? m.admin_btn_copied() : m.admin_btn_copy_link()}
</button>
</div>
</div>
@@ -155,11 +170,13 @@ function statusColor(status: string) {
>
<div class="sm:col-span-2">
<label
for="invite-label"
class="mb-1 block font-sans text-xs font-bold tracking-widest text-ink-2 uppercase"
>
{m.admin_new_invite_label()}
</label>
<input
id="invite-label"
type="text"
name="label"
class="block w-full border border-line px-3 py-2 font-serif text-sm text-ink focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
@@ -167,11 +184,13 @@ function statusColor(status: string) {
</div>
<div>
<label
for="invite-max-uses"
class="mb-1 block font-sans text-xs font-bold tracking-widest text-ink-2 uppercase"
>
{m.admin_new_invite_max_uses()}
</label>
<input
id="invite-max-uses"
type="number"
name="maxUses"
min="1"
@@ -180,11 +199,13 @@ function statusColor(status: string) {
</div>
<div>
<label
for="invite-expires-at"
class="mb-1 block font-sans text-xs font-bold tracking-widest text-ink-2 uppercase"
>
{m.admin_new_invite_expires()}
</label>
<input
id="invite-expires-at"
type="datetime-local"
name="expiresAt"
class="block w-full border border-line px-3 py-2 font-serif text-sm text-ink focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
@@ -192,11 +213,13 @@ function statusColor(status: string) {
</div>
<div>
<label
for="invite-prefill-first"
class="mb-1 block font-sans text-xs font-bold tracking-widest text-ink-2 uppercase"
>
{m.admin_new_invite_prefill_first()}
</label>
<input
id="invite-prefill-first"
type="text"
name="prefillFirstName"
class="block w-full border border-line px-3 py-2 font-serif text-sm text-ink focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
@@ -204,11 +227,13 @@ function statusColor(status: string) {
</div>
<div>
<label
for="invite-prefill-last"
class="mb-1 block font-sans text-xs font-bold tracking-widest text-ink-2 uppercase"
>
{m.admin_new_invite_prefill_last()}
</label>
<input
id="invite-prefill-last"
type="text"
name="prefillLastName"
class="block w-full border border-line px-3 py-2 font-serif text-sm text-ink focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
@@ -216,11 +241,13 @@ function statusColor(status: string) {
</div>
<div class="sm:col-span-2">
<label
for="invite-prefill-email"
class="mb-1 block font-sans text-xs font-bold tracking-widest text-ink-2 uppercase"
>
{m.admin_new_invite_prefill_email()}
</label>
<input
id="invite-prefill-email"
type="email"
name="prefillEmail"
class="block w-full border border-line px-3 py-2 font-serif text-sm text-ink focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
@@ -237,7 +264,7 @@ function statusColor(status: string) {
onclick={() => (showNewForm = false)}
class="px-4 py-2 font-sans text-xs font-bold tracking-widest text-ink-2 uppercase transition-colors hover:text-ink"
>
Abbrechen
{m.btn_cancel()}
</button>
<button
type="submit"
@@ -301,8 +328,10 @@ function statusColor(status: string) {
</td>
<td class="px-4 py-3">
<span
class="rounded px-2 py-0.5 font-sans text-xs font-bold {statusColor(invite.status)}"
class="inline-flex items-center gap-1 rounded px-2 py-0.5 font-sans text-xs font-bold {statusColor(invite.status)}"
aria-label={statusLabel(invite.status)}
>
<span aria-hidden="true">{statusIcon(invite.status)}</span>
{statusLabel(invite.status)}
</span>
</td>
@@ -311,9 +340,10 @@ function statusColor(status: string) {
type="button"
onclick={() => copyLink(invite.id, invite.shareableUrl)}
class="font-sans text-xs text-brand-navy/60 transition-colors hover:text-brand-navy"
aria-label="{m.admin_btn_copy_link()}: {invite.displayCode}"
title={invite.shareableUrl}
>
{copiedId === invite.id ? '✓ Kopiert' : 'Link kopieren'}
{copiedId === invite.id ? m.admin_btn_copied() : m.admin_btn_copy_link()}
</button>
</td>
<td class="px-4 py-3 text-right">
@@ -327,7 +357,7 @@ function statusColor(status: string) {
}}
class="font-sans text-xs font-bold tracking-widest text-red-500 uppercase transition-colors hover:text-red-700"
>
Widerrufen
{m.admin_btn_revoke()}
</button>
</form>
{/if}