Use shared ValidationMessages.en.resx (#773)

This commit is contained in:
Leendert de Borst
2026-03-17 10:17:58 +01:00
parent 53cb7c4961
commit d74bb924cb
6 changed files with 123 additions and 43 deletions

View File

@@ -62,6 +62,7 @@
@code {
private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Auth.Setup.PasswordStep", "AliasVault.Client");
private IStringLocalizer ValidationLocalizer => LocalizerFactory.Create("ValidationMessages", "AliasVault.Client");
/// <summary>
/// The event callback for when the password changes.
@@ -173,7 +174,7 @@
{
if (Password.Length < PasswordStrengthConstants.MinimumGoodPasswordLength)
{
_errorMessage = Localizer["PasswordTooShortError", PasswordStrengthConstants.MinimumGoodPasswordLength];
_errorMessage = ValidationLocalizer["PasswordMinLengthGeneric", PasswordStrengthConstants.MinimumGoodPasswordLength];
await OnPasswordChange.InvokeAsync(string.Empty);
StateHasChanged();
return;
@@ -189,7 +190,7 @@
if (Password != ConfirmPassword)
{
_errorMessage = Localizer["PasswordsMismatchError"];
_errorMessage = ValidationLocalizer["PasswordsDoNotMatchGeneric"];
await OnPasswordChange.InvokeAsync(string.Empty);
StateHasChanged();
return;

View File

@@ -3,7 +3,10 @@
@using AliasVault.Client.Main.Components.Shared
@using AliasVault.Client.Main.Components.Layout
@using AliasVault.Client.Auth.Components
@using AliasVault.Client.Main.Constants
@using System.Timers
@inject IStringLocalizerFactory LocalizerFactory
@implements IDisposable
<FormModal
IsOpen="@IsOpen"
@@ -36,6 +39,7 @@
@bind-Value="_exportPassword"
Placeholder=""
@onkeydown="HandleKeyDown"
@onfocus="OnPasswordFocus"
autofocus="true" />
<PasswordStrengthIndicator Password="@_exportPassword" OnStrengthChanged="@HandleStrengthChanged" />
@@ -49,13 +53,16 @@
Id="confirmExportPassword"
@bind-Value="_confirmPassword"
Placeholder=""
@onkeydown="HandleKeyDown" />
@if (!string.IsNullOrEmpty(_confirmPassword) && _exportPassword != _confirmPassword)
{
<p class="mt-1 text-xs text-red-600 dark:text-red-400">@Localizer["PasswordsDoNotMatch"]</p>
}
@onkeydown="HandleKeyDown"
@onfocus="OnPasswordFocus" />
</div>
@if (!string.IsNullOrEmpty(_validationError))
{
<div class="mt-2 text-sm text-red-600 dark:text-red-400">
@_validationError
</div>
}
</ChildContent>
<FooterContent>
<button
@@ -93,20 +100,57 @@
[Parameter]
public EventCallback OnClose { get; set; }
private string _exportPassword = string.Empty;
private string _confirmPassword = string.Empty;
private string _exportPasswordValue = string.Empty;
private string _confirmPasswordValue = string.Empty;
private int _passwordStrength = 0;
private string _validationError = string.Empty;
private Timer? _passwordDebounceTimer;
private string _exportPassword
{
get => _exportPasswordValue;
set
{
if (_exportPasswordValue != value)
{
_exportPasswordValue = value;
ValidatePasswordWithDebounce();
}
}
}
private string _confirmPassword
{
get => _confirmPasswordValue;
set
{
if (_confirmPasswordValue != value)
{
_confirmPasswordValue = value;
ValidatePasswordImmediate();
}
}
}
private IStringLocalizer Localizer => LocalizerFactory.Create("Pages.Main.Settings.ImportExport.Components.ExportPasswordModal", "AliasVault.Client");
private IStringLocalizer SharedLocalizer => LocalizerFactory.Create("Shared", "AliasVault.Client");
private IStringLocalizer ValidationLocalizer => LocalizerFactory.Create("ValidationMessages", "AliasVault.Client");
protected override void OnInitialized()
{
_passwordDebounceTimer = new Timer(800);
_passwordDebounceTimer.Elapsed += async (sender, e) => await ValidatePasswordDebounced();
_passwordDebounceTimer.AutoReset = false;
}
protected override void OnParametersSet()
{
if (!IsOpen)
{
_exportPassword = string.Empty;
_confirmPassword = string.Empty;
_exportPasswordValue = string.Empty;
_confirmPasswordValue = string.Empty;
_passwordStrength = 0;
_validationError = string.Empty;
}
}
@@ -115,21 +159,75 @@
_passwordStrength = strength;
}
private void OnPasswordFocus(FocusEventArgs e)
{
_validationError = string.Empty;
StateHasChanged();
}
private void ValidatePasswordWithDebounce()
{
_validationError = string.Empty;
StateHasChanged();
_passwordDebounceTimer?.Stop();
_passwordDebounceTimer?.Start();
}
private async void ValidatePasswordImmediate()
{
await ValidatePasswordDebounced();
}
private async Task ValidatePasswordDebounced()
{
await InvokeAsync(() =>
{
var minLength = PasswordStrengthConstants.MinimumGoodPasswordLength;
if (!string.IsNullOrWhiteSpace(_exportPasswordValue) &&
_exportPasswordValue.Length < minLength)
{
_validationError = ValidationLocalizer["PasswordMinLengthGeneric", minLength];
StateHasChanged();
return;
}
if (!string.IsNullOrWhiteSpace(_confirmPasswordValue) &&
_exportPasswordValue != _confirmPasswordValue)
{
_validationError = ValidationLocalizer["PasswordsDoNotMatchGeneric"];
StateHasChanged();
return;
}
_validationError = string.Empty;
StateHasChanged();
});
}
private bool IsPasswordValid()
{
return !string.IsNullOrEmpty(_exportPassword) &&
!string.IsNullOrEmpty(_confirmPassword) &&
_exportPassword == _confirmPassword &&
_exportPassword.Length >= 8;
return !string.IsNullOrEmpty(_exportPasswordValue) &&
!string.IsNullOrEmpty(_confirmPasswordValue) &&
_exportPasswordValue == _confirmPasswordValue &&
string.IsNullOrEmpty(_validationError) &&
_exportPasswordValue.Length >= PasswordStrengthConstants.MinimumGoodPasswordLength;
}
public void Dispose()
{
_passwordDebounceTimer?.Dispose();
}
private async Task HandleSubmit()
{
if (IsPasswordValid())
{
await OnPasswordSubmitted.InvokeAsync(_exportPassword);
_exportPassword = string.Empty;
_confirmPassword = string.Empty;
await OnPasswordSubmitted.InvokeAsync(_exportPasswordValue);
_exportPasswordValue = string.Empty;
_confirmPasswordValue = string.Empty;
_validationError = string.Empty;
}
}

View File

@@ -85,6 +85,7 @@ else
private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Pages.Settings.Security.ChangePassword", "AliasVault.Client");
private IStringLocalizer ApiErrorLocalizer => LocalizerFactory.Create("ApiErrors", "AliasVault.Client");
private IStringLocalizer ValidationLocalizer => LocalizerFactory.Create("ValidationMessages", "AliasVault.Client");
/// <summary>
/// Gets or sets the current user's password salt.
@@ -201,7 +202,7 @@ else
if (!string.IsNullOrWhiteSpace(PasswordChangeFormModel.NewPassword) &&
PasswordChangeFormModel.NewPassword.Length < minLength)
{
PasswordValidationError = Localizer["PasswordStrengthTooWeakError", minLength];
PasswordValidationError = ValidationLocalizer["PasswordMinLengthGeneric", minLength];
StateHasChanged();
return;
}
@@ -209,7 +210,7 @@ else
if (!string.IsNullOrWhiteSpace(PasswordChangeFormModel.NewPasswordConfirm) &&
PasswordChangeFormModel.NewPassword != PasswordChangeFormModel.NewPasswordConfirm)
{
PasswordValidationError = Localizer["PasswordsMismatchError"];
PasswordValidationError = ValidationLocalizer["PasswordsDoNotMatchGeneric"];
StateHasChanged();
return;
}
@@ -295,7 +296,7 @@ else
var minLength = PasswordStrengthConstants.MinimumGoodPasswordLength;
if (PasswordChangeFormModel.NewPassword.Length < minLength)
{
PasswordValidationError = Localizer["PasswordStrengthTooWeakError", minLength];
PasswordValidationError = ValidationLocalizer["PasswordMinLengthGeneric", minLength];
StateHasChanged();
return;
}
@@ -303,7 +304,7 @@ else
// Validate password strength before proceeding
if (PasswordStrengthIndicatorRef == null || !PasswordStrengthIndicatorRef.MeetsMinimumRequirement())
{
PasswordValidationError = Localizer["PasswordStrengthTooWeakError", PasswordStrengthConstants.MinimumGoodPasswordLength];
PasswordValidationError = ValidationLocalizer["PasswordMinLengthGeneric", PasswordStrengthConstants.MinimumGoodPasswordLength];
StateHasChanged();
return;
}

View File

@@ -64,16 +64,8 @@
<value>Password is valid and strong!</value>
<comment>Success message for valid password</comment>
</data>
<data name="PasswordTooShortError">
<value>Master password must be at least {0} characters long.</value>
<comment>Error message for password too short. {0} is the minimum password length.</comment>
</data>
<data name="ConfirmPasswordPrompt">
<value>Confirm your password by entering it again.</value>
<comment>Prompt to confirm password</comment>
</data>
<data name="PasswordsMismatchError">
<value>Passwords do not match.</value>
<comment>Error message when passwords don't match</comment>
</data>
</root>

View File

@@ -116,12 +116,4 @@
<value>Failed to change password. Please refresh the page and try again.</value>
<comment>Error message when password change fails</comment>
</data>
<data name="PasswordStrengthTooWeakError" xml:space="preserve">
<value>Your new password must be at least {0} characters long.</value>
<comment>Error message when password strength is too weak. {0} is the minimum password length.</comment>
</data>
<data name="PasswordsMismatchError" xml:space="preserve">
<value>Passwords do not match.</value>
<comment>Error message when new password and confirmation password don't match</comment>
</data>
</root>

View File

@@ -66,10 +66,6 @@
<value>Confirm Password</value>
<comment>Label for password confirmation field</comment>
</data>
<data name="PasswordsDoNotMatch" xml:space="preserve">
<value>Passwords do not match</value>
<comment>Error when passwords don't match</comment>
</data>
<data name="CreateEncryptedExportButton" xml:space="preserve">
<value>Create Encrypted Export</value>
<comment>Button text for creating encrypted export</comment>