diff --git a/src/Sovrant.Desktop/ViewModels/LoginViewModel.cs b/src/Sovrant.Desktop/ViewModels/LoginViewModel.cs
index 29f46200..4bf369e7 100644
--- a/src/Sovrant.Desktop/ViewModels/LoginViewModel.cs
+++ b/src/Sovrant.Desktop/ViewModels/LoginViewModel.cs
@@ -17,8 +17,18 @@ public partial class LoginViewModel : ObservableObject
[ObservableProperty] private string _email = string.Empty;
[ObservableProperty] private string _password = string.Empty;
[ObservableProperty] private string _errorMessage = string.Empty;
+ [ObservableProperty] private string _infoMessage = string.Empty;
[ObservableProperty] private bool _isBusy;
+ [ObservableProperty] private string _busyLabel = string.Empty;
[ObservableProperty] private bool _isRegistrationOpen;
+ [ObservableProperty] private bool _isFirstRun;
+ [ObservableProperty] private bool _isApprovalRequired;
+
+ /// Registration section is only offered once an admin exists and registration is open.
+ public bool ShowRegistrationSection => !IsFirstRun && IsRegistrationOpen;
+
+ partial void OnIsFirstRunChanged(bool value) => OnPropertyChanged(nameof(ShowRegistrationSection));
+ partial void OnIsRegistrationOpenChanged(bool value) => OnPropertyChanged(nameof(ShowRegistrationSection));
public event Action? LoginSucceeded; // (userId, role, email)
@@ -31,20 +41,31 @@ public LoginViewModel(IIdentityService identity, ITokenService tokens, ICredenti
public async Task InitializeAsync()
{
+ IsFirstRun = await _identity.IsFirstRunAsync().ConfigureAwait(true);
IsRegistrationOpen = await _identity.IsRegistrationOpenAsync().ConfigureAwait(true);
+ IsApprovalRequired = !IsFirstRun && IsRegistrationOpen
+ && await _identity.IsApprovalRequiredAsync().ConfigureAwait(true);
}
- [RelayCommand]
- private async Task LoginAsync()
+ private bool ValidateInput()
{
ErrorMessage = string.Empty;
+ InfoMessage = string.Empty;
if (string.IsNullOrWhiteSpace(Email) || string.IsNullOrWhiteSpace(Password))
{
ErrorMessage = "Email and password are required.";
- return;
+ return false;
}
+ return true;
+ }
+
+ [RelayCommand]
+ private async Task LoginAsync()
+ {
+ if (!ValidateInput()) return;
IsBusy = true;
+ BusyLabel = "Signing you in…";
try
{
var result = await _identity.LoginAsync(Email, Password).ConfigureAwait(true);
@@ -66,14 +87,10 @@ private async Task LoginAsync()
[RelayCommand]
private async Task RegisterAsync()
{
- ErrorMessage = string.Empty;
- if (string.IsNullOrWhiteSpace(Email) || string.IsNullOrWhiteSpace(Password))
- {
- ErrorMessage = "Email and password are required.";
- return;
- }
+ if (!ValidateInput()) return;
IsBusy = true;
+ BusyLabel = IsFirstRun ? "Creating your administrator account…" : "Creating your account…";
try
{
var result = await _identity.RegisterAsync(Email, Password).ConfigureAwait(true);
@@ -85,7 +102,7 @@ private async Task RegisterAsync()
if (result.IsPendingApproval)
{
- ErrorMessage = "Account created. An administrator must approve it before you can sign in.";
+ InfoMessage = "Account created. An administrator must approve it before you can sign in.";
return;
}
diff --git a/src/Sovrant.Desktop/Views/LoginWindow.axaml b/src/Sovrant.Desktop/Views/LoginWindow.axaml
index ba3577c8..f685060b 100644
--- a/src/Sovrant.Desktop/Views/LoginWindow.axaml
+++ b/src/Sovrant.Desktop/Views/LoginWindow.axaml
@@ -4,7 +4,7 @@
x:Class="Sovrant.Desktop.Views.LoginWindow"
x:DataType="vm:LoginViewModel"
Title="Sovrant — Sign in"
- Width="420" Height="460"
+ Width="420" SizeToContent="Height"
CanResize="False"
WindowStartupLocation="CenterScreen">
@@ -17,7 +17,22 @@
+ Margin="0,0,0,4"
+ IsVisible="{Binding !IsFirstRun}"/>
+
+
+
+
+
+
+
@@ -42,15 +57,31 @@
TextWrapping="Wrap"
IsVisible="{Binding ErrorMessage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
+
+
+
+
+
+
-
-
+
+
@@ -60,9 +91,16 @@
Command="{Binding RegisterCommand}"
IsEnabled="{Binding !IsBusy}"
Padding="0,8"/>
+
+
diff --git a/src/Sovrant.Web/Components/Pages/Login.razor b/src/Sovrant.Web/Components/Pages/Login.razor
index 63987602..7f491e4d 100644
--- a/src/Sovrant.Web/Components/Pages/Login.razor
+++ b/src/Sovrant.Web/Components/Pages/Login.razor
@@ -13,13 +13,30 @@
Sovrant
-
Sign in to continue.
+ @if (_firstRun)
+ {
+
Welcome! Let's set up your server.
+
+ First-time setup — no accounts exist yet. The first account you create
+ becomes the administrator, with full control over registration,
+ approvals, and server settings.
+