Forgot password and email confirmations.
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
<ContentDialog
|
||||
x:Class="Wino.Dialogs.WinoAccountEmailConfirmationRequiredDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:domain="using:Wino.Core.Domain"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Title="{x:Bind domain:Translator.WinoAccount_EmailConfirmationPendingDialog_Title}"
|
||||
PrimaryButtonClick="ResendClicked"
|
||||
PrimaryButtonStyle="{ThemeResource AccentButtonStyle}"
|
||||
PrimaryButtonText="{x:Bind domain:Translator.WinoAccount_EmailConfirmationPendingDialog_ResendButton}"
|
||||
SecondaryButtonText="{x:Bind domain:Translator.Buttons_Close}"
|
||||
Style="{StaticResource WinoDialogStyle}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<ContentDialog.Resources>
|
||||
<x:Double x:Key="ContentDialogMinWidth">520</x:Double>
|
||||
<x:Double x:Key="ContentDialogMaxWidth">520</x:Double>
|
||||
</ContentDialog.Resources>
|
||||
|
||||
<StackPanel Spacing="16">
|
||||
<Border
|
||||
Padding="14"
|
||||
Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}"
|
||||
CornerRadius="12">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock
|
||||
x:Name="MessageTextBlock"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
<TextBlock
|
||||
x:Name="CountdownTextBlock"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<ProgressRing
|
||||
x:Name="BusyRing"
|
||||
Width="20"
|
||||
Height="20"
|
||||
HorizontalAlignment="Left"
|
||||
IsActive="False"
|
||||
Visibility="Collapsed" />
|
||||
|
||||
<TextBlock
|
||||
x:Name="ErrorTextBlock"
|
||||
Foreground="{ThemeResource SystemFillColorCriticalBrush}"
|
||||
TextWrapping="WrapWholeWords"
|
||||
Visibility="Collapsed" />
|
||||
</StackPanel>
|
||||
</ContentDialog>
|
||||
@@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Mail.Api.Contracts.Auth;
|
||||
using Wino.Mail.WinUI.Services;
|
||||
|
||||
namespace Wino.Dialogs;
|
||||
|
||||
public sealed partial class WinoAccountEmailConfirmationRequiredDialog : ContentDialog
|
||||
{
|
||||
private readonly IWinoAccountProfileService _profileService;
|
||||
private readonly DispatcherTimer _countdownTimer;
|
||||
private readonly string _email;
|
||||
private readonly string _endpoint;
|
||||
private readonly string _ticket;
|
||||
private DateTimeOffset _resendAvailableAtUtc;
|
||||
|
||||
public WinoAccountEmailConfirmationRequiredDialog(IWinoAccountProfileService profileService, string email, EmailConfirmationRequiredDetailsDto details)
|
||||
{
|
||||
_profileService = profileService;
|
||||
_email = email;
|
||||
_endpoint = details.ResendConfirmationEndpoint;
|
||||
_ticket = details.ResendConfirmationTicket;
|
||||
_resendAvailableAtUtc = details.ResendAvailableAtUtc;
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
MessageTextBlock.Text = string.Format(Translator.WinoAccount_EmailConfirmationPendingDialog_Message, email);
|
||||
|
||||
_countdownTimer = new DispatcherTimer
|
||||
{
|
||||
Interval = TimeSpan.FromSeconds(1)
|
||||
};
|
||||
_countdownTimer.Tick += CountdownTimer_Tick;
|
||||
|
||||
Closing += DialogClosing;
|
||||
|
||||
UpdateCountdown();
|
||||
_countdownTimer.Start();
|
||||
}
|
||||
|
||||
public bool ResendSucceeded { get; private set; }
|
||||
|
||||
private async void ResendClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||
{
|
||||
args.Cancel = true;
|
||||
|
||||
if (DateTimeOffset.UtcNow < _resendAvailableAtUtc)
|
||||
{
|
||||
UpdateCountdown();
|
||||
return;
|
||||
}
|
||||
|
||||
var deferral = args.GetDeferral();
|
||||
|
||||
try
|
||||
{
|
||||
SetBusyState(true);
|
||||
HideError();
|
||||
|
||||
var response = await _profileService.ResendEmailConfirmationAsync(_endpoint, _ticket);
|
||||
if (!response.IsSuccess)
|
||||
{
|
||||
ShowError(WinoAccountAuthErrorTranslator.Translate(response.ErrorCode));
|
||||
return;
|
||||
}
|
||||
|
||||
ResendSucceeded = true;
|
||||
Hide();
|
||||
}
|
||||
finally
|
||||
{
|
||||
SetBusyState(false);
|
||||
deferral.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
private void CountdownTimer_Tick(object? sender, object e) => UpdateCountdown();
|
||||
|
||||
private void UpdateCountdown()
|
||||
{
|
||||
var remaining = _resendAvailableAtUtc - DateTimeOffset.UtcNow;
|
||||
if (remaining <= TimeSpan.Zero)
|
||||
{
|
||||
IsPrimaryButtonEnabled = true;
|
||||
CountdownTextBlock.Text = Translator.WinoAccount_EmailConfirmationPendingDialog_ReadyToResend;
|
||||
return;
|
||||
}
|
||||
|
||||
IsPrimaryButtonEnabled = false;
|
||||
CountdownTextBlock.Text = string.Format(
|
||||
Translator.WinoAccount_EmailConfirmationPendingDialog_Countdown,
|
||||
$"{Math.Max(0, (int)remaining.TotalMinutes):00}:{Math.Max(0, remaining.Seconds):00}");
|
||||
}
|
||||
|
||||
private void SetBusyState(bool isBusy)
|
||||
{
|
||||
IsPrimaryButtonEnabled = !isBusy && DateTimeOffset.UtcNow >= _resendAvailableAtUtc;
|
||||
IsSecondaryButtonEnabled = !isBusy;
|
||||
BusyRing.IsActive = isBusy;
|
||||
BusyRing.Visibility = isBusy ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void ShowError(string message)
|
||||
{
|
||||
ErrorTextBlock.Text = message;
|
||||
ErrorTextBlock.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
private void HideError()
|
||||
{
|
||||
ErrorTextBlock.Text = string.Empty;
|
||||
ErrorTextBlock.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void DialogClosing(ContentDialog sender, ContentDialogClosingEventArgs args)
|
||||
{
|
||||
_countdownTimer.Stop();
|
||||
_countdownTimer.Tick -= CountdownTimer_Tick;
|
||||
Closing -= DialogClosing;
|
||||
}
|
||||
}
|
||||
@@ -152,7 +152,9 @@
|
||||
</Border>
|
||||
|
||||
<!-- Benefits cards -->
|
||||
<StackPanel Spacing="8">
|
||||
<StackPanel
|
||||
x:Name="BenefitsPanel"
|
||||
Spacing="8">
|
||||
<Border
|
||||
Padding="14"
|
||||
Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}"
|
||||
@@ -227,13 +229,32 @@
|
||||
PlaceholderText="{x:Bind domain:Translator.WinoAccount_EmailPlaceholder}"
|
||||
TextChanging="InputChanged" />
|
||||
|
||||
<PasswordBox
|
||||
x:Name="PasswordBox"
|
||||
Header="{x:Bind domain:Translator.WinoAccount_PasswordLabel}"
|
||||
KeyDown="PasswordBox_KeyDown"
|
||||
PasswordChanged="InputChanged" />
|
||||
<StackPanel x:Name="PasswordPanel">
|
||||
<PasswordBox
|
||||
x:Name="PasswordBox"
|
||||
Header="{x:Bind domain:Translator.WinoAccount_PasswordLabel}"
|
||||
KeyDown="PasswordBox_KeyDown"
|
||||
PasswordChanged="InputChanged" />
|
||||
</StackPanel>
|
||||
|
||||
<Border
|
||||
x:Name="ForgotPasswordInfoPanel"
|
||||
Padding="14"
|
||||
Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}"
|
||||
CornerRadius="12"
|
||||
Visibility="Collapsed">
|
||||
<TextBlock
|
||||
Text="{x:Bind domain:Translator.WinoAccount_ForgotPasswordDialog_Description}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<HyperlinkButton
|
||||
x:Name="ModeToggleButton"
|
||||
HorizontalAlignment="Left"
|
||||
Click="ModeToggleButton_Click"
|
||||
Content="{x:Bind domain:Translator.WinoAccount_LoginDialog_ForgotPasswordLink}" />
|
||||
|
||||
<ProgressRing
|
||||
x:Name="BusyRing"
|
||||
Width="20"
|
||||
|
||||
@@ -5,6 +5,7 @@ using Windows.System;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Mail.Api.Contracts.Auth;
|
||||
using Wino.Mail.WinUI.Services;
|
||||
|
||||
namespace Wino.Dialogs;
|
||||
@@ -12,14 +13,19 @@ namespace Wino.Dialogs;
|
||||
public sealed partial class WinoAccountLoginDialog : ContentDialog
|
||||
{
|
||||
private readonly IWinoAccountProfileService _profileService;
|
||||
private bool _isForgotPasswordMode;
|
||||
|
||||
public WinoAccountLoginDialog(IWinoAccountProfileService profileService)
|
||||
{
|
||||
_profileService = profileService;
|
||||
InitializeComponent();
|
||||
UpdateMode();
|
||||
}
|
||||
|
||||
public WinoAccount? Result { get; private set; }
|
||||
public string? PendingConfirmationEmailAddress { get; private set; }
|
||||
public EmailConfirmationRequiredDetailsDto? EmailConfirmationRequiredDetails { get; private set; }
|
||||
public string? PasswordResetEmailAddress { get; private set; }
|
||||
|
||||
private async void LoginClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||
{
|
||||
@@ -36,7 +42,7 @@ public sealed partial class WinoAccountLoginDialog : ContentDialog
|
||||
|
||||
try
|
||||
{
|
||||
await PerformLoginAsync();
|
||||
await PerformPrimaryActionAsync();
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -44,7 +50,7 @@ public sealed partial class WinoAccountLoginDialog : ContentDialog
|
||||
}
|
||||
}
|
||||
|
||||
private async System.Threading.Tasks.Task PerformLoginAsync()
|
||||
private async System.Threading.Tasks.Task PerformPrimaryActionAsync()
|
||||
{
|
||||
var validationError = ValidateInput();
|
||||
if (!string.IsNullOrWhiteSpace(validationError))
|
||||
@@ -58,10 +64,33 @@ public sealed partial class WinoAccountLoginDialog : ContentDialog
|
||||
SetBusyState(true);
|
||||
HideError();
|
||||
|
||||
if (_isForgotPasswordMode)
|
||||
{
|
||||
var forgotPasswordResponse = await _profileService.ForgotPasswordAsync(EmailTextBox.Text.Trim());
|
||||
if (!forgotPasswordResponse.IsSuccess)
|
||||
{
|
||||
ShowError(WinoAccountAuthErrorTranslator.Translate(forgotPasswordResponse.ErrorCode));
|
||||
return;
|
||||
}
|
||||
|
||||
PasswordResetEmailAddress = EmailTextBox.Text.Trim();
|
||||
Hide();
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await _profileService.LoginAsync(EmailTextBox.Text.Trim(), PasswordBox.Password);
|
||||
|
||||
if (!result.IsSuccess || result.Account == null)
|
||||
{
|
||||
var confirmationDetails = WinoAccountEmailConfirmationHelper.Parse(result.ErrorDetails);
|
||||
if (WinoAccountEmailConfirmationHelper.IsEmailConfirmationRequiredError(result.ErrorCode) && confirmationDetails != null)
|
||||
{
|
||||
PendingConfirmationEmailAddress = EmailTextBox.Text.Trim();
|
||||
EmailConfirmationRequiredDetails = confirmationDetails;
|
||||
Hide();
|
||||
return;
|
||||
}
|
||||
|
||||
ShowError(WinoAccountAuthErrorTranslator.Format(result.ErrorCode, result.ErrorMessage));
|
||||
return;
|
||||
}
|
||||
@@ -82,6 +111,11 @@ public sealed partial class WinoAccountLoginDialog : ContentDialog
|
||||
return Translator.WinoAccount_Validation_EmailRequired;
|
||||
}
|
||||
|
||||
if (_isForgotPasswordMode)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(PasswordBox.Password))
|
||||
{
|
||||
return Translator.WinoAccount_Validation_PasswordRequired;
|
||||
@@ -90,10 +124,17 @@ public sealed partial class WinoAccountLoginDialog : ContentDialog
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private void EmailTextBox_KeyDown(object sender, KeyRoutedEventArgs e)
|
||||
private async void EmailTextBox_KeyDown(object sender, KeyRoutedEventArgs e)
|
||||
{
|
||||
if (e.Key == VirtualKey.Enter)
|
||||
{
|
||||
if (_isForgotPasswordMode)
|
||||
{
|
||||
e.Handled = true;
|
||||
await PerformPrimaryActionAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
PasswordBox.Focus(FocusState.Programmatic);
|
||||
e.Handled = true;
|
||||
}
|
||||
@@ -104,10 +145,18 @@ public sealed partial class WinoAccountLoginDialog : ContentDialog
|
||||
if (e.Key == VirtualKey.Enter)
|
||||
{
|
||||
e.Handled = true;
|
||||
await PerformLoginAsync();
|
||||
await PerformPrimaryActionAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private void ModeToggleButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_isForgotPasswordMode = !_isForgotPasswordMode;
|
||||
PasswordBox.Password = string.Empty;
|
||||
HideError();
|
||||
UpdateMode();
|
||||
}
|
||||
|
||||
private void InputChanged(TextBox sender, TextBoxTextChangingEventArgs args) => HideError();
|
||||
|
||||
private void InputChanged(object sender, RoutedEventArgs e) => HideError();
|
||||
@@ -131,4 +180,22 @@ public sealed partial class WinoAccountLoginDialog : ContentDialog
|
||||
ErrorTextBlock.Text = string.Empty;
|
||||
ErrorTextBlock.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void UpdateMode()
|
||||
{
|
||||
Title = _isForgotPasswordMode
|
||||
? Translator.WinoAccount_ForgotPasswordDialog_Title
|
||||
: Translator.WinoAccount_LoginDialog_Title;
|
||||
|
||||
PrimaryButtonText = _isForgotPasswordMode
|
||||
? Translator.WinoAccount_ForgotPasswordDialog_PrimaryButton
|
||||
: Translator.Buttons_SignIn;
|
||||
|
||||
BenefitsPanel.Visibility = _isForgotPasswordMode ? Visibility.Collapsed : Visibility.Visible;
|
||||
PasswordPanel.Visibility = _isForgotPasswordMode ? Visibility.Collapsed : Visibility.Visible;
|
||||
ForgotPasswordInfoPanel.Visibility = _isForgotPasswordMode ? Visibility.Visible : Visibility.Collapsed;
|
||||
ModeToggleButton.Content = _isForgotPasswordMode
|
||||
? Translator.WinoAccount_ForgotPasswordDialog_BackToSignIn
|
||||
: Translator.WinoAccount_LoginDialog_ForgotPasswordLink;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ public sealed partial class WinoAccountRegistrationDialog : ContentDialog
|
||||
}
|
||||
|
||||
public WinoAccount? Result { get; private set; }
|
||||
public string? ConfirmationEmailAddress { get; private set; }
|
||||
|
||||
private async void RegisterClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||
{
|
||||
@@ -68,7 +69,7 @@ public sealed partial class WinoAccountRegistrationDialog : ContentDialog
|
||||
return;
|
||||
}
|
||||
|
||||
Result = result.Account;
|
||||
ConfirmationEmailAddress = result.Account.Email;
|
||||
Hide();
|
||||
}
|
||||
finally
|
||||
|
||||
@@ -225,11 +225,16 @@ public class DialogService : DialogServiceBase, IMailDialogService
|
||||
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
|
||||
};
|
||||
|
||||
var result = await HandleDialogPresentationAsync(dialog);
|
||||
await HandleDialogPresentationAsync(dialog);
|
||||
|
||||
return result == ContentDialogResult.Primary
|
||||
? dialog.Result
|
||||
: null;
|
||||
if (!string.IsNullOrWhiteSpace(dialog.ConfirmationEmailAddress))
|
||||
{
|
||||
await ShowMessageAsync(
|
||||
string.Format(Translator.WinoAccount_EmailConfirmationSentDialog_Message, dialog.ConfirmationEmailAddress),
|
||||
Translator.WinoAccount_EmailConfirmationSentDialog_Title);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<WinoAccount?> ShowWinoAccountLoginDialogAsync()
|
||||
@@ -241,8 +246,37 @@ public class DialogService : DialogServiceBase, IMailDialogService
|
||||
|
||||
var result = await HandleDialogPresentationAsync(dialog);
|
||||
|
||||
return result == ContentDialogResult.Primary
|
||||
? dialog.Result
|
||||
: null;
|
||||
if (dialog.EmailConfirmationRequiredDetails != null && !string.IsNullOrWhiteSpace(dialog.PendingConfirmationEmailAddress))
|
||||
{
|
||||
var confirmationDialog = new WinoAccountEmailConfirmationRequiredDialog(
|
||||
_winoAccountProfileService,
|
||||
dialog.PendingConfirmationEmailAddress,
|
||||
dialog.EmailConfirmationRequiredDetails)
|
||||
{
|
||||
RequestedTheme = ThemeService.RootTheme.ToWindowsElementTheme()
|
||||
};
|
||||
|
||||
await HandleDialogPresentationAsync(confirmationDialog);
|
||||
|
||||
if (confirmationDialog.ResendSucceeded)
|
||||
{
|
||||
await ShowMessageAsync(
|
||||
string.Format(Translator.WinoAccount_EmailConfirmationResentDialog_Message, dialog.PendingConfirmationEmailAddress),
|
||||
Translator.WinoAccount_EmailConfirmationResentDialog_Title);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(dialog.PasswordResetEmailAddress))
|
||||
{
|
||||
await ShowMessageAsync(
|
||||
string.Format(Translator.WinoAccount_ForgotPasswordDialog_SuccessMessage, dialog.PasswordResetEmailAddress),
|
||||
Translator.WinoAccount_ForgotPasswordDialog_SuccessTitle);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return dialog.Result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@ public static class WinoAccountAuthErrorTranslator
|
||||
ApiErrorCodes.AccountBanned => Translator.WinoAccount_Error_AccountBanned,
|
||||
ApiErrorCodes.AccountSuspended => Translator.WinoAccount_Error_AccountSuspended,
|
||||
ApiErrorCodes.EmailNotConfirmed => Translator.WinoAccount_Error_EmailNotConfirmed,
|
||||
ApiErrorCodes.EmailConfirmationRequired => Translator.WinoAccount_Error_EmailConfirmationRequired,
|
||||
ApiErrorCodes.EmailConfirmationResendNotAvailable => Translator.WinoAccount_Error_EmailConfirmationResendNotAvailable,
|
||||
ApiErrorCodes.EmailConfirmationResendInvalid => Translator.WinoAccount_Error_EmailConfirmationResendInvalid,
|
||||
ApiErrorCodes.EmailNotRegistered => Translator.WinoAccount_Error_EmailNotRegistered,
|
||||
ApiErrorCodes.RefreshTokenInvalid => Translator.WinoAccount_Error_RefreshTokenInvalid,
|
||||
ApiErrorCodes.EmailAlreadyRegistered => Translator.WinoAccount_Error_EmailAlreadyRegistered,
|
||||
ApiErrorCodes.ExternalLoginEmailRequired => Translator.WinoAccount_Error_ExternalLoginEmailRequired,
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using Wino.Mail.Api.Contracts.Auth;
|
||||
|
||||
namespace Wino.Mail.WinUI.Services;
|
||||
|
||||
internal static class WinoAccountEmailConfirmationHelper
|
||||
{
|
||||
public static bool IsEmailConfirmationRequiredError(string? errorCode)
|
||||
=> string.Equals(errorCode, Wino.Mail.Api.Contracts.Common.ApiErrorCodes.EmailNotConfirmed, StringComparison.Ordinal) ||
|
||||
string.Equals(errorCode, Wino.Mail.Api.Contracts.Common.ApiErrorCodes.EmailConfirmationRequired, StringComparison.Ordinal);
|
||||
|
||||
public static EmailConfirmationRequiredDetailsDto? Parse(JsonElement? details)
|
||||
{
|
||||
if (details is not JsonElement element || element.ValueKind is JsonValueKind.Undefined or JsonValueKind.Null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!TryGetString(element, "resendConfirmationEndpoint", out var endpoint) ||
|
||||
!TryGetString(element, "resendConfirmationTicket", out var ticket) ||
|
||||
!TryGetDateTimeOffset(element, "resendAvailableAtUtc", out var resendAvailableAtUtc))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
DateTimeOffset? latestSentUtc = null;
|
||||
if (element.TryGetProperty("latestConfirmationEmailSentUtc", out var latestSentElement) &&
|
||||
latestSentElement.ValueKind == JsonValueKind.String &&
|
||||
DateTimeOffset.TryParse(latestSentElement.GetString(), out var latestParsed))
|
||||
{
|
||||
latestSentUtc = latestParsed;
|
||||
}
|
||||
|
||||
return new EmailConfirmationRequiredDetailsDto(endpoint, ticket, latestSentUtc, resendAvailableAtUtc);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryGetString(JsonElement element, string propertyName, out string value)
|
||||
{
|
||||
value = string.Empty;
|
||||
|
||||
if (!element.TryGetProperty(propertyName, out var property) || property.ValueKind != JsonValueKind.String)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
value = property.GetString() ?? string.Empty;
|
||||
return !string.IsNullOrWhiteSpace(value);
|
||||
}
|
||||
|
||||
private static bool TryGetDateTimeOffset(JsonElement element, string propertyName, out DateTimeOffset value)
|
||||
{
|
||||
value = default;
|
||||
|
||||
if (!element.TryGetProperty(propertyName, out var property) || property.ValueKind != JsonValueKind.String)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return DateTimeOffset.TryParse(property.GetString(), out value);
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,8 @@
|
||||
</TransitionCollection>
|
||||
</StackPanel.ChildrenTransitions>
|
||||
<controls:SettingsExpander
|
||||
Description="{x:Bind domain:Translator.SettingsSignatureAndEncryption_MyCertificatesDescription, Mode=OneWay}"
|
||||
Header="{x:Bind domain:Translator.SettingsSignatureAndEncryption_MyCertificatesHeader, Mode=OneWay}"
|
||||
Description="{x:Bind domain:Translator.SettingsSignatureAndEncryption_MyCertificatesDescription, Mode=OneTime}"
|
||||
Header="{x:Bind domain:Translator.SettingsSignatureAndEncryption_MyCertificatesHeader, Mode=OneTime}"
|
||||
IsExpanded="True">
|
||||
<controls:SettingsExpander.Items>
|
||||
<controls:SettingsCard>
|
||||
@@ -44,15 +44,15 @@
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="{x:Bind domain:Translator.SettingsSignatureAndEncryption_NameColumn, Mode=OneWay}" />
|
||||
Text="{x:Bind domain:Translator.SettingsSignatureAndEncryption_NameColumn, Mode=OneTime}" />
|
||||
<TextBlock
|
||||
Grid.Column="2"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="{x:Bind domain:Translator.SettingsSignatureAndEncryption_ExpiresColumn, Mode=OneWay}" />
|
||||
Text="{x:Bind domain:Translator.SettingsSignatureAndEncryption_ExpiresColumn, Mode=OneTime}" />
|
||||
<TextBlock
|
||||
Grid.Column="3"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="{x:Bind domain:Translator.SettingsSignatureAndEncryption_ThumbprintColumn, Mode=OneWay}" />
|
||||
Text="{x:Bind domain:Translator.SettingsSignatureAndEncryption_ThumbprintColumn, Mode=OneTime}" />
|
||||
</Grid>
|
||||
</ListView.Header>
|
||||
<ListView.ItemTemplate>
|
||||
@@ -96,8 +96,8 @@
|
||||
</controls:SettingsExpander.Items>
|
||||
</controls:SettingsExpander>
|
||||
<controls:SettingsExpander
|
||||
Description="{x:Bind domain:Translator.SettingsSignatureAndEncryption_RecipientCertificatesDescription, Mode=OneWay}"
|
||||
Header="{x:Bind domain:Translator.SettingsSignatureAndEncryption_RecipientCertificatesHeader, Mode=OneWay}"
|
||||
Description="{x:Bind domain:Translator.SettingsSignatureAndEncryption_RecipientCertificatesDescription, Mode=OneTime}"
|
||||
Header="{x:Bind domain:Translator.SettingsSignatureAndEncryption_RecipientCertificatesHeader, Mode=OneTime}"
|
||||
IsExpanded="False">
|
||||
<controls:SettingsExpander.Items>
|
||||
<!--<StackPanel Padding="12" Spacing="12">-->
|
||||
@@ -125,15 +125,15 @@
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="{x:Bind domain:Translator.SettingsSignatureAndEncryption_NameColumn, Mode=OneWay}" />
|
||||
Text="{x:Bind domain:Translator.SettingsSignatureAndEncryption_NameColumn, Mode=OneTime}" />
|
||||
<TextBlock
|
||||
Grid.Column="2"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="{x:Bind domain:Translator.SettingsSignatureAndEncryption_ExpiresColumn, Mode=OneWay}" />
|
||||
Text="{x:Bind domain:Translator.SettingsSignatureAndEncryption_ExpiresColumn, Mode=OneTime}" />
|
||||
<TextBlock
|
||||
Grid.Column="3"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="{x:Bind domain:Translator.SettingsSignatureAndEncryption_ThumbprintColumn, Mode=OneWay}" />
|
||||
Text="{x:Bind domain:Translator.SettingsSignatureAndEncryption_ThumbprintColumn, Mode=OneTime}" />
|
||||
</Grid>
|
||||
</ListView.Header>
|
||||
<ListView.ItemTemplate>
|
||||
|
||||
@@ -315,6 +315,16 @@
|
||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</controls:SettingsCard.HeaderIcon>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard
|
||||
Command="{x:Bind ViewModel.ChangePasswordCommand}"
|
||||
Description="{x:Bind domain:Translator.WinoAccount_ChangePassword_Description}"
|
||||
Header="{x:Bind domain:Translator.WinoAccount_ChangePassword_Title}"
|
||||
IsClickEnabled="True">
|
||||
<!--<controls:SettingsCard.HeaderIcon>
|
||||
<SymbolIcon Symbol="Permission" />
|
||||
</controls:SettingsCard.HeaderIcon>-->
|
||||
</controls:SettingsCard>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
Reference in New Issue
Block a user