mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-18 05:18:05 -04:00
Cache localized strings for performance (#1006)
This commit is contained in:
@@ -14,17 +14,17 @@
|
||||
@if (_showTwoFactorAuthStep)
|
||||
{
|
||||
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
@Localizer["TwoFactorAuthenticationTitle"]
|
||||
@_twoFactorAuthenticationTitle
|
||||
</h2>
|
||||
|
||||
<ServerValidationErrors @ref="_serverValidationErrors" />
|
||||
|
||||
<p class="text-gray-700 dark:text-gray-300 mb-6">@Localizer["TwoFactorAuthenticationDescription"]</p>
|
||||
<p class="text-gray-700 dark:text-gray-300 mb-6">@_twoFactorAuthenticationDescription</p>
|
||||
<div class="w-full">
|
||||
<EditForm Model="_loginModel2Fa" FormName="login-with-2fa" OnValidSubmit="Handle2Fa" method="post" class="space-y-6">
|
||||
<DataAnnotationsValidator/>
|
||||
<div>
|
||||
<label for="two-factor-code" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@Localizer["AuthenticatorCodeLabel"]</label>
|
||||
<label for="two-factor-code" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@_authenticatorCodeLabel</label>
|
||||
<InputNumber @bind-Value="_loginModel2Fa.TwoFactorCode"
|
||||
id="two-factor-code"
|
||||
@oninput="OnTwoFactorCodeInput"
|
||||
@@ -37,48 +37,48 @@
|
||||
<InputCheckbox @bind-Value="_loginModel2Fa.RememberMachine" id="remember-machine" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-primary-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-primary-600 dark:ring-offset-gray-800"/>
|
||||
</div>
|
||||
<div class="ml-3 text-sm">
|
||||
<label for="remember-machine" class="font-medium text-gray-900 dark:text-white">@Localizer["RememberMachineLabel"]</label>
|
||||
<label for="remember-machine" class="font-medium text-gray-900 dark:text-white">@_rememberMachineLabel</label>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="w-full text-white bg-primary-600 hover:bg-primary-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">@Localizer["LoginButton"]</button>
|
||||
<button type="submit" class="w-full text-white bg-primary-600 hover:bg-primary-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">@_loginButton</button>
|
||||
</EditForm>
|
||||
</div>
|
||||
<p class="mt-6 text-sm text-gray-700 dark:text-gray-300">
|
||||
@Localizer["DontHaveAuthenticatorText"]
|
||||
<button @onclick="LoginWithRecoveryCode" class="text-primary-600 hover:underline dark:text-primary-500">@Localizer["LoginWithRecoveryCodeLink"]</button>.
|
||||
@_dontHaveAuthenticatorText
|
||||
<button @onclick="LoginWithRecoveryCode" class="text-primary-600 hover:underline dark:text-primary-500">@_loginWithRecoveryCodeLink</button>.
|
||||
</p>
|
||||
}
|
||||
else if (_showLoginWithRecoveryCodeStep)
|
||||
{
|
||||
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
@Localizer["RecoveryCodeVerificationTitle"]
|
||||
@_recoveryCodeVerificationTitle
|
||||
</h2>
|
||||
|
||||
<ServerValidationErrors @ref="_serverValidationErrors" />
|
||||
|
||||
<p class="text-gray-700 dark:text-gray-300 mb-6">
|
||||
@Localizer["RecoveryCodeDescription"]
|
||||
@_recoveryCodeDescription
|
||||
</p>
|
||||
<div class="w-full">
|
||||
<EditForm Model="_loginModelRecoveryCode" FormName="login-with-recovery-code" OnValidSubmit="HandleRecoveryCode" method="post" class="space-y-6">
|
||||
<DataAnnotationsValidator/>
|
||||
<div>
|
||||
<label for="two-factor-code" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@Localizer["RecoveryCodeLabel"]</label>
|
||||
<label for="two-factor-code" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@_recoveryCodeLabel</label>
|
||||
<InputText @bind-Value="_loginModelRecoveryCode.RecoveryCode" id="recovery-code" class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" autocomplete="off"/>
|
||||
<ValidationMessage For="() => _loginModelRecoveryCode.RecoveryCode" class="text-red-600 dark:text-red-400 text-sm mt-1"/>
|
||||
</div>
|
||||
<button type="submit" class="w-full text-white bg-primary-600 hover:bg-primary-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">@Localizer["LoginButton"]</button>
|
||||
<button type="submit" class="w-full text-white bg-primary-600 hover:bg-primary-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">@_loginButton</button>
|
||||
</EditForm>
|
||||
</div>
|
||||
<p class="mt-6 text-sm text-gray-700 dark:text-gray-300">
|
||||
@Localizer["RegainedAccessText"]
|
||||
<button @onclick="LoginWithAuthenticator" class="text-primary-600 hover:underline dark:text-primary-500">@Localizer["LoginWithAuthenticatorLink"]</button> instead.
|
||||
@_regainedAccessText
|
||||
<button @onclick="LoginWithAuthenticator" class="text-primary-600 hover:underline dark:text-primary-500">@_loginWithAuthenticatorLink</button> instead.
|
||||
</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<h2 class="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
@Localizer["PageTitle"]
|
||||
@_pageTitle
|
||||
</h2>
|
||||
|
||||
<FullScreenLoadingIndicator @ref="_loadingIndicator"/>
|
||||
@@ -87,13 +87,13 @@ else
|
||||
<ServerValidationErrors @ref="_serverValidationErrors"/>
|
||||
<DataAnnotationsValidator/>
|
||||
<div>
|
||||
<label asp-for="Input.Email" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@Localizer["UsernameOrEmailLabel"]</label>
|
||||
<InputTextField id="email" @bind-Value="_loginModel.Username" type="text" placeholder="@Localizer["UsernamePlaceholder"]" autocapitalize="off" autocorrect="off"/>
|
||||
<label asp-for="Input.Email" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@_usernameOrEmailLabel</label>
|
||||
<InputTextField id="email" @bind-Value="_loginModel.Username" type="text" placeholder="@_usernamePlaceholder" autocapitalize="off" autocorrect="off"/>
|
||||
<ValidationMessage For="() => _loginModel.Username"/>
|
||||
</div>
|
||||
<div>
|
||||
<label asp-for="Input.Password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@Localizer["PasswordLabel"]</label>
|
||||
<InputTextField id="password" @bind-Value="_loginModel.Password" type="password" placeholder="@Localizer["PasswordPlaceholder"]"/>
|
||||
<label asp-for="Input.Password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@_passwordLabel</label>
|
||||
<InputTextField id="password" @bind-Value="_loginModel.Password" type="password" placeholder="@_passwordPlaceholder"/>
|
||||
<ValidationMessage For="() => _loginModel.Password"/>
|
||||
</div>
|
||||
|
||||
@@ -102,16 +102,16 @@ else
|
||||
<InputCheckbox @bind-Value="_loginModel.RememberMe" id="remember" class="w-4 h-4 border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-primary-300 dark:focus:ring-primary-600 dark:ring-offset-gray-800 dark:bg-gray-700 dark:border-gray-600" />
|
||||
</div>
|
||||
<div class="ml-3 text-sm">
|
||||
<label for="remember" class="font-medium text-gray-900 dark:text-white">@Localizer["RememberMeLabel"]</label>
|
||||
<label for="remember" class="font-medium text-gray-900 dark:text-white">@_rememberMeLabel</label>
|
||||
</div>
|
||||
<a href="/user/forgot-password" class="ml-auto text-sm text-primary-700 hover:underline dark:text-primary-500">@Localizer["LostPasswordLink"]</a>
|
||||
<a href="/user/forgot-password" class="ml-auto text-sm text-primary-700 hover:underline dark:text-primary-500">@_lostPasswordLink</a>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="w-full px-5 py-3 text-base font-medium text-center text-white bg-primary-700 rounded-lg hover:bg-primary-800 focus:ring-4 focus:ring-primary-300 sm:w-auto dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">@Localizer["LoginToAccountButton"]</button>
|
||||
<button type="submit" class="w-full px-5 py-3 text-base font-medium text-center text-white bg-primary-700 rounded-lg hover:bg-primary-800 focus:ring-4 focus:ring-primary-300 sm:w-auto dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">@_loginToAccountButton</button>
|
||||
@if (Config.PublicRegistrationEnabled)
|
||||
{
|
||||
<div class="text-sm font-medium text-gray-500 dark:text-gray-400">
|
||||
@Localizer["NoAccountYetText"] <a href="/user/setup" class="text-primary-700 hover:underline dark:text-primary-500">@Localizer["CreateNewVaultLink"]</a>
|
||||
@_noAccountYetText <a href="/user/setup" class="text-primary-700 hover:underline dark:text-primary-500">@_createNewVaultLink</a>
|
||||
</div>
|
||||
}
|
||||
</EditForm>
|
||||
@@ -133,6 +133,35 @@ else
|
||||
private SrpSession _clientSession = new();
|
||||
private byte[] _passwordHash = [];
|
||||
|
||||
// Cached localized strings
|
||||
private string _twoFactorAuthenticationTitle = string.Empty;
|
||||
private string _twoFactorAuthenticationDescription = string.Empty;
|
||||
private string _authenticatorCodeLabel = string.Empty;
|
||||
private string _rememberMachineLabel = string.Empty;
|
||||
private string _loginButton = string.Empty;
|
||||
private string _dontHaveAuthenticatorText = string.Empty;
|
||||
private string _loginWithRecoveryCodeLink = string.Empty;
|
||||
private string _recoveryCodeVerificationTitle = string.Empty;
|
||||
private string _recoveryCodeDescription = string.Empty;
|
||||
private string _recoveryCodeLabel = string.Empty;
|
||||
private string _regainedAccessText = string.Empty;
|
||||
private string _loginWithAuthenticatorLink = string.Empty;
|
||||
private string _pageTitle = string.Empty;
|
||||
private string _usernameOrEmailLabel = string.Empty;
|
||||
private string _usernamePlaceholder = string.Empty;
|
||||
private string _passwordLabel = string.Empty;
|
||||
private string _passwordPlaceholder = string.Empty;
|
||||
private string _rememberMeLabel = string.Empty;
|
||||
private string _lostPasswordLink = string.Empty;
|
||||
private string _loginToAccountButton = string.Empty;
|
||||
private string _noAccountYetText = string.Empty;
|
||||
private string _createNewVaultLink = string.Empty;
|
||||
private string _loggingInMessage = string.Empty;
|
||||
private string _loginErrorMessage = string.Empty;
|
||||
private string _loginRequestErrorMessage = string.Empty;
|
||||
private string _verifyingRecoveryCodeMessage = string.Empty;
|
||||
private string _verifyingTwoFactorCodeMessage = string.Empty;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
@@ -144,6 +173,35 @@ else
|
||||
// Already authenticated, redirect to home page.
|
||||
NavigationManager.NavigateTo("/");
|
||||
}
|
||||
|
||||
// Cache localized strings to prevent infinite rendering loops
|
||||
_twoFactorAuthenticationTitle = Localizer["TwoFactorAuthenticationTitle"];
|
||||
_twoFactorAuthenticationDescription = Localizer["TwoFactorAuthenticationDescription"];
|
||||
_authenticatorCodeLabel = Localizer["AuthenticatorCodeLabel"];
|
||||
_rememberMachineLabel = Localizer["RememberMachineLabel"];
|
||||
_loginButton = Localizer["LoginButton"];
|
||||
_dontHaveAuthenticatorText = Localizer["DontHaveAuthenticatorText"];
|
||||
_loginWithRecoveryCodeLink = Localizer["LoginWithRecoveryCodeLink"];
|
||||
_recoveryCodeVerificationTitle = Localizer["RecoveryCodeVerificationTitle"];
|
||||
_recoveryCodeDescription = Localizer["RecoveryCodeDescription"];
|
||||
_recoveryCodeLabel = Localizer["RecoveryCodeLabel"];
|
||||
_regainedAccessText = Localizer["RegainedAccessText"];
|
||||
_loginWithAuthenticatorLink = Localizer["LoginWithAuthenticatorLink"];
|
||||
_pageTitle = Localizer["PageTitle"];
|
||||
_usernameOrEmailLabel = Localizer["UsernameOrEmailLabel"];
|
||||
_usernamePlaceholder = Localizer["UsernamePlaceholder"];
|
||||
_passwordLabel = Localizer["PasswordLabel"];
|
||||
_passwordPlaceholder = Localizer["PasswordPlaceholder"];
|
||||
_rememberMeLabel = Localizer["RememberMeLabel"];
|
||||
_lostPasswordLink = Localizer["LostPasswordLink"];
|
||||
_loginToAccountButton = Localizer["LoginToAccountButton"];
|
||||
_noAccountYetText = Localizer["NoAccountYetText"];
|
||||
_createNewVaultLink = Localizer["CreateNewVaultLink"];
|
||||
_loggingInMessage = Localizer["LoggingInMessage"];
|
||||
_loginErrorMessage = Localizer["LoginErrorMessage"];
|
||||
_loginRequestErrorMessage = Localizer["LoginRequestErrorMessage"];
|
||||
_verifyingRecoveryCodeMessage = Localizer["VerifyingRecoveryCodeMessage"];
|
||||
_verifyingTwoFactorCodeMessage = Localizer["VerifyingTwoFactorCodeMessage"];
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -178,7 +236,7 @@ else
|
||||
/// </summary>
|
||||
private async Task HandleLogin()
|
||||
{
|
||||
_loadingIndicator.Show(Localizer["LoggingInMessage"]);
|
||||
_loadingIndicator.Show(_loggingInMessage);
|
||||
_serverValidationErrors.Clear();
|
||||
|
||||
try
|
||||
@@ -199,7 +257,7 @@ else
|
||||
catch
|
||||
{
|
||||
// If in release mode show a generic error.
|
||||
_serverValidationErrors.AddError(Localizer["LoginErrorMessage"]);
|
||||
_serverValidationErrors.AddError(_loginErrorMessage);
|
||||
}
|
||||
#endif
|
||||
finally
|
||||
@@ -233,7 +291,7 @@ else
|
||||
{
|
||||
return
|
||||
[
|
||||
Localizer["LoginRequestErrorMessage"],
|
||||
_loginRequestErrorMessage,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -264,7 +322,7 @@ else
|
||||
{
|
||||
return
|
||||
[
|
||||
Localizer["LoginRequestErrorMessage"],
|
||||
_loginRequestErrorMessage,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -284,7 +342,7 @@ else
|
||||
/// </summary>
|
||||
private async Task HandleRecoveryCode()
|
||||
{
|
||||
_loadingIndicator.Show(Localizer["VerifyingRecoveryCodeMessage"]);
|
||||
_loadingIndicator.Show(_verifyingRecoveryCodeMessage);
|
||||
_serverValidationErrors.Clear();
|
||||
|
||||
try
|
||||
@@ -308,7 +366,7 @@ else
|
||||
var validateLoginResponse = JsonSerializer.Deserialize<ValidateLoginResponse>(responseContent);
|
||||
if (validateLoginResponse == null)
|
||||
{
|
||||
_serverValidationErrors.AddError(Localizer["LoginRequestErrorMessage"]);
|
||||
_serverValidationErrors.AddError(_loginRequestErrorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -328,7 +386,7 @@ else
|
||||
catch
|
||||
{
|
||||
// If in release mode show a generic error.
|
||||
_serverValidationErrors.AddError(Localizer["LoginErrorMessage"]);
|
||||
_serverValidationErrors.AddError(_loginErrorMessage);
|
||||
}
|
||||
#endif
|
||||
finally
|
||||
@@ -342,7 +400,7 @@ else
|
||||
/// </summary>
|
||||
private async Task Handle2Fa()
|
||||
{
|
||||
_loadingIndicator.Show(Localizer["VerifyingTwoFactorCodeMessage"]);
|
||||
_loadingIndicator.Show(_verifyingTwoFactorCodeMessage);
|
||||
_serverValidationErrors.Clear();
|
||||
|
||||
try
|
||||
@@ -366,7 +424,7 @@ else
|
||||
var validateLoginResponse = JsonSerializer.Deserialize<ValidateLoginResponse>(responseContent);
|
||||
if (validateLoginResponse == null)
|
||||
{
|
||||
_serverValidationErrors.AddError(Localizer["LoginRequestErrorMessage"]);
|
||||
_serverValidationErrors.AddError(_loginRequestErrorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -386,7 +444,7 @@ else
|
||||
catch
|
||||
{
|
||||
// If in release mode show a generic error.
|
||||
_serverValidationErrors.AddError(Localizer["LoginErrorMessage"]);
|
||||
_serverValidationErrors.AddError(_loginErrorMessage);
|
||||
}
|
||||
#endif
|
||||
finally
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
@using Microsoft.Extensions.Localization
|
||||
|
||||
<h2 class="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
@Localizer["PageTitle"]
|
||||
@_pageTitle
|
||||
</h2>
|
||||
|
||||
<FullScreenLoadingIndicator @ref="_loadingIndicator" />
|
||||
@@ -16,18 +16,18 @@
|
||||
<EditForm Model="_registerModel" OnValidSubmit="HandleRegister" class="mt-8 space-y-6">
|
||||
<DataAnnotationsValidator/>
|
||||
<div>
|
||||
<label asp-for="Input.Email" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@Localizer["UsernameOrEmailLabel"]</label>
|
||||
<InputTextField id="email" @bind-Value="_registerModel.Username" type="text" placeholder="@Localizer["UsernamePlaceholder"]" />
|
||||
<label asp-for="Input.Email" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@_usernameOrEmailLabel</label>
|
||||
<InputTextField id="email" @bind-Value="_registerModel.Username" type="text" placeholder="@_usernamePlaceholder" />
|
||||
<ValidationMessage For="() => _registerModel.Username"/>
|
||||
</div>
|
||||
<div>
|
||||
<label asp-for="Input.Password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@Localizer["PasswordLabel"]</label>
|
||||
<InputTextField id="password" @bind-Value="_registerModel.Password" type="password" placeholder="@Localizer["PasswordPlaceholder"]" />
|
||||
<label asp-for="Input.Password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@_passwordLabel</label>
|
||||
<InputTextField id="password" @bind-Value="_registerModel.Password" type="password" placeholder="@_passwordPlaceholder" />
|
||||
<ValidationMessage For="() => _registerModel.Password"/>
|
||||
</div>
|
||||
<div>
|
||||
<label asp-for="Input.Password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@Localizer["ConfirmPasswordLabel"]</label>
|
||||
<InputTextField id="password2" @bind-Value="_registerModel.PasswordConfirm" type="password" placeholder="@Localizer["PasswordPlaceholder"]" />
|
||||
<label asp-for="Input.Password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@_confirmPasswordLabel</label>
|
||||
<InputTextField id="password2" @bind-Value="_registerModel.PasswordConfirm" type="password" placeholder="@_passwordPlaceholder" />
|
||||
<ValidationMessage For="() => _registerModel.PasswordConfirm"/>
|
||||
</div>
|
||||
|
||||
@@ -36,14 +36,14 @@
|
||||
<InputCheckbox id="terms" @bind-Value="_registerModel.AcceptTerms" class="w-4 h-4 border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-primary-300 dark:focus:ring-primary-600 dark:ring-offset-gray-800 dark:bg-gray-700 dark:border-gray-600" />
|
||||
</div>
|
||||
<div class="ml-3 text-sm">
|
||||
<label for="terms" class="font-medium text-gray-900 dark:text-white">@Localizer["AcceptTermsLabel"] <a href="https://github.com/lanedirt/AliasVault/blob/main/LICENSE.md" target="_blank" class="text-primary-700 hover:underline dark:text-primary-500">@Localizer["TermsAndConditionsLink"]</a></label>
|
||||
<label for="terms" class="font-medium text-gray-900 dark:text-white">@_acceptTermsLabel <a href="https://github.com/lanedirt/AliasVault/blob/main/LICENSE.md" target="_blank" class="text-primary-700 hover:underline dark:text-primary-500">@_termsAndConditionsLink</a></label>
|
||||
<ValidationMessage For="() => _registerModel.AcceptTerms"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="w-full px-5 py-3 text-base font-medium text-center text-white bg-primary-700 rounded-lg hover:bg-primary-800 focus:ring-4 focus:ring-primary-300 sm:w-auto dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">@Localizer["CreateAccountButton"]</button>
|
||||
<button type="submit" class="w-full px-5 py-3 text-base font-medium text-center text-white bg-primary-700 rounded-lg hover:bg-primary-800 focus:ring-4 focus:ring-primary-300 sm:w-auto dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">@_createAccountButton</button>
|
||||
<div class="text-sm font-medium text-gray-500 dark:text-gray-400">
|
||||
@Localizer["AlreadyRegisteredText"] <a href="/user/login" class="text-primary-700 hover:underline dark:text-primary-500">@Localizer["LoginHereLink"]</a>
|
||||
@_alreadyRegisteredText <a href="/user/login" class="text-primary-700 hover:underline dark:text-primary-500">@_loginHereLink</a>
|
||||
</div>
|
||||
</EditForm>
|
||||
|
||||
@@ -54,9 +54,44 @@
|
||||
|
||||
private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Auth.Register", "AliasVault.Client");
|
||||
|
||||
// Cached localized strings
|
||||
private string _pageTitle = string.Empty;
|
||||
private string _usernameOrEmailLabel = string.Empty;
|
||||
private string _usernamePlaceholder = string.Empty;
|
||||
private string _passwordLabel = string.Empty;
|
||||
private string _passwordPlaceholder = string.Empty;
|
||||
private string _confirmPasswordLabel = string.Empty;
|
||||
private string _acceptTermsLabel = string.Empty;
|
||||
private string _termsAndConditionsLink = string.Empty;
|
||||
private string _createAccountButton = string.Empty;
|
||||
private string _alreadyRegisteredText = string.Empty;
|
||||
private string _loginHereLink = string.Empty;
|
||||
private string _creatingAccountMessage = string.Empty;
|
||||
private string _registrationErrorMessage = string.Empty;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
|
||||
// Cache localized strings to prevent infinite rendering loops
|
||||
_pageTitle = Localizer["PageTitle"];
|
||||
_usernameOrEmailLabel = Localizer["UsernameOrEmailLabel"];
|
||||
_usernamePlaceholder = Localizer["UsernamePlaceholder"];
|
||||
_passwordLabel = Localizer["PasswordLabel"];
|
||||
_passwordPlaceholder = Localizer["PasswordPlaceholder"];
|
||||
_confirmPasswordLabel = Localizer["ConfirmPasswordLabel"];
|
||||
_acceptTermsLabel = Localizer["AcceptTermsLabel"];
|
||||
_termsAndConditionsLink = Localizer["TermsAndConditionsLink"];
|
||||
_createAccountButton = Localizer["CreateAccountButton"];
|
||||
_alreadyRegisteredText = Localizer["AlreadyRegisteredText"];
|
||||
_loginHereLink = Localizer["LoginHereLink"];
|
||||
_creatingAccountMessage = Localizer["CreatingAccountMessage"];
|
||||
_registrationErrorMessage = Localizer["RegistrationErrorMessage"];
|
||||
}
|
||||
|
||||
private async Task HandleRegister()
|
||||
{
|
||||
_loadingIndicator.Show(Localizer["CreatingAccountMessage"]);
|
||||
_loadingIndicator.Show(_creatingAccountMessage);
|
||||
_serverValidationErrors.Clear();
|
||||
|
||||
var (success, errorMessage) = await UserRegistrationService.RegisterUserAsync(_registerModel.Username, _registerModel.Password);
|
||||
@@ -67,7 +102,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
_serverValidationErrors.AddError(errorMessage ?? Localizer["RegistrationErrorMessage"]);
|
||||
_serverValidationErrors.AddError(errorMessage ?? _registrationErrorMessage);
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</div>
|
||||
<div class="ml-3 bg-blue-100 dark:bg-blue-900 rounded-lg p-3">
|
||||
<p class="text-sm text-gray-900 dark:text-white">
|
||||
@Localizer["WelcomeMessage"]
|
||||
@_welcomeMessage
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -25,26 +25,26 @@
|
||||
|
||||
<div class="p-4 mb-6 bg-gray-100 dark:bg-gray-900 rounded-lg text-gray-900 dark:text-gray-100">
|
||||
<p class="text-sm font-semibold">
|
||||
@Localizer["ImportantNote"]
|
||||
@_importantNote
|
||||
</p>
|
||||
<ul class="text-sm mt-3 list-disc list-inside">
|
||||
<li>@Localizer["SecurityPoint1"]</li>
|
||||
<li>@Localizer["SecurityPoint2"]</li>
|
||||
<li>@Localizer["SecurityPoint3"]</li>
|
||||
<li>@_securityPoint1</li>
|
||||
<li>@_securityPoint2</li>
|
||||
<li>@_securityPoint3</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<div class="">
|
||||
<EditFormRow Id="password" Label="@Localizer["MasterPasswordLabel"]" @bind-Value="Password" Type="password" Placeholder="@Localizer["MasterPasswordPlaceholder"]" OnFocus="@OnPasswordInputFocus"/>
|
||||
<EditFormRow Id="password" Label="@_masterPasswordLabel" @bind-Value="Password" Type="password" Placeholder="@_masterPasswordPlaceholder" OnFocus="@OnPasswordInputFocus"/>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<EditFormRow Id="confirmPassword" Label="@Localizer["ConfirmMasterPasswordLabel"]" @bind-Value="ConfirmPassword" Type="password" Placeholder="@Localizer["ConfirmMasterPasswordPlaceholder"]" OnFocus="@OnPasswordInputFocus" />
|
||||
<EditFormRow Id="confirmPassword" Label="@_confirmMasterPasswordLabel" @bind-Value="ConfirmPassword" Type="password" Placeholder="@_confirmMasterPasswordPlaceholder" OnFocus="@OnPasswordInputFocus" />
|
||||
</div>
|
||||
@if (_isValidating)
|
||||
{
|
||||
<div class="mt-2 text-sm text-gray-600 dark:text-gray-400">@Localizer["ValidatingPasswordMessage"]</div>
|
||||
<div class="mt-2 text-sm text-gray-600 dark:text-gray-400">@_validatingPasswordMessage</div>
|
||||
}
|
||||
else if (_isValid)
|
||||
{
|
||||
@@ -54,7 +54,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="mt-2 text-sm text-green-600 dark:text-green-400">@Localizer["PasswordValidAndStrongMessage"]</div>
|
||||
<div class="mt-2 text-sm text-green-600 dark:text-green-400">@_passwordValidAndStrongMessage</div>
|
||||
}
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(_errorMessage))
|
||||
@@ -69,6 +69,21 @@
|
||||
@code {
|
||||
private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Auth.Setup.PasswordStep", "AliasVault.Client");
|
||||
|
||||
// Cached localized strings for performance
|
||||
private string _welcomeMessage = string.Empty;
|
||||
private string _importantNote = string.Empty;
|
||||
private string _securityPoint1 = string.Empty;
|
||||
private string _securityPoint2 = string.Empty;
|
||||
private string _securityPoint3 = string.Empty;
|
||||
private string _masterPasswordLabel = string.Empty;
|
||||
private string _masterPasswordPlaceholder = string.Empty;
|
||||
private string _confirmMasterPasswordLabel = string.Empty;
|
||||
private string _confirmMasterPasswordPlaceholder = string.Empty;
|
||||
private string _validatingPasswordMessage = string.Empty;
|
||||
private string _passwordValidAndStrongMessage = string.Empty;
|
||||
private string _passwordTooShortError = string.Empty;
|
||||
private string _passwordsMismatchError = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The event callback for when the password changes.
|
||||
/// </summary>
|
||||
@@ -122,6 +137,21 @@
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
// Cache localized strings for performance
|
||||
_welcomeMessage = Localizer["WelcomeMessage"];
|
||||
_importantNote = Localizer["ImportantNote"];
|
||||
_securityPoint1 = Localizer["SecurityPoint1"];
|
||||
_securityPoint2 = Localizer["SecurityPoint2"];
|
||||
_securityPoint3 = Localizer["SecurityPoint3"];
|
||||
_masterPasswordLabel = Localizer["MasterPasswordLabel"];
|
||||
_masterPasswordPlaceholder = Localizer["MasterPasswordPlaceholder"];
|
||||
_confirmMasterPasswordLabel = Localizer["ConfirmMasterPasswordLabel"];
|
||||
_confirmMasterPasswordPlaceholder = Localizer["ConfirmMasterPasswordPlaceholder"];
|
||||
_validatingPasswordMessage = Localizer["ValidatingPasswordMessage"];
|
||||
_passwordValidAndStrongMessage = Localizer["PasswordValidAndStrongMessage"];
|
||||
_passwordTooShortError = Localizer["PasswordTooShortError"];
|
||||
_passwordsMismatchError = Localizer["PasswordsMismatchError"];
|
||||
|
||||
_loadingTimer = new Timer(300);
|
||||
_loadingTimer.Elapsed += (sender, e) => FinishLoading();
|
||||
_loadingTimer.AutoReset = false;
|
||||
@@ -177,7 +207,7 @@
|
||||
{
|
||||
_isValidating = false;
|
||||
_isValid = false;
|
||||
_errorMessage = Localizer["PasswordTooShortError"];
|
||||
_errorMessage = _passwordTooShortError;
|
||||
await OnPasswordChange.InvokeAsync(string.Empty);
|
||||
StateHasChanged();
|
||||
return;
|
||||
@@ -197,7 +227,7 @@
|
||||
{
|
||||
_isValidating = false;
|
||||
_isValid = false;
|
||||
_errorMessage = Localizer["PasswordsMismatchError"];
|
||||
_errorMessage = _passwordsMismatchError;
|
||||
await OnPasswordChange.InvokeAsync(string.Empty);
|
||||
StateHasChanged();
|
||||
return;
|
||||
|
||||
@@ -13,18 +13,18 @@
|
||||
<div class="@(_isLoading ? "invisible opacity-0" : "opacity-100") transition-opacity duration-300 w-full">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg lg:shadow-none p-6">
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
||||
@Localizer["PleaseReadAndAgree"]
|
||||
@_pleaseReadAndAgree
|
||||
</p>
|
||||
<div class="bg-gray-100 dark:bg-gray-700 rounded-lg p-4 mb-8 h-80 overflow-y-auto">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">@Localizer["TermsAndConditionsTitle"]</h3>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">@_termsAndConditionsTitle</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 whitespace-pre-line">
|
||||
@Localizer["TermsContent"]
|
||||
@_termsContent
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input type="checkbox" id="agreeTerms" @bind="AgreedToTerms" @bind:after="OnAgreedToTerms" class="mr-2">
|
||||
<label for="agreeTerms" class="text-sm font-bold text-gray-600 dark:text-gray-400">
|
||||
@Localizer["AgreementCheckboxLabel"]
|
||||
@_agreementCheckboxLabel
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -33,6 +33,12 @@
|
||||
|
||||
@code {
|
||||
private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Auth.Setup.TermsAndConditionsStep", "AliasVault.Client");
|
||||
|
||||
// Cached localized strings for performance
|
||||
private string _pleaseReadAndAgree = string.Empty;
|
||||
private string _termsAndConditionsTitle = string.Empty;
|
||||
private string _termsContent = string.Empty;
|
||||
private string _agreementCheckboxLabel = string.Empty;
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the user has agreed to the terms and conditions.
|
||||
/// </summary>
|
||||
@@ -57,6 +63,12 @@
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
// Cache localized strings for performance
|
||||
_pleaseReadAndAgree = Localizer["PleaseReadAndAgree"];
|
||||
_termsAndConditionsTitle = Localizer["TermsAndConditionsTitle"];
|
||||
_termsContent = Localizer["TermsContent"];
|
||||
_agreementCheckboxLabel = Localizer["AgreementCheckboxLabel"];
|
||||
|
||||
_loadingTimer = new Timer(300);
|
||||
_loadingTimer.Elapsed += (sender, e) => FinishLoading();
|
||||
_loadingTimer.AutoReset = false;
|
||||
|
||||
@@ -14,17 +14,17 @@
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg lg:shadow-none p-6 mb-6">
|
||||
<div class="flex items-start mb-4">
|
||||
<div class="flex-shrink-0">
|
||||
<img class="h-10 w-10 rounded-full" src="/img/avatar.webp" alt="@Localizer["AssistantAvatarAlt"]">
|
||||
<img class="h-10 w-10 rounded-full" src="/img/avatar.webp" alt="@_assistantAvatarAlt">
|
||||
</div>
|
||||
<div class="ml-3 bg-blue-100 dark:bg-blue-900 rounded-lg p-3">
|
||||
<p class="text-sm text-gray-900 dark:text-white">
|
||||
@Localizer["GreatNowLetsSetupUsername"]
|
||||
@_greatNowLetsSetupUsername
|
||||
</p>
|
||||
<p class="text-sm text-gray-900 dark:text-white mt-3">
|
||||
@Localizer["EnterUsernameInstructions"]
|
||||
@_enterUsernameInstructions
|
||||
</p>
|
||||
<p class="text-sm text-gray-900 dark:text-white mt-3 font-semibold">
|
||||
@Localizer["RememberUsernameNote"]
|
||||
@_rememberUsernameNote
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -32,14 +32,14 @@
|
||||
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<EditFormRow Id="username" Label="@Localizer["UsernameLabel"]" @bind-Value="Username" Placeholder="@Localizer["UsernamePlaceholder"]" OnFocus="@OnUsernameInputFocus" />
|
||||
<EditFormRow Id="username" Label="@_usernameLabel" @bind-Value="Username" Placeholder="@_usernamePlaceholder" OnFocus="@OnUsernameInputFocus" />
|
||||
@if (_isValidating)
|
||||
{
|
||||
<div class="mt-2 text-sm text-gray-600 dark:text-gray-400">@Localizer["ValidatingUsernameMessage"]</div>
|
||||
<div class="mt-2 text-sm text-gray-600 dark:text-gray-400">@_validatingUsernameMessage</div>
|
||||
}
|
||||
else if (_isValid)
|
||||
{
|
||||
<div class="mt-2 text-sm text-green-600 dark:text-green-400">@Localizer["UsernameAvailableMessage"]</div>
|
||||
<div class="mt-2 text-sm text-green-600 dark:text-green-400">@_usernameAvailableMessage</div>
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(_errorMessage))
|
||||
{
|
||||
@@ -54,6 +54,18 @@
|
||||
private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Auth.Setup.UsernameStep", "AliasVault.Client");
|
||||
private IStringLocalizer ApiErrorLocalizer => LocalizerFactory.Create("ApiErrors", "AliasVault.Client");
|
||||
|
||||
// Cached localized strings for performance
|
||||
private string _assistantAvatarAlt = string.Empty;
|
||||
private string _greatNowLetsSetupUsername = string.Empty;
|
||||
private string _enterUsernameInstructions = string.Empty;
|
||||
private string _rememberUsernameNote = string.Empty;
|
||||
private string _usernameLabel = string.Empty;
|
||||
private string _usernamePlaceholder = string.Empty;
|
||||
private string _validatingUsernameMessage = string.Empty;
|
||||
private string _usernameAvailableMessage = string.Empty;
|
||||
private string _usernameRequiredError = string.Empty;
|
||||
private string _serverCommunicationError = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The username that is previously entered by the user. When a user navigates with back/continue
|
||||
/// and entered a username already, the existing username might be provided by the parent component.
|
||||
@@ -103,6 +115,18 @@
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// Cache localized strings for performance
|
||||
_assistantAvatarAlt = Localizer["AssistantAvatarAlt"];
|
||||
_greatNowLetsSetupUsername = Localizer["GreatNowLetsSetupUsername"];
|
||||
_enterUsernameInstructions = Localizer["EnterUsernameInstructions"];
|
||||
_rememberUsernameNote = Localizer["RememberUsernameNote"];
|
||||
_usernameLabel = Localizer["UsernameLabel"];
|
||||
_usernamePlaceholder = Localizer["UsernamePlaceholder"];
|
||||
_validatingUsernameMessage = Localizer["ValidatingUsernameMessage"];
|
||||
_usernameAvailableMessage = Localizer["UsernameAvailableMessage"];
|
||||
_usernameRequiredError = Localizer["UsernameRequiredError"];
|
||||
_serverCommunicationError = Localizer["ServerCommunicationError"];
|
||||
|
||||
// Set the default username if provided.
|
||||
_username = DefaultUsername;
|
||||
|
||||
@@ -166,7 +190,7 @@
|
||||
{
|
||||
_isValidating = false;
|
||||
_isValid = false;
|
||||
_errorMessage = Localizer["UsernameRequiredError"];
|
||||
_errorMessage = _usernameRequiredError;
|
||||
await OnUsernameChange.InvokeAsync(string.Empty);
|
||||
StateHasChanged();
|
||||
return;
|
||||
@@ -192,7 +216,7 @@
|
||||
}
|
||||
catch
|
||||
{
|
||||
_errorMessage = Localizer["ServerCommunicationError"];
|
||||
_errorMessage = _serverCommunicationError;
|
||||
_isValid = false;
|
||||
await OnUsernameChange.InvokeAsync(string.Empty);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
else if (IsWebAuthnLoading) {
|
||||
<BoldLoadingIndicator />
|
||||
<p class="mt-6 text-center font-normal text-gray-500 dark:text-gray-400">
|
||||
@Localizer["LoggingInWithWebAuthn"]
|
||||
@_loggingInWithWebAuthn
|
||||
</p>
|
||||
}
|
||||
else
|
||||
@@ -33,15 +33,15 @@ else
|
||||
{
|
||||
<div class="mb-6">
|
||||
<p class="text-base font-normal text-gray-500 dark:text-gray-400 mb-4">
|
||||
@Localizer["QuickUnlockDescription"]
|
||||
@_quickUnlockDescription
|
||||
</p>
|
||||
<div class="flex space-x-4">
|
||||
<button type="button" @onclick="UnlockWithWebAuthn" class="flex-grow inline-flex items-center justify-center px-5 py-3 text-base font-medium text-center text-white rounded-lg bg-primary-700 hover:bg-primary-800 focus:ring-4 focus:ring-primary-300 sm:w-auto dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">
|
||||
<svg class="w-5 h-5 mr-2 -ml-1" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z"></path><path fill-rule="evenodd" d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z" clip-rule="evenodd"></path></svg>
|
||||
@Localizer["UnlockWithWebAuthn"]
|
||||
@_unlockWithWebAuthn
|
||||
</button>
|
||||
<button type="button" @onclick="ShowPasswordLogin" class="inline-flex items-center justify-center px-5 py-3 text-base font-medium text-center text-gray-900 rounded-lg border border-gray-300 hover:bg-gray-100 focus:ring-4 focus:ring-gray-100 dark:text-white dark:border-gray-700 dark:hover:bg-gray-700 dark:focus:ring-gray-800">
|
||||
@Localizer["UnlockWithPassword"]
|
||||
@_unlockWithPassword
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -49,26 +49,26 @@ else
|
||||
else
|
||||
{
|
||||
<p class="text-base font-normal text-gray-500 dark:text-gray-400 mb-4">
|
||||
@Localizer["EnterMasterPasswordDescription"]
|
||||
@_enterMasterPasswordDescription
|
||||
</p>
|
||||
|
||||
<EditForm Model="_unlockModel" OnValidSubmit="UnlockSubmit" class="mt-8 space-y-6">
|
||||
<DataAnnotationsValidator/>
|
||||
<div>
|
||||
<label asp-for="Input.Password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@Localizer["YourPasswordLabel"]</label>
|
||||
<label asp-for="Input.Password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@_yourPasswordLabel</label>
|
||||
<InputTextField id="password" @bind-Value="_unlockModel.Password" type="password" placeholder="••••••••"/>
|
||||
<ValidationMessage For="() => _unlockModel.Password"/>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="w-full inline-flex items-center justify-center px-5 py-3 text-base font-medium text-center text-white rounded-lg bg-primary-700 hover:bg-primary-800 focus:ring-4 focus:ring-primary-300 sm:w-auto dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">
|
||||
<svg class="w-5 h-5 mr-2 -ml-1" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M10 2a5 5 0 00-5 5v2a2 2 0 00-2 2v5a2 2 0 002 2h10a2 2 0 002-2v-5a2 2 0 00-2-2H7V7a3 3 0 015.905-.75 1 1 0 001.937-.5A5.002 5.002 0 0010 2z"></path></svg>
|
||||
@Localizer["UnlockButton"]
|
||||
@_unlockButton
|
||||
</button>
|
||||
</EditForm>
|
||||
}
|
||||
|
||||
<div class="text-sm font-medium text-gray-500 dark:text-gray-400 mt-6">
|
||||
@Localizer["SwitchAccountsText"] <a href="/user/logout" class="text-primary-700 hover:underline dark:text-primary-500">@Localizer["LogOutLink"]</a>
|
||||
@_switchAccountsText <a href="/user/logout" class="text-primary-700 hover:underline dark:text-primary-500">@_logOutLink</a>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -89,11 +89,47 @@ else
|
||||
private IStringLocalizer Localizer => LocalizerFactory.Create("Pages.Auth.Unlock", "AliasVault.Client");
|
||||
private IStringLocalizer ApiErrorLocalizer => LocalizerFactory.Create("ApiErrors", "AliasVault.Client");
|
||||
|
||||
// Cached localized strings
|
||||
private string _loggingInWithWebAuthn = string.Empty;
|
||||
private string _quickUnlockDescription = string.Empty;
|
||||
private string _unlockWithWebAuthn = string.Empty;
|
||||
private string _unlockWithPassword = string.Empty;
|
||||
private string _enterMasterPasswordDescription = string.Empty;
|
||||
private string _yourPasswordLabel = string.Empty;
|
||||
private string _unlockButton = string.Empty;
|
||||
private string _switchAccountsText = string.Empty;
|
||||
private string _logOutLink = string.Empty;
|
||||
private string _unlockingVaultMessage = string.Empty;
|
||||
private string _unlockRequestError = string.Empty;
|
||||
private string _incorrectPasswordError = string.Empty;
|
||||
private string _genericUnlockError = string.Empty;
|
||||
private string _sessionTimedOutError = string.Empty;
|
||||
private string _connectionFailedError = string.Empty;
|
||||
private string _webAuthnNotSupportedError = string.Empty;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
// Cache localized strings to prevent infinite rendering loops
|
||||
_loggingInWithWebAuthn = Localizer["LoggingInWithWebAuthn"];
|
||||
_quickUnlockDescription = Localizer["QuickUnlockDescription"];
|
||||
_unlockWithWebAuthn = Localizer["UnlockWithWebAuthn"];
|
||||
_unlockWithPassword = Localizer["UnlockWithPassword"];
|
||||
_enterMasterPasswordDescription = Localizer["EnterMasterPasswordDescription"];
|
||||
_yourPasswordLabel = Localizer["YourPasswordLabel"];
|
||||
_unlockButton = Localizer["UnlockButton"];
|
||||
_switchAccountsText = Localizer["SwitchAccountsText"];
|
||||
_logOutLink = Localizer["LogOutLink"];
|
||||
_unlockingVaultMessage = Localizer["UnlockingVaultMessage"];
|
||||
_unlockRequestError = Localizer["UnlockRequestError"];
|
||||
_incorrectPasswordError = Localizer["IncorrectPasswordError"];
|
||||
_genericUnlockError = Localizer["GenericUnlockError"];
|
||||
_sessionTimedOutError = Localizer["SessionTimedOutError"];
|
||||
_connectionFailedError = Localizer["ConnectionFailedError"];
|
||||
_webAuthnNotSupportedError = Localizer["WebAuthnNotSupportedError"];
|
||||
|
||||
// Trigger status API call to check if the user is still authenticated.
|
||||
// If user is not authenticated a redirect to the login page will be triggered automatically.
|
||||
await StatusCheck();
|
||||
@@ -117,7 +153,7 @@ else
|
||||
/// </summary>
|
||||
private async Task UnlockSubmit()
|
||||
{
|
||||
_loadingIndicator.Show(Localizer["UnlockingVaultMessage"]);
|
||||
_loadingIndicator.Show(_unlockingVaultMessage);
|
||||
_serverValidationErrors.Clear();
|
||||
|
||||
try
|
||||
@@ -142,7 +178,7 @@ else
|
||||
var loginResponse = JsonSerializer.Deserialize<LoginInitiateResponse>(responseContent);
|
||||
if (loginResponse == null)
|
||||
{
|
||||
_serverValidationErrors.AddError(Localizer["UnlockRequestError"]);
|
||||
_serverValidationErrors.AddError(_unlockRequestError);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -154,7 +190,7 @@ else
|
||||
|
||||
if (!validPassword)
|
||||
{
|
||||
_serverValidationErrors.AddError(Localizer["IncorrectPasswordError"]);
|
||||
_serverValidationErrors.AddError(_incorrectPasswordError);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -183,7 +219,7 @@ else
|
||||
catch
|
||||
{
|
||||
// If in release mode show a generic error.
|
||||
_serverValidationErrors.AddError(Localizer["GenericUnlockError"]);
|
||||
_serverValidationErrors.AddError(_genericUnlockError);
|
||||
}
|
||||
#endif
|
||||
finally
|
||||
@@ -202,7 +238,7 @@ else
|
||||
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
|
||||
if (authState.User.Identity?.IsAuthenticated == false) {
|
||||
// Not authenticated (anymore), redirect to login page.
|
||||
GlobalNotificationService.AddErrorMessage(Localizer["SessionTimedOutError"]);
|
||||
GlobalNotificationService.AddErrorMessage(_sessionTimedOutError);
|
||||
NavigationManager.NavigateTo("/user/login");
|
||||
return;
|
||||
}
|
||||
@@ -214,7 +250,7 @@ else
|
||||
// Clear all tokens and redirect to login page.
|
||||
await AuthService.RemoveTokensAsync();
|
||||
GlobalNotificationService.ClearMessages();
|
||||
GlobalNotificationService.AddErrorMessage(Localizer["SessionTimedOutError"]);
|
||||
GlobalNotificationService.AddErrorMessage(_sessionTimedOutError);
|
||||
NavigationManager.NavigateTo("/user/login");
|
||||
return;
|
||||
}
|
||||
@@ -228,7 +264,7 @@ else
|
||||
await AuthService.RemoveTokensAsync();
|
||||
await AuthStateProvider.GetAuthenticationStateAsync();
|
||||
GlobalNotificationService.ClearMessages();
|
||||
GlobalNotificationService.AddErrorMessage(Localizer["SessionTimedOutError"]);
|
||||
GlobalNotificationService.AddErrorMessage(_sessionTimedOutError);
|
||||
NavigationManager.NavigateTo("/user/login");
|
||||
return;
|
||||
}
|
||||
@@ -241,7 +277,7 @@ else
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_serverValidationErrors.AddError(Localizer["ConnectionFailedError"]);
|
||||
_serverValidationErrors.AddError(_connectionFailedError);
|
||||
Logger.LogError(ex, "An error occurred while checking the user status.");
|
||||
StateHasChanged();
|
||||
}
|
||||
@@ -284,7 +320,7 @@ else
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
GlobalNotificationService.AddErrorMessage(Localizer["WebAuthnNotSupportedError"], true);
|
||||
GlobalNotificationService.AddErrorMessage(_webAuthnNotSupportedError, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -37,12 +37,12 @@
|
||||
<!-- 2-column layout for email details -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
<div class="space-y-1">
|
||||
<p>@Localizer["FromLabel"] @(Email?.FromLocal)@@@(Email?.FromDomain)</p>
|
||||
<p>@Localizer["ToLabel"] @(Email?.ToLocal)@@@(Email?.ToDomain)</p>
|
||||
<p>@_fromLabel @(Email?.FromLocal)@@@(Email?.FromDomain)</p>
|
||||
<p>@_toLabel @(Email?.ToLocal)@@@(Email?.ToDomain)</p>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<p>@Localizer["DateLabel"] @Email?.DateSystem</p>
|
||||
<p>@Localizer["ActionsLabel"] <button @onclick="ShowDeleteConfirmation" class="text-red-500 hover:text-red-700 text-sm">@Localizer["DeleteButton"]</button></p>
|
||||
<p>@_dateLabel @Email?.DateSystem</p>
|
||||
<p>@_actionsLabel <button @onclick="ShowDeleteConfirmation" class="text-red-500 hover:text-red-700 text-sm">@_deleteButton</button></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -59,7 +59,7 @@
|
||||
@if (Email?.Attachments?.Any() == true)
|
||||
{
|
||||
<div class="border-t border-gray-200 dark:border-gray-600 pt-4">
|
||||
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400 mb-2">@Localizer["AttachmentsLabel"]</h3>
|
||||
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400 mb-2">@_attachmentsLabel</h3>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
|
||||
@foreach (var attachment in Email.Attachments)
|
||||
{
|
||||
@@ -78,7 +78,7 @@
|
||||
}
|
||||
</div>
|
||||
<div class="mt-6 flex justify-end space-x-4">
|
||||
<button id="close-email-modal" @onclick="Close" class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">@Localizer["CloseButton"]</button>
|
||||
<button id="close-email-modal" @onclick="Close" class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">@_closeButton</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -87,6 +87,23 @@
|
||||
|
||||
@code {
|
||||
private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Email.EmailModal", "AliasVault.Client");
|
||||
|
||||
// Cached localized strings
|
||||
private string _fromLabel = "";
|
||||
private string _toLabel = "";
|
||||
private string _dateLabel = "";
|
||||
private string _actionsLabel = "";
|
||||
private string _deleteButton = "";
|
||||
private string _attachmentsLabel = "";
|
||||
private string _closeButton = "";
|
||||
private string _deleteEmailTitle = "";
|
||||
private string _deleteEmailConfirmation = "";
|
||||
private string _emailDeletedSuccess = "";
|
||||
private string _emailDeleteFailed = "";
|
||||
private string _genericError = "";
|
||||
private string _noEmailBody = "";
|
||||
private string _attachmentDownloadFailed = "";
|
||||
private string _attachmentDownloadError = "";
|
||||
|
||||
/// <summary>
|
||||
/// The email to show in the modal.
|
||||
@@ -128,8 +145,8 @@
|
||||
}
|
||||
|
||||
var result = await ConfirmModalService.ShowConfirmation(
|
||||
Localizer["DeleteEmailTitle"],
|
||||
Localizer["DeleteEmailConfirmation"]
|
||||
_deleteEmailTitle,
|
||||
_deleteEmailConfirmation
|
||||
);
|
||||
|
||||
if (result)
|
||||
@@ -189,18 +206,18 @@
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
await OnEmailDeleted.InvokeAsync(Email.Id);
|
||||
GlobalNotificationService.AddSuccessMessage(Localizer["EmailDeletedSuccess"], true);
|
||||
GlobalNotificationService.AddSuccessMessage(_emailDeletedSuccess, true);
|
||||
await Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
var errorMessage = await response.Content.ReadAsStringAsync();
|
||||
GlobalNotificationService.AddErrorMessage($"{Localizer["EmailDeleteFailed"]}: {errorMessage}", true);
|
||||
GlobalNotificationService.AddErrorMessage($"{_emailDeleteFailed}: {errorMessage}", true);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GlobalNotificationService.AddErrorMessage($"{Localizer["GenericError"]}: {ex.Message}", true);
|
||||
GlobalNotificationService.AddErrorMessage($"{_genericError}: {ex.Message}", true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,22 +237,39 @@
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
await OnEmailDeleted.InvokeAsync(Email.Id);
|
||||
GlobalNotificationService.AddSuccessMessage(Localizer["EmailDeletedSuccess"], true);
|
||||
GlobalNotificationService.AddSuccessMessage(_emailDeletedSuccess, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
GlobalNotificationService.AddErrorMessage(Localizer["EmailDeleteFailed"], true);
|
||||
GlobalNotificationService.AddErrorMessage(_emailDeleteFailed, true);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GlobalNotificationService.AddErrorMessage($"{Localizer["EmailDeleteFailed"]}: {ex.Message}", true);
|
||||
GlobalNotificationService.AddErrorMessage($"{_emailDeleteFailed}: {ex.Message}", true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// Cache localized strings
|
||||
_fromLabel = Localizer["FromLabel"];
|
||||
_toLabel = Localizer["ToLabel"];
|
||||
_dateLabel = Localizer["DateLabel"];
|
||||
_actionsLabel = Localizer["ActionsLabel"];
|
||||
_deleteButton = Localizer["DeleteButton"];
|
||||
_attachmentsLabel = Localizer["AttachmentsLabel"];
|
||||
_closeButton = Localizer["CloseButton"];
|
||||
_deleteEmailTitle = Localizer["DeleteEmailTitle"];
|
||||
_deleteEmailConfirmation = Localizer["DeleteEmailConfirmation"];
|
||||
_emailDeletedSuccess = Localizer["EmailDeletedSuccess"];
|
||||
_emailDeleteFailed = Localizer["EmailDeleteFailed"];
|
||||
_genericError = Localizer["GenericError"];
|
||||
_noEmailBody = Localizer["NoEmailBody"];
|
||||
_attachmentDownloadFailed = Localizer["AttachmentDownloadFailed"];
|
||||
_attachmentDownloadError = Localizer["AttachmentDownloadError"];
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
|
||||
// Determine email body
|
||||
@@ -255,7 +289,7 @@
|
||||
else
|
||||
{
|
||||
// No HTML and no plain text available
|
||||
EmailBody = Localizer["NoEmailBody"];
|
||||
EmailBody = _noEmailBody;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -282,7 +316,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
GlobalNotificationService.AddErrorMessage(Localizer["AttachmentDownloadFailed"], true);
|
||||
GlobalNotificationService.AddErrorMessage(_attachmentDownloadFailed, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -305,13 +339,13 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
GlobalNotificationService.AddErrorMessage(Localizer["AttachmentDownloadFailed"], true);
|
||||
GlobalNotificationService.AddErrorMessage(_attachmentDownloadFailed, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GlobalNotificationService.AddErrorMessage($"{Localizer["AttachmentDownloadError"]}: {ex.Message}", true);
|
||||
GlobalNotificationService.AddErrorMessage($"{_attachmentDownloadError}: {ex.Message}", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,14 +37,14 @@
|
||||
<!-- 2-column layout for email details -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 text-sm text-gray-600 dark:text-gray-300">
|
||||
<div class="space-y-1">
|
||||
<p><span class="font-medium">@Localizer["FromLabel"]</span> @(Email.FromLocal)@@@(Email.FromDomain)</p>
|
||||
<p><span class="font-medium">@Localizer["ToLabel"]</span> @(Email.ToLocal)@@@(Email.ToDomain)</p>
|
||||
<p><span class="font-medium">@_fromLabel</span> @(Email.FromLocal)@@@(Email.FromDomain)</p>
|
||||
<p><span class="font-medium">@_toLabel</span> @(Email.ToLocal)@@@(Email.ToDomain)</p>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<p><span class="font-medium">@Localizer["DateLabel"]</span> @Email.DateSystem</p>
|
||||
<p><span class="font-medium">@_dateLabel</span> @Email.DateSystem</p>
|
||||
@if (!string.IsNullOrEmpty(CredentialName) && CredentialId != Guid.Empty)
|
||||
{
|
||||
<p><span class="font-medium">@Localizer["CredentialLabel"]</span>
|
||||
<p><span class="font-medium">@_credentialLabel</span>
|
||||
<button @onclick="@(() => OnCredentialClick.InvokeAsync(CredentialId))"
|
||||
class="text-blue-600 hover:underline dark:text-blue-400 cursor-pointer">
|
||||
@CredentialName
|
||||
@@ -53,7 +53,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<p><span class="font-medium">@Localizer["CredentialLabel"]</span> <span class="text-gray-400 dark:text-gray-500">@Localizer["NoneValue"]</span></p>
|
||||
<p><span class="font-medium">@_credentialLabel</span> <span class="text-gray-400 dark:text-gray-500">@_noneValue</span></p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -73,7 +73,7 @@
|
||||
{
|
||||
<!-- Attachments Section - Fixed height -->
|
||||
<div class="border-t border-gray-200 dark:border-gray-600 p-4 bg-gray-50 dark:bg-gray-800">
|
||||
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400 mb-2">@Localizer["AttachmentsLabel"]</h3>
|
||||
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400 mb-2">@_attachmentsLabel</h3>
|
||||
<div class="grid grid-cols-1 gap-2 max-h-32 overflow-y-auto">
|
||||
@foreach (var attachment in Email.Attachments)
|
||||
{
|
||||
@@ -99,7 +99,7 @@
|
||||
<svg class="mx-auto h-12 w-12 text-gray-300 dark:text-gray-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 4.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
|
||||
</svg>
|
||||
<p class="mt-2 text-sm">@Localizer["SelectEmailMessage"]</p>
|
||||
<p class="mt-2 text-sm">@_selectEmailMessage</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -107,6 +107,23 @@
|
||||
|
||||
@code {
|
||||
private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Email.EmailPreview", "AliasVault.Client");
|
||||
|
||||
// Cached localized strings
|
||||
private string _fromLabel = "";
|
||||
private string _toLabel = "";
|
||||
private string _dateLabel = "";
|
||||
private string _credentialLabel = "";
|
||||
private string _noneValue = "";
|
||||
private string _attachmentsLabel = "";
|
||||
private string _selectEmailMessage = "";
|
||||
private string _deleteEmailTitle = "";
|
||||
private string _deleteEmailConfirmation = "";
|
||||
private string _emailDeletedSuccess = "";
|
||||
private string _emailDeleteFailed = "";
|
||||
private string _genericError = "";
|
||||
private string _noEmailBody = "";
|
||||
private string _attachmentDownloadFailed = "";
|
||||
private string _attachmentDownloadError = "";
|
||||
/// <summary>
|
||||
/// The email to show in the preview.
|
||||
/// </summary>
|
||||
@@ -159,8 +176,8 @@
|
||||
}
|
||||
|
||||
var result = await ConfirmModalService.ShowConfirmation(
|
||||
Localizer["DeleteEmailTitle"],
|
||||
Localizer["DeleteEmailConfirmation"]
|
||||
_deleteEmailTitle,
|
||||
_deleteEmailConfirmation
|
||||
);
|
||||
|
||||
if (result)
|
||||
@@ -211,17 +228,17 @@
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
await OnEmailDeleted.InvokeAsync(Email.Id);
|
||||
GlobalNotificationService.AddSuccessMessage(Localizer["EmailDeletedSuccess"], true);
|
||||
GlobalNotificationService.AddSuccessMessage(_emailDeletedSuccess, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
var errorMessage = await response.Content.ReadAsStringAsync();
|
||||
GlobalNotificationService.AddErrorMessage($"{Localizer["EmailDeleteFailed"]}: {errorMessage}", true);
|
||||
GlobalNotificationService.AddErrorMessage($"{_emailDeleteFailed}: {errorMessage}", true);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GlobalNotificationService.AddErrorMessage($"{Localizer["GenericError"]}: {ex.Message}", true);
|
||||
GlobalNotificationService.AddErrorMessage($"{_genericError}: {ex.Message}", true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,19 +258,41 @@
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
await OnEmailDeleted.InvokeAsync(Email.Id);
|
||||
GlobalNotificationService.AddSuccessMessage(Localizer["EmailDeletedSuccess"], true);
|
||||
GlobalNotificationService.AddSuccessMessage(_emailDeletedSuccess, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
GlobalNotificationService.AddErrorMessage(Localizer["EmailDeleteFailed"], true);
|
||||
GlobalNotificationService.AddErrorMessage(_emailDeleteFailed, true);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GlobalNotificationService.AddErrorMessage($"{Localizer["EmailDeleteFailed"]}: {ex.Message}", true);
|
||||
GlobalNotificationService.AddErrorMessage($"{_emailDeleteFailed}: {ex.Message}", true);
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// Cache localized strings
|
||||
_fromLabel = Localizer["FromLabel"];
|
||||
_toLabel = Localizer["ToLabel"];
|
||||
_dateLabel = Localizer["DateLabel"];
|
||||
_credentialLabel = Localizer["CredentialLabel"];
|
||||
_noneValue = Localizer["NoneValue"];
|
||||
_attachmentsLabel = Localizer["AttachmentsLabel"];
|
||||
_selectEmailMessage = Localizer["SelectEmailMessage"];
|
||||
_deleteEmailTitle = Localizer["DeleteEmailTitle"];
|
||||
_deleteEmailConfirmation = Localizer["DeleteEmailConfirmation"];
|
||||
_emailDeletedSuccess = Localizer["EmailDeletedSuccess"];
|
||||
_emailDeleteFailed = Localizer["EmailDeleteFailed"];
|
||||
_genericError = Localizer["GenericError"];
|
||||
_noEmailBody = Localizer["NoEmailBody"];
|
||||
_attachmentDownloadFailed = Localizer["AttachmentDownloadFailed"];
|
||||
_attachmentDownloadError = Localizer["AttachmentDownloadError"];
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
@@ -275,7 +314,7 @@
|
||||
else
|
||||
{
|
||||
// No HTML and no plain text available
|
||||
EmailBody = Localizer["NoEmailBody"];
|
||||
EmailBody = _noEmailBody;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -302,7 +341,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
GlobalNotificationService.AddErrorMessage(Localizer["AttachmentDownloadFailed"], true);
|
||||
GlobalNotificationService.AddErrorMessage(_attachmentDownloadFailed, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -325,13 +364,13 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
GlobalNotificationService.AddErrorMessage(Localizer["AttachmentDownloadFailed"], true);
|
||||
GlobalNotificationService.AddErrorMessage(_attachmentDownloadFailed, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GlobalNotificationService.AddErrorMessage($"{Localizer["AttachmentDownloadError"]}: {ex.Message}", true);
|
||||
GlobalNotificationService.AddErrorMessage($"{_attachmentDownloadError}: {ex.Message}", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
}
|
||||
@if (IsNewEmail)
|
||||
{
|
||||
<div class="w-2 h-2 ml-1 bg-yellow-500 rounded-full animate-pulse flex-shrink-0" title="@Localizer["NewEmailTooltip"]"></div>
|
||||
<div class="w-2 h-2 ml-1 bg-yellow-500 rounded-full animate-pulse flex-shrink-0" title="@_newEmailTooltip"></div>
|
||||
}
|
||||
</div>
|
||||
<!-- Subject (smaller, below from) -->
|
||||
@@ -44,6 +44,10 @@
|
||||
|
||||
@code {
|
||||
private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Email.EmailRow", "AliasVault.Client");
|
||||
|
||||
// Cached localized strings
|
||||
private string _newEmailTooltip = "";
|
||||
|
||||
/// <summary>
|
||||
/// The email model.
|
||||
/// </summary>
|
||||
@@ -68,4 +72,12 @@
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool IsNewEmail { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// Cache localized strings
|
||||
_newEmailTooltip = Localizer["NewEmailTooltip"];
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,12 +26,12 @@
|
||||
<div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
|
||||
<div class="flex justify-between">
|
||||
<div>
|
||||
<h3 class="mb-4 text-xl font-semibold dark:text-white">@Localizer["EmailSectionTitle"]</h3>
|
||||
<h3 class="mb-4 text-xl font-semibold dark:text-white">@_emailSectionTitle</h3>
|
||||
</div>
|
||||
<div class="flex justify-end items-center space-x-2">
|
||||
@if (DbService.Settings.AutoEmailRefresh)
|
||||
{
|
||||
<div class="w-3 h-3 mr-2 rounded-full bg-primary-300 border-2 border-primary-100 animate-pulse" title="@Localizer["AutoRefreshEnabledTooltip"]"></div>
|
||||
<div class="w-3 h-3 mr-2 rounded-full bg-primary-300 border-2 border-primary-100 animate-pulse" title="@_autoRefreshEnabledTooltip"></div>
|
||||
}
|
||||
<button id="recent-email-refresh" @onclick="ManualRefresh" type="button" class="text-blue-700 border border-blue-700 hover:bg-blue-700 hover:text-white focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-full text-sm p-2 text-center inline-flex items-center dark:border-blue-500 dark:text-blue-500 dark:hover:text-white dark:focus:ring-blue-800 dark:hover:bg-blue-500">
|
||||
<svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor">
|
||||
@@ -51,10 +51,10 @@
|
||||
<thead class="bg-gray-50 dark:bg-gray-700">
|
||||
<tr>
|
||||
<th scope="col" class="p-4 text-xs font-medium tracking-wider text-left text-gray-500 uppercase dark:text-white">
|
||||
@Localizer["SubjectColumn"]
|
||||
@_subjectColumn
|
||||
</th>
|
||||
<th scope="col" class="p-4 text-xs font-medium tracking-wider text-left text-gray-500 uppercase dark:text-white">
|
||||
@Localizer["DateColumn"]
|
||||
@_dateColumn
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -87,7 +87,7 @@
|
||||
}
|
||||
else if (MailboxEmails.Count == 0)
|
||||
{
|
||||
<div class="text-gray-500 dark:text-gray-400">@Localizer["NoEmailsReceivedMessage"]</div>
|
||||
<div class="text-gray-500 dark:text-gray-400">@_noEmailsReceivedMessage</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -99,10 +99,10 @@
|
||||
<thead class="bg-gray-50 dark:bg-gray-700">
|
||||
<tr>
|
||||
<th scope="col" class="p-4 text-xs font-medium tracking-wider text-left text-gray-500 uppercase dark:text-white">
|
||||
@Localizer["SubjectColumn"]
|
||||
@_subjectColumn
|
||||
</th>
|
||||
<th scope="col" class="p-4 text-xs font-medium tracking-wider text-left text-gray-500 uppercase dark:text-white">
|
||||
@Localizer["DateColumn"]
|
||||
@_dateColumn
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -150,6 +150,15 @@
|
||||
private bool IsLoading => LoadingService.IsLoading("recentemails");
|
||||
private bool IsSpamOk { get; set; } = false;
|
||||
private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Email.RecentEmails", "AliasVault.Client");
|
||||
|
||||
// Cached localized strings
|
||||
private string _emailSectionTitle = "";
|
||||
private string _autoRefreshEnabledTooltip = "";
|
||||
private string _subjectColumn = "";
|
||||
private string _dateColumn = "";
|
||||
private string _noEmailsReceivedMessage = "";
|
||||
private string _emailAddressInUseError = "";
|
||||
private string _emailLoadError = "";
|
||||
|
||||
private const int ACTIVE_TAB_REFRESH_INTERVAL = 2000; // 2 seconds
|
||||
private CancellationTokenSource? _pollingCts;
|
||||
@@ -229,6 +238,15 @@
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// Cache localized strings
|
||||
_emailSectionTitle = Localizer["EmailSectionTitle"];
|
||||
_autoRefreshEnabledTooltip = Localizer["AutoRefreshEnabledTooltip"];
|
||||
_subjectColumn = Localizer["SubjectColumn"];
|
||||
_dateColumn = Localizer["DateColumn"];
|
||||
_noEmailsReceivedMessage = Localizer["NoEmailsReceivedMessage"];
|
||||
_emailAddressInUseError = Localizer["EmailAddressInUseError"];
|
||||
_emailLoadError = Localizer["EmailLoadError"];
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
|
||||
if (EmailAddress is null)
|
||||
@@ -430,10 +448,10 @@
|
||||
switch (errorResponse.Code)
|
||||
{
|
||||
case "CLAIM_DOES_NOT_MATCH_USER":
|
||||
Error = Localizer["EmailAddressInUseError"];
|
||||
Error = _emailAddressInUseError;
|
||||
break;
|
||||
case "CLAIM_DOES_NOT_EXIST":
|
||||
Error = Localizer["EmailLoadError"];
|
||||
Error = _emailLoadError;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException(errorResponse.Message);
|
||||
|
||||
@@ -14,13 +14,16 @@
|
||||
@if (Copied)
|
||||
{
|
||||
<span class="absolute inset-y-0 right-0 flex items-center pr-3 text-green-500 dark:text-green-400">
|
||||
@Localizer["CopiedMessage"]
|
||||
@_copiedMessage
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Forms.CopyPasteFormRow", "AliasVault.Client");
|
||||
|
||||
// Cached localized strings
|
||||
private string _copiedMessage = "";
|
||||
|
||||
/// <summary>
|
||||
/// Id for the input field. Defaults to a random GUID if not provided.
|
||||
@@ -45,6 +48,9 @@
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
// Cache localized strings
|
||||
_copiedMessage = Localizer["CopiedMessage"];
|
||||
|
||||
ClipboardCopyService.OnCopy += HandleCopy;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,13 +20,13 @@
|
||||
@if (IsCustomDomain)
|
||||
{
|
||||
<button type="button" class="text-sm text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-200" @onclick="TogglePopup">
|
||||
@Localizer["UseDomainChooserButton"]
|
||||
@_useDomainChooserButton
|
||||
</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" class="text-sm text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-200" @onclick="ToggleCustomDomain">
|
||||
@Localizer["EnterCustomDomainButton"]
|
||||
@_enterCustomDomainButton
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
@@ -36,13 +36,13 @@
|
||||
<div class="fixed inset-0 bg-gray-600 bg-opacity-50 z-30 overflow-y-auto h-full w-full" @onclick="ClosePopup">
|
||||
<div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white dark:bg-gray-800" @onclick:stopPropagation>
|
||||
<div class="mt-3 text-center">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-white">@Localizer["SelectEmailDomainTitle"]</h3>
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-white">@_selectEmailDomainTitle</h3>
|
||||
<div class="mt-2 px-7 py-3">
|
||||
@if (ShowPrivateDomains)
|
||||
{
|
||||
<div class="mb-4">
|
||||
<h4 class="text-md font-semibold text-gray-700 dark:text-gray-300">@Localizer["PrivateEmailTitle"]</h4>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">@Localizer["PrivateEmailDescription"]</p>
|
||||
<h4 class="text-md font-semibold text-gray-700 dark:text-gray-300">@_privateEmailTitle</h4>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">@_privateEmailDescription</p>
|
||||
@foreach (var domain in PrivateDomains)
|
||||
{
|
||||
<button class="mt-2 px-4 py-2 bg-primary-300 text-gray-700 rounded hover:bg-primary-400 focus:outline-none focus:ring-2 focus:ring-gray-400 mr-2" @onclick="() => SelectDomain(domain)">
|
||||
@@ -52,8 +52,8 @@
|
||||
</div>
|
||||
}
|
||||
<div class="@(ShowPrivateDomains ? "border-t border-gray-200 dark:border-gray-600 pt-4" : "")">
|
||||
<h4 class="text-md font-semibold text-gray-700 dark:text-gray-300">@Localizer["PublicEmailTitle"]</h4>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">@Localizer["PublicEmailDescription"]</p>
|
||||
<h4 class="text-md font-semibold text-gray-700 dark:text-gray-300">@_publicEmailTitle</h4>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">@_publicEmailDescription</p>
|
||||
@foreach (var domain in PublicDomains)
|
||||
{
|
||||
<button class="mt-2 px-4 py-2 bg-gray-200 text-gray-700 rounded hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-400 mr-2" @onclick="() => SelectDomain(domain)">
|
||||
@@ -81,6 +81,15 @@
|
||||
public string Label { get; set; } = "Email Address";
|
||||
|
||||
private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Forms.EditEmailFormRow", "AliasVault.Client");
|
||||
|
||||
// Cached localized strings
|
||||
private string _useDomainChooserButton = "";
|
||||
private string _enterCustomDomainButton = "";
|
||||
private string _selectEmailDomainTitle = "";
|
||||
private string _privateEmailTitle = "";
|
||||
private string _privateEmailDescription = "";
|
||||
private string _publicEmailTitle = "";
|
||||
private string _publicEmailDescription = "";
|
||||
|
||||
/// <summary>
|
||||
/// The value of the input field. This should be the full email address.
|
||||
@@ -107,6 +116,15 @@
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
// Cache localized strings
|
||||
_useDomainChooserButton = Localizer["UseDomainChooserButton"];
|
||||
_enterCustomDomainButton = Localizer["EnterCustomDomainButton"];
|
||||
_selectEmailDomainTitle = Localizer["SelectEmailDomainTitle"];
|
||||
_privateEmailTitle = Localizer["PrivateEmailTitle"];
|
||||
_privateEmailDescription = Localizer["PrivateEmailDescription"];
|
||||
_publicEmailTitle = Localizer["PublicEmailTitle"];
|
||||
_publicEmailDescription = Localizer["PublicEmailDescription"];
|
||||
|
||||
base.OnInitialized();
|
||||
|
||||
IsCustomDomain = !PublicDomains.Contains(SelectedDomain) && !PrivateDomains.Contains(SelectedDomain);
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
@using Microsoft.Extensions.Localization
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="password-generator-settings-modal" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@Localizer["PasswordGeneratorSettingsLabel"]</label>
|
||||
<label for="password-generator-settings-modal" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@_passwordGeneratorSettingsLabel</label>
|
||||
<button type="button" id="password-generator-settings-modal" class="px-4 py-2 bg-primary-600 text-white rounded-md hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-primary-700 dark:hover:bg-primary-600" @onclick="OpenSettings">
|
||||
@Localizer["ConfigureButton"]
|
||||
@_configureButton
|
||||
</button>
|
||||
<span class="block text-sm font-normal text-gray-500 truncate dark:text-gray-400 mt-2">
|
||||
@Localizer["PasswordGeneratorSettingsDescription"]
|
||||
@_passwordGeneratorSettingsDescription
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -23,6 +23,11 @@
|
||||
|
||||
@code {
|
||||
private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Settings.DefaultPasswordSettings", "AliasVault.Client");
|
||||
|
||||
// Cached localized strings
|
||||
private string _passwordGeneratorSettingsLabel = "";
|
||||
private string _configureButton = "";
|
||||
private string _passwordGeneratorSettingsDescription = "";
|
||||
|
||||
private PasswordSettings PasswordSettings { get; set; } = new();
|
||||
private bool IsSettingsVisible { get; set; }
|
||||
@@ -30,6 +35,11 @@
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// Cache localized strings
|
||||
_passwordGeneratorSettingsLabel = Localizer["PasswordGeneratorSettingsLabel"];
|
||||
_configureButton = Localizer["ConfigureButton"];
|
||||
_passwordGeneratorSettingsDescription = Localizer["PasswordGeneratorSettingsDescription"];
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
PasswordSettings = DbService.Settings.PasswordSettings;
|
||||
}
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
<ModalWrapper OnEnter="HandleEnterKey">
|
||||
<div id="passwordSettingsModal" class="relative top-20 mx-auto p-5 shadow-lg rounded-md bg-white dark:bg-gray-800 border-2 border-gray-300 dark:border-gray-400">
|
||||
<div class="m-2">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-white">@Localizer["Title"]</h3>
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-white">@_title</h3>
|
||||
<div class="mt-4 space-y-4">
|
||||
<div>
|
||||
<label for="password-length" class="block text-sm font-medium text-gray-700 dark:text-gray-300">@string.Format(Localizer["PasswordLengthLabel"], _workingSettings.Length)</label>
|
||||
<label for="password-length" class="block text-sm font-medium text-gray-700 dark:text-gray-300">@string.Format(_passwordLengthLabel, _workingSettings.Length)</label>
|
||||
<input type="range" id="password-length" min="8" max="64"
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white"
|
||||
@bind="_workingSettings.Length" @oninput="HandleLengthInput">
|
||||
@@ -22,35 +22,35 @@
|
||||
<div class="flex items-center">
|
||||
<input id="use-lowercase" type="checkbox" class="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded dark:bg-gray-700 dark:border-gray-600"
|
||||
@bind="_workingSettings.UseLowercase" @bind:after="OnPasswordSettingsChanged">
|
||||
<label for="use-lowercase" class="ml-2 block text-sm text-gray-700 dark:text-gray-300">@Localizer["IncludeLowercaseLabel"]</label>
|
||||
<label for="use-lowercase" class="ml-2 block text-sm text-gray-700 dark:text-gray-300">@_includeLowercaseLabel</label>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<input id="use-uppercase" type="checkbox" class="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded dark:bg-gray-700 dark:border-gray-600"
|
||||
@bind="_workingSettings.UseUppercase" @bind:after="OnPasswordSettingsChanged">
|
||||
<label for="use-uppercase" class="ml-2 block text-sm text-gray-700 dark:text-gray-300">@Localizer["IncludeUppercaseLabel"]</label>
|
||||
<label for="use-uppercase" class="ml-2 block text-sm text-gray-700 dark:text-gray-300">@_includeUppercaseLabel</label>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<input id="use-numbers" type="checkbox" class="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded dark:bg-gray-700 dark:border-gray-600"
|
||||
@bind="_workingSettings.UseNumbers" @bind:after="OnPasswordSettingsChanged">
|
||||
<label for="use-numbers" class="ml-2 block text-sm text-gray-700 dark:text-gray-300">@Localizer["IncludeNumbersLabel"]</label>
|
||||
<label for="use-numbers" class="ml-2 block text-sm text-gray-700 dark:text-gray-300">@_includeNumbersLabel</label>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<input id="use-special-chars" type="checkbox" class="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded dark:bg-gray-700 dark:border-gray-600"
|
||||
@bind="_workingSettings.UseSpecialChars" @bind:after="OnPasswordSettingsChanged">
|
||||
<label for="use-special-chars" class="ml-2 block text-sm text-gray-700 dark:text-gray-300">@Localizer["IncludeSpecialCharsLabel"]</label>
|
||||
<label for="use-special-chars" class="ml-2 block text-sm text-gray-700 dark:text-gray-300">@_includeSpecialCharsLabel</label>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<input id="use-non-ambiguous" type="checkbox" class="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded dark:bg-gray-700 dark:border-gray-600"
|
||||
@bind="_workingSettings.UseNonAmbiguousChars" @bind:after="OnPasswordSettingsChanged">
|
||||
<label for="use-non-ambiguous" class="ml-2 block text-sm text-gray-700 dark:text-gray-300">@Localizer["AvoidAmbiguousCharsLabel"]</label>
|
||||
<label for="use-non-ambiguous" class="ml-2 block text-sm text-gray-700 dark:text-gray-300">@_avoidAmbiguousCharsLabel</label>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">@Localizer["PreviewLabel"]</label>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">@_previewLabel</label>
|
||||
<div class="mt-1 flex">
|
||||
<CopyPasteFormRow Id="preview-password" Value="@_previewPassword" />
|
||||
<button type="button" class="ml-2 px-3 py-2 text-sm text-gray-500 dark:text-white bg-gray-200 hover:bg-gray-300 focus:ring-4 focus:outline-none focus:ring-gray-300 font-medium rounded-md dark:bg-gray-600 dark:hover:bg-gray-700 dark:focus:ring-gray-800" @onclick="RefreshPreview">
|
||||
@@ -63,16 +63,16 @@
|
||||
|
||||
<div class="flex @(IsTemporary ? "justify-between" : "justify-end") pt-4 gap-2">
|
||||
<button type="button" class="px-4 py-2 bg-gray-200 text-gray-800 rounded-md hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-400 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600" @onclick="OnClose">
|
||||
@Localizer["CancelButton"]
|
||||
@_cancelButton
|
||||
</button>
|
||||
@if (IsTemporary)
|
||||
{
|
||||
<button type="button" class="px-4 py-2 bg-gray-200 text-gray-800 dark:bg-gray-700 dark:text-white rounded-md hover:bg-gray-300 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-500" @onclick="OnSaveTemporary">
|
||||
@Localizer["UseJustOnceButton"]
|
||||
@_useJustOnceButton
|
||||
</button>
|
||||
}
|
||||
<button type="button" id="save-button" class="px-4 py-2 bg-primary-600 text-white rounded-md hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-primary-700 dark:hover:bg-primary-600" @onclick="OnSaveGlobal">
|
||||
@Localizer["SaveGloballyButton"]
|
||||
@_saveGloballyButton
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -83,6 +83,20 @@
|
||||
|
||||
@code {
|
||||
private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Settings.PasswordSettingsPopup", "AliasVault.Client");
|
||||
|
||||
// Cached localized strings
|
||||
private string _title = "";
|
||||
private string _passwordLengthLabel = "";
|
||||
private string _includeLowercaseLabel = "";
|
||||
private string _includeUppercaseLabel = "";
|
||||
private string _includeNumbersLabel = "";
|
||||
private string _includeSpecialCharsLabel = "";
|
||||
private string _avoidAmbiguousCharsLabel = "";
|
||||
private string _previewLabel = "";
|
||||
private string _cancelButton = "";
|
||||
private string _useJustOnceButton = "";
|
||||
private string _saveGloballyButton = "";
|
||||
private string _settingsUpdatedMessage = "";
|
||||
|
||||
/// <summary>
|
||||
/// The PasswordSettings to mutate.
|
||||
@@ -122,6 +136,20 @@
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// Cache localized strings
|
||||
_title = Localizer["Title"];
|
||||
_passwordLengthLabel = Localizer["PasswordLengthLabel"];
|
||||
_includeLowercaseLabel = Localizer["IncludeLowercaseLabel"];
|
||||
_includeUppercaseLabel = Localizer["IncludeUppercaseLabel"];
|
||||
_includeNumbersLabel = Localizer["IncludeNumbersLabel"];
|
||||
_includeSpecialCharsLabel = Localizer["IncludeSpecialCharsLabel"];
|
||||
_avoidAmbiguousCharsLabel = Localizer["AvoidAmbiguousCharsLabel"];
|
||||
_previewLabel = Localizer["PreviewLabel"];
|
||||
_cancelButton = Localizer["CancelButton"];
|
||||
_useJustOnceButton = Localizer["UseJustOnceButton"];
|
||||
_saveGloballyButton = Localizer["SaveGloballyButton"];
|
||||
_settingsUpdatedMessage = Localizer["SettingsUpdatedMessage"];
|
||||
|
||||
// Clone the settings to avoid reference issues
|
||||
_workingSettings = new PasswordSettings
|
||||
{
|
||||
@@ -182,7 +210,7 @@
|
||||
var settingsJson = System.Text.Json.JsonSerializer.Serialize(_workingSettings);
|
||||
await DbService.Settings.SetSettingAsync("PasswordGenerationSettings", settingsJson);
|
||||
GlobalLoadingService.Hide();
|
||||
GlobalNotificationService.AddSuccessMessage(Localizer["SettingsUpdatedMessage"], true);
|
||||
GlobalNotificationService.AddSuccessMessage(_settingsUpdatedMessage, true);
|
||||
|
||||
// Notify parent with both settings and the generated password.
|
||||
await OnSaveSettings.InvokeAsync((_workingSettings, _previewPassword));
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
<div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
|
||||
<div class="flex justify-between">
|
||||
<div>
|
||||
<h3 class="mb-4 text-xl font-semibold dark:text-white">@Localizer["TwoFactorAuthenticationTitle"]</h3>
|
||||
<h3 class="mb-4 text-xl font-semibold dark:text-white">@_twoFactorAuthenticationTitle</h3>
|
||||
</div>
|
||||
@if (TotpCodeList.Any(t => !t.IsDeleted) && !IsAddFormVisible)
|
||||
{
|
||||
<div>
|
||||
<button id="add-totp-code" @onclick="ShowAddForm" type="button" class="text-blue-700 hover:text-white border border-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-3 py-2 text-center dark:border-blue-500 dark:text-blue-500 dark:hover:text-white dark:hover:bg-blue-600 dark:focus:ring-blue-800">
|
||||
@Localizer["AddTotpCodeButton"]
|
||||
@_addTotpCodeButton
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
@@ -24,7 +24,7 @@
|
||||
@if ((TotpCodeList.Count == 0 || TotpCodeList.All(t => t.IsDeleted)) && !IsAddFormVisible)
|
||||
{
|
||||
<div class="flex flex-col justify-center">
|
||||
<p class="text-gray-500 dark:text-gray-400"><a @onclick="ShowAddForm" id="add-totp-code" href="javascript:void(0)" class="text-blue-600 hover:text-blue-800 dark:text-blue-500 dark:hover:text-blue-400">@Localizer["AddTotpCodeDescription"]</a></p>
|
||||
<p class="text-gray-500 dark:text-gray-400"><a @onclick="ShowAddForm" id="add-totp-code" href="javascript:void(0)" class="text-blue-600 hover:text-blue-800 dark:text-blue-500 dark:hover:text-blue-400">@_addTotpCodeDescription</a></p>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
@@ -35,28 +35,28 @@
|
||||
<EditForm Model="@NewTotpCode" OnValidSubmit="AddTotpCode">
|
||||
<DataAnnotationsValidator />
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h4 class="text-lg font-medium text-gray-900 dark:text-white">@Localizer["AddTotpCodeModalTitle"]</h4>
|
||||
<h4 class="text-lg font-medium text-gray-900 dark:text-white">@_addTotpCodeModalTitle</h4>
|
||||
<button @onclick="HideAddForm" type="button" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white">
|
||||
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
|
||||
</svg>
|
||||
<span class="sr-only">@Localizer["CloseFormButton"]</span>
|
||||
<span class="sr-only">@_closeFormButton</span>
|
||||
</button>
|
||||
</div>
|
||||
<p class="mb-4 text-sm text-gray-500 dark:text-gray-400">@Localizer["TotpInstructions"]</p>
|
||||
<p class="mb-4 text-sm text-gray-500 dark:text-gray-400">@_totpInstructions</p>
|
||||
<div class="mb-4">
|
||||
<label for="name" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@Localizer["NameOptionalLabel"]</label>
|
||||
<label for="name" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@_nameOptionalLabel</label>
|
||||
<InputText id="name" @bind-Value="NewTotpCode.Name" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white" />
|
||||
<ValidationMessage For="@(() => NewTotpCode.Name)" class="text-red-600 dark:text-red-400 text-sm mt-1" />
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="secretKey" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@Localizer["SecretKeyLabel"]</label>
|
||||
<InputText id="secretKey" @bind-Value="NewTotpCode.SecretKey" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white" placeholder="@Localizer["SecretKeyPlaceholder"]" />
|
||||
<label for="secretKey" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@_secretKeyLabel</label>
|
||||
<InputText id="secretKey" @bind-Value="NewTotpCode.SecretKey" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white" placeholder="@_secretKeyPlaceholder" />
|
||||
<ValidationMessage For="@(() => NewTotpCode.SecretKey)" class="text-red-600 dark:text-red-400 text-sm mt-1" />
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<button id="save-totp-code" type="submit" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">
|
||||
@Localizer["SaveButton"]
|
||||
@_saveButton
|
||||
</button>
|
||||
</div>
|
||||
</EditForm>
|
||||
@@ -73,7 +73,7 @@
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex flex-col items-end">
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">@Localizer["SaveToViewCodeMessage"]</div>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">@_saveToViewCodeMessage</div>
|
||||
</div>
|
||||
<button type="button" @onclick="() => DeleteTotpCode(totpCode)" class="delete-totp-code text-red-600 hover:text-red-800 dark:text-red-500 dark:hover:text-red-400">
|
||||
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
@@ -90,6 +90,21 @@
|
||||
|
||||
@code {
|
||||
private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Components.TotpCodes.TotpCodes", "AliasVault.Client");
|
||||
|
||||
// Cached localized strings
|
||||
private string _twoFactorAuthenticationTitle = "";
|
||||
private string _addTotpCodeButton = "";
|
||||
private string _addTotpCodeDescription = "";
|
||||
private string _addTotpCodeModalTitle = "";
|
||||
private string _closeFormButton = "";
|
||||
private string _totpInstructions = "";
|
||||
private string _nameOptionalLabel = "";
|
||||
private string _secretKeyLabel = "";
|
||||
private string _secretKeyPlaceholder = "";
|
||||
private string _saveButton = "";
|
||||
private string _saveToViewCodeMessage = "";
|
||||
private string _deleteTotpCodeTitle = "";
|
||||
private string _deleteTotpCodeConfirmation = "";
|
||||
|
||||
/// <summary>
|
||||
/// The list of TOTP codes.
|
||||
@@ -110,6 +125,21 @@
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// Cache localized strings
|
||||
_twoFactorAuthenticationTitle = Localizer["TwoFactorAuthenticationTitle"];
|
||||
_addTotpCodeButton = Localizer["AddTotpCodeButton"];
|
||||
_addTotpCodeDescription = Localizer["AddTotpCodeDescription"];
|
||||
_addTotpCodeModalTitle = Localizer["AddTotpCodeModalTitle"];
|
||||
_closeFormButton = Localizer["CloseFormButton"];
|
||||
_totpInstructions = Localizer["TotpInstructions"];
|
||||
_nameOptionalLabel = Localizer["NameOptionalLabel"];
|
||||
_secretKeyLabel = Localizer["SecretKeyLabel"];
|
||||
_secretKeyPlaceholder = Localizer["SecretKeyPlaceholder"];
|
||||
_saveButton = Localizer["SaveButton"];
|
||||
_saveToViewCodeMessage = Localizer["SaveToViewCodeMessage"];
|
||||
_deleteTotpCodeTitle = Localizer["DeleteTotpCodeTitle"];
|
||||
_deleteTotpCodeConfirmation = Localizer["DeleteTotpCodeConfirmation"];
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
@@ -177,7 +207,7 @@
|
||||
private async Task DeleteTotpCode(TotpCode totpCode)
|
||||
{
|
||||
// Show confirmation modal.
|
||||
var result = await ConfirmModalService.ShowConfirmation(Localizer["DeleteTotpCodeTitle"], Localizer["DeleteTotpCodeConfirmation"]);
|
||||
var result = await ConfirmModalService.ShowConfirmation(_deleteTotpCodeTitle, _deleteTotpCodeConfirmation);
|
||||
if (!result)
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
|
||||
<div class="flex justify-between">
|
||||
<div>
|
||||
<h3 class="mb-4 text-xl font-semibold dark:text-white">@Localizer["TwoFactorAuthenticationTitle"]</h3>
|
||||
<h3 class="mb-4 text-xl font-semibold dark:text-white">@_twoFactorAuthenticationTitle</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
else if (TotpCodeList.Count == 0)
|
||||
{
|
||||
<div class="flex flex-col justify-center">
|
||||
<p class="text-gray-500 dark:text-gray-400">@Localizer["NoTotpCodesMessage"]</p>
|
||||
<p class="text-gray-500 dark:text-gray-400">@_noTotpCodesMessage</p>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
@@ -41,7 +41,7 @@
|
||||
<div class="text-xs">
|
||||
@if (IsCopied(totpCode.Id.ToString()))
|
||||
{
|
||||
<span class="text-green-600 dark:text-green-400">@Localizer["CopiedMessage"]</span>
|
||||
<span class="text-green-600 dark:text-green-400">@_copiedMessage</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -62,6 +62,11 @@
|
||||
|
||||
@code {
|
||||
private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Components.TotpCodes.TotpViewer", "AliasVault.Client");
|
||||
|
||||
// Cached localized strings
|
||||
private string _twoFactorAuthenticationTitle = "";
|
||||
private string _noTotpCodesMessage = "";
|
||||
private string _copiedMessage = "";
|
||||
|
||||
/// <summary>
|
||||
/// The list of TOTP codes to display.
|
||||
@@ -86,6 +91,11 @@
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// Cache localized strings
|
||||
_twoFactorAuthenticationTitle = Localizer["TwoFactorAuthenticationTitle"];
|
||||
_noTotpCodesMessage = Localizer["NoTotpCodesMessage"];
|
||||
_copiedMessage = Localizer["CopiedMessage"];
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
|
||||
// Generate initial codes
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
@implements IAsyncDisposable
|
||||
|
||||
<button @ref="buttonRef" @onclick="TogglePopup" id="quickIdentityButton" class="px-4 py-2 text-sm font-medium text-white bg-gradient-to-r from-primary-500 to-primary-600 hover:from-primary-600 hover:to-primary-700 focus:outline-none dark:from-primary-400 dark:to-primary-500 dark:hover:from-primary-500 dark:hover:to-primary-600 rounded-md shadow-sm transition duration-150 ease-in-out transform hover:scale-105 active:scale-95 focus:shadow-outline">
|
||||
@Localizer["NewAliasButtonShort"] <span class="hidden md:inline">@Localizer["NewAliasButtonText"].Value.Substring(1).Trim()</span>
|
||||
@_newAliasButtonShort <span class="hidden md:inline">@_newAliasButtonText.Substring(1).Trim()</span>
|
||||
</button>
|
||||
|
||||
@if (IsPopupVisible)
|
||||
@@ -15,25 +15,25 @@
|
||||
<ClickOutsideHandler OnClose="ClosePopup" ContentId="quickIdentityPopup,quickIdentityButton">
|
||||
<div id="quickIdentityPopup" class="absolute z-50 mt-2 p-4 bg-white rounded-lg shadow-xl border border-gray-300 dark:bg-gray-800 dark:border-gray-400"
|
||||
style="@PopupStyle">
|
||||
<h3 class="text-lg font-semibold mb-4 text-gray-900 dark:text-white">@Localizer["CreateNewAliasTitle"]</h3>
|
||||
<h3 class="text-lg font-semibold mb-4 text-gray-900 dark:text-white">@_createNewAliasTitle</h3>
|
||||
<EditForm Model="Model" OnValidSubmit="CreateIdentity">
|
||||
<DataAnnotationsValidator />
|
||||
<div class="mb-4">
|
||||
<EditFormRow Id="serviceName" Label="@Localizer["ServiceNameLabel"]" Placeholder="@Localizer["ServiceNamePlaceholder"]" @bind-Value="Model.ServiceName"></EditFormRow>
|
||||
<EditFormRow Id="serviceName" Label="@_serviceNameLabel" Placeholder="@_serviceNamePlaceholder" @bind-Value="Model.ServiceName"></EditFormRow>
|
||||
<ValidationMessage For="() => Model.ServiceName"/>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<EditFormRow Id="serviceUrl" Label="@Localizer["ServiceUrlLabel"]" OnFocus="OnFocusUrlInput" @bind-Value="Model.ServiceUrl"></EditFormRow>
|
||||
<EditFormRow Id="serviceUrl" Label="@_serviceUrlLabel" OnFocus="OnFocusUrlInput" @bind-Value="Model.ServiceUrl"></EditFormRow>
|
||||
<ValidationMessage For="() => Model.ServiceUrl"/>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<button id="quickIdentitySubmit" type="submit" class="bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded">
|
||||
@Localizer["CreateButton"]
|
||||
@_createButton
|
||||
</button>
|
||||
</div>
|
||||
<div class="pt-2">
|
||||
<a href="#" @onclick="OpenAdvancedMode" @onclick:preventDefault class="text-sm text-blue-500 hover:text-blue-700">
|
||||
@Localizer["AdvancedModeLink"]
|
||||
@_advancedModeLink
|
||||
</a>
|
||||
</div>
|
||||
</EditForm>
|
||||
@@ -43,6 +43,19 @@
|
||||
|
||||
@code {
|
||||
private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Widgets.CreateNewIdentityWidget", "AliasVault.Client");
|
||||
|
||||
// Cached localized strings
|
||||
private string _newAliasButtonShort = "";
|
||||
private string _newAliasButtonText = "";
|
||||
private string _createNewAliasTitle = "";
|
||||
private string _serviceNameLabel = "";
|
||||
private string _serviceNamePlaceholder = "";
|
||||
private string _serviceUrlLabel = "";
|
||||
private string _createButton = "";
|
||||
private string _advancedModeLink = "";
|
||||
private string _creatingNewAliasMessage = "";
|
||||
private string _createCredentialErrorMessage = "";
|
||||
private string _credentialCreatedSuccessMessage = "";
|
||||
|
||||
private bool IsPopupVisible = false;
|
||||
private bool IsCreating = false;
|
||||
@@ -61,6 +74,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// Cache localized strings
|
||||
_newAliasButtonShort = Localizer["NewAliasButtonShort"];
|
||||
_newAliasButtonText = Localizer["NewAliasButtonText"];
|
||||
_createNewAliasTitle = Localizer["CreateNewAliasTitle"];
|
||||
_serviceNameLabel = Localizer["ServiceNameLabel"];
|
||||
_serviceNamePlaceholder = Localizer["ServiceNamePlaceholder"];
|
||||
_serviceUrlLabel = Localizer["ServiceUrlLabel"];
|
||||
_createButton = Localizer["CreateButton"];
|
||||
_advancedModeLink = Localizer["AdvancedModeLink"];
|
||||
_creatingNewAliasMessage = Localizer["CreatingNewAliasMessage"];
|
||||
_createCredentialErrorMessage = Localizer["CreateCredentialErrorMessage"];
|
||||
_credentialCreatedSuccessMessage = Localizer["CredentialCreatedSuccessMessage"];
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
@@ -150,7 +181,7 @@
|
||||
}
|
||||
|
||||
IsCreating = true;
|
||||
GlobalLoadingSpinner.Show(Localizer["CreatingNewAliasMessage"]);
|
||||
GlobalLoadingSpinner.Show(_creatingNewAliasMessage);
|
||||
StateHasChanged();
|
||||
|
||||
var credential = new Credential();
|
||||
@@ -173,12 +204,12 @@
|
||||
// Error saving.
|
||||
IsCreating = false;
|
||||
GlobalLoadingSpinner.Hide();
|
||||
GlobalNotificationService.AddErrorMessage(Localizer["CreateCredentialErrorMessage"], true);
|
||||
GlobalNotificationService.AddErrorMessage(_createCredentialErrorMessage, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// No error, add success message.
|
||||
GlobalNotificationService.AddSuccessMessage(Localizer["CredentialCreatedSuccessMessage"]);
|
||||
GlobalNotificationService.AddSuccessMessage(_credentialCreatedSuccessMessage);
|
||||
|
||||
NavigationManager.NavigateTo("/credentials/" + id);
|
||||
|
||||
|
||||
@@ -6,78 +6,90 @@
|
||||
@implements IAsyncDisposable
|
||||
@using Microsoft.Extensions.Localization
|
||||
|
||||
<ClickOutsideHandler OnClose="OnClose" ContentId="searchWidgetContainer">
|
||||
<div class="relative" id="searchWidgetContainer">
|
||||
<input
|
||||
id="searchWidget"
|
||||
type="text"
|
||||
placeholder="@Localizer["SearchVaultPlaceholder"]"
|
||||
autocomplete="off"
|
||||
class="w-full px-4 py-2 text-gray-700 bg-white border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 dark:focus:ring-primary-500"
|
||||
@bind-value="SearchTerm"
|
||||
@oninput="SearchTermChanged"
|
||||
@onfocus="OnFocus"
|
||||
@onkeydown="HandleKeyDown"/>
|
||||
<div class="relative" id="searchWidgetContainer">
|
||||
<input
|
||||
id="searchWidget"
|
||||
type="text"
|
||||
placeholder="@_searchPlaceholder"
|
||||
autocomplete="off"
|
||||
class="w-full px-4 py-2 text-gray-700 bg-white border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 dark:focus:ring-primary-500"
|
||||
@bind-value="SearchTerm"
|
||||
@oninput="SearchTermChanged"
|
||||
@onfocus="OnFocus"
|
||||
@onkeydown="HandleKeyDown"/>
|
||||
|
||||
@if (ShowHelpText)
|
||||
{
|
||||
<div class="absolute z-10 w-full mt-1 bg-white rounded-md shadow-lg dark:bg-gray-800 p-2 text-sm text-gray-600 dark:text-gray-400">
|
||||
@if (string.IsNullOrEmpty(SearchTerm))
|
||||
{
|
||||
<p>@Localizer["SearchHelpText"]</p>
|
||||
}
|
||||
else if (SearchTerm.Length == 1)
|
||||
{
|
||||
<p>@Localizer["SearchTooShortMessage"]</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p>@string.Format(Localizer["SearchingForMessage"], SearchTerm)</p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (ShowResults && SearchTerm.Length >= 2)
|
||||
{
|
||||
@if (SearchResults.Any())
|
||||
@if (ShowHelpText || ShowResults)
|
||||
{
|
||||
<ClickOutsideHandler OnClose="OnClose" ContentId="searchWidgetContainer">
|
||||
@if (ShowHelpText)
|
||||
{
|
||||
<div class="absolute z-10 w-screen left-0 sm:left-auto sm:w-full mt-1 bg-white rounded-md shadow-lg dark:bg-gray-800 text-sm text-gray-600 dark:text-gray-400">
|
||||
@for (int i = 0; i < SearchResults.Count; i++)
|
||||
<div class="absolute z-10 w-full mt-1 bg-white rounded-md shadow-lg dark:bg-gray-800 p-2 text-sm text-gray-600 dark:text-gray-400">
|
||||
@if (string.IsNullOrEmpty(SearchTerm))
|
||||
{
|
||||
var result = SearchResults[i];
|
||||
<div
|
||||
class="search-result @(i == SelectedIndex ? "bg-gray-100 dark:bg-gray-700" : "") px-4 py-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center"
|
||||
@onclick="() => SelectResult(result)">
|
||||
<DisplayFavicon FaviconBytes="@result.Service.Logo" Width="24" />
|
||||
<div class="ml-2">
|
||||
<div>@result.Service.Name</div>
|
||||
@if (!string.IsNullOrEmpty(result.Alias.Email))
|
||||
{
|
||||
<span class="text-gray-500">(@result.Alias.Email)</span>
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(result.Username))
|
||||
{
|
||||
<span class="text-gray-500">(@result.Username)</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<p>@_searchHelpText</p>
|
||||
}
|
||||
else if (SearchTerm.Length == 1)
|
||||
{
|
||||
<p>@_searchTooShortMessage</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p>@string.Format(_searchingForMessage, SearchTerm)</p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
|
||||
@if (ShowResults && SearchTerm.Length >= 2)
|
||||
{
|
||||
<div class="absolute z-10 w-screen left-0 sm:left-auto sm:w-full mt-1 bg-white rounded-md shadow-lg dark:bg-gray-800 text-sm text-gray-600 dark:text-gray-400">
|
||||
<div class="px-4 py-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700">
|
||||
@Localizer["NoResultsFoundMessage"]
|
||||
@if (SearchResults.Any())
|
||||
{
|
||||
<div class="absolute z-10 w-screen left-0 sm:left-auto sm:w-full mt-1 bg-white rounded-md shadow-lg dark:bg-gray-800 text-sm text-gray-600 dark:text-gray-400">
|
||||
@for (int i = 0; i < SearchResults.Count; i++)
|
||||
{
|
||||
var result = SearchResults[i];
|
||||
<div
|
||||
class="search-result @(i == SelectedIndex ? "bg-gray-100 dark:bg-gray-700" : "") px-4 py-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center"
|
||||
@onclick="() => SelectResult(result)">
|
||||
<DisplayFavicon FaviconBytes="@result.Service.Logo" Width="24" />
|
||||
<div class="ml-2">
|
||||
<div>@result.Service.Name</div>
|
||||
@if (!string.IsNullOrEmpty(result.Alias.Email))
|
||||
{
|
||||
<span class="text-gray-500">(@result.Alias.Email)</span>
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(result.Username))
|
||||
{
|
||||
<span class="text-gray-500">(@result.Username)</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="absolute z-10 w-screen left-0 sm:left-auto sm:w-full mt-1 bg-white rounded-md shadow-lg dark:bg-gray-800 text-sm text-gray-600 dark:text-gray-400">
|
||||
<div class="px-4 py-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700">
|
||||
@_noResultsFoundMessage
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</ClickOutsideHandler>
|
||||
</ClickOutsideHandler>
|
||||
}
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Widgets.SearchWidget", "AliasVault.Client");
|
||||
private IStringLocalizer? _localizer;
|
||||
private IStringLocalizer Localizer => _localizer ??= LocalizerFactory.Create("Components.Main.Widgets.SearchWidget", "AliasVault.Client");
|
||||
|
||||
// Cached localized strings
|
||||
private string _searchPlaceholder = string.Empty;
|
||||
private string _searchHelpText = string.Empty;
|
||||
private string _searchTooShortMessage = string.Empty;
|
||||
private string _searchingForMessage = string.Empty;
|
||||
private string _noResultsFoundMessage = string.Empty;
|
||||
|
||||
private string SearchTerm { get; set; } = string.Empty;
|
||||
private List<Credential> SearchResults { get; set; } = new();
|
||||
private bool ShowResults { get; set; }
|
||||
@@ -99,6 +111,13 @@
|
||||
|
||||
if (firstRender)
|
||||
{
|
||||
// Initialize localized strings once
|
||||
_searchPlaceholder = Localizer["SearchVaultPlaceholder"];
|
||||
_searchHelpText = Localizer["SearchHelpText"];
|
||||
_searchTooShortMessage = Localizer["SearchTooShortMessage"];
|
||||
_searchingForMessage = Localizer["SearchingForMessage"];
|
||||
_noResultsFoundMessage = Localizer["NoResultsFoundMessage"];
|
||||
|
||||
await KeyboardShortcutService.RegisterShortcutAsync("gs", FocusSearchField);
|
||||
await KeyboardShortcutService.RegisterShortcutAsync("gf", FocusSearchField);
|
||||
NavigationManager.LocationChanged += ResetSearchField;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<div class="container mx-auto px-4 py-4">
|
||||
<div class="flex flex-col lg:flex-row justify-between items-center">
|
||||
<p class="text-sm text-center text-gray-500 mb-4 lg:mb-0">
|
||||
© 2024 <span>@AppInfo.ApplicationName v@(AppInfo.GetFullVersion())</span>. @Localizer["CopyrightText"]
|
||||
© 2024 <span>@AppInfo.ApplicationName v@(AppInfo.GetFullVersion())</span>. @_copyrightText
|
||||
</p>
|
||||
<div class="hidden lg:block text-center text-gray-400 text-sm">@_randomQuote</div>
|
||||
<ul class="flex flex-wrap items-center justify-center">
|
||||
@@ -29,14 +29,9 @@
|
||||
|
||||
private IStringLocalizer Localizer => LocalizerFactory.Create("Layout.Footer", "AliasVault.Client");
|
||||
|
||||
private string[] Quotes =>
|
||||
[
|
||||
Localizer["TipCreateShortcut"],
|
||||
Localizer["TipFindShortcut"],
|
||||
Localizer["TipHomeShortcut"],
|
||||
Localizer["TipLockShortcut"],
|
||||
];
|
||||
|
||||
// Cached localized strings
|
||||
private string _copyrightText = string.Empty;
|
||||
private string[] _quotes = [];
|
||||
private string _randomQuote = string.Empty;
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -50,7 +45,16 @@
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
_randomQuote = Quotes[Random.Shared.Next(Quotes.Length)];
|
||||
// Cache localized strings to prevent infinite rendering loops
|
||||
_copyrightText = Localizer["CopyrightText"];
|
||||
_quotes = [
|
||||
Localizer["TipCreateShortcut"],
|
||||
Localizer["TipFindShortcut"],
|
||||
Localizer["TipHomeShortcut"],
|
||||
Localizer["TipLockShortcut"],
|
||||
];
|
||||
|
||||
_randomQuote = _quotes[Random.Shared.Next(_quotes.Length)];
|
||||
NavigationManager.LocationChanged += RefreshQuote;
|
||||
}
|
||||
}
|
||||
@@ -60,7 +64,7 @@
|
||||
/// </summary>
|
||||
private void RefreshQuote(object? sender, LocationChangedEventArgs e)
|
||||
{
|
||||
_randomQuote = Quotes[Random.Shared.Next(Quotes.Length)];
|
||||
_randomQuote = _quotes[Random.Shared.Next(_quotes.Length)];
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
@inherits AliasVault.Client.Main.Pages.MainBase
|
||||
@using Microsoft.Extensions.Localization
|
||||
@using AliasVault.Client.Services
|
||||
@inject LanguageService LanguageService
|
||||
@implements IDisposable
|
||||
|
||||
<header>
|
||||
@@ -10,17 +12,17 @@
|
||||
<img src="/img/icon-nopadding.png" class="mr-3 h-8 w-10" alt="AliasVault Logo">
|
||||
<span class="self-center hidden sm:flex text-2xl font-semibold content-start align-top whitespace-nowrap dark:text-white">
|
||||
AliasVault
|
||||
<span class="text-primary-500 text-[10px] ml-1 font-normal hidden sm:inline-block">@Localizer["BetaLabel"]</span>
|
||||
<span class="text-primary-500 text-[10px] ml-1 font-normal hidden sm:inline-block">@_betaLabel</span>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<div class="hidden justify-between items-center w-full lg:flex lg:w-auto lg:order-1">
|
||||
<ul class="flex flex-col mt-4 space-x-6 text-sm font-medium lg:flex-row xl:space-x-8 lg:mt-0">
|
||||
<NavLink href="/credentials" class="block text-gray-700 hover:text-primary-700 dark:text-gray-400 dark:hover:text-white" ActiveClass="text-primary-700 dark:text-primary-500" Match="NavLinkMatch.All">
|
||||
@Localizer["CredentialsNav"]
|
||||
@_credentialsNav
|
||||
</NavLink>
|
||||
<NavLink href="/emails" class="block text-gray-700 hover:text-primary-700 dark:text-gray-400 dark:hover:text-white" ActiveClass="text-primary-700 dark:text-primary-500" Match="NavLinkMatch.All">
|
||||
@Localizer["EmailsNav"]
|
||||
@_emailsNav
|
||||
</NavLink>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -36,7 +38,7 @@
|
||||
<DbLockButton />
|
||||
<DbStatusIndicator />
|
||||
<button @onclick="ToggleMobileMenu" type="button" id="toggleMobileMenuButton" class="items-center p-2 text-gray-500 rounded-lg md:ml-2 hover:text-gray-900 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-white dark:hover:bg-gray-700 focus:ring-4 focus:ring-gray-300 dark:focus:ring-gray-600" aria-expanded="false" data-dropdown-toggle="mobileMenuDropdown">
|
||||
<span class="sr-only">@Localizer["OpenMenuLabel"]</span>
|
||||
<span class="sr-only">@_openMenuLabel</span>
|
||||
<svg class="w-6 h-6" aria-hidden="true" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" clip-rule="evenodd"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
@@ -45,12 +47,12 @@
|
||||
<ul class="lg:hidden py-1 font-light text-gray-500 dark:text-gray-400" aria-labelledby="mobileMenuDropdownButton">
|
||||
<li>
|
||||
<NavLink href="/credentials" class="block py-2 px-4 text-sm hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-400 dark:hover:text-white" ActiveClass="text-primary-700 dark:text-primary-500" Match="NavLinkMatch.Prefix">
|
||||
@Localizer["CredentialsNav"]
|
||||
@_credentialsNav
|
||||
</NavLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavLink href="/emails" class="block py-2 px-4 text-sm hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-400 dark:hover:text-white" ActiveClass="text-primary-700 dark:text-primary-500" Match="NavLinkMatch.All">
|
||||
@Localizer["EmailsNav"]
|
||||
@_emailsNav
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -60,37 +62,37 @@
|
||||
<ul class="py-1 font-light text-gray-500 dark:text-gray-400" aria-labelledby="mobileMenuDropdownButton">
|
||||
<li>
|
||||
<NavLink href="/settings/general" class="block py-2 px-4 text-sm hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-400 dark:hover:text-white" ActiveClass="text-primary-700 dark:text-primary-500" Match="NavLinkMatch.All">
|
||||
@Localizer["GeneralSettingsNav"]
|
||||
@_generalSettingsNav
|
||||
</NavLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavLink href="/settings/security" class="block py-2 px-4 text-sm hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-400 dark:hover:text-white" ActiveClass="text-primary-700 dark:text-primary-500" Match="NavLinkMatch.All">
|
||||
@Localizer["SecuritySettingsNav"]
|
||||
@_securitySettingsNav
|
||||
</NavLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavLink href="/settings/import-export" class="block py-2 px-4 text-sm hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-400 dark:hover:text-white" ActiveClass="text-primary-700 dark:text-primary-500" Match="NavLinkMatch.All">
|
||||
@Localizer["ImportExportNav"]
|
||||
@_importExportNav
|
||||
</NavLink>
|
||||
</li>
|
||||
<li class="border-t border-b border-gray-100 dark:border-gray-600">
|
||||
<NavLink href="/settings/apps" class="block py-2 px-4 text-sm hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-400 dark:hover:text-white" ActiveClass="text-primary-700 dark:text-primary-500" Match="NavLinkMatch.All">
|
||||
@Localizer["ExtensionsAppsNav"]
|
||||
@_extensionsAppsNav
|
||||
<span class="ml-2 inline-flex items-center justify-center px-2 py-0.5 text-xs font-medium rounded bg-primary-100 text-primary-800 dark:bg-primary-900 dark:text-primary-200">
|
||||
@Localizer["NewLabel"]
|
||||
@_newLabel
|
||||
</span>
|
||||
</NavLink>
|
||||
</li>
|
||||
<li>
|
||||
<button id="theme-toggle" data-tooltip-target="tooltip-toggle" type="button" class="w-full text-start py-2 px-4 text-sm hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-400 dark:hover:text-white">
|
||||
@Localizer["ToggleDarkMode"]
|
||||
@_toggleDarkMode
|
||||
<svg id="theme-toggle-dark-icon" class="hidden w-5 h-5 align-middle inline-block" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"></path></svg>
|
||||
<svg id="theme-toggle-light-icon" class="hidden w-5 h-5 align-middle inline-block" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z" fill-rule="evenodd" clip-rule="evenodd"></path></svg>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<NavLink href="/user/logout" class="block py-2 px-4 font-bold text-sm text-primary-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-primary-200 dark:hover:text-white" ActiveClass="text-primary-700 dark:text-primary-500" Match="NavLinkMatch.All">
|
||||
@Localizer["LogOut"]
|
||||
@_logOut
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -101,11 +103,25 @@
|
||||
</header>
|
||||
|
||||
@code {
|
||||
private IStringLocalizer Localizer => LocalizerFactory.Create("Layout.TopMenu", "AliasVault.Client");
|
||||
private IStringLocalizer? _localizer;
|
||||
private IStringLocalizer Localizer => _localizer ??= LocalizerFactory.Create("Layout.TopMenu", "AliasVault.Client");
|
||||
private bool IsUserMenuOpen { get; set; } = false;
|
||||
private bool IsMobileMenuOpen { get; set; } = false;
|
||||
private string Username { get; set; } = string.Empty;
|
||||
|
||||
// Cached localized strings
|
||||
private string _betaLabel = string.Empty;
|
||||
private string _credentialsNav = string.Empty;
|
||||
private string _emailsNav = string.Empty;
|
||||
private string _openMenuLabel = string.Empty;
|
||||
private string _generalSettingsNav = string.Empty;
|
||||
private string _securitySettingsNav = string.Empty;
|
||||
private string _importExportNav = string.Empty;
|
||||
private string _extensionsAppsNav = string.Empty;
|
||||
private string _newLabel = string.Empty;
|
||||
private string _toggleDarkMode = string.Empty;
|
||||
private string _logOut = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Close the menu.
|
||||
/// </summary>
|
||||
@@ -132,6 +148,7 @@
|
||||
public void Dispose()
|
||||
{
|
||||
NavigationManager.LocationChanged -= LocationChanged;
|
||||
LanguageService.LanguageChanged -= OnLanguageChanged;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -140,6 +157,40 @@
|
||||
await base.OnInitializedAsync();
|
||||
Username = await GetUsernameAsync();
|
||||
NavigationManager.LocationChanged += LocationChanged;
|
||||
LanguageService.LanguageChanged += OnLanguageChanged;
|
||||
|
||||
// Cache localized strings to prevent infinite rendering loops
|
||||
RefreshLocalizedStrings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes all cached localized strings.
|
||||
/// </summary>
|
||||
private void RefreshLocalizedStrings()
|
||||
{
|
||||
_betaLabel = Localizer["BetaLabel"];
|
||||
_credentialsNav = Localizer["CredentialsNav"];
|
||||
_emailsNav = Localizer["EmailsNav"];
|
||||
_openMenuLabel = Localizer["OpenMenuLabel"];
|
||||
_generalSettingsNav = Localizer["GeneralSettingsNav"];
|
||||
_securitySettingsNav = Localizer["SecuritySettingsNav"];
|
||||
_importExportNav = Localizer["ImportExportNav"];
|
||||
_extensionsAppsNav = Localizer["ExtensionsAppsNav"];
|
||||
_newLabel = Localizer["NewLabel"];
|
||||
_toggleDarkMode = Localizer["ToggleDarkMode"];
|
||||
_logOut = Localizer["LogOut"];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles language change events.
|
||||
/// </summary>
|
||||
/// <param name="newLanguage">The new language code.</param>
|
||||
private void OnLanguageChanged(string newLanguage)
|
||||
{
|
||||
// Re-create the localizer to pick up the new culture
|
||||
_localizer = null;
|
||||
RefreshLocalizedStrings();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
<PageHeader
|
||||
BreadcrumbItems="@BreadcrumbItems"
|
||||
Title="@Localizer["PageTitle"]"
|
||||
Description="@Localizer["PageDescription"]">
|
||||
Title="@_pageTitle"
|
||||
Description="@_pageDescription">
|
||||
<CustomActions>
|
||||
<div class="relative">
|
||||
<button @onclick="ToggleSettingsDropdown" id="settingsButton" class="p-2 text-gray-500 rounded-lg hover:text-gray-900 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-white dark:hover:bg-gray-700">
|
||||
@@ -24,17 +24,17 @@
|
||||
<div id="settingsDropdown" class="absolute right-0 z-10 mt-2 min-w-[220px] origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:bg-gray-700">
|
||||
<div class="p-4">
|
||||
<div class="mb-4">
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@Localizer["ViewModeLabel"]</label>
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@_viewModeLabel</label>
|
||||
<select @bind="ViewMode" @bind:after="CloseSettingsPopup" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
|
||||
<option value="grid">@Localizer["GridViewOption"]</option>
|
||||
<option value="table">@Localizer["TableViewOption"]</option>
|
||||
<option value="grid">@_gridViewOption</option>
|
||||
<option value="table">@_tableViewOption</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@Localizer["SortOrderLabel"]</label>
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@_sortOrderLabel</label>
|
||||
<select @bind="SortOrder" @bind:after="CloseSettingsPopup" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
|
||||
<option value="asc">@Localizer["OldestFirstOption"]</option>
|
||||
<option value="desc">@Localizer["NewestFirstOption"]</option>
|
||||
<option value="asc">@_oldestFirstOption</option>
|
||||
<option value="desc">@_newestFirstOption</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -65,23 +65,23 @@ else
|
||||
{
|
||||
<div class="credential-card col-span-full p-4 space-y-2 bg-amber-50 border border-primary-500 rounded-lg shadow-sm dark:border-primary-700 dark:bg-gray-800">
|
||||
<div class="px-4 py-6 text-gray-700 dark:text-gray-200 rounded text-center flex flex-col items-center">
|
||||
<p class="mb-2 text-lg font-semibold text-primary-700 dark:text-primary-400">@Localizer["NoCredentialsTitle"]</p>
|
||||
<p class="mb-2 text-lg font-semibold text-primary-700 dark:text-primary-400">@_noCredentialsTitle</p>
|
||||
|
||||
<div class="max-w-md mx-auto">
|
||||
<div class="mb-6">
|
||||
<p class="text-sm mb-2">@Localizer["CreateFirstCredentialText"] <span class="hidden md:inline">@Localizer["NewAliasButtonText"]</span><span class="md:hidden">@Localizer["NewAliasButtonTextMobile"]</span> @Localizer["ButtonLocationText"]</p>
|
||||
<p class="text-sm mb-2">@_createFirstCredentialText <span class="hidden md:inline">@_newAliasButtonText</span><span class="md:hidden">@_newAliasButtonTextMobile</span> @_buttonLocationText</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center my-6">
|
||||
<div class="flex-1 h-px bg-gray-300 dark:bg-gray-600"></div>
|
||||
<span class="px-4 text-sm text-gray-500 dark:text-gray-400">@Localizer["OrText"]</span>
|
||||
<span class="px-4 text-sm text-gray-500 dark:text-gray-400">@_orText</span>
|
||||
<div class="flex-1 h-px bg-gray-300 dark:bg-gray-600"></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-sm mb-2">@Localizer["ImportCredentialsText"]</p>
|
||||
<p class="text-sm mb-2">@_importCredentialsText</p>
|
||||
<a href="/settings/import-export" class="inline-block text-sm px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors dark:bg-primary-700 dark:hover:bg-primary-600">
|
||||
@Localizer["ImportButtonText"]
|
||||
@_importButtonText
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -99,6 +99,25 @@ else
|
||||
@code {
|
||||
private IStringLocalizer Localizer => LocalizerFactory.Create("Pages.Main.Credentials.Home", "AliasVault.Client");
|
||||
|
||||
// Cached localized strings
|
||||
private string _pageTitle = string.Empty;
|
||||
private string _pageDescription = string.Empty;
|
||||
private string _viewModeLabel = string.Empty;
|
||||
private string _gridViewOption = string.Empty;
|
||||
private string _tableViewOption = string.Empty;
|
||||
private string _sortOrderLabel = string.Empty;
|
||||
private string _oldestFirstOption = string.Empty;
|
||||
private string _newestFirstOption = string.Empty;
|
||||
private string _noCredentialsTitle = string.Empty;
|
||||
private string _createFirstCredentialText = string.Empty;
|
||||
private string _newAliasButtonText = string.Empty;
|
||||
private string _newAliasButtonTextMobile = string.Empty;
|
||||
private string _buttonLocationText = string.Empty;
|
||||
private string _orText = string.Empty;
|
||||
private string _importCredentialsText = string.Empty;
|
||||
private string _importButtonText = string.Empty;
|
||||
private string _failedToLoadCredentialsMessage = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the credentials are being loaded.
|
||||
/// </summary>
|
||||
@@ -159,6 +178,25 @@ else
|
||||
|
||||
if (firstRender)
|
||||
{
|
||||
// Cache localized strings to prevent infinite rendering loops
|
||||
_pageTitle = Localizer["PageTitle"];
|
||||
_pageDescription = Localizer["PageDescription"];
|
||||
_viewModeLabel = Localizer["ViewModeLabel"];
|
||||
_gridViewOption = Localizer["GridViewOption"];
|
||||
_tableViewOption = Localizer["TableViewOption"];
|
||||
_sortOrderLabel = Localizer["SortOrderLabel"];
|
||||
_oldestFirstOption = Localizer["OldestFirstOption"];
|
||||
_newestFirstOption = Localizer["NewestFirstOption"];
|
||||
_noCredentialsTitle = Localizer["NoCredentialsTitle"];
|
||||
_createFirstCredentialText = Localizer["CreateFirstCredentialText"];
|
||||
_newAliasButtonText = Localizer["NewAliasButtonText"];
|
||||
_newAliasButtonTextMobile = Localizer["NewAliasButtonTextMobile"];
|
||||
_buttonLocationText = Localizer["ButtonLocationText"];
|
||||
_orText = Localizer["OrText"];
|
||||
_importCredentialsText = Localizer["ImportCredentialsText"];
|
||||
_importButtonText = Localizer["ImportButtonText"];
|
||||
_failedToLoadCredentialsMessage = Localizer["FailedToLoadCredentialsMessage"];
|
||||
|
||||
await LoadCredentialsAsync();
|
||||
}
|
||||
}
|
||||
@@ -176,7 +214,7 @@ else
|
||||
if (credentialListEntries is null)
|
||||
{
|
||||
// Error loading aliases.
|
||||
GlobalNotificationService.AddErrorMessage(Localizer["FailedToLoadCredentialsMessage"], true);
|
||||
GlobalNotificationService.AddErrorMessage(_failedToLoadCredentialsMessage, true);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
@using Microsoft.Extensions.Localization
|
||||
@implements IAsyncDisposable
|
||||
|
||||
<LayoutPageTitle>@Localizer["PageTitle"]</LayoutPageTitle>
|
||||
<LayoutPageTitle>@_pageTitle</LayoutPageTitle>
|
||||
|
||||
@if (EmailModalVisible)
|
||||
{
|
||||
@@ -25,14 +25,14 @@
|
||||
|
||||
<PageHeader
|
||||
BreadcrumbItems="@BreadcrumbItems"
|
||||
Title="@Localizer["PageTitle"]"
|
||||
Description="@Localizer["PageDescription"]">
|
||||
Title="@_pageTitle"
|
||||
Description="@_pageDescription">
|
||||
<CustomActions>
|
||||
@if (DbService.Settings.AutoEmailRefresh)
|
||||
{
|
||||
<div class="w-3 h-3 mr-2 rounded-full bg-primary-300 border-2 border-primary-100 animate-pulse" title="@Localizer["AutoRefreshEnabledTooltip"]"></div>
|
||||
<div class="w-3 h-3 mr-2 rounded-full bg-primary-300 border-2 border-primary-100 animate-pulse" title="@_autoRefreshEnabledTooltip"></div>
|
||||
}
|
||||
<RefreshButton OnClick="RefreshData" ButtonText="@Localizer["RefreshButton"]" />
|
||||
<RefreshButton OnClick="RefreshData" ButtonText="@_refreshButton" />
|
||||
</CustomActions>
|
||||
</PageHeader>
|
||||
|
||||
@@ -76,7 +76,7 @@ else if (NoEmailClaims)
|
||||
{
|
||||
<div class="p-4 mx-4 mt-4 bg-white border border-gray-200 rounded-lg shadow-sm dark:border-gray-700 dark:bg-gray-800">
|
||||
<div class="px-4 py-2 text-gray-400 rounded">
|
||||
<p class="text-gray-500 dark:text-gray-400">@Localizer["NoEmailClaimsMessage"]</p>
|
||||
<p class="text-gray-500 dark:text-gray-400">@_noEmailClaimsMessage</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -92,7 +92,7 @@ else
|
||||
@if (EmailList.Count == 0)
|
||||
{
|
||||
<li class="p-4 text-center text-gray-500 dark:text-gray-300">
|
||||
@Localizer["NoEmailsReceivedMessage"]
|
||||
@_noEmailsReceivedMessage
|
||||
</li>
|
||||
}
|
||||
else
|
||||
@@ -150,12 +150,12 @@ else
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
@Localizer["LoadingText"]
|
||||
@_loadingText
|
||||
</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>@string.Format(Localizer["LoadMoreButtonText"], TotalRecords - EmailList.Count)</span>
|
||||
<span>@string.Format(_loadMoreButtonText, TotalRecords - EmailList.Count)</span>
|
||||
}
|
||||
</button>
|
||||
</li>
|
||||
@@ -182,6 +182,20 @@ else
|
||||
|
||||
@code {
|
||||
private IStringLocalizer Localizer => LocalizerFactory.Create("Pages.Main.Emails.Home", "AliasVault.Client");
|
||||
|
||||
// Cached localized strings
|
||||
private string _pageTitle = string.Empty;
|
||||
private string _pageDescription = string.Empty;
|
||||
private string _autoRefreshEnabledTooltip = string.Empty;
|
||||
private string _refreshButton = string.Empty;
|
||||
private string _noEmailClaimsMessage = string.Empty;
|
||||
private string _noEmailsReceivedMessage = string.Empty;
|
||||
private string _loadingText = string.Empty;
|
||||
private string _loadMoreButtonText = string.Empty;
|
||||
private string _claimDoesNotExistError = string.Empty;
|
||||
private string _loadEmailsFailedMessage = string.Empty;
|
||||
private string _loadMoreEmailsFailedMessage = string.Empty;
|
||||
|
||||
private List<MailListViewModel> EmailList { get; set; } = [];
|
||||
private bool IsLoading => LoadingService.IsLoading("emails");
|
||||
private int CurrentPage { get; set; } = 1;
|
||||
@@ -361,7 +375,7 @@ else
|
||||
switch (errorResponse.Code)
|
||||
{
|
||||
case "CLAIM_DOES_NOT_EXIST":
|
||||
GlobalNotificationService.AddErrorMessage(Localizer["ClaimDoesNotExistError"], true);
|
||||
GlobalNotificationService.AddErrorMessage(_claimDoesNotExistError, true);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException(errorResponse.Message);
|
||||
@@ -447,6 +461,19 @@ else
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
|
||||
// Cache localized strings to prevent infinite rendering loops
|
||||
_pageTitle = Localizer["PageTitle"];
|
||||
_pageDescription = Localizer["PageDescription"];
|
||||
_autoRefreshEnabledTooltip = Localizer["AutoRefreshEnabledTooltip"];
|
||||
_refreshButton = Localizer["RefreshButton"];
|
||||
_noEmailClaimsMessage = Localizer["NoEmailClaimsMessage"];
|
||||
_noEmailsReceivedMessage = Localizer["NoEmailsReceivedMessage"];
|
||||
_loadingText = Localizer["LoadingText"];
|
||||
_loadMoreButtonText = Localizer["LoadMoreButtonText"];
|
||||
_claimDoesNotExistError = Localizer["ClaimDoesNotExistError"];
|
||||
_loadEmailsFailedMessage = Localizer["LoadEmailsFailedMessage"];
|
||||
_loadMoreEmailsFailedMessage = Localizer["LoadMoreEmailsFailedMessage"];
|
||||
|
||||
// Create a single object reference for JS interop
|
||||
_dotNetRef = DotNetObjectReference.Create(this);
|
||||
await JsInteropService.RegisterVisibilityCallback(_dotNetRef);
|
||||
@@ -634,7 +661,7 @@ else
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GlobalNotificationService.AddErrorMessage(string.Format(Localizer["LoadEmailsFailedMessage"], ex.Message), true);
|
||||
GlobalNotificationService.AddErrorMessage(string.Format(_loadEmailsFailedMessage, ex.Message), true);
|
||||
Logger.LogError(ex, "An error occurred while loading email for preview");
|
||||
}
|
||||
}
|
||||
@@ -730,7 +757,7 @@ else
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GlobalNotificationService.AddErrorMessage(string.Format(Localizer["LoadMoreEmailsFailedMessage"], ex.Message), true);
|
||||
GlobalNotificationService.AddErrorMessage(string.Format(_loadMoreEmailsFailedMessage, ex.Message), true);
|
||||
Logger.LogError(ex, "An error occurred while loading more emails");
|
||||
}
|
||||
finally
|
||||
|
||||
@@ -8,18 +8,18 @@
|
||||
@using AliasVault.Client.Main.Pages.Settings.ImportExport.Components
|
||||
@using AliasVault.ImportExport
|
||||
|
||||
<LayoutPageTitle>@Localizer["PageTitle"]</LayoutPageTitle>
|
||||
<LayoutPageTitle>@_pageTitle</LayoutPageTitle>
|
||||
|
||||
<PageHeader
|
||||
BreadcrumbItems="@BreadcrumbItems"
|
||||
Title="@Localizer["PageTitle"]"
|
||||
Description="@Localizer["PageDescription"]">
|
||||
Title="@_pageTitle"
|
||||
Description="@_pageDescription">
|
||||
</PageHeader>
|
||||
|
||||
<div class="p-4 mx-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
|
||||
<h3 class="mb-4 text-xl font-semibold dark:text-white">@Localizer["ImportSectionTitle"]</h3>
|
||||
<h3 class="mb-4 text-xl font-semibold dark:text-white">@_importSectionTitle</h3>
|
||||
<div class="mb-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
@((MarkupString)Localizer["ImportSectionDescription"].Value)
|
||||
@((MarkupString)_importSectionDescription)
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<ImportService1Password />
|
||||
@@ -38,16 +38,16 @@
|
||||
</div>
|
||||
|
||||
<div class="p-4 mx-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
|
||||
<h3 class="mb-4 text-xl font-semibold dark:text-white">@Localizer["ExportSectionTitle"]</h3>
|
||||
<h3 class="mb-4 text-xl font-semibold dark:text-white">@_exportSectionTitle</h3>
|
||||
<div class="mb-4">
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-4">
|
||||
@Localizer["ExportSectionDescription"]
|
||||
@_exportSectionDescription
|
||||
</p>
|
||||
<div>
|
||||
<Button OnClick="@(() => ShowExportConfirmation(ExportType.Csv))">@Localizer["ExportCsvButton"]</Button>
|
||||
<Button OnClick="@(() => ShowExportConfirmation(ExportType.Csv))">@_exportCsvButton</Button>
|
||||
</div>
|
||||
<div class="mt-6">
|
||||
<Button OnClick="@(() => ShowExportConfirmation(ExportType.Sqlite))">@Localizer["ExportSqliteButton"]</Button>
|
||||
<Button OnClick="@(() => ShowExportConfirmation(ExportType.Sqlite))">@_exportSqliteButton</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -56,6 +56,18 @@
|
||||
private IStringLocalizer Localizer => LocalizerFactory.Create("Pages.Main.Settings.ImportExport.ImportExport", "AliasVault.Client");
|
||||
private ExportType _currentExportType;
|
||||
|
||||
// Cached localized strings for performance
|
||||
private string _pageTitle = string.Empty;
|
||||
private string _pageDescription = string.Empty;
|
||||
private string _importSectionTitle = string.Empty;
|
||||
private string _importSectionDescription = string.Empty;
|
||||
private string _exportSectionTitle = string.Empty;
|
||||
private string _exportSectionDescription = string.Empty;
|
||||
private string _exportCsvButton = string.Empty;
|
||||
private string _exportSqliteButton = string.Empty;
|
||||
private string _exportWarningMessage = string.Empty;
|
||||
private string _exportConfirmTitle = string.Empty;
|
||||
|
||||
private enum ExportType
|
||||
{
|
||||
Csv,
|
||||
@@ -66,15 +78,28 @@
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = Localizer["PageTitle"] });
|
||||
|
||||
// Cache localized strings for performance
|
||||
_pageTitle = Localizer["PageTitle"];
|
||||
_pageDescription = Localizer["PageDescription"];
|
||||
_importSectionTitle = Localizer["ImportSectionTitle"];
|
||||
_importSectionDescription = Localizer["ImportSectionDescription"];
|
||||
_exportSectionTitle = Localizer["ExportSectionTitle"];
|
||||
_exportSectionDescription = Localizer["ExportSectionDescription"];
|
||||
_exportCsvButton = Localizer["ExportCsvButton"];
|
||||
_exportSqliteButton = Localizer["ExportSqliteButton"];
|
||||
_exportWarningMessage = Localizer["ExportWarningMessage"];
|
||||
_exportConfirmTitle = Localizer["ExportConfirmTitle"];
|
||||
|
||||
BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = _pageTitle });
|
||||
}
|
||||
|
||||
private async Task ShowExportConfirmation(ExportType exportType)
|
||||
{
|
||||
_currentExportType = exportType;
|
||||
var confirmMessage = Localizer["ExportWarningMessage"];
|
||||
var confirmMessage = _exportWarningMessage;
|
||||
|
||||
var result = await ConfirmModalService.ShowConfirmation(Localizer["ExportConfirmTitle"], confirmMessage, SharedLocalizer["Confirm"], SharedLocalizer["Cancel"]);
|
||||
var result = await ConfirmModalService.ShowConfirmation(_exportConfirmTitle, confirmMessage, SharedLocalizer["Confirm"], SharedLocalizer["Cancel"]);
|
||||
if (!result)
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
@inherits MainBase
|
||||
@inject HttpClient Http
|
||||
|
||||
<LayoutPageTitle>@Localizer["PageTitle"]</LayoutPageTitle>
|
||||
<LayoutPageTitle>@_pageTitle</LayoutPageTitle>
|
||||
|
||||
<div class="grid grid-cols-1 px-4 pt-6 xl:grid-cols-3 xl:gap-4 dark:bg-gray-900">
|
||||
<div class="mb-4 col-span-full xl:mb-2">
|
||||
<Breadcrumb BreadcrumbItems="BreadcrumbItems"/>
|
||||
<H1>@Localizer["PageTitle"]</H1>
|
||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">@Localizer["PageDescription"]</p>
|
||||
<H1>@_pageTitle</H1>
|
||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">@_pageDescription</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -31,21 +31,21 @@ else
|
||||
<ValidationSummary />
|
||||
|
||||
<div>
|
||||
<label for="currentPassword" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@Localizer["CurrentPasswordLabel"]</label>
|
||||
<label for="currentPassword" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@_currentPasswordLabel</label>
|
||||
<InputText type="password" id="currentPassword" @bind-Value="PasswordChangeFormModel.CurrentPassword"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
|
||||
required />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="newPassword" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@Localizer["NewPasswordLabel"]</label>
|
||||
<label for="newPassword" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@_newPasswordLabel</label>
|
||||
<InputText type="password" id="newPassword" @bind-Value="PasswordChangeFormModel.NewPassword"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
|
||||
required />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="newPasswordConfirm" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@Localizer["ConfirmNewPasswordLabel"]</label>
|
||||
<label for="newPasswordConfirm" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@_confirmNewPasswordLabel</label>
|
||||
<InputText type="password" id="newPasswordConfirm" @bind-Value="PasswordChangeFormModel.NewPasswordConfirm"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
|
||||
required />
|
||||
@@ -53,7 +53,7 @@ else
|
||||
|
||||
<button type="submit"
|
||||
class="w-full bg-primary-500 text-white py-2 px-4 rounded-md hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 transition duration-150 ease-in-out">
|
||||
@Localizer["ChangePasswordButton"]
|
||||
@_changePasswordButton
|
||||
</button>
|
||||
</EditForm>
|
||||
</div>
|
||||
@@ -70,8 +70,24 @@ else
|
||||
/// </summary>
|
||||
private PasswordChangeFormModel PasswordChangeFormModel { get; set; } = new();
|
||||
|
||||
private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Pages.Settings.Security.ChangePassword", "AliasVault.Client");
|
||||
private IStringLocalizer ApiErrorLocalizer => LocalizerFactory.Create("ApiErrors", "AliasVault.Client");
|
||||
private IStringLocalizer? _localizer;
|
||||
private IStringLocalizer Localizer => _localizer ??= LocalizerFactory.Create("Components.Main.Pages.Settings.Security.ChangePassword", "AliasVault.Client");
|
||||
private IStringLocalizer? _apiErrorLocalizer;
|
||||
private IStringLocalizer ApiErrorLocalizer => _apiErrorLocalizer ??= LocalizerFactory.Create("ApiErrors", "AliasVault.Client");
|
||||
|
||||
// Cached localized strings for performance
|
||||
private string _pageTitle = string.Empty;
|
||||
private string _pageDescription = string.Empty;
|
||||
private string _breadcrumbSecuritySettings = string.Empty;
|
||||
private string _breadcrumbChangePassword = string.Empty;
|
||||
private string _currentPasswordLabel = string.Empty;
|
||||
private string _newPasswordLabel = string.Empty;
|
||||
private string _confirmNewPasswordLabel = string.Empty;
|
||||
private string _changePasswordButton = string.Empty;
|
||||
private string _failedToInitiatePasswordChange = string.Empty;
|
||||
private string _changingPasswordMessage = string.Empty;
|
||||
private string _failedToChangePassword = string.Empty;
|
||||
private string _passwordChangedSuccessfully = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current user's password salt.
|
||||
@@ -101,8 +117,22 @@ else
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
|
||||
BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = Localizer["BreadcrumbSecuritySettings"], Url = "/settings/security" });
|
||||
BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = Localizer["BreadcrumbChangePassword"] });
|
||||
// Cache localized strings for performance
|
||||
_pageTitle = Localizer["PageTitle"];
|
||||
_pageDescription = Localizer["PageDescription"];
|
||||
_breadcrumbSecuritySettings = Localizer["BreadcrumbSecuritySettings"];
|
||||
_breadcrumbChangePassword = Localizer["BreadcrumbChangePassword"];
|
||||
_currentPasswordLabel = Localizer["CurrentPasswordLabel"];
|
||||
_newPasswordLabel = Localizer["NewPasswordLabel"];
|
||||
_confirmNewPasswordLabel = Localizer["ConfirmNewPasswordLabel"];
|
||||
_changePasswordButton = Localizer["ChangePasswordButton"];
|
||||
_failedToInitiatePasswordChange = Localizer["FailedToInitiatePasswordChange"];
|
||||
_changingPasswordMessage = Localizer["ChangingPasswordMessage"];
|
||||
_failedToChangePassword = Localizer["FailedToChangePassword"];
|
||||
_passwordChangedSuccessfully = Localizer["PasswordChangedSuccessfully"];
|
||||
|
||||
BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = _breadcrumbSecuritySettings, Url = "/settings/security" });
|
||||
BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = _breadcrumbChangePassword });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -131,7 +161,7 @@ else
|
||||
|
||||
if (response == null)
|
||||
{
|
||||
GlobalNotificationService.AddErrorMessage(Localizer["FailedToInitiatePasswordChange"], true);
|
||||
GlobalNotificationService.AddErrorMessage(_failedToInitiatePasswordChange, true);
|
||||
IsLoading = false;
|
||||
StateHasChanged();
|
||||
return;
|
||||
@@ -148,7 +178,7 @@ else
|
||||
/// </summary>
|
||||
private async Task InitiatePasswordChange()
|
||||
{
|
||||
GlobalLoadingSpinner.Show(Localizer["ChangingPasswordMessage"]);
|
||||
GlobalLoadingSpinner.Show(_changingPasswordMessage);
|
||||
GlobalNotificationService.ClearMessages();
|
||||
StateHasChanged();
|
||||
|
||||
@@ -238,7 +268,7 @@ else
|
||||
}
|
||||
catch
|
||||
{
|
||||
GlobalNotificationService.AddErrorMessage(Localizer["FailedToChangePassword"], true);
|
||||
GlobalNotificationService.AddErrorMessage(_failedToChangePassword, true);
|
||||
|
||||
// Set currentPasswordHash back to original, so we're back to the original state.
|
||||
await AuthService.StoreEncryptionKeyAsync(backupPasswordHash);
|
||||
@@ -249,7 +279,7 @@ else
|
||||
}
|
||||
|
||||
// Set success message.
|
||||
GlobalNotificationService.AddSuccessMessage(Localizer["PasswordChangedSuccessfully"], true);
|
||||
GlobalNotificationService.AddSuccessMessage(_passwordChangedSuccessfully, true);
|
||||
|
||||
// Get the new password ephemeral and salt from the server, which is required if the usre
|
||||
// wants to change the password again.
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
@using AliasVault.Client.Resources
|
||||
@inject HttpClient Http
|
||||
|
||||
<LayoutPageTitle>@Localizer["PageTitle"]</LayoutPageTitle>
|
||||
<LayoutPageTitle>@_pageTitle</LayoutPageTitle>
|
||||
|
||||
<div class="grid grid-cols-1 px-4 pt-6 xl:grid-cols-3 xl:gap-4 dark:bg-gray-900">
|
||||
<div class="mb-4 col-span-full xl:mb-2">
|
||||
<Breadcrumb BreadcrumbItems="BreadcrumbItems"/>
|
||||
<H1>@Localizer["PageTitle"]</H1>
|
||||
<H1>@_pageTitle</H1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -23,25 +23,25 @@
|
||||
@if (!_showPasswordConfirm)
|
||||
{
|
||||
<div class="mb-6">
|
||||
<MessageWarning Message="@Localizer["PermanentActionWarning"]" />
|
||||
<MessageWarning Message="@_permanentActionWarning" />
|
||||
|
||||
<div class="mt-4 mb-6 text-gray-600 dark:text-gray-400">
|
||||
<p class="mb-2">@Localizer["PleaseNote"]</p>
|
||||
<p class="mb-2">@_pleaseNote</p>
|
||||
<ul class="list-disc list-inside space-y-2">
|
||||
<li>@Localizer["VaultsDeletedNote"]</li>
|
||||
<li>@Localizer["EmailAliasesOrphanedNote"]</li>
|
||||
<li>@Localizer["AccountCannotBeRecoveredNote"]</li>
|
||||
<li>@_vaultsDeletedNote</li>
|
||||
<li>@_emailAliasesOrphanedNote</li>
|
||||
<li>@_accountCannotBeRecoveredNote</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<EditForm Model="@_usernameModel" OnSubmit="@ConfirmUsername">
|
||||
<div class="mb-4">
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@Localizer["ConfirmUsernameLabel"]</label>
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@_confirmUsernameLabel</label>
|
||||
<InputText id="username" @bind-Value="_usernameModel.Username" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500" />
|
||||
</div>
|
||||
|
||||
<div class="flex space-x-3">
|
||||
<Button Type="submit" Color="danger">@Localizer["ContinueWithAccountDeletion"]</Button>
|
||||
<Button Type="submit" Color="danger">@_continueWithAccountDeletion</Button>
|
||||
<Button Type="button" Color="secondary" OnClick="Cancel">Cancel</Button>
|
||||
</div>
|
||||
</EditForm>
|
||||
@@ -50,12 +50,12 @@
|
||||
else
|
||||
{
|
||||
<div class="mb-6">
|
||||
<MessageWarning Message="@Localizer["FinalWarning"]" />
|
||||
<MessageWarning Message="@_finalWarning" />
|
||||
|
||||
<div class="mt-4 mb-6 text-gray-600 dark:text-gray-400">
|
||||
<p class="mb-2">@Localizer["PleaseNote"]</p>
|
||||
<p class="mb-2">@_pleaseNote</p>
|
||||
<ul class="list-disc list-inside space-y-2">
|
||||
<li>@Localizer["DeletionIrreversibleNote"]</li>
|
||||
<li>@_deletionIrreversibleNote</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -64,12 +64,12 @@
|
||||
<ValidationSummary />
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@Localizer["EnterPasswordLabel"]</label>
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@_enterPasswordLabel</label>
|
||||
<InputText id="password" type="password" @bind-Value="_passwordModel.Password" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500" />
|
||||
</div>
|
||||
|
||||
<div class="flex space-x-3">
|
||||
<Button Type="submit" Color="danger">@Localizer["DeleteMyAccount"]</Button>
|
||||
<Button Type="submit" Color="danger">@_deleteMyAccount</Button>
|
||||
<Button Type="button" Color="secondary" OnClick="Cancel">Cancel</Button>
|
||||
</div>
|
||||
</EditForm>
|
||||
@@ -88,8 +88,30 @@
|
||||
/// </summary>
|
||||
private readonly DeleteAccountPasswordModel _passwordModel = new();
|
||||
|
||||
private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Pages.Settings.Security.DeleteAccount", "AliasVault.Client");
|
||||
private IStringLocalizer ApiErrorLocalizer => LocalizerFactory.Create("ApiErrors", "AliasVault.Client");
|
||||
private IStringLocalizer? _localizer;
|
||||
private IStringLocalizer Localizer => _localizer ??= LocalizerFactory.Create("Components.Main.Pages.Settings.Security.DeleteAccount", "AliasVault.Client");
|
||||
private IStringLocalizer? _apiErrorLocalizer;
|
||||
private IStringLocalizer ApiErrorLocalizer => _apiErrorLocalizer ??= LocalizerFactory.Create("ApiErrors", "AliasVault.Client");
|
||||
|
||||
// Cached localized strings for performance
|
||||
private string _pageTitle = string.Empty;
|
||||
private string _breadcrumbSecuritySettings = string.Empty;
|
||||
private string _breadcrumbDeleteAccount = string.Empty;
|
||||
private string _permanentActionWarning = string.Empty;
|
||||
private string _pleaseNote = string.Empty;
|
||||
private string _vaultsDeletedNote = string.Empty;
|
||||
private string _emailAliasesOrphanedNote = string.Empty;
|
||||
private string _accountCannotBeRecoveredNote = string.Empty;
|
||||
private string _confirmUsernameLabel = string.Empty;
|
||||
private string _continueWithAccountDeletion = string.Empty;
|
||||
private string _finalWarning = string.Empty;
|
||||
private string _deletionIrreversibleNote = string.Empty;
|
||||
private string _enterPasswordLabel = string.Empty;
|
||||
private string _deleteMyAccount = string.Empty;
|
||||
private string _usernameRequired = string.Empty;
|
||||
private string _usernameDoesNotMatch = string.Empty;
|
||||
private string _deletingAccountMessage = string.Empty;
|
||||
private string _errorProcessingRequest = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to show the password confirmation step.
|
||||
@@ -111,8 +133,28 @@
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
|
||||
BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = Localizer["BreadcrumbSecuritySettings"], Url = "/settings/security" });
|
||||
BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = Localizer["BreadcrumbDeleteAccount"] });
|
||||
// Cache localized strings for performance
|
||||
_pageTitle = Localizer["PageTitle"];
|
||||
_breadcrumbSecuritySettings = Localizer["BreadcrumbSecuritySettings"];
|
||||
_breadcrumbDeleteAccount = Localizer["BreadcrumbDeleteAccount"];
|
||||
_permanentActionWarning = Localizer["PermanentActionWarning"];
|
||||
_pleaseNote = Localizer["PleaseNote"];
|
||||
_vaultsDeletedNote = Localizer["VaultsDeletedNote"];
|
||||
_emailAliasesOrphanedNote = Localizer["EmailAliasesOrphanedNote"];
|
||||
_accountCannotBeRecoveredNote = Localizer["AccountCannotBeRecoveredNote"];
|
||||
_confirmUsernameLabel = Localizer["ConfirmUsernameLabel"];
|
||||
_continueWithAccountDeletion = Localizer["ContinueWithAccountDeletion"];
|
||||
_finalWarning = Localizer["FinalWarning"];
|
||||
_deletionIrreversibleNote = Localizer["DeletionIrreversibleNote"];
|
||||
_enterPasswordLabel = Localizer["EnterPasswordLabel"];
|
||||
_deleteMyAccount = Localizer["DeleteMyAccount"];
|
||||
_usernameRequired = Localizer["UsernameRequired"];
|
||||
_usernameDoesNotMatch = Localizer["UsernameDoesNotMatch"];
|
||||
_deletingAccountMessage = Localizer["DeletingAccountMessage"];
|
||||
_errorProcessingRequest = Localizer["ErrorProcessingRequest"];
|
||||
|
||||
BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = _breadcrumbSecuritySettings, Url = "/settings/security" });
|
||||
BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = _breadcrumbDeleteAccount });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -124,7 +166,7 @@
|
||||
|
||||
if (string.IsNullOrEmpty(_usernameModel.Username))
|
||||
{
|
||||
GlobalNotificationService.AddErrorMessage(Localizer["UsernameRequired"], true);
|
||||
GlobalNotificationService.AddErrorMessage(_usernameRequired, true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -132,7 +174,7 @@
|
||||
var usernameMatches = string.Equals(_usernameModel.Username.Trim(), username.Trim(), StringComparison.OrdinalIgnoreCase);
|
||||
if (!usernameMatches)
|
||||
{
|
||||
GlobalNotificationService.AddErrorMessage(Localizer["UsernameDoesNotMatch"], true);
|
||||
GlobalNotificationService.AddErrorMessage(_usernameDoesNotMatch, true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -145,7 +187,7 @@
|
||||
/// </summary>
|
||||
private async Task DeleteAccountConfirmed()
|
||||
{
|
||||
GlobalLoadingSpinner.Show(Localizer["DeletingAccountMessage"]);
|
||||
GlobalLoadingSpinner.Show(_deletingAccountMessage);
|
||||
GlobalNotificationService.ClearMessages();
|
||||
|
||||
try
|
||||
@@ -167,7 +209,7 @@
|
||||
var loginResponse = JsonSerializer.Deserialize<LoginInitiateResponse>(responseContent);
|
||||
if (loginResponse == null)
|
||||
{
|
||||
GlobalNotificationService.AddErrorMessage(Localizer["ErrorProcessingRequest"], true);
|
||||
GlobalNotificationService.AddErrorMessage(_errorProcessingRequest, true);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
@page "/settings/security/disable-2fa"
|
||||
@inherits MainBase
|
||||
@inject HttpClient Http
|
||||
@using Microsoft.Extensions.Localization
|
||||
|
||||
<LayoutPageTitle>Disable two-factor authentication</LayoutPageTitle>
|
||||
<LayoutPageTitle>@_pageTitle</LayoutPageTitle>
|
||||
|
||||
@if (IsLoading)
|
||||
{
|
||||
@@ -13,17 +14,17 @@ else
|
||||
<div class="grid grid-cols-1 px-4 pt-6 xl:grid-cols-3 xl:gap-4 dark:bg-gray-900">
|
||||
<div class="mb-4 col-span-full xl:mb-2">
|
||||
<Breadcrumb BreadcrumbItems="BreadcrumbItems"/>
|
||||
<H1>Disable two-factor authentication</H1>
|
||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">Disabling two-factor authentication means you will be able to login with only your password.</p>
|
||||
<H1>@_pageTitle</H1>
|
||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">@_pageDescription</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-4 mb-4 mx-4 bg-white border border-gray-200 rounded-lg shadow-sm dark:border-gray-700 sm:p-6 dark:bg-gray-800">
|
||||
<AlertMessageError Message="Please note: after disabling two-factor authentication any configured authenticator app(s) will stop working. When you want to re-enable two-factor authentication you will have to configure the authenticator app(s) again." />
|
||||
<div class="mb-3 text-sm text-gray-600 dark:text-gray-400">Two factor authentication is currently enabled. Disable it in order to be able to access your vault with your password only.</div>
|
||||
<AlertMessageError Message="@_alertMessage" />
|
||||
<div class="mb-3 text-sm text-gray-600 dark:text-gray-400">@_descriptionText</div>
|
||||
<button @onclick="DisableTwoFactor"
|
||||
class="bg-red-500 text-white py-2 px-4 rounded-md hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 transition duration-150 ease-in-out">
|
||||
Confirm Disable Two-Factor Authentication
|
||||
@_confirmDisableButton
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
@@ -31,13 +32,40 @@ else
|
||||
@code {
|
||||
private bool IsLoading { get; set; } = true;
|
||||
|
||||
private IStringLocalizer? _localizer;
|
||||
private IStringLocalizer Localizer => _localizer ??= LocalizerFactory.Create("Components.Main.Pages.Settings.Security.Disable2Fa", "AliasVault.Client");
|
||||
|
||||
// Cached localized strings for performance
|
||||
private string _pageTitle = string.Empty;
|
||||
private string _pageDescription = string.Empty;
|
||||
private string _breadcrumbSecuritySettings = string.Empty;
|
||||
private string _breadcrumbDisable2Fa = string.Empty;
|
||||
private string _alertMessage = string.Empty;
|
||||
private string _descriptionText = string.Empty;
|
||||
private string _confirmDisableButton = string.Empty;
|
||||
private string _twoFactorNotEnabled = string.Empty;
|
||||
private string _twoFactorDisabledSuccess = string.Empty;
|
||||
private string _failedToDisable2Fa = string.Empty;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
|
||||
BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = "Security settings", Url = "/settings/security" });
|
||||
BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = "Disable two-factor authentication" });
|
||||
// Cache localized strings for performance
|
||||
_pageTitle = Localizer["PageTitle"];
|
||||
_pageDescription = Localizer["PageDescription"];
|
||||
_breadcrumbSecuritySettings = Localizer["BreadcrumbSecuritySettings"];
|
||||
_breadcrumbDisable2Fa = Localizer["BreadcrumbDisable2Fa"];
|
||||
_alertMessage = Localizer["AlertMessage"];
|
||||
_descriptionText = Localizer["DescriptionText"];
|
||||
_confirmDisableButton = Localizer["ConfirmDisableButton"];
|
||||
_twoFactorNotEnabled = Localizer["TwoFactorNotEnabled"];
|
||||
_twoFactorDisabledSuccess = Localizer["TwoFactorDisabledSuccess"];
|
||||
_failedToDisable2Fa = Localizer["FailedToDisable2Fa"];
|
||||
|
||||
BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = _breadcrumbSecuritySettings, Url = "/settings/security" });
|
||||
BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = _breadcrumbDisable2Fa });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -51,7 +79,7 @@ else
|
||||
var response = await Http.GetFromJsonAsync<TwoFactorEnabledResult>("v1/TwoFactorAuth/status");
|
||||
if (response is not null && !response.TwoFactorEnabled)
|
||||
{
|
||||
GlobalNotificationService.AddErrorMessage("Two-factor authentication is not enabled.");
|
||||
GlobalNotificationService.AddErrorMessage(_twoFactorNotEnabled);
|
||||
NavigationManager.NavigateTo("/settings/security");
|
||||
return;
|
||||
}
|
||||
@@ -66,13 +94,13 @@ else
|
||||
var response = await Http.PostAsync("v1/TwoFactorAuth/disable", null);
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
GlobalNotificationService.AddSuccessMessage("Two-factor authentication is now successfully disabled.");
|
||||
GlobalNotificationService.AddSuccessMessage(_twoFactorDisabledSuccess);
|
||||
NavigationManager.NavigateTo("/settings/security");
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle error
|
||||
GlobalNotificationService.AddErrorMessage("Failed to disable two-factor authentication.", true);
|
||||
GlobalNotificationService.AddErrorMessage(_failedToDisable2Fa, true);
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
@inherits MainBase
|
||||
@inject HttpClient Http
|
||||
|
||||
<LayoutPageTitle>@Localizer["PageTitle"]</LayoutPageTitle>
|
||||
<LayoutPageTitle>@_pageTitle</LayoutPageTitle>
|
||||
|
||||
<div class="grid grid-cols-1 px-4 pt-6 xl:grid-cols-3 xl:gap-4 dark:bg-gray-900">
|
||||
<div class="mb-4 col-span-full xl:mb-2">
|
||||
<Breadcrumb BreadcrumbItems="BreadcrumbItems"/>
|
||||
<H1>@Localizer["PageTitle"]</H1>
|
||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">@Localizer["PageDescription"]</p>
|
||||
<H1>@_pageTitle</H1>
|
||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">@_pageDescription</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -31,7 +31,7 @@ else
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-gray-600 text-center">
|
||||
@Localizer["QrCodeInstructions"]
|
||||
@_qrCodeInstructions
|
||||
</p>
|
||||
<div class="text-lg font-mono text-center bg-gray-100 p-2 rounded" id="authenticator-secret">@Secret</div>
|
||||
|
||||
@@ -39,11 +39,11 @@ else
|
||||
<div>
|
||||
<InputText id="verificationCode" @bind-Value="VerifyModel.Code"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="@Localizer["VerificationCodePlaceholder"]"/>
|
||||
placeholder="@_verificationCodePlaceholder"/>
|
||||
</div>
|
||||
<button type="submit"
|
||||
class="w-full bg-primary-500 text-white py-2 px-4 rounded-md hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 transition duration-150 ease-in-out">
|
||||
@Localizer["VerifyAndEnableButton"]
|
||||
@_verifyAndEnableButton
|
||||
</button>
|
||||
</EditForm>
|
||||
</div>
|
||||
@@ -57,15 +57,38 @@ else
|
||||
private List<string>? RecoveryCodes { get; set; }
|
||||
private readonly VerificationModel VerifyModel = new();
|
||||
|
||||
private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Pages.Settings.Security.Enable2Fa", "AliasVault.Client");
|
||||
private IStringLocalizer? _localizer;
|
||||
private IStringLocalizer Localizer => _localizer ??= LocalizerFactory.Create("Components.Main.Pages.Settings.Security.Enable2Fa", "AliasVault.Client");
|
||||
|
||||
// Cached localized strings
|
||||
private string _pageTitle = string.Empty;
|
||||
private string _pageDescription = string.Empty;
|
||||
private string _breadcrumbSecuritySettings = string.Empty;
|
||||
private string _breadcrumbEnable2Fa = string.Empty;
|
||||
private string _qrCodeInstructions = string.Empty;
|
||||
private string _verificationCodePlaceholder = string.Empty;
|
||||
private string _verifyAndEnableButton = string.Empty;
|
||||
private string _twoFactorEnabledSuccess = string.Empty;
|
||||
private string _failedToEnable2Fa = string.Empty;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
|
||||
BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = Localizer["BreadcrumbSecuritySettings"], Url = "/settings/security" });
|
||||
BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = Localizer["BreadcrumbEnable2Fa"] });
|
||||
// Initialize localized strings once
|
||||
_pageTitle = Localizer["PageTitle"];
|
||||
_pageDescription = Localizer["PageDescription"];
|
||||
_breadcrumbSecuritySettings = Localizer["BreadcrumbSecuritySettings"];
|
||||
_breadcrumbEnable2Fa = Localizer["BreadcrumbEnable2Fa"];
|
||||
_qrCodeInstructions = Localizer["QrCodeInstructions"];
|
||||
_verificationCodePlaceholder = Localizer["VerificationCodePlaceholder"];
|
||||
_verifyAndEnableButton = Localizer["VerifyAndEnableButton"];
|
||||
_twoFactorEnabledSuccess = Localizer["TwoFactorEnabledSuccess"];
|
||||
_failedToEnable2Fa = Localizer["FailedToEnable2Fa"];
|
||||
|
||||
BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = _breadcrumbSecuritySettings, Url = "/settings/security" });
|
||||
BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = _breadcrumbEnable2Fa });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -110,7 +133,7 @@ else
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
GlobalNotificationService.AddSuccessMessage(Localizer["TwoFactorEnabledSuccess"], true);
|
||||
GlobalNotificationService.AddSuccessMessage(_twoFactorEnabledSuccess, true);
|
||||
|
||||
// Show recovery codes.
|
||||
RecoveryCodes = result.RecoveryCodes;
|
||||
@@ -120,7 +143,7 @@ else
|
||||
}
|
||||
}
|
||||
|
||||
GlobalNotificationService.AddErrorMessage(Localizer["FailedToEnable2Fa"], true);
|
||||
GlobalNotificationService.AddErrorMessage(_failedToEnable2Fa, true);
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
@inherits MainBase
|
||||
@using Microsoft.Extensions.Localization
|
||||
|
||||
<LayoutPageTitle>@Localizer["PageTitle"]</LayoutPageTitle>
|
||||
<LayoutPageTitle>@_pageTitle</LayoutPageTitle>
|
||||
|
||||
<PageHeader
|
||||
BreadcrumbItems="@BreadcrumbItems"
|
||||
Title="@Localizer["PageTitle"]"
|
||||
Description="@Localizer["PageDescription"]">
|
||||
Title="@_pageTitle"
|
||||
Description="@_pageDescription">
|
||||
<CustomActions>
|
||||
<RefreshButton OnClick="LoadData" ButtonText="@Localizer["RefreshButton"]" />
|
||||
<RefreshButton OnClick="LoadData" ButtonText="@_refreshButton" />
|
||||
</CustomActions>
|
||||
</PageHeader>
|
||||
|
||||
@@ -23,6 +23,12 @@
|
||||
|
||||
@code {
|
||||
private IStringLocalizer Localizer => LocalizerFactory.Create("Pages.Main.Settings.Security.Security", "AliasVault.Client");
|
||||
|
||||
// Cached localized strings for performance
|
||||
private string _pageTitle = string.Empty;
|
||||
private string _pageDescription = string.Empty;
|
||||
private string _refreshButton = string.Empty;
|
||||
private string _breadcrumbTitle = string.Empty;
|
||||
private TwoFactorAuthenticationSection? TwoFactorSection;
|
||||
private QuickVaultUnlockSection? QuickVaultUnlockSection;
|
||||
private ActiveSessionsSection? SessionsSection;
|
||||
@@ -33,7 +39,13 @@
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
|
||||
BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = Localizer["BreadcrumbTitle"] });
|
||||
// Cache localized strings for performance
|
||||
_pageTitle = Localizer["PageTitle"];
|
||||
_pageDescription = Localizer["PageDescription"];
|
||||
_refreshButton = Localizer["RefreshButton"];
|
||||
_breadcrumbTitle = Localizer["BreadcrumbTitle"];
|
||||
|
||||
BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = _breadcrumbTitle });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
case TutorialStep.Welcome:
|
||||
<div class="space-y-4">
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
@Localizer["WelcomeMessage"]
|
||||
@_welcomeMessage
|
||||
</p>
|
||||
</div>
|
||||
break;
|
||||
@@ -34,13 +34,13 @@
|
||||
case TutorialStep.HowAliasVaultWorks:
|
||||
<div class="space-y-4">
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
@Localizer["HowItWorksIntro"]
|
||||
@_howItWorksIntro
|
||||
</p>
|
||||
<ol class="list-decimal list-inside space-y-2 text-gray-600 dark:text-gray-400">
|
||||
<li>@Localizer["HowItWorksStep1"]</li>
|
||||
<li>@Localizer["HowItWorksStep2"]</li>
|
||||
<li>@Localizer["HowItWorksStep3"]</li>
|
||||
<li>@Localizer["HowItWorksStep4"]</li>
|
||||
<li>@_howItWorksStep1</li>
|
||||
<li>@_howItWorksStep2</li>
|
||||
<li>@_howItWorksStep3</li>
|
||||
<li>@_howItWorksStep4</li>
|
||||
</ol>
|
||||
</div>
|
||||
break;
|
||||
@@ -49,18 +49,18 @@
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-3">
|
||||
<div class="p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
||||
<h4 class="font-semibold text-gray-900 dark:text-white">@Localizer["MasterPasswordTipTitle"]</h4>
|
||||
<h4 class="font-semibold text-gray-900 dark:text-white">@_masterPasswordTipTitle</h4>
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
@Localizer["MasterPasswordTipContent"]
|
||||
@_masterPasswordTipContent
|
||||
</p>
|
||||
</div>
|
||||
<div class="p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
||||
<h4 class="font-semibold text-gray-900 dark:text-white">@Localizer["TwoFactorTipTitle"]</h4>
|
||||
<p class="text-gray-600 dark:text-gray-400">@Localizer["TwoFactorTipContent"]</p>
|
||||
<h4 class="font-semibold text-gray-900 dark:text-white">@_twoFactorTipTitle</h4>
|
||||
<p class="text-gray-600 dark:text-gray-400">@_twoFactorTipContent</p>
|
||||
</div>
|
||||
<div class="p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
||||
<h4 class="font-semibold text-gray-900 dark:text-white">@Localizer["ExtensionsAppsTipTitle"]</h4>
|
||||
<p class="text-gray-600 dark:text-gray-400 mb-4">@Localizer["ExtensionsAppsTipContent"]</p>
|
||||
<h4 class="font-semibold text-gray-900 dark:text-white">@_extensionsAppsTipTitle</h4>
|
||||
<p class="text-gray-600 dark:text-gray-400 mb-4">@_extensionsAppsTipContent</p>
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-3">
|
||||
@foreach (var extension in AliasVault.Shared.Core.BrowserExtensions.Constants.Extensions.Where(x => x.Key != BrowserType.Unknown).Select(x => x.Value))
|
||||
{
|
||||
@@ -80,7 +80,7 @@
|
||||
<div class="flex flex-col items-center p-3 rounded-lg bg-gray-200 dark:bg-gray-600">
|
||||
<img src="@extension.IconPath" alt="@extension.Name" class="w-8 h-8 mb-2 opacity-50">
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">
|
||||
@Localizer["ComingSoonLabel"]
|
||||
@_comingSoonLabel
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
@@ -94,7 +94,7 @@
|
||||
class="flex flex-col items-center p-3 rounded-lg bg-gray-200 dark:bg-gray-600 hover:bg-gray-300 dark:hover:bg-gray-500 transition-colors">
|
||||
<img src="@app.IconPath" alt="@app.Name" class="w-8 h-8 mb-2">
|
||||
<span class="text-xs text-primary-600 hover:text-primary-700 dark:text-primary-400 dark:hover:text-primary-300">
|
||||
@Localizer["DownloadForPrefix"] @app.Name
|
||||
@_downloadForPrefix @app.Name
|
||||
</span>
|
||||
</a>
|
||||
}
|
||||
@@ -103,7 +103,7 @@
|
||||
<div class="flex flex-col items-center p-3 rounded-lg bg-gray-200 dark:bg-gray-600">
|
||||
<img src="@app.IconPath" alt="@app.Name" class="w-8 h-8 mb-2 opacity-50">
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">
|
||||
@app.Name @Localizer["SoonSuffix"]
|
||||
@app.Name @_soonSuffix
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
@@ -116,14 +116,14 @@
|
||||
|
||||
case TutorialStep.CreateFirstIdentity:
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-2xl font-bold text-gray-900 dark:text-white">@Localizer["ReadyToStartTitle"]</h3>
|
||||
<h3 class="text-2xl font-bold text-gray-900 dark:text-white">@_readyToStartTitle</h3>
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
@Localizer["ReadyToStartMessage"]
|
||||
@_readyToStartMessage
|
||||
</p>
|
||||
<div class="mt-4">
|
||||
<button @onclick="CreateFirstIdentity"
|
||||
class="w-full bg-primary-600 hover:bg-primary-700 text-white font-semibold py-3 px-4 rounded-lg transition duration-300">
|
||||
@Localizer["CreateFirstIdentityButton"]
|
||||
@_createFirstIdentityButton
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -136,14 +136,14 @@
|
||||
{
|
||||
<button @onclick="GoNext"
|
||||
class="w-full py-3 px-4 bg-primary-600 hover:bg-primary-700 text-white font-semibold rounded-lg transition duration-300">
|
||||
@Localizer["ContinueButton"]
|
||||
@_continueButton
|
||||
</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button @onclick="FinishTutorial"
|
||||
class="w-full py-3 px-4 bg-green-600 hover:bg-green-700 text-white font-semibold rounded-lg transition duration-300">
|
||||
@Localizer["GetStartedButton"]
|
||||
@_getStartedButton
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
@@ -154,6 +154,34 @@
|
||||
@code {
|
||||
private IStringLocalizer Localizer => LocalizerFactory.Create("Pages.Main.Welcome", "AliasVault.Client");
|
||||
|
||||
// Cached localized strings for performance
|
||||
private string _welcomeStepTitle = string.Empty;
|
||||
private string _howAliasVaultWorksStepTitle = string.Empty;
|
||||
private string _tipsStepTitle = string.Empty;
|
||||
private string _getStartedStepTitle = string.Empty;
|
||||
private string _tutorialStepTitle = string.Empty;
|
||||
private string _welcomeMessage = string.Empty;
|
||||
private string _howItWorksIntro = string.Empty;
|
||||
private string _howItWorksStep1 = string.Empty;
|
||||
private string _howItWorksStep2 = string.Empty;
|
||||
private string _howItWorksStep3 = string.Empty;
|
||||
private string _howItWorksStep4 = string.Empty;
|
||||
private string _masterPasswordTipTitle = string.Empty;
|
||||
private string _masterPasswordTipContent = string.Empty;
|
||||
private string _twoFactorTipTitle = string.Empty;
|
||||
private string _twoFactorTipContent = string.Empty;
|
||||
private string _extensionsAppsTipTitle = string.Empty;
|
||||
private string _extensionsAppsTipContent = string.Empty;
|
||||
private string _comingSoonLabel = string.Empty;
|
||||
private string _downloadForPrefix = string.Empty;
|
||||
private string _soonSuffix = string.Empty;
|
||||
private string _readyToStartTitle = string.Empty;
|
||||
private string _readyToStartMessage = string.Empty;
|
||||
private string _createFirstIdentityButton = string.Empty;
|
||||
private string _continueButton = string.Empty;
|
||||
private string _getStartedButton = string.Empty;
|
||||
private string _finishingTutorialMessage = string.Empty;
|
||||
|
||||
private TutorialStep _currentStep = TutorialStep.Welcome;
|
||||
|
||||
private enum TutorialStep
|
||||
@@ -168,11 +196,11 @@
|
||||
{
|
||||
return step switch
|
||||
{
|
||||
TutorialStep.Welcome => Localizer["WelcomeStepTitle"],
|
||||
TutorialStep.HowAliasVaultWorks => Localizer["HowAliasVaultWorksStepTitle"],
|
||||
TutorialStep.Tips => Localizer["TipsStepTitle"],
|
||||
TutorialStep.CreateFirstIdentity => Localizer["GetStartedStepTitle"],
|
||||
_ => Localizer["TutorialStepTitle"]
|
||||
TutorialStep.Welcome => _welcomeStepTitle,
|
||||
TutorialStep.HowAliasVaultWorks => _howAliasVaultWorksStepTitle,
|
||||
TutorialStep.Tips => _tipsStepTitle,
|
||||
TutorialStep.CreateFirstIdentity => _getStartedStepTitle,
|
||||
_ => _tutorialStepTitle
|
||||
};
|
||||
}
|
||||
|
||||
@@ -181,6 +209,34 @@
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
|
||||
// Cache localized strings for performance
|
||||
_welcomeStepTitle = Localizer["WelcomeStepTitle"];
|
||||
_howAliasVaultWorksStepTitle = Localizer["HowAliasVaultWorksStepTitle"];
|
||||
_tipsStepTitle = Localizer["TipsStepTitle"];
|
||||
_getStartedStepTitle = Localizer["GetStartedStepTitle"];
|
||||
_tutorialStepTitle = Localizer["TutorialStepTitle"];
|
||||
_welcomeMessage = Localizer["WelcomeMessage"];
|
||||
_howItWorksIntro = Localizer["HowItWorksIntro"];
|
||||
_howItWorksStep1 = Localizer["HowItWorksStep1"];
|
||||
_howItWorksStep2 = Localizer["HowItWorksStep2"];
|
||||
_howItWorksStep3 = Localizer["HowItWorksStep3"];
|
||||
_howItWorksStep4 = Localizer["HowItWorksStep4"];
|
||||
_masterPasswordTipTitle = Localizer["MasterPasswordTipTitle"];
|
||||
_masterPasswordTipContent = Localizer["MasterPasswordTipContent"];
|
||||
_twoFactorTipTitle = Localizer["TwoFactorTipTitle"];
|
||||
_twoFactorTipContent = Localizer["TwoFactorTipContent"];
|
||||
_extensionsAppsTipTitle = Localizer["ExtensionsAppsTipTitle"];
|
||||
_extensionsAppsTipContent = Localizer["ExtensionsAppsTipContent"];
|
||||
_comingSoonLabel = Localizer["ComingSoonLabel"];
|
||||
_downloadForPrefix = Localizer["DownloadForPrefix"];
|
||||
_soonSuffix = Localizer["SoonSuffix"];
|
||||
_readyToStartTitle = Localizer["ReadyToStartTitle"];
|
||||
_readyToStartMessage = Localizer["ReadyToStartMessage"];
|
||||
_createFirstIdentityButton = Localizer["CreateFirstIdentityButton"];
|
||||
_continueButton = Localizer["ContinueButton"];
|
||||
_getStartedButton = Localizer["GetStartedButton"];
|
||||
_finishingTutorialMessage = Localizer["FinishingTutorialMessage"];
|
||||
|
||||
// If tutorial is already done, redirect to the home page.
|
||||
if (DbService.Settings.TutorialDone)
|
||||
{
|
||||
@@ -208,7 +264,7 @@
|
||||
|
||||
private async Task FinishTutorial()
|
||||
{
|
||||
GlobalLoadingSpinner.Show(Localizer["FinishingTutorialMessage"]);
|
||||
GlobalLoadingSpinner.Show(_finishingTutorialMessage);
|
||||
await DbService.Settings.SetTutorialDoneAsync(true);
|
||||
NavigationManager.NavigateTo("credentials");
|
||||
GlobalLoadingSpinner.Hide();
|
||||
|
||||
Reference in New Issue
Block a user