From 5f66029528d7edaefea48606df67272824d1341a Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Sun, 16 Nov 2025 20:35:00 +0000 Subject: [PATCH] Add login page. --- Marechai.App/App.xaml | 6 +- Marechai.App/App.xaml.cs | 4 + .../Presentation/ViewModels/LoginViewModel.cs | 93 ++++++++ .../Presentation/ViewModels/MainViewModel.cs | 49 +++- .../Presentation/ViewModels/ShellViewModel.cs | 3 +- .../Presentation/Views/LoginPage.xaml | 209 ++++++++++++++++++ .../Presentation/Views/LoginPage.xaml.cs | 33 +++ Marechai.App/Strings/en/Resources.resw | 47 ++++ Marechai.App/Strings/es/Resources.resw | 47 ++++ Marechai.App/Strings/fr/Resources.resw | 49 +++- Marechai.App/Strings/pt-BR/Resources.resw | 49 +++- 11 files changed, 576 insertions(+), 13 deletions(-) create mode 100644 Marechai.App/Presentation/ViewModels/LoginViewModel.cs create mode 100644 Marechai.App/Presentation/Views/LoginPage.xaml create mode 100644 Marechai.App/Presentation/Views/LoginPage.xaml.cs diff --git a/Marechai.App/App.xaml b/Marechai.App/App.xaml index 115bc560..f7b0ef4a 100644 --- a/Marechai.App/App.xaml +++ b/Marechai.App/App.xaml @@ -8,7 +8,7 @@ - + @@ -17,6 +17,10 @@ + + + + diff --git a/Marechai.App/App.xaml.cs b/Marechai.App/App.xaml.cs index b329691d..d026f431 100644 --- a/Marechai.App/App.xaml.cs +++ b/Marechai.App/App.xaml.cs @@ -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(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); }) .UseNavigation(RegisterRoutes)); @@ -181,6 +183,7 @@ public partial class App : Application { views.Register(new ViewMap(ViewModel: typeof(ShellViewModel)), new ViewMap(), + new ViewMap(), new ViewMap(), new ViewMap(), new ViewMap(), @@ -203,6 +206,7 @@ public partial class App : Application views.FindByViewModel(), Nested: [ + new RouteMap("Login", views.FindByViewModel()), new RouteMap("Main", views.FindByViewModel(), true, diff --git a/Marechai.App/Presentation/ViewModels/LoginViewModel.cs b/Marechai.App/Presentation/ViewModels/LoginViewModel.cs new file mode 100644 index 00000000..429b0425 --- /dev/null +++ b/Marechai.App/Presentation/ViewModels/LoginViewModel.cs @@ -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 + { + ["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; +} \ No newline at end of file diff --git a/Marechai.App/Presentation/ViewModels/MainViewModel.cs b/Marechai.App/Presentation/ViewModels/MainViewModel.cs index bdc3912d..ea7e7e55 100644 --- a/Marechai.App/Presentation/ViewModels/MainViewModel.cs +++ b/Marechai.App/Presentation/ViewModels/MainViewModel.cs @@ -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 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) diff --git a/Marechai.App/Presentation/ViewModels/ShellViewModel.cs b/Marechai.App/Presentation/ViewModels/ShellViewModel.cs index 13edb3f6..45b4f5a3 100644 --- a/Marechai.App/Presentation/ViewModels/ShellViewModel.cs +++ b/Marechai.App/Presentation/ViewModels/ShellViewModel.cs @@ -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 } \ No newline at end of file diff --git a/Marechai.App/Presentation/Views/LoginPage.xaml b/Marechai.App/Presentation/Views/LoginPage.xaml new file mode 100644 index 00000000..35fc19b2 --- /dev/null +++ b/Marechai.App/Presentation/Views/LoginPage.xaml @@ -0,0 +1,209 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Marechai.App/Presentation/Views/LoginPage.xaml.cs b/Marechai.App/Presentation/Views/LoginPage.xaml.cs new file mode 100644 index 00000000..a53a5479 --- /dev/null +++ b/Marechai.App/Presentation/Views/LoginPage.xaml.cs @@ -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; + } + } +} \ No newline at end of file diff --git a/Marechai.App/Strings/en/Resources.resw b/Marechai.App/Strings/en/Resources.resw index 8a318269..c5ae151f 100644 --- a/Marechai.App/Strings/en/Resources.resw +++ b/Marechai.App/Strings/en/Resources.resw @@ -224,4 +224,51 @@ Design system changes will be applied the next time you start the application. Color theme changes are applied immediately. + + + + Sign In to Marechai + + + Welcome back! Please sign in to continue. + + + Email Address + + + Enter your email + + + Password + + + Enter your password + + + Sign In + + + Sign In + + + Signing in... + + + Forgot your password? + + + Don't have an account? + + + Sign up + + + Email address is required. + + + Password is required. + + + Login failed. Please check your credentials and try again. + diff --git a/Marechai.App/Strings/es/Resources.resw b/Marechai.App/Strings/es/Resources.resw index 07bac86e..697a2816 100644 --- a/Marechai.App/Strings/es/Resources.resw +++ b/Marechai.App/Strings/es/Resources.resw @@ -224,4 +224,51 @@ 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. + + + + Iniciar Sesión en Marechai + + + ¡Bienvenido de nuevo! Por favor, inicia sesión para continuar. + + + Dirección de Correo Electrónico + + + Ingrese su correo electrónico + + + Contraseña + + + Ingrese su contraseña + + + Iniciar Sesión + + + Iniciar Sesión + + + Iniciando sesión... + + + ¿Olvidó su contraseña? + + + ¿No tiene una cuenta? + + + Registrarse + + + La dirección de correo electrónico es obligatoria. + + + La contraseña es obligatoria. + + + Error al iniciar sesión. Por favor, verifique sus credenciales e intente nuevamente. + diff --git a/Marechai.App/Strings/fr/Resources.resw b/Marechai.App/Strings/fr/Resources.resw index e16e0b04..4cb720e7 100644 --- a/Marechai.App/Strings/fr/Resources.resw +++ b/Marechai.App/Strings/fr/Resources.resw @@ -222,6 +222,53 @@ Système de Conception - 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. + 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. + + + + + Se Connecter à Marechai + + + Bienvenue ! Veuillez vous connecter pour continuer. + + + Adresse E-mail + + + Entrez votre e-mail + + + Mot de Passe + + + Entrez votre mot de passe + + + Se Connecter + + + Se Connecter + + + Connexion en cours... + + + Mot de passe oublié ? + + + Vous n'avez pas de compte ? + + + S'inscrire + + + L'adresse e-mail est requise. + + + Le mot de passe est requis. + + + Échec de la connexion. Veuillez vérifier vos identifiants et réessayer. diff --git a/Marechai.App/Strings/pt-BR/Resources.resw b/Marechai.App/Strings/pt-BR/Resources.resw index 96a3ee19..8cae6460 100644 --- a/Marechai.App/Strings/pt-BR/Resources.resw +++ b/Marechai.App/Strings/pt-BR/Resources.resw @@ -222,6 +222,53 @@ Sistema de Design - 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. + 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. + + + + + Entrar no Marechai + + + Bem-vindo de volta! Por favor, faça login para continuar. + + + Endereço de E-mail + + + Digite seu e-mail + + + Senha + + + Digite sua senha + + + Entrar + + + Entrar + + + Entrando... + + + Esqueceu sua senha? + + + Não tem uma conta? + + + Cadastre-se + + + O endereço de e-mail é obrigatório. + + + A senha é obrigatória. + + + Falha no login. Por favor, verifique suas credenciais e tente novamente.