diff --git a/src/PostIt/PostIt.Tests/HomePageProviderFlowTests.cs b/src/PostIt/PostIt.Tests/HomePageProviderFlowTests.cs index 21d532cd1..16f01c5e9 100644 --- a/src/PostIt/PostIt.Tests/HomePageProviderFlowTests.cs +++ b/src/PostIt/PostIt.Tests/HomePageProviderFlowTests.cs @@ -1,4 +1,3 @@ -using Yavsc.Abstract.Identity; using PostIt.ViewModels; namespace PostIt.Tests; @@ -13,157 +12,4 @@ public class HomePageProviderFlowTests Assert.NotNull(vm.OpenProviderRequests); Assert.True(vm.OpenProviderRequests.CanExecute(null)); } - - [Fact] - public void HomePage_exposes_profile_command() - { - var vm = new HomePageViewModel(); - - Assert.NotNull(vm.OpenProfile); - Assert.True(vm.OpenProfile.CanExecute(null)); - } - - [Fact] - public void HomePage_exposes_performer_configuration_command() - { - var vm = new HomePageViewModel(); - - Assert.NotNull(vm.OpenPerformerConfiguration); - Assert.True(vm.OpenPerformerConfiguration.CanExecute(null)); - } - - [Fact] - public void HomePage_exposes_administration_command() - { - var vm = new HomePageViewModel(); - - Assert.NotNull(vm.OpenAdministration); - Assert.True(vm.OpenAdministration.CanExecute(null)); - } - - [Fact] - public void Administration_page_filters_users_by_search_text() - { - var vm = new AdministrationPageViewModel(); - vm.Users.Add(new Yavsc.Abstract.Identity.UserInfo { UserId = "1", UserName = "alice", FullName = "Alice Example" }); - vm.Users.Add(new Yavsc.Abstract.Identity.UserInfo { UserId = "2", UserName = "bob", FullName = "Bob Jones" }); - vm.Users.Add(new Yavsc.Abstract.Identity.UserInfo { UserId = "3", UserName = "carol", FullName = "Carol Smith" }); - - vm.SearchText = "ali"; - - Assert.Single(vm.FilteredUsers); - Assert.Equal("alice", vm.FilteredUsers[0].UserName); - } - - [Fact] - public void Administration_page_filters_users_by_full_name() - { - var vm = new AdministrationPageViewModel(); - vm.Users.Add(new Yavsc.Abstract.Identity.UserInfo { UserId = "1", UserName = "alice", FullName = "Alice Example" }); - vm.Users.Add(new Yavsc.Abstract.Identity.UserInfo { UserId = "2", UserName = "bob", FullName = "Bob Jones" }); - - vm.SearchText = "example"; - - Assert.Single(vm.FilteredUsers); - Assert.Equal("alice", vm.FilteredUsers[0].UserName); - } - - [Fact] - public void UserProfilePage_exposes_full_name_and_address_fields() - { - var vm = new UserProfilePageViewModel(); - - Assert.Equal("—", vm.FullName); - Assert.Equal("—", vm.Address); - Assert.Equal("Non configuré", vm.DedicatedGoogleCalendar); - Assert.Equal("Aucune information bancaire", vm.BankInfoSummary); - Assert.NotNull(vm.RefreshCommand); - } - - [Fact] - public void ProfileUpdateRequest_allows_username_updates() - { - var request = new ProfileUpdateRequest - { - UserName = "alice.new" - }; - - Assert.Equal("alice.new", request.UserName); - } - - [Fact] - public void UserProfilePage_can_save_profile_values() - { - var vm = new UserProfilePageViewModel - { - UserName = "alice.new", - FullName = "Alice Example", - Address = "14 rue de l’Érable", - DedicatedGoogleCalendar = "calendar-123", - BankInfoSummary = "FR14 2004 1010 0505 0001 3M02 606" - }; - - Assert.NotNull(vm.SaveProfileCommand); - Assert.False(vm.SaveProfileCommand.CanExecute(null)); - } - - [Fact] - public void UserProfilePage_builds_profile_payload_for_calendar_and_bank_values() - { - var vm = new UserProfilePageViewModel - { - UserName = "alice.new", - FullName = "Alice Example", - Address = "14 rue de l’Érable", - DedicatedGoogleCalendar = "calendar-123", - BankInfoSummary = "FR14 2004 1010 0505 0001 3M02 606" - }; - - var payload = vm.BuildProfileUpdateRequest(); - - Assert.Equal("alice.new", payload.UserName); - Assert.Equal("calendar-123", payload.GoogleCalendarId); - Assert.Null(payload.BankInfoSummary); - } - - [Fact] - public void UserProfilePage_turns_placeholder_values_into_empty_payload_fields() - { - var vm = new UserProfilePageViewModel - { - UserName = "alice.new", - FullName = "Alice Example", - Address = "14 rue de l’Érable", - DedicatedGoogleCalendar = "Non configuré", - BankInfoSummary = "Aucune information bancaire" - }; - - var payload = vm.BuildProfileUpdateRequest(); - - Assert.Equal(string.Empty, payload.GoogleCalendarId); - Assert.Null(payload.BankInfoSummary); - } - - [Fact] - public void PerformerConfigurationPage_preserves_current_user_id_in_save_payload() - { - var vm = new PerformerConfigurationPageViewModel - { - PerformerId = "user-42", - UserName = "alice", - ExerciseCountryCode = "fr", - SIREN = "123456789", - Website = "https://example.com", - Active = true, - AcceptNotifications = true, - AcceptPublicContact = true, - UseGeoLocalizationToReduceDistanceWithClients = true - }; - - var payload = vm.BuildPerformerProfileSettings(); - - Assert.Equal("user-42", payload.PerformerId); - Assert.Equal("alice", payload.UserName); - Assert.Equal("fr", payload.ExerciseCountryCode); - } } diff --git a/src/PostIt/PostIt.Tests/ResourceUsageApiClientTests.cs b/src/PostIt/PostIt.Tests/ResourceUsageApiClientTests.cs deleted file mode 100644 index 407183b77..000000000 --- a/src/PostIt/PostIt.Tests/ResourceUsageApiClientTests.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System.Net.Http; -using System.Text.Json; -using Yavsc.Abstract.Resources; -using Yavsc.Api.Client; - -namespace PostIt.Tests; - -public class ResourceUsageApiClientTests -{ - [Fact] - public async Task GetCurrentAsync_calls_user_usage_endpoint() - { - var fake = new FakeYavscApiClient(); - var client = new ResourceUsageApiClient(fake, "https://example.invalid/api/v1/"); - - var usage = await client.GetCurrentAsync(); - - Assert.Equal("https://example.invalid/api/v1/resource-usage/me", fake.LastPath); - Assert.Equal("GET", fake.LastMethod); - Assert.Equal(12.5m, usage.ApiCalls); - Assert.Equal(3.2m, usage.CpuSeconds); - } - - [Fact] - public async Task GetAdminOverviewAsync_calls_admin_usage_endpoint() - { - var fake = new FakeYavscApiClient(); - var client = new ResourceUsageApiClient(fake, "https://example.invalid/api/v1/"); - - var usage = await client.GetAdminOverviewAsync(); - - Assert.Equal("https://example.invalid/api/v1/resource-usage/admin", fake.LastPath); - Assert.Equal("GET", fake.LastMethod); - Assert.Equal(2, usage.Count); - Assert.Equal("administrator", usage[0].UserName); - } - - private sealed class FakeYavscApiClient : IYavscApiClient - { - public HttpClient Http { get; } = new(); - public string? LastPath { get; private set; } - public string? LastMethod { get; private set; } - - public Task CallAsync(HttpMethod method, string path, object? body = null, CancellationToken ct = default) - { - LastMethod = method.Method; - LastPath = path; - - if (typeof(T) == typeof(ResourceUsageSummary)) - { - var summary = new ResourceUsageSummary - { - UserId = "user-42", - UserName = "alice", - ApiCalls = 12.5m, - CpuSeconds = 3.2m, - BandwidthMb = 18.4m, - StorageMb = 51.2m, - EstimatedCompensation = 4.9m, - Currency = "EUR", - RecordedAtUtc = DateTimeOffset.UtcNow - }; - - return Task.FromResult((T)(object)summary); - } - - if (typeof(T) == typeof(List)) - { - var list = new List - { - new() - { - UserId = "admin-user", - UserName = "administrator", - ApiCalls = 90m, - CpuSeconds = 14m, - BandwidthMb = 500m, - StorageMb = 2048m, - EstimatedCompensation = 32m, - Currency = "EUR", - RecordedAtUtc = DateTimeOffset.UtcNow - }, - new() - { - UserId = "user-99", - UserName = "bob", - ApiCalls = 40m, - CpuSeconds = 7m, - BandwidthMb = 120m, - StorageMb = 600m, - EstimatedCompensation = 15m, - Currency = "EUR", - RecordedAtUtc = DateTimeOffset.UtcNow - } - }; - - return Task.FromResult((T)(object)list); - } - - throw new NotSupportedException($"Unsupported type: {typeof(T).FullName}"); - } - - public Task CallAsync(HttpMethod method, string path, Func contentFactory, CancellationToken ct = default) - => throw new NotSupportedException(); - - public Task CallAsync(HttpMethod method, string path, object? body = null, CancellationToken ct = default) - => Task.CompletedTask; - - public Task CallAsync(HttpMethod method, string path, Func contentFactory, CancellationToken ct = default) - => Task.CompletedTask; - - public Task DownloadAsync(HttpMethod method, string path, CancellationToken ct = default) - => Task.FromResult(Array.Empty()); - - public ValueTask DisposeAsync() => ValueTask.CompletedTask; - } -} diff --git a/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs b/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs index 22885aca8..3d135987b 100644 --- a/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs +++ b/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs @@ -76,9 +76,6 @@ public static class ServiceCollectionHelpers services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); services.AddTransient(); // ViewModels @@ -98,9 +95,6 @@ public static class ServiceCollectionHelpers services.AddSingleton(userDirectory); services.AddSingleton(); services.AddSingleton(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/PostIt/PostIt/ViewLocator.cs b/src/PostIt/PostIt/ViewLocator.cs index 986ddc92f..01ecb0c25 100644 --- a/src/PostIt/PostIt/ViewLocator.cs +++ b/src/PostIt/PostIt/ViewLocator.cs @@ -60,9 +60,6 @@ public class ViewLocator : IDataTemplate EstimateListPageViewModel => services.GetRequiredService(), EstimateValidationPageViewModel => services.GetRequiredService(), MyFilesViewModel => services.GetRequiredService(), - UserProfilePageViewModel => services.GetRequiredService(), - PerformerConfigurationPageViewModel => services.GetRequiredService(), - AdministrationPageViewModel => services.GetRequiredService(), ResourceUsagePageViewModel => services.GetRequiredService(), ChatViewModel => services.GetRequiredService(), null => new TextBlock { Text = "No view for " }, diff --git a/src/PostIt/PostIt/ViewModels/AdministrationPageViewModel.cs b/src/PostIt/PostIt/ViewModels/AdministrationPageViewModel.cs deleted file mode 100644 index 7ef626c56..000000000 --- a/src/PostIt/PostIt/ViewModels/AdministrationPageViewModel.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using PostIt.Services; -using Yavsc.Abstract.Identity; - -namespace PostIt.ViewModels; - -public partial class AdministrationPageViewModel : ViewModelBase -{ - private readonly YavscApiClient? _api; - - [ObservableProperty] - public partial bool IsBusy { get; set; } - - [ObservableProperty] - public partial ObservableCollection Users { get; set; } = new(); - - [ObservableProperty] - public partial string SearchText { get; set; } = string.Empty; - - [ObservableProperty] - public partial string StatusMessage { get; set; } = "Chargement…"; - - public IReadOnlyList FilteredUsers => - string.IsNullOrWhiteSpace(SearchText) - ? Users.ToList() - : Users.Where(user => - (user.UserName ?? string.Empty).Contains(SearchText, StringComparison.OrdinalIgnoreCase) - || (user.FullName ?? string.Empty).Contains(SearchText, StringComparison.OrdinalIgnoreCase) - || (user.Email ?? string.Empty).Contains(SearchText, StringComparison.OrdinalIgnoreCase) - || (user.UserId ?? string.Empty).Contains(SearchText, StringComparison.OrdinalIgnoreCase)) - .ToList(); - - public override bool CanNavigateNext { get => false; protected set { _ = value; } } - public override bool CanNavigatePrevious { get => true; protected set { _ = value; } } - - public AdministrationPageViewModel(YavscApiClient? api) - { - _api = api; - } - - public AdministrationPageViewModel() : this(null) { } - - public Task InitializeAsync() => RefreshAsync(); - - partial void OnSearchTextChanged(string value) - { - OnPropertyChanged(nameof(FilteredUsers)); - } - - [RelayCommand] - public async Task RefreshAsync() - { - if (_api is null) - { - StatusMessage = "Client API d’administration indisponible."; - return; - } - - IsBusy = true; - try - { - var query = SearchText.Trim(); - var usersEndpoint = string.IsNullOrWhiteSpace(query) - ? new Uri(new Uri(_api.Settings.ApiUrl.TrimEnd('/') + "/", UriKind.Absolute), "users").ToString() - : new Uri(new Uri(_api.Settings.ApiUrl.TrimEnd('/') + "/", UriKind.Absolute), $"users/search/{Uri.EscapeDataString(query)}").ToString(); - - var users = await _api.CallAsync>(HttpMethod.Get, usersEndpoint).ConfigureAwait(true); - - Users.Clear(); - foreach (var user in users ?? []) - { - Users.Add(user); - } - - OnPropertyChanged(nameof(FilteredUsers)); - StatusMessage = Users.Count > 0 ? $"{Users.Count} utilisateur(s) chargé(s)." : "Aucun utilisateur trouvé."; - } - catch (Exception ex) - { - Users.Clear(); - OnPropertyChanged(nameof(FilteredUsers)); - StatusMessage = $"Accès administration refusé ou indisponible : {ex.Message}"; - } - finally - { - IsBusy = false; - } - } -} diff --git a/src/PostIt/PostIt/ViewModels/Layout/HomePageViewModel.cs b/src/PostIt/PostIt/ViewModels/Layout/HomePageViewModel.cs index 6a29afbc6..56a3979e0 100644 --- a/src/PostIt/PostIt/ViewModels/Layout/HomePageViewModel.cs +++ b/src/PostIt/PostIt/ViewModels/Layout/HomePageViewModel.cs @@ -36,9 +36,6 @@ public class HomePageViewModel : ViewModelBase OpenProviderEstimates = new AsyncRelayCommand(() => OpenEstimateListAsync(EstimateListPerspective.Provider)); OpenBlogs = new AsyncRelayCommand(App.PushBlogsPageAsync); OpenMyFiles = new AsyncRelayCommand(OpenMyFilesAsync); - OpenProfile = new AsyncRelayCommand(OpenProfileAsync); - OpenPerformerConfiguration = new AsyncRelayCommand(OpenPerformerConfigurationAsync); - OpenAdministration = new AsyncRelayCommand(OpenAdministrationAsync); OpenResourceUsage = new AsyncRelayCommand(OpenResourceUsageAsync); OpenChat = new AsyncRelayCommand(OpenChatAsync); } @@ -46,9 +43,6 @@ public class HomePageViewModel : ViewModelBase public IAsyncRelayCommand OpenChat { get; } public IAsyncRelayCommand OpenBlogs { get; } - public IAsyncRelayCommand OpenProfile { get; } - public IAsyncRelayCommand OpenPerformerConfiguration { get; } - public IAsyncRelayCommand OpenAdministration { get; } public IAsyncRelayCommand OpenResourceUsage { get; } public IAsyncRelayCommand OpenActivities { get; } public IAsyncRelayCommand OpenProviderRequests { get; } @@ -146,60 +140,6 @@ public class HomePageViewModel : ViewModelBase await app.PushPageAsync(vm); } - private async Task OpenProfileAsync() - { - var app = (App?)Application.Current; - if (app is null) - { - throw new InvalidOperationException("Application PostIt indisponible."); - } - - var vm = app.ServiceProvider?.GetRequiredService(); - if (vm is null) - { - throw new InvalidOperationException("Page profil indisponible."); - } - - await vm.InitializeAsync(); - await app.PushPageAsync(vm); - } - - private async Task OpenPerformerConfigurationAsync() - { - var app = (App?)Application.Current; - if (app is null) - { - throw new InvalidOperationException("Application PostIt indisponible."); - } - - var vm = app.ServiceProvider?.GetRequiredService(); - if (vm is null) - { - throw new InvalidOperationException("Page performer indisponible."); - } - - await vm.InitializeAsync(); - await app.PushPageAsync(vm); - } - - private async Task OpenAdministrationAsync() - { - var app = (App?)Application.Current; - if (app is null) - { - throw new InvalidOperationException("Application PostIt indisponible."); - } - - var vm = app.ServiceProvider?.GetRequiredService(); - if (vm is null) - { - throw new InvalidOperationException("Page administration indisponible."); - } - - await vm.InitializeAsync(); - await app.PushPageAsync(vm); - } - private async Task OpenResourceUsageAsync() { var app = (App?)Application.Current; diff --git a/src/PostIt/PostIt/ViewModels/Layout/UserProfilePageViewModel.cs b/src/PostIt/PostIt/ViewModels/Layout/UserProfilePageViewModel.cs deleted file mode 100644 index ed70665f0..000000000 --- a/src/PostIt/PostIt/ViewModels/Layout/UserProfilePageViewModel.cs +++ /dev/null @@ -1,612 +0,0 @@ -using System; -using System.Collections.ObjectModel; -using System.Net.Http; -using System.Text.Json; -using System.Threading.Tasks; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using PostIt.Services; -using Yavsc.Abstract.Identity; - -namespace PostIt.ViewModels; - -public partial class UserProfilePageViewModel : ViewModelBase -{ - private readonly YavscApiClient? _api; - private readonly SessionStatusViewModel? _sessionStatus; - - [ObservableProperty] - [NotifyCanExecuteChangedFor(nameof(SaveProfileCommand))] - [NotifyCanExecuteChangedFor(nameof(SetMonthlyEmailPreferenceCommand))] - [NotifyCanExecuteChangedFor(nameof(SetTwoFactorCommand))] - [NotifyCanExecuteChangedFor(nameof(ChangePasswordCommand))] - [NotifyCanExecuteChangedFor(nameof(AddBankAccountCommand))] - [NotifyCanExecuteChangedFor(nameof(DeleteBankAccountCommand))] - [NotifyCanExecuteChangedFor(nameof(RemoveExternalLoginCommand))] - [NotifyCanExecuteChangedFor(nameof(SendEmailConfirmationCommand))] - [NotifyCanExecuteChangedFor(nameof(LoadCalendarsCommand))] - [NotifyCanExecuteChangedFor(nameof(ChooseCalendarCommand))] - [NotifyCanExecuteChangedFor(nameof(DeleteAccountCommand))] - public partial bool IsBusy { get; set; } - - [ObservableProperty] - [NotifyCanExecuteChangedFor(nameof(SaveProfileCommand))] - [NotifyCanExecuteChangedFor(nameof(SetMonthlyEmailPreferenceCommand))] - [NotifyCanExecuteChangedFor(nameof(SetTwoFactorCommand))] - [NotifyCanExecuteChangedFor(nameof(ChangePasswordCommand))] - [NotifyCanExecuteChangedFor(nameof(AddBankAccountCommand))] - [NotifyCanExecuteChangedFor(nameof(DeleteBankAccountCommand))] - [NotifyCanExecuteChangedFor(nameof(RemoveExternalLoginCommand))] - [NotifyCanExecuteChangedFor(nameof(SendEmailConfirmationCommand))] - [NotifyCanExecuteChangedFor(nameof(LoadCalendarsCommand))] - [NotifyCanExecuteChangedFor(nameof(ChooseCalendarCommand))] - [NotifyCanExecuteChangedFor(nameof(DeleteAccountCommand))] - public partial bool IsProfileLoaded { get; set; } - - [ObservableProperty] - public partial string UserName { get; set; } = "—"; - - [ObservableProperty] - public partial string CurrentUserName { get; set; } = "—"; - - [ObservableProperty] - public partial string UserId { get; set; } = "—"; - - [ObservableProperty] - public partial string FullName { get; set; } = "—"; - - [ObservableProperty] - public partial string Email { get; set; } = "—"; - - [ObservableProperty] - public partial string Address { get; set; } = "—"; - - [ObservableProperty] - public partial string Avatar { get; set; } = "—"; - - [ObservableProperty] - public partial string Roles { get; set; } = "—"; - - [ObservableProperty] - public partial string DedicatedGoogleCalendar { get; set; } = "Non configuré"; - - [ObservableProperty] - public partial string BankInfoSummary { get; set; } = "Aucune information bancaire"; - - [ObservableProperty] - public partial string StatusMessage { get; set; } = "Prêt."; - - [ObservableProperty] - public partial bool EmailConfirmed { get; set; } - - [ObservableProperty] - public partial bool AllowMonthlyEmail { get; set; } - - [ObservableProperty] - public partial bool HasPassword { get; set; } - - [ObservableProperty] - public partial bool TwoFactorEnabled { get; set; } - - [ObservableProperty] - public partial int ExternalLoginCount { get; set; } - - [ObservableProperty] - public partial long PostsCounter { get; set; } - - [ObservableProperty] - public partial long DiskUsage { get; set; } - - [ObservableProperty] - public partial long DiskQuota { get; set; } - - [ObservableProperty] - public partial decimal Credits { get; set; } - - [ObservableProperty] - public partial string CurrentPassword { get; set; } = string.Empty; - - [ObservableProperty] - public partial string NewPassword { get; set; } = string.Empty; - - [ObservableProperty] - public partial string ConfirmPassword { get; set; } = string.Empty; - - [ObservableProperty] - public partial string BankIban { get; set; } = string.Empty; - - [ObservableProperty] - public partial string BankBic { get; set; } = string.Empty; - - [ObservableProperty] - public partial string BankCode { get; set; } = string.Empty; - - [ObservableProperty] - public partial string BankWicketCode { get; set; } = string.Empty; - - [ObservableProperty] - public partial string BankAccountNumber { get; set; } = string.Empty; - - [ObservableProperty] - public partial string UsernameConfirmation { get; set; } = string.Empty; - - [ObservableProperty] - public partial int BankedKey { get; set; } - - public ObservableCollection BankAccounts { get; } = []; - public ObservableCollection ExternalLogins { get; } = []; - public ObservableCollection Calendars { get; } = []; - - public override bool CanNavigateNext { get => false; protected set { _ = value; } } - public override bool CanNavigatePrevious { get => true; protected set { _ = value; } } - - public UserProfilePageViewModel(YavscApiClient? api, SessionStatusViewModel? sessionStatus = null) - { - _api = api; - _sessionStatus = sessionStatus; - } - - public UserProfilePageViewModel() : this(null, null) { } - - public Task InitializeAsync() => RefreshAsync(); - - [RelayCommand] - public async Task RefreshAsync() - { - if (_api is null) - { - IsProfileLoaded = false; - StatusMessage = "Client API de profil indisponible."; - return; - } - - IsBusy = true; - IsProfileLoaded = false; - try - { - var me = await _api.CallAsync(HttpMethod.Get, new Uri(new Uri(_api.Settings.ApiUrl.TrimEnd('/') + "/", UriKind.Absolute), "account/me").ToString()).ConfigureAwait(true); - if (me is null) - { - throw new InvalidOperationException("Le serveur a renvoyé un profil vide."); - } - - UserName = me.UserName ?? "—"; - CurrentUserName = UserName; - UserId = me.UserId; - FullName = me.FullName ?? "—"; - Email = me.Email ?? "—"; - Address = me.Address ?? "—"; - Avatar = string.IsNullOrWhiteSpace(me.Avatar) ? "Aucun avatar" : me.Avatar; - Roles = me.Roles is { Length: > 0 } ? string.Join(", ", me.Roles) : "Aucun rôle"; - DedicatedGoogleCalendar = string.IsNullOrWhiteSpace(me.DedicatedGoogleCalendar) ? "Non configuré" : me.DedicatedGoogleCalendar; - BankInfoSummary = string.IsNullOrWhiteSpace(me.BankInfoSummary) ? "Aucune information bancaire" : me.BankInfoSummary; - EmailConfirmed = me.EmailConfirmed; - AllowMonthlyEmail = me.AllowMonthlyEmail; - HasPassword = me.HasPassword; - TwoFactorEnabled = me.TwoFactorEnabled; - ExternalLoginCount = me.ExternalLoginCount; - PostsCounter = me.PostsCounter; - DiskUsage = me.DiskUsage; - DiskQuota = me.DiskQuota; - Credits = me.Credits; - BankAccounts.Clear(); - foreach (var account in me.BankAccounts) - { - BankAccounts.Add(account); - } - ExternalLogins.Clear(); - foreach (var login in me.ExternalLogins) - { - ExternalLogins.Add(login); - } - IsProfileLoaded = true; - StatusMessage = "Profil chargé."; - } - catch (Exception ex) - { - IsProfileLoaded = false; - UserName = "—"; - CurrentUserName = "—"; - UserId = "—"; - FullName = "—"; - Email = "—"; - Address = "—"; - Avatar = "—"; - Roles = "—"; - DedicatedGoogleCalendar = "Non configuré"; - BankInfoSummary = "Aucune information bancaire"; - EmailConfirmed = false; - AllowMonthlyEmail = false; - HasPassword = false; - TwoFactorEnabled = false; - ExternalLoginCount = 0; - PostsCounter = 0; - DiskUsage = 0; - DiskQuota = 0; - Credits = 0; - BankAccounts.Clear(); - ExternalLogins.Clear(); - Calendars.Clear(); - StatusMessage = $"Impossible de charger le profil : {ex.Message}"; - } - finally - { - IsBusy = false; - } - } - - public ProfileUpdateRequest BuildProfileUpdateRequest() - { - static string NormalizeField(string? value, string placeholder) - { - if (string.IsNullOrWhiteSpace(value)) - { - return string.Empty; - } - - var trimmed = value.Trim(); - return string.Equals(trimmed, placeholder, StringComparison.OrdinalIgnoreCase) - ? string.Empty - : trimmed; - } - - return new ProfileUpdateRequest - { - UserName = NormalizeField(UserName, "—"), - FullName = NormalizeField(FullName, "—"), - Address = NormalizeField(Address, "—"), - GoogleCalendarId = NormalizeField(DedicatedGoogleCalendar, "Non configuré") - }; - } - - private bool CanSaveProfile() => _api is not null && IsProfileLoaded && !IsBusy; - - [RelayCommand(CanExecute = nameof(CanSaveProfile))] - public async Task SaveProfileAsync() - { - if (_api is null) - { - StatusMessage = "Client API de profil indisponible."; - return; - } - - IsBusy = true; - try - { - var payload = BuildProfileUpdateRequest(); - - var endpoint = new Uri(new Uri(_api.Settings.ApiUrl.TrimEnd('/') + "/", UriKind.Absolute), "account/me").ToString(); - await _api.CallAsync(HttpMethod.Put, endpoint, payload).ConfigureAwait(true); - StatusMessage = "Profil enregistré."; - await RefreshAsync().ConfigureAwait(true); - } - catch (Exception ex) - { - StatusMessage = $"Impossible d’enregistrer le profil : {ex.Message}"; - } - finally - { - IsBusy = false; - } - } - - [RelayCommand(CanExecute = nameof(CanModifyAccount))] - public async Task SetMonthlyEmailPreferenceAsync() - { - if (_api is null) - { - StatusMessage = "Client API de profil indisponible."; - return; - } - - await UpdateBooleanPreferenceAsync( - "account/preferences/monthly-email", - new MonthlyEmailPreferenceRequest { Enabled = AllowMonthlyEmail }, - enabled => AllowMonthlyEmail = enabled, - "préférence de courriel"); - } - - [RelayCommand(CanExecute = nameof(CanModifyAccount))] - public async Task SetTwoFactorAsync() - { - if (_api is null) - { - StatusMessage = "Client API de profil indisponible."; - return; - } - - await UpdateBooleanPreferenceAsync( - "account/security/two-factor", - new TwoFactorRequest { Enabled = TwoFactorEnabled }, - enabled => TwoFactorEnabled = enabled, - "authentification à deux facteurs"); - } - - [RelayCommand(CanExecute = nameof(CanModifyAccount))] - public async Task ChangePasswordAsync() - { - if (_api is null) - { - StatusMessage = "Client API de profil indisponible."; - return; - } - - if (string.IsNullOrWhiteSpace(NewPassword) - || !string.Equals(NewPassword, ConfirmPassword, StringComparison.Ordinal)) - { - StatusMessage = "Le nouveau mot de passe et sa confirmation doivent être identiques."; - return; - } - - IsBusy = true; - try - { - var endpoint = BuildEndpoint("account/security/password"); - await _api.CallAsync(HttpMethod.Put, endpoint, new ChangePasswordRequest - { - CurrentPassword = CurrentPassword, - NewPassword = NewPassword, - ConfirmPassword = ConfirmPassword - }).ConfigureAwait(true); - CurrentPassword = string.Empty; - NewPassword = string.Empty; - ConfirmPassword = string.Empty; - HasPassword = true; - StatusMessage = "Mot de passe mis à jour."; - } - catch (Exception ex) - { - StatusMessage = $"Impossible de modifier le mot de passe : {ex.Message}"; - } - finally - { - IsBusy = false; - } - } - - [RelayCommand(CanExecute = nameof(CanModifyAccount))] - public async Task AddBankAccountAsync() - { - if (_api is null) - { - StatusMessage = "Client API de profil indisponible."; - return; - } - - if (string.IsNullOrWhiteSpace(BankIban) && string.IsNullOrWhiteSpace(BankBic)) - { - StatusMessage = "Renseignez un IBAN ou un BIC."; - return; - } - - IsBusy = true; - try - { - var endpoint = BuildEndpoint("account/bank-info"); - await _api.CallAsync(HttpMethod.Post, endpoint, new BankAccountInfoRequest - { - IBAN = BankIban.Trim(), - BIC = BankBic.Trim(), - BankCode = BankCode.Trim(), - WicketCode = BankWicketCode.Trim(), - AccountNumber = BankAccountNumber.Trim(), - BankedKey = BankedKey - }).ConfigureAwait(true); - BankIban = string.Empty; - BankBic = string.Empty; - BankCode = string.Empty; - BankWicketCode = string.Empty; - BankAccountNumber = string.Empty; - BankedKey = 0; - await RefreshAsync().ConfigureAwait(true); - } - catch (Exception ex) - { - StatusMessage = $"Impossible d’ajouter le compte bancaire : {ex.Message}"; - } - finally - { - IsBusy = false; - } - } - - [RelayCommand(CanExecute = nameof(CanModifyAccount))] - public async Task DeleteBankAccountAsync(BankAccountInfo? account) - { - if (_api is null || account is null) - { - return; - } - - IsBusy = true; - try - { - await _api.CallAsync(HttpMethod.Delete, BuildEndpoint($"account/bank-info/{account.Id}")).ConfigureAwait(true); - await RefreshAsync().ConfigureAwait(true); - } - catch (Exception ex) - { - StatusMessage = $"Impossible de supprimer le compte bancaire : {ex.Message}"; - } - finally - { - IsBusy = false; - } - } - - [RelayCommand(CanExecute = nameof(CanModifyAccount))] - public async Task RemoveExternalLoginAsync(LinkedExternalLogin? login) - { - if (_api is null || login is null) - { - return; - } - - IsBusy = true; - try - { - var query = $"provider={Uri.EscapeDataString(login.Provider)}&providerKey={Uri.EscapeDataString(login.ProviderKey)}"; - await _api.CallAsync(HttpMethod.Delete, BuildEndpoint($"account/security/external-logins?{query}")).ConfigureAwait(true); - await RefreshAsync().ConfigureAwait(true); - } - catch (Exception ex) - { - StatusMessage = $"Impossible de supprimer cette connexion externe : {ex.Message}"; - } - finally - { - IsBusy = false; - } - } - - [RelayCommand(CanExecute = nameof(CanModifyAccount))] - public async Task SendEmailConfirmationAsync() - { - if (_api is null) - { - StatusMessage = "Client API de profil indisponible."; - return; - } - - IsBusy = true; - try - { - await _api.CallAsync(HttpMethod.Post, BuildEndpoint("account/email/confirmation")).ConfigureAwait(true); - StatusMessage = "E-mail de confirmation envoyé."; - } - catch (Exception ex) - { - StatusMessage = $"Impossible d’envoyer l’e-mail de confirmation : {ex.Message}"; - } - finally - { - IsBusy = false; - } - } - - [RelayCommand(CanExecute = nameof(CanModifyAccount))] - public async Task LoadCalendarsAsync() - { - if (_api is null) - { - StatusMessage = "Client API de profil indisponible."; - return; - } - - IsBusy = true; - try - { - var calendars = await _api.CallAsync( - HttpMethod.Get, - BuildEndpoint("account/calendars")).ConfigureAwait(true); - Calendars.Clear(); - foreach (var calendar in calendars ?? []) - { - Calendars.Add(calendar); - } - - StatusMessage = Calendars.Count == 0 - ? "Aucun agenda Google dont vous êtes propriétaire n’est disponible." - : "Agendas Google chargés."; - } - catch (Exception ex) - { - StatusMessage = $"Impossible de charger les agendas Google : {ex.Message}"; - } - finally - { - IsBusy = false; - } - } - - [RelayCommand(CanExecute = nameof(CanModifyAccount))] - public async Task ChooseCalendarAsync(CalendarOption? calendar) - { - if (calendar is null) - { - DedicatedGoogleCalendar = "Non configuré"; - } - else - { - DedicatedGoogleCalendar = calendar.Id; - } - - await SaveProfileAsync().ConfigureAwait(true); - } - - [RelayCommand(CanExecute = nameof(CanModifyAccount))] - public async Task DeleteAccountAsync() - { - if (_api is null) - { - StatusMessage = "Client API de profil indisponible."; - return; - } - - if (!string.Equals(UsernameConfirmation, CurrentUserName, StringComparison.Ordinal)) - { - StatusMessage = "Saisissez exactement votre nom d’utilisateur pour confirmer la suppression."; - return; - } - - IsBusy = true; - try - { - await _api.CallAsync(HttpMethod.Delete, BuildEndpoint("account/me"), new DeleteAccountRequest - { - UsernameConfirmation = UsernameConfirmation - }).ConfigureAwait(true); - UsernameConfirmation = string.Empty; - StatusMessage = "Compte supprimé."; - - if (_sessionStatus is not null) - { - await _sessionStatus.LogoutAsync().ConfigureAwait(true); - } - else - { - await _api.LogoutAsync().ConfigureAwait(true); - } - } - catch (Exception ex) - { - StatusMessage = $"Impossible de supprimer le compte : {ex.Message}"; - } - finally - { - IsBusy = false; - } - } - - private async Task UpdateBooleanPreferenceAsync( - string path, - TRequest request, - Action applyValue, - string preferenceName) - where TRequest : class - { - IsBusy = true; - try - { - var endpoint = BuildEndpoint(path); - var response = await _api!.CallAsync(HttpMethod.Put, endpoint, request).ConfigureAwait(true); - var enabled = response.GetProperty("enabled").GetBoolean(); - applyValue(enabled); - StatusMessage = $"Préférence mise à jour : {preferenceName}."; - } - catch (Exception ex) - { - await RefreshAsync().ConfigureAwait(true); - StatusMessage = $"Impossible de mettre à jour {preferenceName} : {ex.Message}"; - } - finally - { - IsBusy = false; - } - } - - private string BuildEndpoint(string path) => - new Uri(new Uri(_api!.Settings.ApiUrl.TrimEnd('/') + "/", UriKind.Absolute), path).ToString(); - - private bool CanModifyAccount() => _api is not null && IsProfileLoaded && !IsBusy; -} diff --git a/src/PostIt/PostIt/ViewModels/PerformerConfigurationPageViewModel.cs b/src/PostIt/PostIt/ViewModels/PerformerConfigurationPageViewModel.cs deleted file mode 100644 index 91b6d0572..000000000 --- a/src/PostIt/PostIt/ViewModels/PerformerConfigurationPageViewModel.cs +++ /dev/null @@ -1,196 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using PostIt.Services; -using Yavsc.Abstract.Workflow; -using Yavsc.Api.Client; - -namespace PostIt.ViewModels; - -public partial class ActivitySelectionOption : ObservableObject -{ - public string Code { get; } - public string Name { get; } - - [ObservableProperty] - public partial bool IsSelected { get; set; } - - public ActivitySelectionOption(string code, string name) - { - Code = code; - Name = name; - } -} - -public partial class PerformerConfigurationPageViewModel : ViewModelBase -{ - private readonly YavscApiClient? _api; - private readonly ActivityApiClient? _activityClient; - - [ObservableProperty] - public partial bool IsBusy { get; set; } - - [ObservableProperty] - public partial string PerformerId { get; set; } = string.Empty; - - [ObservableProperty] - public partial string UserName { get; set; } = "—"; - - [ObservableProperty] - public partial string ExerciseCountryCode { get; set; } = "fr"; - - [ObservableProperty] - public partial string SIREN { get; set; } = string.Empty; - - [ObservableProperty] - public partial string Website { get; set; } = string.Empty; - - [ObservableProperty] - public partial bool Active { get; set; } - - [ObservableProperty] - public partial bool AcceptNotifications { get; set; } - - [ObservableProperty] - public partial bool AcceptPublicContact { get; set; } - - [ObservableProperty] - public partial bool UseGeoLocalizationToReduceDistanceWithClients { get; set; } - - [ObservableProperty] - public partial ObservableCollection AvailableActivities { get; set; } = new(); - - [ObservableProperty] - public partial string StatusMessage { get; set; } = "Prêt."; - - public override bool CanNavigateNext { get => false; protected set { _ = value; } } - public override bool CanNavigatePrevious { get => true; protected set { _ = value; } } - - public PerformerConfigurationPageViewModel(YavscApiClient? api, ActivityApiClient? activityClient) - { - _api = api; - _activityClient = activityClient; - } - - public PerformerConfigurationPageViewModel() : this(null, null) { } - - public Task InitializeAsync() => RefreshAsync(); - - [RelayCommand] - public async Task RefreshAsync() - { - if (_api is null || _activityClient is null) - { - StatusMessage = "Client API de configuration indisponible."; - return; - } - - IsBusy = true; - try - { - var endpoint = new Uri(new Uri(_api.Settings.ApiUrl.TrimEnd('/') + "/", UriKind.Absolute), "account/performer-profile").ToString(); - var dto = await _api.CallAsync(HttpMethod.Get, endpoint).ConfigureAwait(true); - - PerformerId = dto?.PerformerId ?? string.Empty; - UserName = dto?.UserName ?? "—"; - ExerciseCountryCode = string.IsNullOrWhiteSpace(dto?.ExerciseCountryCode) ? "fr" : dto.ExerciseCountryCode; - SIREN = dto?.SIREN ?? string.Empty; - Website = dto?.WebSite ?? string.Empty; - Active = dto?.Active ?? false; - AcceptNotifications = dto?.AcceptNotifications ?? false; - AcceptPublicContact = dto?.AcceptPublicContact ?? false; - UseGeoLocalizationToReduceDistanceWithClients = dto?.UseGeoLocalizationToReduceDistanceWithClients ?? false; - - var catalog = await _activityClient.GetCatalogAsync().ConfigureAwait(true); - var options = Flatten(catalog) - .OrderBy(a => a.Name, StringComparer.OrdinalIgnoreCase) - .Select(a => new ActivitySelectionOption(a.Code, a.Name) - { - IsSelected = dto?.SelectedActivityCodes.Contains(a.Code, StringComparer.OrdinalIgnoreCase) == true - }) - .ToList(); - - AvailableActivities.Clear(); - foreach (var option in options) - { - AvailableActivities.Add(option); - } - - StatusMessage = "Profil performer chargé."; - } - catch (Exception ex) - { - StatusMessage = $"Impossible de charger la configuration performer : {ex.Message}"; - } - finally - { - IsBusy = false; - } - } - - public PerformerProfileSettings BuildPerformerProfileSettings() - { - return new PerformerProfileSettings - { - PerformerId = string.IsNullOrWhiteSpace(PerformerId) ? string.Empty : PerformerId, - UserName = UserName, - ExerciseCountryCode = string.IsNullOrWhiteSpace(ExerciseCountryCode) ? "fr" : ExerciseCountryCode, - SIREN = SIREN, - WebSite = Website, - Active = Active, - AcceptNotifications = AcceptNotifications, - AcceptPublicContact = AcceptPublicContact, - UseGeoLocalizationToReduceDistanceWithClients = UseGeoLocalizationToReduceDistanceWithClients, - SelectedActivityCodes = AvailableActivities - .Where(a => a.IsSelected) - .Select(a => a.Code) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList() - }; - } - - [RelayCommand] - public async Task SaveAsync() - { - if (_api is null) - { - StatusMessage = "Client API de configuration indisponible."; - return; - } - - IsBusy = true; - try - { - var dto = BuildPerformerProfileSettings(); - - var endpoint = new Uri(new Uri(_api.Settings.ApiUrl.TrimEnd('/') + "/", UriKind.Absolute), "account/performer-profile").ToString(); - var response = await _api.CallAsync(HttpMethod.Put, endpoint, dto).ConfigureAwait(true); - StatusMessage = response is not null ? "Configuration performer enregistrée." : "Configuration performer enregistrée."; - } - catch (Exception ex) - { - StatusMessage = $"Impossible d’enregistrer la configuration performer : {ex.Message}"; - } - finally - { - IsBusy = false; - } - } - - private static IEnumerable Flatten(IEnumerable activities) - { - foreach (var activity in activities) - { - yield return activity; - foreach (var child in Flatten(activity.Children ?? Enumerable.Empty())) - { - yield return child; - } - } - } -} diff --git a/src/PostIt/PostIt/Views/Layout/AdministrationPage.axaml b/src/PostIt/PostIt/Views/Layout/AdministrationPage.axaml deleted file mode 100644 index 692b4972c..000000000 --- a/src/PostIt/PostIt/Views/Layout/AdministrationPage.axaml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - -