Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/src/common/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export type UserDto = {
id: string;
name: string;
pictureUrl?: string;
email: string;
email?: string | null;
firstName?: string;
lastName?: string;
realmRoles: RealmRole[];
Expand Down
21 changes: 17 additions & 4 deletions frontend/src/common/formvalidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ export class FormValidator {
firstName?: string,
lastName?: string,
username: string,
email: string,
email?: string | null,
password: string,
passwordConfirm: string,
isEditMode: boolean,
initialEmail?: string | null,
pictureUrl?: string,
isValidImageUrl?: boolean
}): ValidationResult {
Expand All @@ -30,9 +31,12 @@ export class FormValidator {
if (!data.username.trim()) errors.username = t('userEditCreate.validation.required');

// Email validation
if (!data.email.trim()) {
const emailTrimmed = data.email?.trim() ?? '';
const emailRequired = this.isEmailRequired(data.isEditMode, data.initialEmail);

if (emailRequired && !emailTrimmed) {
errors.email = t('userEditCreate.validation.required');
} else if (!this.isValidEmail(data.email.trim())) {
} else if (emailTrimmed && !this.isValidEmail(emailTrimmed)) {
errors.email = t('userEditCreate.invalidEmail');
}

Expand Down Expand Up @@ -93,10 +97,19 @@ export class FormValidator {
/**
* Validates email format
*/
static isValidEmail(email: string): boolean {
static isValidEmail(email: string | null | undefined): boolean {
if (!email) return false;
return /^[^\s@]+@[^\s@]+\.[a-z]{2,}$/i.test(email.trim());
}

/**
* Checks if email is required (always for CREATE, only if previously set for EDIT)
*/
static isEmailRequired(isEditMode: boolean, initialEmail?: string | null): boolean {
if (!isEditMode) return true;
return !!initialEmail?.trim();
}

/**
* Evaluates password strength
*/
Expand Down
12 changes: 7 additions & 5 deletions frontend/src/components/authority/UserEditCreate.vue
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,9 @@
{{ t('userEditCreate.email') }}
</label>
<div class="mt-1 md:mt-0 md:col-span-2 lg:col-span-1">
<input id="email" v-model="data.email" type="email" required :class="[errors.email ? 'border-red-300 focus:border-red-500 focus:ring-red-500' : 'border-gray-300 focus:ring-primary focus:border-primary', 'block w-full max-w-md shadow-sm sm:text-sm rounded-md']"/>
<input id="email" v-model="data.email" type="email" :required="emailRequired" :class="[errors.email ? 'border-red-300 focus:border-red-500 focus:ring-red-500' : 'border-gray-300 focus:ring-primary focus:border-primary', 'block w-full max-w-md shadow-sm sm:text-sm rounded-md']"/>
<p v-if="errors.email" class="mt-1 text-sm text-red-600">{{ errors.email }}</p>
<p v-else-if="data.email && !isValidEmail(data.email?.trim())" class="mt-1 text-sm text-red-600">
<p v-else-if="data.email?.trim() && !isValidEmail(data.email)" class="mt-1 text-sm text-red-600">
{{ t('userEditCreate.invalidEmail') }}
</p>
</div>
Expand Down Expand Up @@ -341,10 +341,11 @@ async function validateForm(): Promise<boolean> {
password: password.value,
passwordConfirm: passwordConfirm.value,
isEditMode: props.mode === 'EDIT',
pictureUrl: data.pictureUrl,
initialEmail: initialData.value.email,
pictureUrl: data.pictureUrl,
isValidImageUrl: await FormValidator.validateImageUrl(data.pictureUrl)
});

errors.value = result.errors;
return result.valid;
}
Expand All @@ -368,6 +369,7 @@ const passwordStrengthColor = computed(() => {
});

const isValidEmail = FormValidator.isValidEmail;
const emailRequired = computed(() => FormValidator.isEmailRequired(props.mode === 'EDIT', initialData.value.email));

function evaluatePasswordStrength(pw: string): 'weak' | 'medium' | 'strong' | '' {
return FormValidator.evaluatePasswordStrength(pw);
Expand All @@ -393,7 +395,7 @@ async function onSubmit() {
data.firstName = data.firstName?.trim();
data.lastName = data.lastName?.trim();
data.name = data.name.trim();
data.email = data.email.trim();
data.email = data.email?.trim() ?? '';
data.pictureUrl = data.pictureUrl?.trim();

try {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/authority/UserList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
<img :src="user.pictureUrl" :alt="t('userList.profileImage')" class="w-10 h-10 rounded-full object-cover border border-gray-300"/>
<div class="flex flex-col min-w-0 flex-1">
<button type="button" class="truncate block hover:underline cursor-pointer text-left" :title="user.name" @click="router.push(`users/${user.id}`)">{{ user.name }}</button>
<span class="text-xs text-gray-500 truncate" :title="user.firstName || user.lastName ? `${user.firstName ?? ''} ${user.lastName ?? ''}`.trim() : user.email">{{ user.firstName || user.lastName ? `${user.firstName ?? ''} ${user.lastName ?? ''}`.trim() : user.email }}</span>
<span class="text-xs text-gray-500 truncate" :title="user.firstName || user.lastName ? `${user.firstName ?? ''} ${user.lastName ?? ''}`.trim() : user.email ?? undefined">{{ user.firstName || user.lastName ? `${user.firstName ?? ''} ${user.lastName ?? ''}`.trim() : user.email }}</span>
</div>
</div>
</td>
Expand Down