Add login page.

This commit is contained in:
2025-11-16 20:35:00 +00:00
parent 869e675ee3
commit 5f66029528
11 changed files with 576 additions and 13 deletions

View File

@@ -8,7 +8,7 @@
<ResourceDictionary.MergedDictionaries>
<!-- Load WinUI resources -->
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<!-- Load Uno.UI.Toolkit resources -->
<ToolkitResources xmlns="using:Uno.Toolkit.UI" />
</ResourceDictionary.MergedDictionaries>
@@ -17,6 +17,10 @@
<local:ObjectToVisibilityConverter x:Key="ObjectToVisibilityConverter" />
<local:StringToVisibilityConverter x:Key="StringToVisibilityConverter" />
<local:ZeroToVisibilityConverter x:Key="ZeroToVisibilityConverter" />
<local:InvertBoolConverter x:Key="InvertBoolConverter" />
<local:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<local:InvertBoolToVisibilityConverter x:Key="InvertBoolToVisibilityConverter" />
<local:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
</ResourceDictionary>
</Application.Resources>

View File

@@ -27,6 +27,7 @@ using ProcessorsListViewModel = Marechai.App.Presentation.ViewModels.ProcessorsL
using SettingsViewModel = Marechai.App.Presentation.ViewModels.SettingsViewModel;
using SoundSynthDetailViewModel = Marechai.App.Presentation.ViewModels.SoundSynthDetailViewModel;
using SoundSynthsListViewModel = Marechai.App.Presentation.ViewModels.SoundSynthsListViewModel;
using LoginViewModel = Marechai.App.Presentation.ViewModels.LoginViewModel;
namespace Marechai.App;
@@ -164,6 +165,7 @@ public partial class App : Application
services.AddTransient<SoundSynthsListViewModel>();
services.AddTransient<SoundSynthDetailViewModel>();
services.AddTransient<SettingsViewModel>();
services.AddTransient<LoginViewModel>();
})
.UseNavigation(RegisterRoutes));
@@ -181,6 +183,7 @@ public partial class App : Application
{
views.Register(new ViewMap(ViewModel: typeof(ShellViewModel)),
new ViewMap<MainPage, MainViewModel>(),
new ViewMap<LoginPage, LoginViewModel>(),
new ViewMap<NewsPage, NewsViewModel>(),
new ViewMap<ComputersPage, ComputersViewModel>(),
new ViewMap<ComputersListPage, ComputersListViewModel>(),
@@ -203,6 +206,7 @@ public partial class App : Application
views.FindByViewModel<ShellViewModel>(),
Nested:
[
new RouteMap("Login", views.FindByViewModel<LoginViewModel>()),
new RouteMap("Main",
views.FindByViewModel<MainViewModel>(),
true,

View File

@@ -0,0 +1,93 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Uno.Extensions.Authentication;
using Uno.Extensions.Navigation;
namespace Marechai.App.Presentation.ViewModels;
public partial class LoginViewModel : ObservableObject
{
private readonly IAuthenticationService _authService;
private readonly INavigator _navigator;
private readonly IStringLocalizer _stringLocalizer;
[ObservableProperty]
private string _email = string.Empty;
[ObservableProperty]
private string? _errorMessage;
[ObservableProperty]
private bool _isLoggingIn;
[ObservableProperty]
private string _password = string.Empty;
public LoginViewModel(INavigator navigator, IAuthenticationService authService, IStringLocalizer stringLocalizer)
{
_navigator = navigator;
_authService = authService;
_stringLocalizer = stringLocalizer;
}
[RelayCommand]
private async Task LoginAsync()
{
// Clear previous error
ErrorMessage = null;
// Validate inputs
if(string.IsNullOrWhiteSpace(Email))
{
ErrorMessage = _stringLocalizer["LoginPage.Error.EmailRequired"];
return;
}
if(string.IsNullOrWhiteSpace(Password))
{
ErrorMessage = _stringLocalizer["LoginPage.Error.PasswordRequired"];
return;
}
IsLoggingIn = true;
try
{
var credentials = new Dictionary<string, string>
{
["Email"] = Email,
["Password"] = Password
};
bool success = await _authService.LoginAsync(null, credentials, null, CancellationToken.None);
if(success)
{
// Navigate back to main page on successful login
await _navigator.NavigateRouteAsync(this, "/Main");
}
else
{
// Check if there's an error message in credentials
if(credentials.TryGetValue("error", out string? error))
ErrorMessage = error;
else
ErrorMessage = _stringLocalizer["LoginPage.Error.LoginFailed"];
}
}
catch(Exception ex)
{
ErrorMessage = ex.Message;
}
finally
{
IsLoggingIn = false;
}
}
[RelayCommand]
private void ClearError() => ErrorMessage = null;
}

View File

@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
using Marechai.App.Services;
using Uno.Extensions.Authentication;
using Uno.Extensions.Navigation;
using Uno.Extensions.Toolkit;
@@ -10,8 +12,9 @@ namespace Marechai.App.Presentation.ViewModels;
public partial class MainViewModel : ObservableObject
{
private readonly IStringLocalizer _localizer;
private readonly INavigator _navigator;
private readonly IAuthenticationService _authService;
private readonly IStringLocalizer _localizer;
private readonly INavigator _navigator;
[ObservableProperty]
private bool _isSidebarOpen = true;
[ObservableProperty]
@@ -27,10 +30,12 @@ public partial class MainViewModel : ObservableObject
private bool _sidebarContentVisible = true;
public MainViewModel(IStringLocalizer localizer, IOptions<AppConfig> appInfo, INavigator navigator,
NewsViewModel newsViewModel, IColorThemeService colorThemeService, IThemeService themeService)
NewsViewModel newsViewModel, IColorThemeService colorThemeService, IThemeService themeService,
IAuthenticationService authService)
{
_navigator = navigator;
_localizer = localizer;
_authService = authService;
NewsViewModel = newsViewModel;
Title = "Marechai";
Title += $" - {localizer["ApplicationName"]}";
@@ -62,6 +67,9 @@ public partial class MainViewModel : ObservableObject
LoginLogoutCommand = new RelayCommand(HandleLoginLogout);
ToggleSidebarCommand = new RelayCommand(() => IsSidebarOpen = !IsSidebarOpen);
// Subscribe to authentication events
_authService.LoggedOut += OnLoggedOut;
UpdateLoginLogoutButtonText();
}
@@ -157,16 +165,39 @@ public partial class MainViewModel : ObservableObject
};
}
private void UpdateLoginLogoutButtonText()
private async void UpdateLoginLogoutButtonText()
{
// TODO: Check if user is logged in
// For now, always show "Login"
LoginLogoutButtonText = LocalizedStrings["Login"];
bool isAuthenticated = await _authService.IsAuthenticated(CancellationToken.None);
LoginLogoutButtonText = isAuthenticated ? LocalizedStrings["Logout"] : LocalizedStrings["Login"];
}
private static void HandleLoginLogout()
private void OnLoggedOut(object? sender, EventArgs e)
{
// TODO: Implement login/logout logic
// Update button text when user logs out
UpdateLoginLogoutButtonText();
}
public void RefreshAuthenticationState()
{
// Public method to refresh authentication state (called after login)
UpdateLoginLogoutButtonText();
}
private async void HandleLoginLogout()
{
bool isAuthenticated = await _authService.IsAuthenticated(CancellationToken.None);
if(isAuthenticated)
{
// Logout
await _authService.LogoutAsync(null, CancellationToken.None);
UpdateLoginLogoutButtonText();
}
else
{
// Navigate to login page - use absolute path starting from root
await _navigator.NavigateRouteAsync(this, "/Login");
}
}
private async Task NavigateTo(string destination)

View File

@@ -8,5 +8,6 @@ public class ShellViewModel
public ShellViewModel(INavigator navigator) => _navigator = navigator;
// Add code here to initialize or attach event handlers to singleton services
// Users can browse the app without authentication
// Login is available from the sidebar when needed
}

View File

@@ -0,0 +1,209 @@
<?xml version="1.0"
encoding="utf-8"?>
<Page x:Class="Marechai.App.Presentation.Views.LoginPage"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:Marechai.App.Presentation.ViewModels"
xmlns:utu="using:Uno.Toolkit.UI"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
d:DataContext="{d:DesignInstance vm:LoginViewModel}">
<Grid utu:SafeArea.Insets="VisibleBounds">
<!-- Center content on screen -->
<Grid HorizontalAlignment="Center"
VerticalAlignment="Center"
MaxWidth="480"
Padding="32">
<!-- Login Card with Mac OS 9 styling -->
<Border Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="2"
CornerRadius="8"
Padding="0"
Translation="0,0,16">
<Border.Shadow>
<ThemeShadow />
</Border.Shadow>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Header with Mac OS 9 Title Bar Style -->
<Border Grid.Row="0"
Background="{ThemeResource AccentFillColorDefaultBrush}"
Padding="16,12"
CornerRadius="6,6,0,0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Icon -->
<FontIcon Grid.Column="0"
Glyph="&#xE77B;"
FontSize="24"
Foreground="White"
Margin="0,0,12,0"
VerticalAlignment="Center" />
<!-- Title -->
<TextBlock Grid.Column="1"
x:Uid="LoginPage_Title"
Text="Sign In to Marechai"
Style="{StaticResource TitleTextBlockStyle}"
Foreground="White"
VerticalAlignment="Center" />
</Grid>
</Border>
<!-- Login Form Content -->
<ScrollViewer Grid.Row="1"
VerticalScrollBarVisibility="Auto">
<StackPanel Padding="32"
Spacing="24">
<!-- Welcome Message -->
<TextBlock x:Uid="LoginPage_WelcomeMessage"
Text="Welcome back! Please sign in to continue."
Style="{StaticResource BodyTextBlockStyle}"
TextWrapping="Wrap"
HorizontalAlignment="Center"
TextAlignment="Center" />
<!-- Email Input -->
<StackPanel Spacing="8">
<TextBlock x:Uid="LoginPage_EmailLabel"
Text="Email Address"
Style="{StaticResource BodyStrongTextBlockStyle}" />
<TextBox x:Name="EmailTextBox"
x:Uid="LoginPage_EmailTextBox"
Text="{Binding Email, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
PlaceholderText="Enter your email"
InputScope="EmailSmtpAddress"
IsSpellCheckEnabled="False"
AutomationProperties.Name="Email address"
KeyDown="OnEmailKeyDown" />
</StackPanel>
<!-- Password Input -->
<StackPanel Spacing="8">
<TextBlock x:Uid="LoginPage_PasswordLabel"
Text="Password"
Style="{StaticResource BodyStrongTextBlockStyle}" />
<PasswordBox x:Name="PasswordBox"
x:Uid="LoginPage_PasswordBox"
Password="{Binding Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
PlaceholderText="Enter your password"
AutomationProperties.Name="Password"
KeyDown="OnPasswordKeyDown" />
</StackPanel>
<!-- Error Message -->
<Border x:Name="ErrorBorder"
Background="#FFF4E6"
BorderBrush="#FFB84D"
BorderThickness="2"
CornerRadius="4"
Padding="12"
Visibility="{Binding ErrorMessage, Converter={StaticResource NullToVisibilityConverter}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- Warning Icon -->
<FontIcon Grid.Column="0"
Glyph="&#xE7BA;"
Foreground="#D97706"
FontSize="20"
Margin="0,0,8,0"
VerticalAlignment="Top" />
<!-- Error Text -->
<TextBlock Grid.Column="1"
Text="{Binding ErrorMessage}"
Foreground="#92400E"
TextWrapping="Wrap"
VerticalAlignment="Center" />
<!-- Close Button -->
<Button Grid.Column="2"
Command="{Binding ClearErrorCommand}"
Background="Transparent"
BorderThickness="0"
Padding="8"
Margin="8,0,0,0"
VerticalAlignment="Center"
AutomationProperties.Name="Close error message">
<FontIcon Glyph="&#xE711;"
FontSize="12"
Foreground="#92400E" />
</Button>
</Grid>
</Border>
<!-- Login Button -->
<Button x:Name="LoginButton"
x:Uid="LoginPage_LoginButton"
Command="{Binding LoginCommand}"
IsEnabled="{Binding IsLoggingIn, Converter={StaticResource InvertBoolConverter}}"
HorizontalAlignment="Stretch"
Padding="16,12"
Style="{StaticResource AccentButtonStyle}"
AutomationProperties.Name="Sign in button">
<Button.Content>
<Grid>
<TextBlock x:Uid="LoginPage_LoginButtonText"
Text="Sign In"
Visibility="{Binding IsLoggingIn, Converter={StaticResource InvertBoolToVisibilityConverter}}" />
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center"
Spacing="8"
Visibility="{Binding IsLoggingIn, Converter={StaticResource BoolToVisibilityConverter}}">
<ProgressRing IsActive="True"
Width="20"
Height="20"
Foreground="White" />
<TextBlock x:Uid="LoginPage_SigningInText"
Text="Signing in..."
Foreground="White" />
</StackPanel>
</Grid>
</Button.Content>
</Button>
<!-- Additional Options -->
<StackPanel Spacing="8"
HorizontalAlignment="Center">
<HyperlinkButton x:Uid="LoginPage_ForgotPasswordLink"
Content="Forgot your password?"
HorizontalAlignment="Center"
Visibility="Collapsed" />
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center"
Spacing="4"
Visibility="Collapsed">
<TextBlock x:Uid="LoginPage_NoAccountText"
Text="Don't have an account?"
VerticalAlignment="Center" />
<HyperlinkButton x:Uid="LoginPage_SignUpLink"
Content="Sign up"
Padding="4,0" />
</StackPanel>
</StackPanel>
</StackPanel>
</ScrollViewer>
</Grid>
</Border>
</Grid>
</Grid>
</Page>

View File

@@ -0,0 +1,33 @@
using Windows.System;
using Marechai.App.Presentation.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
namespace Marechai.App.Presentation.Views;
public sealed partial class LoginPage : Page
{
public LoginPage() => InitializeComponent();
private void OnEmailKeyDown(object sender, KeyRoutedEventArgs e)
{
if(e.Key == VirtualKey.Enter)
{
// Move focus to password field
PasswordBox.Focus(FocusState.Keyboard);
e.Handled = true;
}
}
private void OnPasswordKeyDown(object sender, KeyRoutedEventArgs e)
{
if(e.Key == VirtualKey.Enter)
{
// Trigger login when Enter is pressed
if(DataContext is LoginViewModel viewModel && LoginButton.IsEnabled) viewModel.LoginCommand.Execute(null);
e.Handled = true;
}
}
}

View File

@@ -224,4 +224,51 @@
<data name="SettingsPage_DesignSystemChange_Notice.Text" xml:space="preserve">
<value>Design system changes will be applied the next time you start the application. Color theme changes are applied immediately.</value>
</data>
<!-- Login Page -->
<data name="LoginPage_Title.Text" xml:space="preserve">
<value>Sign In to Marechai</value>
</data>
<data name="LoginPage_WelcomeMessage.Text" xml:space="preserve">
<value>Welcome back! Please sign in to continue.</value>
</data>
<data name="LoginPage_EmailLabel.Text" xml:space="preserve">
<value>Email Address</value>
</data>
<data name="LoginPage_EmailTextBox.PlaceholderText" xml:space="preserve">
<value>Enter your email</value>
</data>
<data name="LoginPage_PasswordLabel.Text" xml:space="preserve">
<value>Password</value>
</data>
<data name="LoginPage_PasswordBox.PlaceholderText" xml:space="preserve">
<value>Enter your password</value>
</data>
<data name="LoginPage_LoginButton.Content" xml:space="preserve">
<value>Sign In</value>
</data>
<data name="LoginPage_LoginButtonText.Text" xml:space="preserve">
<value>Sign In</value>
</data>
<data name="LoginPage_SigningInText.Text" xml:space="preserve">
<value>Signing in...</value>
</data>
<data name="LoginPage_ForgotPasswordLink.Content" xml:space="preserve">
<value>Forgot your password?</value>
</data>
<data name="LoginPage_NoAccountText.Text" xml:space="preserve">
<value>Don't have an account?</value>
</data>
<data name="LoginPage_SignUpLink.Content" xml:space="preserve">
<value>Sign up</value>
</data>
<data name="LoginPage.Error.EmailRequired" xml:space="preserve">
<value>Email address is required.</value>
</data>
<data name="LoginPage.Error.PasswordRequired" xml:space="preserve">
<value>Password is required.</value>
</data>
<data name="LoginPage.Error.LoginFailed" xml:space="preserve">
<value>Login failed. Please check your credentials and try again.</value>
</data>
</root>

View File

@@ -224,4 +224,51 @@
<data name="SettingsPage_DesignSystemChange_Notice.Text" xml:space="preserve">
<value>Los cambios del sistema de diseño se aplicarán la próxima vez que inicie la aplicación. Los cambios de tema de color se aplican inmediatamente.</value>
</data>
<!-- Login Page -->
<data name="LoginPage_Title.Text" xml:space="preserve">
<value>Iniciar Sesión en Marechai</value>
</data>
<data name="LoginPage_WelcomeMessage.Text" xml:space="preserve">
<value>¡Bienvenido de nuevo! Por favor, inicia sesión para continuar.</value>
</data>
<data name="LoginPage_EmailLabel.Text" xml:space="preserve">
<value>Dirección de Correo Electrónico</value>
</data>
<data name="LoginPage_EmailTextBox.PlaceholderText" xml:space="preserve">
<value>Ingrese su correo electrónico</value>
</data>
<data name="LoginPage_PasswordLabel.Text" xml:space="preserve">
<value>Contraseña</value>
</data>
<data name="LoginPage_PasswordBox.PlaceholderText" xml:space="preserve">
<value>Ingrese su contraseña</value>
</data>
<data name="LoginPage_LoginButton.Content" xml:space="preserve">
<value>Iniciar Sesión</value>
</data>
<data name="LoginPage_LoginButtonText.Text" xml:space="preserve">
<value>Iniciar Sesión</value>
</data>
<data name="LoginPage_SigningInText.Text" xml:space="preserve">
<value>Iniciando sesión...</value>
</data>
<data name="LoginPage_ForgotPasswordLink.Content" xml:space="preserve">
<value>¿Olvidó su contraseña?</value>
</data>
<data name="LoginPage_NoAccountText.Text" xml:space="preserve">
<value>¿No tiene una cuenta?</value>
</data>
<data name="LoginPage_SignUpLink.Content" xml:space="preserve">
<value>Registrarse</value>
</data>
<data name="LoginPage.Error.EmailRequired" xml:space="preserve">
<value>La dirección de correo electrónico es obligatoria.</value>
</data>
<data name="LoginPage.Error.PasswordRequired" xml:space="preserve">
<value>La contraseña es obligatoria.</value>
</data>
<data name="LoginPage.Error.LoginFailed" xml:space="preserve">
<value>Error al iniciar sesión. Por favor, verifique sus credenciales e intente nuevamente.</value>
</data>
</root>

View File

@@ -222,6 +222,53 @@
<value>Système de Conception</value>
</data>
<data name="SettingsPage_DesignSystemChange_Notice.Text" xml:space="preserve">
<value>Les modifications du système de conception seront appliquées au prochain démarrage de l'application. Les modifications du thème de couleur sont appliquées immédiatement.</value>
<value>Les modifications du système de conception seront appliquées la prochaine fois que vous démarrerez l'application. Les modifications du thème de couleur sont appliquées immédiatement.</value>
</data>
<!-- Login Page -->
<data name="LoginPage_Title.Text" xml:space="preserve">
<value>Se Connecter à Marechai</value>
</data>
<data name="LoginPage_WelcomeMessage.Text" xml:space="preserve">
<value>Bienvenue ! Veuillez vous connecter pour continuer.</value>
</data>
<data name="LoginPage_EmailLabel.Text" xml:space="preserve">
<value>Adresse E-mail</value>
</data>
<data name="LoginPage_EmailTextBox.PlaceholderText" xml:space="preserve">
<value>Entrez votre e-mail</value>
</data>
<data name="LoginPage_PasswordLabel.Text" xml:space="preserve">
<value>Mot de Passe</value>
</data>
<data name="LoginPage_PasswordBox.PlaceholderText" xml:space="preserve">
<value>Entrez votre mot de passe</value>
</data>
<data name="LoginPage_LoginButton.Content" xml:space="preserve">
<value>Se Connecter</value>
</data>
<data name="LoginPage_LoginButtonText.Text" xml:space="preserve">
<value>Se Connecter</value>
</data>
<data name="LoginPage_SigningInText.Text" xml:space="preserve">
<value>Connexion en cours...</value>
</data>
<data name="LoginPage_ForgotPasswordLink.Content" xml:space="preserve">
<value>Mot de passe oublié ?</value>
</data>
<data name="LoginPage_NoAccountText.Text" xml:space="preserve">
<value>Vous n'avez pas de compte ?</value>
</data>
<data name="LoginPage_SignUpLink.Content" xml:space="preserve">
<value>S'inscrire</value>
</data>
<data name="LoginPage.Error.EmailRequired" xml:space="preserve">
<value>L'adresse e-mail est requise.</value>
</data>
<data name="LoginPage.Error.PasswordRequired" xml:space="preserve">
<value>Le mot de passe est requis.</value>
</data>
<data name="LoginPage.Error.LoginFailed" xml:space="preserve">
<value>Échec de la connexion. Veuillez vérifier vos identifiants et réessayer.</value>
</data>
</root>

View File

@@ -222,6 +222,53 @@
<value>Sistema de Design</value>
</data>
<data name="SettingsPage_DesignSystemChange_Notice.Text" xml:space="preserve">
<value>As alterações do sistema de design serão aplicadas na próxima vez que você iniciar o aplicativo. As alterações de tema de cor são aplicadas imediatamente.</value>
<value>As alterações no sistema de design serão aplicadas na próxima vez que você iniciar o aplicativo. As alterações no tema de cores são aplicadas imediatamente.</value>
</data>
<!-- Login Page -->
<data name="LoginPage_Title.Text" xml:space="preserve">
<value>Entrar no Marechai</value>
</data>
<data name="LoginPage_WelcomeMessage.Text" xml:space="preserve">
<value>Bem-vindo de volta! Por favor, faça login para continuar.</value>
</data>
<data name="LoginPage_EmailLabel.Text" xml:space="preserve">
<value>Endereço de E-mail</value>
</data>
<data name="LoginPage_EmailTextBox.PlaceholderText" xml:space="preserve">
<value>Digite seu e-mail</value>
</data>
<data name="LoginPage_PasswordLabel.Text" xml:space="preserve">
<value>Senha</value>
</data>
<data name="LoginPage_PasswordBox.PlaceholderText" xml:space="preserve">
<value>Digite sua senha</value>
</data>
<data name="LoginPage_LoginButton.Content" xml:space="preserve">
<value>Entrar</value>
</data>
<data name="LoginPage_LoginButtonText.Text" xml:space="preserve">
<value>Entrar</value>
</data>
<data name="LoginPage_SigningInText.Text" xml:space="preserve">
<value>Entrando...</value>
</data>
<data name="LoginPage_ForgotPasswordLink.Content" xml:space="preserve">
<value>Esqueceu sua senha?</value>
</data>
<data name="LoginPage_NoAccountText.Text" xml:space="preserve">
<value>Não tem uma conta?</value>
</data>
<data name="LoginPage_SignUpLink.Content" xml:space="preserve">
<value>Cadastre-se</value>
</data>
<data name="LoginPage.Error.EmailRequired" xml:space="preserve">
<value>O endereço de e-mail é obrigatório.</value>
</data>
<data name="LoginPage.Error.PasswordRequired" xml:space="preserve">
<value>A senha é obrigatória.</value>
</data>
<data name="LoginPage.Error.LoginFailed" xml:space="preserve">
<value>Falha no login. Por favor, verifique suas credenciais e tente novamente.</value>
</data>
</root>