diff --git a/backend/internal/dto/user_dto.go b/backend/internal/dto/user_dto.go index 5a39f46c..10814f3f 100644 --- a/backend/internal/dto/user_dto.go +++ b/backend/internal/dto/user_dto.go @@ -27,7 +27,7 @@ type UserCreateDto struct { Email string `json:"email" binding:"required,email" unorm:"nfc"` FirstName string `json:"firstName" binding:"required,min=1,max=50" unorm:"nfc"` LastName string `json:"lastName" binding:"max=50" unorm:"nfc"` - DisplayName string `json:"displayName" binding:"required,max=100" unorm:"nfc"` + DisplayName string `json:"displayName" binding:"required,min=1,max=100" unorm:"nfc"` IsAdmin bool `json:"isAdmin"` Locale *string `json:"locale"` Disabled bool `json:"disabled"` diff --git a/backend/internal/service/ldap_service.go b/backend/internal/service/ldap_service.go index 6522efcb..8c24505f 100644 --- a/backend/internal/service/ldap_service.go +++ b/backend/internal/service/ldap_service.go @@ -355,6 +355,11 @@ func (s *LdapService) SyncUsers(ctx context.Context, tx *gorm.DB, client *ldap.C IsAdmin: isAdmin, LdapID: ldapId, } + + if newUser.DisplayName == "" { + newUser.DisplayName = strings.TrimSpace(newUser.FirstName + " " + newUser.LastName) + } + dto.Normalize(newUser) err = newUser.Validate() diff --git a/frontend/src/lib/components/form/form-input.svelte b/frontend/src/lib/components/form/form-input.svelte index f44889c0..0ba8e81b 100644 --- a/frontend/src/lib/components/form/form-input.svelte +++ b/frontend/src/lib/components/form/form-input.svelte @@ -36,7 +36,7 @@
{#if label} - + {/if} {#if description}

diff --git a/frontend/src/lib/components/header/header-avatar.svelte b/frontend/src/lib/components/header/header-avatar.svelte index 7a5474a0..119a4c41 100644 --- a/frontend/src/lib/components/header/header-avatar.svelte +++ b/frontend/src/lib/components/header/header-avatar.svelte @@ -26,8 +26,7 @@

- {$userStore?.firstName} - {$userStore?.lastName} + {$userStore?.displayName}

{$userStore?.email}

diff --git a/frontend/src/lib/components/ui/label/label.svelte b/frontend/src/lib/components/ui/label/label.svelte index 91a5be2e..d249e2c7 100644 --- a/frontend/src/lib/components/ui/label/label.svelte +++ b/frontend/src/lib/components/ui/label/label.svelte @@ -5,16 +5,25 @@ let { ref = $bindable(null), class: className, + required = false, + children, ...restProps - }: LabelPrimitive.RootProps = $props(); + }: LabelPrimitive.RootProps & { + required?: boolean; + } = $props(); +> + {#if children} + {@render children()} + {/if} + diff --git a/frontend/src/lib/utils/form-util.ts b/frontend/src/lib/utils/form-util.ts index 3251d8cb..3c5f0313 100644 --- a/frontend/src/lib/utils/form-util.ts +++ b/frontend/src/lib/utils/form-util.ts @@ -4,6 +4,7 @@ import { z } from 'zod/v4'; export type FormInput = { value: T; error: string | null; + required: boolean; }; type FormInputs = { @@ -17,11 +18,18 @@ export function createForm>(schema: T, initialValu function initializeInputs(initialValues: z.infer): FormInputs> { const inputs: FormInputs> = {} as FormInputs>; + + const shape = + schema instanceof z.ZodObject ? (schema.shape as Record) : {}; + for (const key in initialValues) { if (Object.prototype.hasOwnProperty.call(initialValues, key)) { + const fieldSchema = shape[key]; + inputs[key as keyof z.infer] = { value: initialValues[key as keyof z.infer], - error: null + error: null, + required: fieldSchema ? isRequired(fieldSchema) : false }; } } @@ -31,7 +39,6 @@ export function createForm>(schema: T, initialValu function validate() { let success = true; inputsStore.update((inputs) => { - // Extract values from inputs to validate against the schema const values = Object.fromEntries( Object.entries(inputs).map(([key, input]) => [key, input.value]) ); @@ -54,7 +61,7 @@ export function createForm>(schema: T, initialValu inputs[input as keyof z.infer].error = null; } } - // Update the input values with the parsed data + for (const key in result.data) { if (Object.prototype.hasOwnProperty.call(inputs, key)) { inputs[key as keyof z.infer].value = result.data[key]; @@ -82,7 +89,9 @@ export function createForm>(schema: T, initialValu function reset() { inputsStore.update((inputs) => { for (const input of Object.keys(inputs)) { + const current = inputs[input as keyof z.infer]; inputs[input as keyof z.infer] = { + ...current, value: initialValues[input as keyof z.infer], error: null }; @@ -98,7 +107,6 @@ export function createForm>(schema: T, initialValu }); } - // Trims whitespace from string values and arrays of strings function trimValue(value: any) { if (typeof value === 'string') { value = value.trim(); @@ -113,6 +121,26 @@ export function createForm>(schema: T, initialValu return value; } + function isRequired(fieldSchema: z.ZodTypeAny): boolean { + // Handle unions like callbackUrlSchema + if (fieldSchema instanceof z.ZodUnion) { + return !fieldSchema.def.options.some((o: any) => { + return o.def.type == 'optional'; + }); + } + + // Handle pipes like emptyToUndefined + if (fieldSchema instanceof z.ZodPipe) { + return isRequired(fieldSchema.def.out as z.ZodTypeAny); + } + + // Handle the normal cases + if (fieldSchema instanceof z.ZodOptional || fieldSchema instanceof z.ZodDefault) { + return false; + } + return true; + } + return { schema, inputs: inputsStore, diff --git a/frontend/src/routes/settings/account/account-form.svelte b/frontend/src/routes/settings/account/account-form.svelte index 15cc3379..eb617bd3 100644 --- a/frontend/src/routes/settings/account/account-form.svelte +++ b/frontend/src/routes/settings/account/account-form.svelte @@ -34,7 +34,7 @@ const formSchema = z.object({ firstName: z.string().min(1).max(50), lastName: emptyToUndefined(z.string().max(50).optional()), - displayName: z.string().max(100), + displayName: z.string().min(1).max(100), username: usernameSchema, email: z.email(), isAdmin: z.boolean() diff --git a/frontend/src/routes/settings/admin/application-configuration/forms/app-config-ldap-form.svelte b/frontend/src/routes/settings/admin/application-configuration/forms/app-config-ldap-form.svelte index cd2cbbd3..afd56fb3 100644 --- a/frontend/src/routes/settings/admin/application-configuration/forms/app-config-ldap-form.svelte +++ b/frontend/src/routes/settings/admin/application-configuration/forms/app-config-ldap-form.svelte @@ -37,13 +37,13 @@ ldapAttributeUserUsername: z.string().min(1), ldapAttributeUserEmail: z.string().min(1), ldapAttributeUserFirstName: z.string().min(1), - ldapAttributeUserLastName: z.string().min(1), - ldapAttributeUserDisplayName: z.string().min(1), - ldapAttributeUserProfilePicture: z.string(), - ldapAttributeGroupMember: z.string(), + ldapAttributeUserLastName: z.string().optional(), + ldapAttributeUserDisplayName: z.string().optional(), + ldapAttributeUserProfilePicture: z.string().optional(), + ldapAttributeGroupMember: z.string().optional(), ldapAttributeGroupUniqueIdentifier: z.string().min(1), ldapAttributeGroupName: z.string().min(1), - ldapAttributeAdminGroup: z.string(), + ldapAttributeAdminGroup: z.string().optional(), ldapSoftDeleteUsers: z.boolean() }); diff --git a/frontend/src/routes/settings/admin/oidc-clients/federated-identities-input.svelte b/frontend/src/routes/settings/admin/oidc-clients/federated-identities-input.svelte index 136c7583..f7c5794a 100644 --- a/frontend/src/routes/settings/admin/oidc-clients/federated-identities-input.svelte +++ b/frontend/src/routes/settings/admin/oidc-clients/federated-identities-input.svelte @@ -82,7 +82,7 @@
- +
- + updateFederatedIdentity(i, 'subject', e.currentTarget.value)} aria-invalid={!!getFieldError(i, 'subject')} @@ -110,7 +110,7 @@
- +
- + {m.oidc_data_preview()} {#if user} - {m.preview_for_user({ name: user.firstName + ' ' + user.lastName, email: user.email })} + {m.preview_for_user({ name: user.displayName, email: user.email })} {:else} {m.preview_the_oidc_data_that_would_be_sent_for_this_user()} {/if} diff --git a/frontend/src/routes/settings/admin/user-groups/user-selection.svelte b/frontend/src/routes/settings/admin/user-groups/user-selection.svelte index bf16706f..96c741ef 100644 --- a/frontend/src/routes/settings/admin/user-groups/user-selection.svelte +++ b/frontend/src/routes/settings/admin/user-groups/user-selection.svelte @@ -43,7 +43,7 @@ {selectionDisabled} > {#snippet rows({ item })} - {item.firstName} {item.lastName} + {item.displayName} {item.email} {/snippet} diff --git a/frontend/src/routes/settings/admin/users/user-form.svelte b/frontend/src/routes/settings/admin/users/user-form.svelte index 0edc28eb..de19a797 100644 --- a/frontend/src/routes/settings/admin/users/user-form.svelte +++ b/frontend/src/routes/settings/admin/users/user-form.svelte @@ -35,7 +35,7 @@ const formSchema = z.object({ firstName: z.string().min(1).max(50), lastName: emptyToUndefined(z.string().max(50).optional()), - displayName: z.string().max(100), + displayName: z.string().min(1).max(100), username: usernameSchema, email: z.email(), isAdmin: z.boolean(),