feat(auth): migrate frontend from username to email-only authentication

- Login page: email input replaces username field (type=email, name=email)
- Login server action: reads email, uses i18n error for missing credentials
- AccountSection: email input (type=email) replaces username text field
- New user server action: sends email as required field, drops username
- UsersListPanel: displays and searches by email instead of username
- Admin edit user page: heading and delete confirm use email
- Profile page: fullName fallback uses email, drops @username display
- app.d.ts: email required on User, username removed
- Generated API types: AppUser.email required, username removed; CreateUserRequest.email required, username removed
- i18n: login_label_email, login_error_missing_credentials, admin_col_login updated (de/en/es)
- errors.ts: MISSING_CREDENTIALS → login_error_missing_credentials

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-18 21:34:46 +02:00
committed by marcel
parent 5e01db1c74
commit d816e94a90
19 changed files with 64 additions and 55 deletions

View File

@@ -24,9 +24,8 @@ export const actions: Actions = {
const birthDateRaw = data.get('birthDate') as string;
const result = await api.POST('/api/users', {
body: {
username: data.get('username') as string,
email: data.get('email') as string,
initialPassword: data.get('password') as string,
email: (data.get('email') as string) || undefined,
groupIds: data.getAll('groupIds') as string[],
firstName: (data.get('firstName') as string) || null,
lastName: (data.get('lastName') as string) || null,

View File

@@ -11,9 +11,10 @@ import { m } from '$lib/paraglide/messages.js';
{m.admin_col_login()}
</span>
<input
type="text"
name="username"
type="email"
name="email"
required
autocomplete="email"
class="w-full rounded-sm border border-line px-3 py-2 font-serif text-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
/>
</label>

View File

@@ -22,9 +22,10 @@ describe('Admin new user page rendering', () => {
await expect.element(page.getByText(/Neuen Benutzer anlegen/i)).toBeInTheDocument();
});
it('renders the login input', async () => {
it('renders the email input', async () => {
render(Page, { data: baseData, form: null });
await expect.element(page.getByRole('textbox', { name: /Login/i })).toBeInTheDocument();
const input = document.querySelector<HTMLInputElement>('input[name="email"]');
expect(input).not.toBeNull();
});
it('renders group checkboxes for each available group', async () => {