From 1df4671c41f1180a2ec2db1418eda0f482faa324 Mon Sep 17 00:00:00 2001 From: maayjayy Date: Wed, 28 Oct 2026 18:46:04 -0400 Subject: [PATCH 01/17] feat(desktop): always use light theme --- src/MoissonMontreal.Desktop/App.axaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/MoissonMontreal.Desktop/App.axaml b/src/MoissonMontreal.Desktop/App.axaml index f0cf256..72b7a9c 100644 --- a/src/MoissonMontreal.Desktop/App.axaml +++ b/src/MoissonMontreal.Desktop/App.axaml @@ -2,8 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="MoissonMontreal.Desktop.App" xmlns:local="using:MoissonMontreal.Desktop" - RequestedThemeVariant="Default"> - + RequestedThemeVariant="Light"> From 72fadabf1878a53fa308fdda99de5eb3e02a90eb Mon Sep 17 00:00:00 2001 From: maayjayy Date: Wed, 28 Oct 2026 19:12:01 -0400 Subject: [PATCH 02/17] feat(desktop): show pages in the main window, starting with login --- .../ViewModels/LoginViewModel.cs | 9 +++++++++ .../ViewModels/MainViewModel.cs | 2 +- src/MoissonMontreal.Desktop/Views/LoginView.axaml | 11 +++++++++++ src/MoissonMontreal.Desktop/Views/LoginView.axaml.cs | 11 +++++++++++ src/MoissonMontreal.Desktop/Views/MainWindow.axaml | 4 +--- 5 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 src/MoissonMontreal.Desktop/ViewModels/LoginViewModel.cs create mode 100644 src/MoissonMontreal.Desktop/Views/LoginView.axaml create mode 100644 src/MoissonMontreal.Desktop/Views/LoginView.axaml.cs diff --git a/src/MoissonMontreal.Desktop/ViewModels/LoginViewModel.cs b/src/MoissonMontreal.Desktop/ViewModels/LoginViewModel.cs new file mode 100644 index 0000000..5251fa7 --- /dev/null +++ b/src/MoissonMontreal.Desktop/ViewModels/LoginViewModel.cs @@ -0,0 +1,9 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace MoissonMontreal.Desktop.ViewModels; + +public partial class LoginViewModel : ViewModelBase +{ + [ObservableProperty] + public partial string Title { get; set; } = "Sign in"; +} \ No newline at end of file diff --git a/src/MoissonMontreal.Desktop/ViewModels/MainViewModel.cs b/src/MoissonMontreal.Desktop/ViewModels/MainViewModel.cs index 4c69889..5c4f2df 100644 --- a/src/MoissonMontreal.Desktop/ViewModels/MainViewModel.cs +++ b/src/MoissonMontreal.Desktop/ViewModels/MainViewModel.cs @@ -5,5 +5,5 @@ namespace MoissonMontreal.Desktop.ViewModels; public partial class MainViewModel : ViewModelBase { [ObservableProperty] - public partial string Greeting { get; set; } = "Welcome to Avalonia!"; + public partial ViewModelBase CurrentPage { get; set; } = new LoginViewModel(); } diff --git a/src/MoissonMontreal.Desktop/Views/LoginView.axaml b/src/MoissonMontreal.Desktop/Views/LoginView.axaml new file mode 100644 index 0000000..cbb4832 --- /dev/null +++ b/src/MoissonMontreal.Desktop/Views/LoginView.axaml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/src/MoissonMontreal.Desktop/Views/LoginView.axaml.cs b/src/MoissonMontreal.Desktop/Views/LoginView.axaml.cs new file mode 100644 index 0000000..0a749ae --- /dev/null +++ b/src/MoissonMontreal.Desktop/Views/LoginView.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace MoissonMontreal.Desktop.Views; + +public partial class LoginView : UserControl +{ + public LoginView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/src/MoissonMontreal.Desktop/Views/MainWindow.axaml b/src/MoissonMontreal.Desktop/Views/MainWindow.axaml index a99a146..8750ce4 100644 --- a/src/MoissonMontreal.Desktop/Views/MainWindow.axaml +++ b/src/MoissonMontreal.Desktop/Views/MainWindow.axaml @@ -15,8 +15,6 @@ - + From 97dee3a50f1029421e67e11a9e2540593314f5a7 Mon Sep 17 00:00:00 2001 From: maayjayy Date: Wed, 28 Oct 2026 19:51:36 -0400 Subject: [PATCH 03/17] feat(desktop): navigate from login to home on sign-in --- .../ViewModels/HomeViewModel.cs | 9 +++++++++ .../ViewModels/LoginViewModel.cs | 17 ++++++++++++++++- .../ViewModels/MainViewModel.cs | 7 ++++++- .../Views/HomeView.axaml | 11 +++++++++++ .../Views/HomeView.axaml.cs | 11 +++++++++++ .../Views/LoginView.axaml | 11 ++++++++--- 6 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 src/MoissonMontreal.Desktop/ViewModels/HomeViewModel.cs create mode 100644 src/MoissonMontreal.Desktop/Views/HomeView.axaml create mode 100644 src/MoissonMontreal.Desktop/Views/HomeView.axaml.cs diff --git a/src/MoissonMontreal.Desktop/ViewModels/HomeViewModel.cs b/src/MoissonMontreal.Desktop/ViewModels/HomeViewModel.cs new file mode 100644 index 0000000..bb90fd4 --- /dev/null +++ b/src/MoissonMontreal.Desktop/ViewModels/HomeViewModel.cs @@ -0,0 +1,9 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace MoissonMontreal.Desktop.ViewModels; + +public partial class HomeViewModel : ViewModelBase +{ + [ObservableProperty] + public partial string Title { get; set; } = "Welcome"; +} \ No newline at end of file diff --git a/src/MoissonMontreal.Desktop/ViewModels/LoginViewModel.cs b/src/MoissonMontreal.Desktop/ViewModels/LoginViewModel.cs index 5251fa7..a7a3dac 100644 --- a/src/MoissonMontreal.Desktop/ViewModels/LoginViewModel.cs +++ b/src/MoissonMontreal.Desktop/ViewModels/LoginViewModel.cs @@ -1,9 +1,24 @@ +using System; using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; namespace MoissonMontreal.Desktop.ViewModels; public partial class LoginViewModel : ViewModelBase { [ObservableProperty] - public partial string Title { get; set; } = "Sign in"; + public partial string Title { get; set; } = "Moisson Montréal"; + + private readonly Action _onSignedIn; + + public LoginViewModel(Action onSignedIn) + { + _onSignedIn = onSignedIn; + } + + [RelayCommand] + private void SignIn() + { + _onSignedIn(); + } } \ No newline at end of file diff --git a/src/MoissonMontreal.Desktop/ViewModels/MainViewModel.cs b/src/MoissonMontreal.Desktop/ViewModels/MainViewModel.cs index 5c4f2df..20ee4e1 100644 --- a/src/MoissonMontreal.Desktop/ViewModels/MainViewModel.cs +++ b/src/MoissonMontreal.Desktop/ViewModels/MainViewModel.cs @@ -5,5 +5,10 @@ namespace MoissonMontreal.Desktop.ViewModels; public partial class MainViewModel : ViewModelBase { [ObservableProperty] - public partial ViewModelBase CurrentPage { get; set; } = new LoginViewModel(); + public partial ViewModelBase CurrentPage { get; set; } + + public MainViewModel() + { + CurrentPage = new LoginViewModel(() => CurrentPage = new HomeViewModel()); + } } diff --git a/src/MoissonMontreal.Desktop/Views/HomeView.axaml b/src/MoissonMontreal.Desktop/Views/HomeView.axaml new file mode 100644 index 0000000..0ecdd0c --- /dev/null +++ b/src/MoissonMontreal.Desktop/Views/HomeView.axaml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/src/MoissonMontreal.Desktop/Views/HomeView.axaml.cs b/src/MoissonMontreal.Desktop/Views/HomeView.axaml.cs new file mode 100644 index 0000000..6cad35f --- /dev/null +++ b/src/MoissonMontreal.Desktop/Views/HomeView.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace MoissonMontreal.Desktop.Views; + +public partial class HomeView : UserControl +{ + public HomeView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/src/MoissonMontreal.Desktop/Views/LoginView.axaml b/src/MoissonMontreal.Desktop/Views/LoginView.axaml index cbb4832..4118702 100644 --- a/src/MoissonMontreal.Desktop/Views/LoginView.axaml +++ b/src/MoissonMontreal.Desktop/Views/LoginView.axaml @@ -4,8 +4,13 @@ x:Class="MoissonMontreal.Desktop.Views.LoginView" x:DataType="vm:LoginViewModel"> - + + + + \ No newline at end of file From c0a790b340d0ed426e7c45b2b2c117edc20878ea Mon Sep 17 00:00:00 2001 From: maayjayy Date: Wed, 28 Oct 2026 20:27:19 -0400 Subject: [PATCH 04/17] feat(desktop): add placeholder pages, nav bar and sign out --- .../ViewModels/DonationsViewModel.cs | 9 +++++ .../ViewModels/DonorsViewModel.cs | 9 +++++ .../ViewModels/MainViewModel.cs | 34 +++++++++++++++++-- .../Views/DonationsView.axaml | 11 ++++++ .../Views/DonationsView.axaml.cs | 11 ++++++ .../Views/DonorsView.axaml | 11 ++++++ .../Views/DonorsView.axaml.cs | 11 ++++++ .../Views/MainWindow.axaml | 18 ++++++++-- 8 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 src/MoissonMontreal.Desktop/ViewModels/DonationsViewModel.cs create mode 100644 src/MoissonMontreal.Desktop/ViewModels/DonorsViewModel.cs create mode 100644 src/MoissonMontreal.Desktop/Views/DonationsView.axaml create mode 100644 src/MoissonMontreal.Desktop/Views/DonationsView.axaml.cs create mode 100644 src/MoissonMontreal.Desktop/Views/DonorsView.axaml create mode 100644 src/MoissonMontreal.Desktop/Views/DonorsView.axaml.cs diff --git a/src/MoissonMontreal.Desktop/ViewModels/DonationsViewModel.cs b/src/MoissonMontreal.Desktop/ViewModels/DonationsViewModel.cs new file mode 100644 index 0000000..8056d93 --- /dev/null +++ b/src/MoissonMontreal.Desktop/ViewModels/DonationsViewModel.cs @@ -0,0 +1,9 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace MoissonMontreal.Desktop.ViewModels; + +public partial class DonationsViewModel : ViewModelBase +{ + [ObservableProperty] + public partial string Title { get; set; } = "Donations"; +} \ No newline at end of file diff --git a/src/MoissonMontreal.Desktop/ViewModels/DonorsViewModel.cs b/src/MoissonMontreal.Desktop/ViewModels/DonorsViewModel.cs new file mode 100644 index 0000000..0694013 --- /dev/null +++ b/src/MoissonMontreal.Desktop/ViewModels/DonorsViewModel.cs @@ -0,0 +1,9 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace MoissonMontreal.Desktop.ViewModels; + +public partial class DonorsViewModel : ViewModelBase +{ + [ObservableProperty] + public partial string Title { get; set; } = "Donors"; +} \ No newline at end of file diff --git a/src/MoissonMontreal.Desktop/ViewModels/MainViewModel.cs b/src/MoissonMontreal.Desktop/ViewModels/MainViewModel.cs index 20ee4e1..c4d806f 100644 --- a/src/MoissonMontreal.Desktop/ViewModels/MainViewModel.cs +++ b/src/MoissonMontreal.Desktop/ViewModels/MainViewModel.cs @@ -1,14 +1,44 @@ using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; namespace MoissonMontreal.Desktop.ViewModels; public partial class MainViewModel : ViewModelBase { [ObservableProperty] - public partial ViewModelBase CurrentPage { get; set; } + [NotifyPropertyChangedFor(nameof(IsSignedIn))] + public partial ViewModelBase CurrentPage {get; set;} + + public bool IsSignedIn => CurrentPage is not LoginViewModel; public MainViewModel() { - CurrentPage = new LoginViewModel(() => CurrentPage = new HomeViewModel()); + CurrentPage = CreateLoginPage(); + } + + private LoginViewModel CreateLoginPage() => new LoginViewModel(ShowHome); + + [RelayCommand] + private void ShowHome() + { + CurrentPage = new HomeViewModel(); + } + + [RelayCommand] + private void ShowDonations() + { + CurrentPage = new DonationsViewModel(); + } + + [RelayCommand] + private void ShowDonors() + { + CurrentPage = new DonorsViewModel(); + } + + [RelayCommand] + private void SignOut() + { + CurrentPage = CreateLoginPage(); } } diff --git a/src/MoissonMontreal.Desktop/Views/DonationsView.axaml b/src/MoissonMontreal.Desktop/Views/DonationsView.axaml new file mode 100644 index 0000000..429566f --- /dev/null +++ b/src/MoissonMontreal.Desktop/Views/DonationsView.axaml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/src/MoissonMontreal.Desktop/Views/DonationsView.axaml.cs b/src/MoissonMontreal.Desktop/Views/DonationsView.axaml.cs new file mode 100644 index 0000000..8828886 --- /dev/null +++ b/src/MoissonMontreal.Desktop/Views/DonationsView.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace MoissonMontreal.Desktop.Views; + +public partial class DonationsView : UserControl +{ + public DonationsView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/src/MoissonMontreal.Desktop/Views/DonorsView.axaml b/src/MoissonMontreal.Desktop/Views/DonorsView.axaml new file mode 100644 index 0000000..14766c2 --- /dev/null +++ b/src/MoissonMontreal.Desktop/Views/DonorsView.axaml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/src/MoissonMontreal.Desktop/Views/DonorsView.axaml.cs b/src/MoissonMontreal.Desktop/Views/DonorsView.axaml.cs new file mode 100644 index 0000000..5a3111b --- /dev/null +++ b/src/MoissonMontreal.Desktop/Views/DonorsView.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace MoissonMontreal.Desktop.Views; + +public partial class DonorsView : UserControl +{ + public DonorsView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/src/MoissonMontreal.Desktop/Views/MainWindow.axaml b/src/MoissonMontreal.Desktop/Views/MainWindow.axaml index 8750ce4..8925b87 100644 --- a/src/MoissonMontreal.Desktop/Views/MainWindow.axaml +++ b/src/MoissonMontreal.Desktop/Views/MainWindow.axaml @@ -7,7 +7,7 @@ x:Class="MoissonMontreal.Desktop.Views.MainWindow" x:DataType="vm:MainViewModel" Icon="/Assets/avalonia-logo.ico" - Title="MoissonMontreal.Desktop"> + Title="Moisson Montréal"> - - - Date: Thu, 29 Oct 2026 14:05:56 -0400 Subject: [PATCH 08/17] fix: handle all errors in Home API check --- .../ViewModels/HomeViewModel.cs | 13 ++++++++++--- src/MoissonMontreal.Desktop/Views/HomeView.axaml.cs | 13 +++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/MoissonMontreal.Desktop/ViewModels/HomeViewModel.cs b/src/MoissonMontreal.Desktop/ViewModels/HomeViewModel.cs index 386f96c..a8efb11 100644 --- a/src/MoissonMontreal.Desktop/ViewModels/HomeViewModel.cs +++ b/src/MoissonMontreal.Desktop/ViewModels/HomeViewModel.cs @@ -1,3 +1,4 @@ +using System; using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; @@ -18,14 +19,20 @@ public partial class HomeViewModel : ViewModelBase public HomeViewModel(IApiClient apiClient) { _apiClient = apiClient; - CheckApiCommand.Execute(null); } [RelayCommand] private async Task CheckApiAsync() { ApiStatus = "Checking the API..."; - var isReachable = await _apiClient.IsApiReachableAsync(); - ApiStatus = isReachable ? "API online" : "Server unavailable"; + try + { + var isReachable = await _apiClient.IsApiReachableAsync(); + ApiStatus = isReachable ? "API online" : "Server unavailable"; + } + catch (Exception) + { + ApiStatus = "Server unavailable"; + } } } \ No newline at end of file diff --git a/src/MoissonMontreal.Desktop/Views/HomeView.axaml.cs b/src/MoissonMontreal.Desktop/Views/HomeView.axaml.cs index 6cad35f..4f7e0e3 100644 --- a/src/MoissonMontreal.Desktop/Views/HomeView.axaml.cs +++ b/src/MoissonMontreal.Desktop/Views/HomeView.axaml.cs @@ -1,4 +1,7 @@ using Avalonia.Controls; +using Avalonia.Interactivity; + +using MoissonMontreal.Desktop.ViewModels; namespace MoissonMontreal.Desktop.Views; @@ -8,4 +11,14 @@ public HomeView() { InitializeComponent(); } + + protected override void OnLoaded(RoutedEventArgs e) + { + base.OnLoaded(e); + + if (DataContext is HomeViewModel viewModel) + { + viewModel.CheckApiCommand.Execute(null); + } + } } \ No newline at end of file From 3833ade338fe865bdc2f3861c72d3de748451907 Mon Sep 17 00:00:00 2001 From: maayjayy Date: Thu, 29 Oct 2026 15:00:54 -0400 Subject: [PATCH 09/17] fix: resolve a new ApiClient on each Home navigation --- src/MoissonMontreal.Desktop/App.axaml.cs | 1 + src/MoissonMontreal.Desktop/ViewModels/MainViewModel.cs | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/MoissonMontreal.Desktop/App.axaml.cs b/src/MoissonMontreal.Desktop/App.axaml.cs index b550426..0a0ad75 100644 --- a/src/MoissonMontreal.Desktop/App.axaml.cs +++ b/src/MoissonMontreal.Desktop/App.axaml.cs @@ -37,6 +37,7 @@ public override void OnFrameworkInitializationCompleted() client.BaseAddress = new Uri(apiBaseUrl); client.Timeout = TimeSpan.FromSeconds(15); }); + services.AddSingleton>(sp => () => sp.GetRequiredService()); services.AddSingleton(); var serviceProvider = services.BuildServiceProvider(); diff --git a/src/MoissonMontreal.Desktop/ViewModels/MainViewModel.cs b/src/MoissonMontreal.Desktop/ViewModels/MainViewModel.cs index 3aa1bf4..389242e 100644 --- a/src/MoissonMontreal.Desktop/ViewModels/MainViewModel.cs +++ b/src/MoissonMontreal.Desktop/ViewModels/MainViewModel.cs @@ -1,3 +1,4 @@ +using System; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using MoissonMontreal.Desktop.Services; @@ -6,7 +7,7 @@ namespace MoissonMontreal.Desktop.ViewModels; public partial class MainViewModel : ViewModelBase { - private readonly IApiClient _apiClient; + private readonly Func _createApiClient; [ObservableProperty] [NotifyPropertyChangedFor(nameof(IsSignedIn))] @@ -14,9 +15,9 @@ public partial class MainViewModel : ViewModelBase public bool IsSignedIn => CurrentPage is not LoginViewModel; - public MainViewModel(IApiClient apiClient) + public MainViewModel(Func createApiClient) { - _apiClient = apiClient; + _createApiClient = createApiClient; CurrentPage = CreateLoginPage(); } @@ -25,7 +26,7 @@ public MainViewModel(IApiClient apiClient) [RelayCommand] private void ShowHome() { - CurrentPage = new HomeViewModel(_apiClient); + CurrentPage = new HomeViewModel(_createApiClient()); } [RelayCommand] From 63031a6f154abc60d1f3c2933192a2430e604811 Mon Sep 17 00:00:00 2001 From: maayjayy Date: Thu, 29 Oct 2026 15:23:50 -0400 Subject: [PATCH 10/17] docs(desktop): add XML documentation comments --- src/MoissonMontreal.Desktop/App.axaml.cs | 10 ++++++ src/MoissonMontreal.Desktop/Program.cs | 19 ++++++++--- .../Services/ApiClient.cs | 11 +++++++ .../Services/IApiClient.cs | 9 ++++++ src/MoissonMontreal.Desktop/ViewLocator.cs | 10 ++++++ .../ViewModels/DonationsViewModel.cs | 6 ++++ .../ViewModels/DonorsViewModel.cs | 6 ++++ .../ViewModels/HomeViewModel.cs | 19 +++++++++++ .../ViewModels/LoginViewModel.cs | 18 ++++++++++- .../ViewModels/MainViewModel.cs | 32 +++++++++++++++++++ .../ViewModels/ViewModelBase.cs | 3 ++ .../Views/DonationsView.axaml.cs | 6 ++++ .../Views/DonorsView.axaml.cs | 6 ++++ .../Views/HomeView.axaml.cs | 10 ++++++ .../Views/LoginView.axaml.cs | 6 ++++ .../Views/MainWindow.axaml.cs | 6 ++++ 16 files changed, 172 insertions(+), 5 deletions(-) diff --git a/src/MoissonMontreal.Desktop/App.axaml.cs b/src/MoissonMontreal.Desktop/App.axaml.cs index 0a0ad75..fb1321a 100644 --- a/src/MoissonMontreal.Desktop/App.axaml.cs +++ b/src/MoissonMontreal.Desktop/App.axaml.cs @@ -11,13 +11,23 @@ namespace MoissonMontreal.Desktop; +/// +/// Entry point of the Avalonia application: reads the configuration, sets up the services and opens the main window. +/// public partial class App : Application { + /// + /// Loads the application's XAML (styles and resources). + /// public override void Initialize() { AvaloniaXamlLoader.Load(this); } + /// + /// Runs once Avalonia is ready: reads appsettings.json, registers the services and opens the main window. + /// + /// Thrown when Api:BaseUrl is missing from appsettings.json. public override void OnFrameworkInitializationCompleted() { if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) diff --git a/src/MoissonMontreal.Desktop/Program.cs b/src/MoissonMontreal.Desktop/Program.cs index 122194e..9795526 100644 --- a/src/MoissonMontreal.Desktop/Program.cs +++ b/src/MoissonMontreal.Desktop/Program.cs @@ -3,16 +3,27 @@ namespace MoissonMontreal.Desktop; +/// +/// Starts the desktop application. +/// sealed class Program { - // Initialization code. Don't use any Avalonia, third-party APIs or any - // SynchronizationContext-reliant code before AppMain is called: things aren't initialized - // yet and stuff might break. + /// + /// Program entry point: starts Avalonia with a classic desktop window. + /// + /// + /// Don't use any Avalonia, third-party APIs or any SynchronizationContext-reliant code + /// before AppMain is called: things aren't initialized yet and stuff might break. + /// + /// Command-line arguments. [STAThread] public static void Main(string[] args) => BuildAvaloniaApp() .StartWithClassicDesktopLifetime(args); - // Avalonia configuration, don't remove; also used by visual designer. + /// + /// Configures Avalonia. Don't remove: the visual designer uses it too. + /// + /// The configured application builder. public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() .UsePlatformDetect() diff --git a/src/MoissonMontreal.Desktop/Services/ApiClient.cs b/src/MoissonMontreal.Desktop/Services/ApiClient.cs index 1c797ad..79b04c7 100644 --- a/src/MoissonMontreal.Desktop/Services/ApiClient.cs +++ b/src/MoissonMontreal.Desktop/Services/ApiClient.cs @@ -3,15 +3,26 @@ namespace MoissonMontreal.Desktop.Services; +/// +/// Calls the central API over HTTP. +/// public class ApiClient : IApiClient { + /// + /// HTTP client configured in App with the API's base address and timeout. + /// private readonly HttpClient _httpClient; + /// + /// Creates the client. + /// + /// HTTP client provided by the dependency injection container. public ApiClient(HttpClient httpClient) { _httpClient = httpClient; } + /// public async Task IsApiReachableAsync() { try diff --git a/src/MoissonMontreal.Desktop/Services/IApiClient.cs b/src/MoissonMontreal.Desktop/Services/IApiClient.cs index d908e27..86c3840 100644 --- a/src/MoissonMontreal.Desktop/Services/IApiClient.cs +++ b/src/MoissonMontreal.Desktop/Services/IApiClient.cs @@ -2,7 +2,16 @@ namespace MoissonMontreal.Desktop.Services; +/// +/// Talks to the central Moisson Montréal API. +/// public interface IApiClient { + /// + /// Checks whether the API answers. + /// + /// true if the API answered successfully; false if it is unreachable or returned an error. Task IsApiReachableAsync(); } + + diff --git a/src/MoissonMontreal.Desktop/ViewLocator.cs b/src/MoissonMontreal.Desktop/ViewLocator.cs index a7a5bce..8821c7b 100644 --- a/src/MoissonMontreal.Desktop/ViewLocator.cs +++ b/src/MoissonMontreal.Desktop/ViewLocator.cs @@ -14,6 +14,11 @@ namespace MoissonMontreal.Desktop; Url = "https://docs.avaloniaui.net/docs/concepts/view-locator")] public class ViewLocator : IDataTemplate { + /// + /// Creates the view that matches a ViewModel (for example HomeViewModel → HomeView). + /// + /// The ViewModel to show. + /// The matching view, a "Not Found" text if there is none, or null if is null. public Control? Build(object? param) { if (param is null) @@ -30,6 +35,11 @@ public class ViewLocator : IDataTemplate return new TextBlock { Text = "Not Found: " + name }; } + /// + /// Tells Avalonia whether this locator can show the given object. + /// + /// The object to show. + /// true if the object is a ViewModel. public bool Match(object? data) { return data is ViewModelBase; diff --git a/src/MoissonMontreal.Desktop/ViewModels/DonationsViewModel.cs b/src/MoissonMontreal.Desktop/ViewModels/DonationsViewModel.cs index 8056d93..a834e8f 100644 --- a/src/MoissonMontreal.Desktop/ViewModels/DonationsViewModel.cs +++ b/src/MoissonMontreal.Desktop/ViewModels/DonationsViewModel.cs @@ -2,8 +2,14 @@ namespace MoissonMontreal.Desktop.ViewModels; +/// +/// Logic of the donations page (placeholder for now). +/// public partial class DonationsViewModel : ViewModelBase { + /// + /// Title shown at the top of the donations page. + /// [ObservableProperty] public partial string Title { get; set; } = "Donations"; } \ No newline at end of file diff --git a/src/MoissonMontreal.Desktop/ViewModels/DonorsViewModel.cs b/src/MoissonMontreal.Desktop/ViewModels/DonorsViewModel.cs index 0694013..78610ba 100644 --- a/src/MoissonMontreal.Desktop/ViewModels/DonorsViewModel.cs +++ b/src/MoissonMontreal.Desktop/ViewModels/DonorsViewModel.cs @@ -2,8 +2,14 @@ namespace MoissonMontreal.Desktop.ViewModels; +/// +/// Logic of the donors page (placeholder for now). +/// public partial class DonorsViewModel : ViewModelBase { + /// + /// Title shown at the top of the donors page. + /// [ObservableProperty] public partial string Title { get; set; } = "Donors"; } \ No newline at end of file diff --git a/src/MoissonMontreal.Desktop/ViewModels/HomeViewModel.cs b/src/MoissonMontreal.Desktop/ViewModels/HomeViewModel.cs index a8efb11..d97fa21 100644 --- a/src/MoissonMontreal.Desktop/ViewModels/HomeViewModel.cs +++ b/src/MoissonMontreal.Desktop/ViewModels/HomeViewModel.cs @@ -6,21 +6,40 @@ namespace MoissonMontreal.Desktop.ViewModels; +/// +/// Logic of the home page: shows whether the API is reachable. +/// public partial class HomeViewModel : ViewModelBase { + /// + /// Client used to check whether the API is reachable. + /// private readonly IApiClient _apiClient; + /// + /// Title shown at the top of the home page. + /// [ObservableProperty] public partial string Title { get; set; } = "Welcome"; + /// + /// API status message shown to the user. + /// [ObservableProperty] public partial string ApiStatus { get; set; } = "Checking the API..."; + /// + /// Creates the home page. The API check starts when the view is loaded. + /// + /// Client used to call the API. public HomeViewModel(IApiClient apiClient) { _apiClient = apiClient; } + /// + /// Checks the API and updates . Any error shows "Server unavailable" instead of crashing the app. + /// [RelayCommand] private async Task CheckApiAsync() { diff --git a/src/MoissonMontreal.Desktop/ViewModels/LoginViewModel.cs b/src/MoissonMontreal.Desktop/ViewModels/LoginViewModel.cs index a7a3dac..ca28900 100644 --- a/src/MoissonMontreal.Desktop/ViewModels/LoginViewModel.cs +++ b/src/MoissonMontreal.Desktop/ViewModels/LoginViewModel.cs @@ -4,18 +4,34 @@ namespace MoissonMontreal.Desktop.ViewModels; +/// +/// Logic of the login page. Sign-in is simulated for now (real sign-in comes with Entra ID). +/// public partial class LoginViewModel : ViewModelBase { + /// + /// Title shown at the top of the login page. + /// [ObservableProperty] public partial string Title { get; set; } = "Moisson Montréal"; + /// + /// Action to run once the user is signed in (opens the home page). + /// private readonly Action _onSignedIn; + /// + /// Creates the login page. + /// + /// Action to run once the user is signed in. public LoginViewModel(Action onSignedIn) { _onSignedIn = onSignedIn; } - + + /// + /// Signs the user in (simulated) and runs the "signed in" action. + /// [RelayCommand] private void SignIn() { diff --git a/src/MoissonMontreal.Desktop/ViewModels/MainViewModel.cs b/src/MoissonMontreal.Desktop/ViewModels/MainViewModel.cs index 389242e..cef0f71 100644 --- a/src/MoissonMontreal.Desktop/ViewModels/MainViewModel.cs +++ b/src/MoissonMontreal.Desktop/ViewModels/MainViewModel.cs @@ -5,42 +5,74 @@ namespace MoissonMontreal.Desktop.ViewModels; +/// +/// Logic of the main window: decides which page is shown and handles navigation. +/// public partial class MainViewModel : ViewModelBase { + /// + /// Creates a new API client each time the home page is opened. + /// private readonly Func _createApiClient; + /// + /// Page currently shown in the main window. + /// [ObservableProperty] [NotifyPropertyChangedFor(nameof(IsSignedIn))] public partial ViewModelBase CurrentPage {get; set;} + /// + /// true when the user is past the login page; shows the navigation bar and the Sign out button. + /// public bool IsSignedIn => CurrentPage is not LoginViewModel; + /// + /// Creates the main ViewModel and shows the login page. + /// + /// Function that returns a new API client on each call. public MainViewModel(Func createApiClient) { _createApiClient = createApiClient; CurrentPage = CreateLoginPage(); } + /// + /// Creates the login page, which opens the home page after sign-in. + /// + /// A new login page. private LoginViewModel CreateLoginPage() => new LoginViewModel(ShowHome); + /// + /// Shows the home page with a new API client. + /// [RelayCommand] private void ShowHome() { CurrentPage = new HomeViewModel(_createApiClient()); } + /// + /// Shows the donations page. + /// [RelayCommand] private void ShowDonations() { CurrentPage = new DonationsViewModel(); } + /// + /// Shows the donors page. + /// [RelayCommand] private void ShowDonors() { CurrentPage = new DonorsViewModel(); } + /// + /// Signs the user out and goes back to the login page. + /// [RelayCommand] private void SignOut() { diff --git a/src/MoissonMontreal.Desktop/ViewModels/ViewModelBase.cs b/src/MoissonMontreal.Desktop/ViewModels/ViewModelBase.cs index c178d94..c490cd6 100644 --- a/src/MoissonMontreal.Desktop/ViewModels/ViewModelBase.cs +++ b/src/MoissonMontreal.Desktop/ViewModels/ViewModelBase.cs @@ -2,6 +2,9 @@ namespace MoissonMontreal.Desktop.ViewModels; +/// +/// Base class for every ViewModel. Lets the screen update when a property changes. +/// public abstract class ViewModelBase : ObservableObject { } diff --git a/src/MoissonMontreal.Desktop/Views/DonationsView.axaml.cs b/src/MoissonMontreal.Desktop/Views/DonationsView.axaml.cs index 8828886..6a037bc 100644 --- a/src/MoissonMontreal.Desktop/Views/DonationsView.axaml.cs +++ b/src/MoissonMontreal.Desktop/Views/DonationsView.axaml.cs @@ -2,8 +2,14 @@ namespace MoissonMontreal.Desktop.Views; +/// +/// Donations page screen. +/// public partial class DonationsView : UserControl { + /// + /// Creates the view and loads its XAML. + /// public DonationsView() { InitializeComponent(); diff --git a/src/MoissonMontreal.Desktop/Views/DonorsView.axaml.cs b/src/MoissonMontreal.Desktop/Views/DonorsView.axaml.cs index 5a3111b..6de75fa 100644 --- a/src/MoissonMontreal.Desktop/Views/DonorsView.axaml.cs +++ b/src/MoissonMontreal.Desktop/Views/DonorsView.axaml.cs @@ -2,8 +2,14 @@ namespace MoissonMontreal.Desktop.Views; +/// +/// Donors page screen. +/// public partial class DonorsView : UserControl { + /// + /// Creates the view and loads its XAML. + /// public DonorsView() { InitializeComponent(); diff --git a/src/MoissonMontreal.Desktop/Views/HomeView.axaml.cs b/src/MoissonMontreal.Desktop/Views/HomeView.axaml.cs index 4f7e0e3..2933112 100644 --- a/src/MoissonMontreal.Desktop/Views/HomeView.axaml.cs +++ b/src/MoissonMontreal.Desktop/Views/HomeView.axaml.cs @@ -5,13 +5,23 @@ namespace MoissonMontreal.Desktop.Views; +/// +/// Home page screen. +/// public partial class HomeView : UserControl { + /// + /// Creates the view and loads its XAML. + /// public HomeView() { InitializeComponent(); } + /// + /// Starts the API check once the view is shown on screen. + /// + /// Information about the event. protected override void OnLoaded(RoutedEventArgs e) { base.OnLoaded(e); diff --git a/src/MoissonMontreal.Desktop/Views/LoginView.axaml.cs b/src/MoissonMontreal.Desktop/Views/LoginView.axaml.cs index 0a749ae..f2d2b52 100644 --- a/src/MoissonMontreal.Desktop/Views/LoginView.axaml.cs +++ b/src/MoissonMontreal.Desktop/Views/LoginView.axaml.cs @@ -2,8 +2,14 @@ namespace MoissonMontreal.Desktop.Views; +/// +/// Login page screen. +/// public partial class LoginView : UserControl { + /// + /// Creates the view and loads its XAML. + /// public LoginView() { InitializeComponent(); diff --git a/src/MoissonMontreal.Desktop/Views/MainWindow.axaml.cs b/src/MoissonMontreal.Desktop/Views/MainWindow.axaml.cs index 3020590..03438a8 100644 --- a/src/MoissonMontreal.Desktop/Views/MainWindow.axaml.cs +++ b/src/MoissonMontreal.Desktop/Views/MainWindow.axaml.cs @@ -2,8 +2,14 @@ namespace MoissonMontreal.Desktop.Views; +/// +/// Main window of the app. Shows the navigation bar and the current page. +/// public partial class MainWindow : Window { + /// + /// Creates the window and loads its XAML. + /// public MainWindow() { InitializeComponent(); From a84ab2a42e01581c7f4e8c0efed6a90f2d14911b Mon Sep 17 00:00:00 2001 From: maayjayy Date: Thu, 29 Oct 2026 16:43:01 -0400 Subject: [PATCH 11/17] refactor: make page titles read-only --- .../ViewModels/DonationsViewModel.cs | 7 ++----- src/MoissonMontreal.Desktop/ViewModels/DonorsViewModel.cs | 7 ++----- src/MoissonMontreal.Desktop/ViewModels/LoginViewModel.cs | 4 +--- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/MoissonMontreal.Desktop/ViewModels/DonationsViewModel.cs b/src/MoissonMontreal.Desktop/ViewModels/DonationsViewModel.cs index a834e8f..464e97f 100644 --- a/src/MoissonMontreal.Desktop/ViewModels/DonationsViewModel.cs +++ b/src/MoissonMontreal.Desktop/ViewModels/DonationsViewModel.cs @@ -1,15 +1,12 @@ -using CommunityToolkit.Mvvm.ComponentModel; - namespace MoissonMontreal.Desktop.ViewModels; /// /// Logic of the donations page (placeholder for now). /// -public partial class DonationsViewModel : ViewModelBase +public class DonationsViewModel : ViewModelBase { /// /// Title shown at the top of the donations page. /// - [ObservableProperty] - public partial string Title { get; set; } = "Donations"; + public string Title => "Donations"; } \ No newline at end of file diff --git a/src/MoissonMontreal.Desktop/ViewModels/DonorsViewModel.cs b/src/MoissonMontreal.Desktop/ViewModels/DonorsViewModel.cs index 78610ba..f2299e1 100644 --- a/src/MoissonMontreal.Desktop/ViewModels/DonorsViewModel.cs +++ b/src/MoissonMontreal.Desktop/ViewModels/DonorsViewModel.cs @@ -1,15 +1,12 @@ -using CommunityToolkit.Mvvm.ComponentModel; - namespace MoissonMontreal.Desktop.ViewModels; /// /// Logic of the donors page (placeholder for now). /// -public partial class DonorsViewModel : ViewModelBase +public class DonorsViewModel : ViewModelBase { /// /// Title shown at the top of the donors page. /// - [ObservableProperty] - public partial string Title { get; set; } = "Donors"; + public string Title => "Donors"; } \ No newline at end of file diff --git a/src/MoissonMontreal.Desktop/ViewModels/LoginViewModel.cs b/src/MoissonMontreal.Desktop/ViewModels/LoginViewModel.cs index ca28900..93d6f49 100644 --- a/src/MoissonMontreal.Desktop/ViewModels/LoginViewModel.cs +++ b/src/MoissonMontreal.Desktop/ViewModels/LoginViewModel.cs @@ -1,5 +1,4 @@ using System; -using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; namespace MoissonMontreal.Desktop.ViewModels; @@ -12,8 +11,7 @@ public partial class LoginViewModel : ViewModelBase /// /// Title shown at the top of the login page. /// - [ObservableProperty] - public partial string Title { get; set; } = "Moisson Montréal"; + public string Title => "Moisson Montréal"; /// /// Action to run once the user is signed in (opens the home page). From 83a4a6512238365971126c3f4540e6eee68d4837 Mon Sep 17 00:00:00 2001 From: maayjayy Date: Thu, 29 Oct 2026 16:51:39 -0400 Subject: [PATCH 12/17] refactor: move Home status messages into constants --- .../ViewModels/HomeViewModel.cs | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/MoissonMontreal.Desktop/ViewModels/HomeViewModel.cs b/src/MoissonMontreal.Desktop/ViewModels/HomeViewModel.cs index d97fa21..b562ebd 100644 --- a/src/MoissonMontreal.Desktop/ViewModels/HomeViewModel.cs +++ b/src/MoissonMontreal.Desktop/ViewModels/HomeViewModel.cs @@ -16,6 +16,21 @@ public partial class HomeViewModel : ViewModelBase /// private readonly IApiClient _apiClient; + /// + /// Status message shown while the API check is running. + /// + private const string CheckingApiMessage = "Checking the API..."; + + /// + /// Status message shown when the API cannot be reached. + /// + private const string ServerUnavailableMessage = "Server unavailable"; + + /// + /// Status message shown when the API answered successfully. + /// + private const string ApiOnlineMessage = "API online"; + /// /// Title shown at the top of the home page. /// @@ -26,7 +41,7 @@ public partial class HomeViewModel : ViewModelBase /// API status message shown to the user. /// [ObservableProperty] - public partial string ApiStatus { get; set; } = "Checking the API..."; + public partial string ApiStatus { get; set; } = CheckingApiMessage; /// /// Creates the home page. The API check starts when the view is loaded. @@ -38,20 +53,20 @@ public HomeViewModel(IApiClient apiClient) } /// - /// Checks the API and updates . Any error shows "Server unavailable" instead of crashing the app. + /// Checks the API and updates . Any error shows instead of crashing the app. /// [RelayCommand] private async Task CheckApiAsync() { - ApiStatus = "Checking the API..."; + ApiStatus = CheckingApiMessage; try { var isReachable = await _apiClient.IsApiReachableAsync(); - ApiStatus = isReachable ? "API online" : "Server unavailable"; + ApiStatus = isReachable ? ApiOnlineMessage : ServerUnavailableMessage; } catch (Exception) { - ApiStatus = "Server unavailable"; + ApiStatus = ServerUnavailableMessage; } } } \ No newline at end of file From 97cbbc05446b54c26123f04482ac0e520cd5f2a6 Mon Sep 17 00:00:00 2001 From: maayjayy Date: Thu, 29 Oct 2026 16:53:32 -0400 Subject: [PATCH 13/17] refactor: make home title read-only --- src/MoissonMontreal.Desktop/ViewModels/HomeViewModel.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/MoissonMontreal.Desktop/ViewModels/HomeViewModel.cs b/src/MoissonMontreal.Desktop/ViewModels/HomeViewModel.cs index b562ebd..e28b682 100644 --- a/src/MoissonMontreal.Desktop/ViewModels/HomeViewModel.cs +++ b/src/MoissonMontreal.Desktop/ViewModels/HomeViewModel.cs @@ -34,8 +34,7 @@ public partial class HomeViewModel : ViewModelBase /// /// Title shown at the top of the home page. /// - [ObservableProperty] - public partial string Title { get; set; } = "Welcome"; + public string Title => "Welcome"; /// /// API status message shown to the user. From db88f8820931788b441a3b4cc750bd013b5b095a Mon Sep 17 00:00:00 2001 From: maayjayy Date: Thu, 29 Oct 2026 17:26:51 -0400 Subject: [PATCH 14/17] fix: validate API config on startup --- src/MoissonMontreal.Desktop/App.axaml.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/MoissonMontreal.Desktop/App.axaml.cs b/src/MoissonMontreal.Desktop/App.axaml.cs index fb1321a..0628a89 100644 --- a/src/MoissonMontreal.Desktop/App.axaml.cs +++ b/src/MoissonMontreal.Desktop/App.axaml.cs @@ -27,24 +27,31 @@ public override void Initialize() /// /// Runs once Avalonia is ready: reads appsettings.json, registers the services and opens the main window. /// - /// Thrown when Api:BaseUrl is missing from appsettings.json. + /// Thrown when appsettings.json is missing, or when Api:BaseUrl is missing or invalid. public override void OnFrameworkInitializationCompleted() { if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { var configuration = new ConfigurationBuilder() .SetBasePath(AppContext.BaseDirectory) - .AddJsonFile("appsettings.json") + .AddJsonFile("appsettings.json", optional: true) .Build(); var apiBaseUrl = configuration["Api:BaseUrl"] - ?? throw new InvalidOperationException("Missing 'Api:BaseUrl' in appsettings.json."); + ?? throw new InvalidOperationException( + $"Missing 'Api:BaseUrl': check that appsettings.json exists in '{AppContext.BaseDirectory}' and contains it."); + + if (!Uri.TryCreate(apiBaseUrl, UriKind.Absolute, out var apiBaseUri) + || (apiBaseUri.Scheme != Uri.UriSchemeHttp && apiBaseUri.Scheme != Uri.UriSchemeHttps)) + { + throw new InvalidOperationException($"Invalid 'Api:BaseUrl' in appsettings.json: '{apiBaseUrl}'. It must be a full URL starting with http:// or https://."); + } var services = new ServiceCollection(); services.AddHttpClient(client => { - client.BaseAddress = new Uri(apiBaseUrl); + client.BaseAddress = apiBaseUri; client.Timeout = TimeSpan.FromSeconds(15); }); services.AddSingleton>(sp => () => sp.GetRequiredService()); From 9c761526cbc4d749ba2da49c6ee1868afedfeecb Mon Sep 17 00:00:00 2001 From: maayjayy Date: Thu, 29 Oct 2026 17:47:52 -0400 Subject: [PATCH 15/17] fix: cancel the Home Api check when leaving the page --- src/MoissonMontreal.Desktop/Services/ApiClient.cs | 7 ++++--- src/MoissonMontreal.Desktop/Services/IApiClient.cs | 4 +++- .../ViewModels/HomeViewModel.cs | 6 ++++-- .../Views/HomeView.axaml.cs | 14 ++++++++++++++ 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/MoissonMontreal.Desktop/Services/ApiClient.cs b/src/MoissonMontreal.Desktop/Services/ApiClient.cs index 79b04c7..2e421da 100644 --- a/src/MoissonMontreal.Desktop/Services/ApiClient.cs +++ b/src/MoissonMontreal.Desktop/Services/ApiClient.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using System.Net.Http; +using System.Threading; namespace MoissonMontreal.Desktop.Services; @@ -22,13 +23,13 @@ public ApiClient(HttpClient httpClient) _httpClient = httpClient; } - /// - public async Task IsApiReachableAsync() + /// + public async Task IsApiReachableAsync(CancellationToken cancellationToken = default) { try { // Any endpoint that answers successfully proves the API is reachable. - using var response = await _httpClient.GetAsync("WeatherForecast"); + using var response = await _httpClient.GetAsync("WeatherForecast", cancellationToken); return response.IsSuccessStatusCode; } catch (HttpRequestException) diff --git a/src/MoissonMontreal.Desktop/Services/IApiClient.cs b/src/MoissonMontreal.Desktop/Services/IApiClient.cs index 86c3840..1d6976d 100644 --- a/src/MoissonMontreal.Desktop/Services/IApiClient.cs +++ b/src/MoissonMontreal.Desktop/Services/IApiClient.cs @@ -1,3 +1,4 @@ +using System.Threading; using System.Threading.Tasks; namespace MoissonMontreal.Desktop.Services; @@ -10,8 +11,9 @@ public interface IApiClient /// /// Checks whether the API answers. /// + /// Lets the caller stop the check early (for example when leaving the page). If cancelled, the method returns false. /// true if the API answered successfully; false if it is unreachable or returned an error. - Task IsApiReachableAsync(); + Task IsApiReachableAsync(CancellationToken cancellationToken = default); } diff --git a/src/MoissonMontreal.Desktop/ViewModels/HomeViewModel.cs b/src/MoissonMontreal.Desktop/ViewModels/HomeViewModel.cs index e28b682..3387ceb 100644 --- a/src/MoissonMontreal.Desktop/ViewModels/HomeViewModel.cs +++ b/src/MoissonMontreal.Desktop/ViewModels/HomeViewModel.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; @@ -54,13 +55,14 @@ public HomeViewModel(IApiClient apiClient) /// /// Checks the API and updates . Any error shows instead of crashing the app. /// + /// Cancelled when the user leaves the home page, so the check doesn't keep running. [RelayCommand] - private async Task CheckApiAsync() + private async Task CheckApiAsync(CancellationToken cancellationToken) { ApiStatus = CheckingApiMessage; try { - var isReachable = await _apiClient.IsApiReachableAsync(); + var isReachable = await _apiClient.IsApiReachableAsync(cancellationToken); ApiStatus = isReachable ? ApiOnlineMessage : ServerUnavailableMessage; } catch (Exception) diff --git a/src/MoissonMontreal.Desktop/Views/HomeView.axaml.cs b/src/MoissonMontreal.Desktop/Views/HomeView.axaml.cs index 2933112..caf7805 100644 --- a/src/MoissonMontreal.Desktop/Views/HomeView.axaml.cs +++ b/src/MoissonMontreal.Desktop/Views/HomeView.axaml.cs @@ -31,4 +31,18 @@ protected override void OnLoaded(RoutedEventArgs e) viewModel.CheckApiCommand.Execute(null); } } + + /// + /// Cancels the API check when the view is removed fromt the screen. (for example when the user goes to another page). + /// + /// Information about the event. + protected override void OnUnloaded(RoutedEventArgs e) + { + base.OnUnloaded(e); + + if (DataContext is HomeViewModel viewModel) + { + viewModel.CheckApiCommand.Cancel(); + } + } } \ No newline at end of file From e19eea0c44a7d3e80d5c934ed6eaea531d14dc4d Mon Sep 17 00:00:00 2001 From: maayjayy Date: Thu, 29 Oct 2026 22:04:43 -0400 Subject: [PATCH 16/17] feat(desktop): add design-time data for the IDE previewer --- .../Design/DesignData.cs | 24 +++++++++++++++++++ .../Design/FakeApiClient.cs | 17 +++++++++++++ .../Views/DonationsView.axaml | 4 ++++ .../Views/DonorsView.axaml | 4 ++++ .../Views/HomeView.axaml | 4 +++- .../Views/LoginView.axaml | 4 +++- .../Views/MainWindow.axaml | 2 ++ 7 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 src/MoissonMontreal.Desktop/Design/DesignData.cs create mode 100644 src/MoissonMontreal.Desktop/Design/FakeApiClient.cs diff --git a/src/MoissonMontreal.Desktop/Design/DesignData.cs b/src/MoissonMontreal.Desktop/Design/DesignData.cs new file mode 100644 index 0000000..b805689 --- /dev/null +++ b/src/MoissonMontreal.Desktop/Design/DesignData.cs @@ -0,0 +1,24 @@ +using MoissonMontreal.Desktop.ViewModels; + +namespace MoissonMontreal.Desktop.Design; + +/// +/// Ready-made ViewModels for the IDE previewer. Never used when the app runs. +/// +public static class DesignData +{ + /// + /// Main window ViewModel backed by a . + /// + public static MainViewModel Main { get; } = new(() => new FakeApiClient()); + + /// + /// Home page ViewModel backed by a . + /// + public static HomeViewModel Home { get; } = new(new FakeApiClient()); + + /// + /// Login page ViewModel whose sign-in does nothing. + /// + public static LoginViewModel Login { get; } = new(() => { }); +} diff --git a/src/MoissonMontreal.Desktop/Design/FakeApiClient.cs b/src/MoissonMontreal.Desktop/Design/FakeApiClient.cs new file mode 100644 index 0000000..6c974bf --- /dev/null +++ b/src/MoissonMontreal.Desktop/Design/FakeApiClient.cs @@ -0,0 +1,17 @@ +using System.Threading; +using System.Threading.Tasks; +using MoissonMontreal.Desktop.Services; + +namespace MoissonMontreal.Desktop.Design; + +/// +/// Fake API client for the IDE previewer. Always reports the API as online, without any network call. +/// +public class FakeApiClient : IApiClient +{ + /// + public Task IsApiReachableAsync(CancellationToken cancellationToken = default) + { + return Task.FromResult(true); + } +} diff --git a/src/MoissonMontreal.Desktop/Views/DonationsView.axaml b/src/MoissonMontreal.Desktop/Views/DonationsView.axaml index 429566f..42cd8d1 100644 --- a/src/MoissonMontreal.Desktop/Views/DonationsView.axaml +++ b/src/MoissonMontreal.Desktop/Views/DonationsView.axaml @@ -4,6 +4,10 @@ x:Class="MoissonMontreal.Desktop.Views.DonationsView" x:DataType="vm:DonationsViewModel"> + + + + diff --git a/src/MoissonMontreal.Desktop/Views/DonorsView.axaml b/src/MoissonMontreal.Desktop/Views/DonorsView.axaml index 14766c2..731a1f4 100644 --- a/src/MoissonMontreal.Desktop/Views/DonorsView.axaml +++ b/src/MoissonMontreal.Desktop/Views/DonorsView.axaml @@ -4,6 +4,10 @@ x:Class="MoissonMontreal.Desktop.Views.DonorsView" x:DataType="vm:DonorsViewModel"> + + + + diff --git a/src/MoissonMontreal.Desktop/Views/HomeView.axaml b/src/MoissonMontreal.Desktop/Views/HomeView.axaml index 7bbabad..87271ae 100644 --- a/src/MoissonMontreal.Desktop/Views/HomeView.axaml +++ b/src/MoissonMontreal.Desktop/Views/HomeView.axaml @@ -1,8 +1,10 @@ + x:DataType="vm:HomeViewModel" + Design.DataContext="{x:Static design:DesignData.Home}"> + x:DataType="vm:LoginViewModel" + Design.DataContext="{x:Static design:DesignData.Login}"> From 748647e1b5d2c8ede62c1097d76f17d568c26460 Mon Sep 17 00:00:00 2001 From: maayjayy Date: Thu, 29 Oct 2026 22:09:10 -0400 Subject: [PATCH 17/17] docs: fix typo in HomeView OnUnloaded comment --- src/MoissonMontreal.Desktop/Views/HomeView.axaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MoissonMontreal.Desktop/Views/HomeView.axaml.cs b/src/MoissonMontreal.Desktop/Views/HomeView.axaml.cs index caf7805..49442eb 100644 --- a/src/MoissonMontreal.Desktop/Views/HomeView.axaml.cs +++ b/src/MoissonMontreal.Desktop/Views/HomeView.axaml.cs @@ -33,7 +33,7 @@ protected override void OnLoaded(RoutedEventArgs e) } /// - /// Cancels the API check when the view is removed fromt the screen. (for example when the user goes to another page). + /// Cancels the API check when the view is removed from the screen. (for example when the user goes to another page). /// /// Information about the event. protected override void OnUnloaded(RoutedEventArgs e)