Skip to content

Feature/01 black on white - #46

Open
maayjayy wants to merge 7 commits into
developfrom
feature/01-black-on-white
Open

maayjayy wants to merge 7 commits into
developfrom
feature/01-black-on-white

Conversation

@maayjayy

Copy link
Copy Markdown
Collaborator

Closes #1

Overview

Provisional black-on-white desktop app, so the team can test the flow end to end while the final design is in progress. It will be replaced by the design system's components later.

  • Light theme forced, no styling
  • Simulated sign-in: a "Sign in" button with no real authentication yet (Entra ID comes in [Issue]: Entra ID sign-in in the desktop app #9)
  • Navigation between Home, Donations and Donors (placeholders), with a nav bar shown only when signed in, and sign out
  • ApiClient service (IApiClient) registered with dependency injection; the API base URL is read from appsettings.json
  • The Home page checks the API on open and shows "API online" or "Server unavailable", with a Retry button

Testing

dotnet run --project src/MoissonMontreal.Api       # terminal 1
dotnet run --project src/MoissonMontreal.Desktop   # terminal 2
Criterion Test Result
The app opens on the login screen Launch the app ✅ Login screen, no nav bar
After logging in, the user lands on a home screen Click "Sign in" ✅ Home page with the nav bar
The user can navigate between screens Click Home, Donations, Donors, then Sign out ✅ Each page shows; Sign out returns to login and hides the nav bar
The app communicates with the API API running, open Home ✅ "API online"
Stop the API, click Retry ✅ "Server unavailable"
Restart the API, click Retry ✅ "API online" again
Black on white macOS in dark mode ✅ The app stays black on white
Build dotnet build ✅ 0 errors, 0 warnings
Tests dotnet test ✅ Passing

Screenshots / Screencasts

Login
image

Home, API online
image

Navigation (Donations)
image

Home, API stopped
image

Checklist

  • Code is neat, readable, and works
  • Code is commented where appropriate and well-documented
  • Commit messages follow our guidelines
  • Issue number is linked
  • Branch is linked
  • Reviewers are assigned (one of your tech leads)

Notes

  • Tested against the local API, as agreed with the team, not the deployed one (criterion 4 says "deployed API").
  • The app calls the template endpoint GET /WeatherForecast, since it is the only endpoint on develop. This PR does not change the Api project, so it is independent of Feature/11 api postgres #43. Once Feature/11 api postgres #43 is merged (it removes WeatherForecast and adds /health), the endpoint in ApiClient.cs needs a one-line change.
  • The timeout is 15 s, to allow for Azure Container Apps' cold start once deployed.

@maayjayy maayjayy self-assigned this Oct 29, 2026
@maayjayy maayjayy added this to the Sprint - 1 milestone Oct 29, 2026
@maayjayy maayjayy linked an issue Oct 29, 2026 that may be closed by this pull request
8 tasks

@anis022 anis022 left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR, the flow works well.

Before we merge, there are a few changes to make once #43 is merged. First, please update your branch with develop so it includes the new /health endpoint. Then, in ApiClient.cs, call health instead of WeatherForecast, since #43 removes that endpoint (see my inline comment).

Please also update the testing section of the PR. With #43 , the API needs the database to start, so you have to run docker compose up -d db and add the connection string to your user-secrets first. It would also be good to add a test where the database is stopped, to check that the app shows "Server unavailable".

try
{
// Any endpoint that answers successfully proves the API is reachable.
using var response = await _httpClient.GetAsync("WeatherForecast");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Lightsul 's PR #43 removes WeatherForecast. Once it's merged, this call will get a 404 and the app will show "Server unavailable" even when the API is running. Please replace "WeatherForecast" with "health" and update the comment above it.

@lauriechammah lauriechammah left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! A few comments below. I think the first two (unhandled exception in HomeViewModel and the HttpClient captured by the singleton) should be fixed before merging; the rest can go in a follow-up PR if you prefer.

public HomeViewModel(IApiClient apiClient)
{
_apiClient = apiClient;
CheckApiCommand.Execute(null);

@lauriechammah lauriechammah Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Crash risk: the async command is started from the constructor without being awaited, and IsApiReachableAsync only catches HttpRequestException and TaskCanceledException. If any other exception escapes (e.g. InvalidOperationException or ObjectDisposedException), AsyncRelayCommand rethrows it on the UI thread and the app crashes on sign-in or when clicking Home.

Suggestion: add a fallback catch (Exception) (in CheckApiAsync or in ApiClient), and ideally trigger the check from the view (Loaded) rather than from the constructor.

client.BaseAddress = new Uri(apiBaseUrl);
client.Timeout = TimeSpan.FromSeconds(15);
});
services.AddSingleton<MainViewModel>();

@lauriechammah lauriechammah Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The typed client registered with AddHttpClient<IApiClient, ApiClient> is transient, but MainViewModel is a singleton that holds onto it for the app's whole lifetime (captive dependency). The handler is never rotated, so if the API's DNS/IP changes during a long session, the app will be stuck on "Server unavailable".

Suggestion: inject IHttpClientFactory or a Func<IApiClient> into MainViewModel, or register HomeViewModel in the container and resolve it on each navigation.

{
var configuration = new ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json")

@lauriechammah lauriechammah Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

appsettings.json is required (optional: false by default) and new Uri(apiBaseUrl) isn't validated. If the file is missing (e.g. in a publish folder) or the URL has no scheme (localhost:5297), we get a FileNotFoundException or UriFormatException before any window exists, and the app closes with no message.

Suggestion: use Uri.TryCreate(apiBaseUrl, UriKind.Absolute, out var uri) with a clear error message, and make sure appsettings.json is copied to the output directory (CopyToOutputDirectory).

try
{
// Any endpoint that answers successfully proves the API is reachable.
using var response = await _httpClient.GetAsync("WeatherForecast");

@lauriechammah lauriechammah Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The check calls WeatherForecast, which comes from the template and will be removed. After that, the app will show "Server unavailable" even though the API is up. Also, with a BaseUrl like http://host/api (no trailing /), the request goes to http://host/WeatherForecast.

Suggestion: add builder.Services.AddHealthChecks() + app.MapHealthChecks("/health") on the API side and call health here.

[RelayCommand]
private void ShowHome()
{
CurrentPage = new HomeViewModel(_apiClient);

@lauriechammah lauriechammah Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every click on Home creates a new HomeViewModel, which fires a new request, and previous ones are never cancelled. If the API is down, several 15-second requests can run in parallel.

Suggestion: keep a single HomeViewModel instance, or pass a CancellationToken that gets cancelled when navigating away.

[NotifyPropertyChangedFor(nameof(IsSignedIn))]
public partial ViewModelBase CurrentPage {get; set;}

public bool IsSignedIn => CurrentPage is not LoginViewModel;

@lauriechammah lauriechammah Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IsSignedIn is derived from the current page's type. As soon as we add another page reachable before sign-in (forgot password, splash screen…), the nav bar and Sign out button will show up incorrectly.

Suggestion: keep an actual session state (e.g. a boolean or an auth service) and derive IsSignedIn from it. This can wait until auth is wired up.

x:DataType="vm:MainViewModel"
Icon="/Assets/avalonia-logo.ico"
Title="MoissonMontreal.Desktop">
Title="Moisson Montréal">

@lauriechammah lauriechammah Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The <Design.DataContext> block was removed. Since MainViewModel no longer has a parameterless constructor, the IDE previewer has no data, here or in the new views.

Suggestion (optional): add a Design.DataContext backed by a fake IApiClient.

public partial string Title { get; set; } = "Welcome";

[ObservableProperty]
public partial string ApiStatus { get; set; } = "Checking the API...";

@lauriechammah lauriechammah Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "Checking the API..." string is set here and again at the start of CheckApiAsync. Since the check runs right away, we could drop the initializer, or move the status messages into constants.

public partial class DonationsViewModel : ViewModelBase
{
[ObservableProperty]
public partial string Title { get; set; } = "Donations";

@lauriechammah lauriechammah Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Title never changes, so it doesn't need to be an [ObservableProperty]. public string Title => "Donations"; is enough. Same for DonorsViewModel and LoginViewModel.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Issue]: Provisional desktop front-end (black on white)

3 participants