diff --git a/backend/internal/dto/signup_dto.go b/backend/internal/dto/signup_dto.go index b135a49f..f2ab2c55 100644 --- a/backend/internal/dto/signup_dto.go +++ b/backend/internal/dto/signup_dto.go @@ -3,7 +3,7 @@ package dto type SignUpDto struct { Username string `json:"username" binding:"required,username,min=2,max=50" unorm:"nfc"` Email *string `json:"email" binding:"omitempty,email" unorm:"nfc"` - FirstName string `json:"firstName" binding:"required,min=1,max=50" unorm:"nfc"` + FirstName string `json:"firstName" binding:"max=50" unorm:"nfc"` LastName string `json:"lastName" binding:"max=50" unorm:"nfc"` Token string `json:"token"` } diff --git a/backend/internal/dto/user_dto.go b/backend/internal/dto/user_dto.go index 90e457ff..000b5381 100644 --- a/backend/internal/dto/user_dto.go +++ b/backend/internal/dto/user_dto.go @@ -26,9 +26,9 @@ type UserCreateDto struct { Username string `json:"username" binding:"required,username,min=2,max=50" unorm:"nfc"` Email *string `json:"email" binding:"omitempty,email" unorm:"nfc"` EmailVerified bool `json:"emailVerified"` - FirstName string `json:"firstName" binding:"required,min=1,max=50" unorm:"nfc"` + FirstName string `json:"firstName" binding:"max=50" unorm:"nfc"` LastName string `json:"lastName" binding:"max=50" unorm:"nfc"` - DisplayName string `json:"displayName" binding:"required,min=1,max=100" unorm:"nfc"` + DisplayName string `json:"displayName" binding:"max=100" unorm:"nfc"` IsAdmin bool `json:"isAdmin"` Locale *string `json:"locale"` Disabled bool `json:"disabled"` diff --git a/backend/internal/dto/user_dto_test.go b/backend/internal/dto/user_dto_test.go index 251dfece..62dd6188 100644 --- a/backend/internal/dto/user_dto_test.go +++ b/backend/internal/dto/user_dto_test.go @@ -33,14 +33,24 @@ func TestUserCreateDto_Validate(t *testing.T) { }, wantErr: "Field validation for 'Username' failed on the 'required' tag", }, + { + name: "missing first name", + input: UserCreateDto{ + Username: "testuser", + Email: new("test@example.com"), + LastName: "Doe", + }, + wantErr: "", + }, { name: "missing display name", input: UserCreateDto{ + Username: "testuser", Email: new("test@example.com"), FirstName: "John", LastName: "Doe", }, - wantErr: "Field validation for 'DisplayName' failed on the 'required' tag", + wantErr: "", }, { name: "username contains invalid characters", @@ -73,7 +83,7 @@ func TestUserCreateDto_Validate(t *testing.T) { LastName: "Doe", DisplayName: "John Doe", }, - wantErr: "Field validation for 'FirstName' failed on the 'required' tag", + wantErr: "", }, { name: "last name too long", diff --git a/backend/internal/model/user.go b/backend/internal/model/user.go index 4426512a..bfd90cc2 100644 --- a/backend/internal/model/user.go +++ b/backend/internal/model/user.go @@ -39,7 +39,7 @@ func (u User) WebAuthnDisplayName() string { if u.DisplayName != "" { return u.DisplayName } - return u.FirstName + " " + u.LastName + return u.FullName() } func (u User) WebAuthnIcon() string { return "" } @@ -76,7 +76,16 @@ func (u User) WebAuthnCredentialDescriptors() (descriptors []protocol.Credential } func (u User) FullName() string { - return u.FirstName + " " + u.LastName + fullname := strings.TrimSpace(u.FirstName + " " + u.LastName) + if fullname != "" { + return fullname + } + + if u.DisplayName != "" { + return u.DisplayName + } + + return u.Username } func (u User) Initials() string { diff --git a/frontend/src/lib/components/signup/signup-form.svelte b/frontend/src/lib/components/signup/signup-form.svelte index f0a0ca41..ea57f85c 100644 --- a/frontend/src/lib/components/signup/signup-form.svelte +++ b/frontend/src/lib/components/signup/signup-form.svelte @@ -26,7 +26,7 @@ }; const formSchema = z.object({ - firstName: z.string().min(1).max(50), + firstName: z.string().max(50), lastName: emptyToUndefined(z.string().max(50).optional()), username: usernameSchema, email: get(appConfigStore).requireUserEmail ? z.email() : emptyToUndefined(z.email().optional()) @@ -52,12 +52,12 @@
+ + +
- - -
diff --git a/frontend/src/lib/utils/form-util.ts b/frontend/src/lib/utils/form-util.ts index 3c5f0313..7c665608 100644 --- a/frontend/src/lib/utils/form-util.ts +++ b/frontend/src/lib/utils/form-util.ts @@ -122,6 +122,11 @@ export function createForm>(schema: T, initialValu } function isRequired(fieldSchema: z.ZodTypeAny): boolean { + // Handle string allow empty + if (fieldSchema instanceof z.ZodString) { + return fieldSchema.minLength !== null && fieldSchema.minLength > 0; + } + // Handle unions like callbackUrlSchema if (fieldSchema instanceof z.ZodUnion) { return !fieldSchema.def.options.some((o: any) => { @@ -138,6 +143,7 @@ export function createForm>(schema: T, initialValu if (fieldSchema instanceof z.ZodOptional || fieldSchema instanceof z.ZodDefault) { return false; } + return true; } diff --git a/frontend/src/routes/settings/account/account-form.svelte b/frontend/src/routes/settings/account/account-form.svelte index c0c231fc..9152cdf0 100644 --- a/frontend/src/routes/settings/account/account-form.svelte +++ b/frontend/src/routes/settings/account/account-form.svelte @@ -35,9 +35,9 @@ const userService = new UserService(); const formSchema = z.object({ - firstName: z.string().min(1).max(50), + firstName: z.string().max(50), lastName: emptyToUndefined(z.string().max(50).optional()), - displayName: z.string().min(1).max(100), + displayName: z.string().max(100), username: usernameSchema, email: get(appConfigStore).requireUserEmail ? z.email() : emptyToUndefined(z.email().optional()) }); @@ -52,7 +52,7 @@ if (!hasManualDisplayNameEdit) { $inputs.displayName.value = `${$inputs.firstName.value}${ $inputs.lastName?.value ? ' ' + $inputs.lastName.value : '' - }`; + }`.trim(); } } @@ -91,6 +91,8 @@
+ + (hasManualDisplayNameEdit = true)} /> - -
diff --git a/frontend/src/routes/settings/admin/users/user-form.svelte b/frontend/src/routes/settings/admin/users/user-form.svelte index 3513abbc..83d8a745 100644 --- a/frontend/src/routes/settings/admin/users/user-form.svelte +++ b/frontend/src/routes/settings/admin/users/user-form.svelte @@ -40,9 +40,9 @@ }; const formSchema = z.object({ - firstName: z.string().min(1).max(50), + firstName: z.string().max(50), lastName: emptyToUndefined(z.string().max(50).optional()), - displayName: z.string().min(1).max(100), + displayName: z.string().max(100), username: usernameSchema, email: get(appConfigStore).requireUserEmail ? z.email() @@ -67,7 +67,7 @@ if (!hasManualDisplayNameEdit) { $inputs.displayName.value = `${$inputs.firstName.value}${ $inputs.lastName?.value ? ' ' + $inputs.lastName.value : '' - }`; + }`.trim(); } } @@ -75,13 +75,6 @@
- - - (hasManualDisplayNameEdit = true)} - bind:input={$inputs.displayName} - />
+ + + (hasManualDisplayNameEdit = true)} + bind:input={$inputs.displayName} + />
{ await expect(page.getByText('Taal', { exact: true })).toBeVisible(); // Check if the validation messages are translated because they are provided by Zod - await page.getByRole('textbox', { name: 'Voornaam' }).fill(''); + await page.getByRole('textbox', { name: 'Gebruikersnaam' }).fill(''); await page.getByRole('button', { name: 'Opslaan' }).click(); - await expect(page.getByText('Te kort: verwacht dat string >=1 tekens heeft')).toBeVisible(); + await expect(page.getByText('Te kort: verwacht dat string >=2 tekens heeft')).toBeVisible(); // Clear all cookies and sign in again to check if the language is still set to Dutch await page.context().clearCookies(); @@ -74,9 +74,9 @@ test('Change Locale', async ({ page }) => { await expect(page.getByText('Taal', { exact: true })).toBeVisible(); - await page.getByRole('textbox', { name: 'Voornaam' }).fill(''); + await page.getByRole('textbox', { name: 'Gebruikersnaam' }).fill(''); await page.getByRole('button', { name: 'Opslaan' }).click(); - await expect(page.getByText('Te kort: verwacht dat string >=1 tekens heeft')).toBeVisible(); + await expect(page.getByText('Te kort: verwacht dat string >=2 tekens heeft')).toBeVisible(); }); test('Add passkey to an account', async ({ page }) => {