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(),