From 48bac643bfbf285f5b8856e44abd6123af327659 Mon Sep 17 00:00:00 2001 From: Enes Efe Tokta Date: Mon, 11 Aug 2025 15:48:03 +0300 Subject: [PATCH 1/5] refactor(*.cs & *.xaml): Refactor user and settings management, add dialog and refresh services Replaces and restructures user and settings DTOs, expanding UserModel and UserProfileDto with detailed fields. Adds dialog and store refresh services, updates dependency injection, and refactors ViewModels to use the new IUserStore and related services for profile, app, and notification settings. Implements event-driven updates for currencies and user data, and introduces periodic store refresh in the BottomBarViewModel. --- FinTrack/App.xaml.cs | 4 + .../Dtos/SettingsDtos/ProfileSettingsDto.cs | 12 - .../SettingsDtos/ProfileSettingsUpdateDto.cs | 9 - .../SettingsDtos/UpdateProfilePictureDto.cs | 7 + .../Dtos/SettingsDtos/UpdateUserEmailDto.cs | 8 + .../Dtos/SettingsDtos/UpdateUserNameDto.cs | 8 + .../SettingsDtos/UpdateUserPasswordDto.cs | 8 + FinTrack/Dtos/UserDtos/UserProfileDto.cs | 42 +++ FinTrack/Dtos/UserProfileDto.cs | 11 - FinTrack/Enums/LanguageType.cs | 12 + FinTrack/Models/User/UserModel.cs | 38 ++- .../Services/Currencies/CurrenciesStore.cs | 8 + .../Services/Currencies/ICurrenciesStore.cs | 2 + FinTrack/Services/Dialog/IDialogService.cs | 7 + FinTrack/Services/Dialog/WpfDialogService.cs | 17 ++ .../Services/StoresRefresh/IStoresRefresh.cs | 9 + .../Services/StoresRefresh/StoresRefresh.cs | 76 ++++++ FinTrack/Services/Users/IUserStore.cs | 13 +- FinTrack/Services/Users/UserStore.cs | 240 +++++++++++++----- FinTrack/ViewModels/AccountViewModel.cs | 9 +- .../ViewModels/AppSettingsContentViewModel.cs | 71 +++--- FinTrack/ViewModels/BottomBarViewModel.cs | 60 ++++- FinTrack/ViewModels/BudgetViewModel.cs | 1 - FinTrack/ViewModels/CurrenciesViewModel.cs | 31 ++- FinTrack/ViewModels/DashboardViewModel.cs | 22 +- .../NotificationSettingsContentViewModel.cs | 114 +++------ .../ProfileSettingsContentViewModel.cs | 107 +++++--- .../SecuritySettingsContentViewModel.cs | 48 +++- FinTrack/ViewModels/TopBarViewModel.cs | 6 +- FinTrack/ViewModels/TransactionsViewModel.cs | 2 - FinTrack/Views/AppSettingsContentView.xaml | 4 +- FinTrack/Views/OtpInputDialogWindow.xaml | 15 ++ FinTrack/Views/OtpInputDialogWindow.xaml.cs | 26 ++ .../Views/ProfileSettingsContentView.xaml | 60 ++++- .../Views/SecuritySettingsContentView.xaml | 2 +- 35 files changed, 810 insertions(+), 299 deletions(-) delete mode 100644 FinTrack/Dtos/SettingsDtos/ProfileSettingsDto.cs delete mode 100644 FinTrack/Dtos/SettingsDtos/ProfileSettingsUpdateDto.cs create mode 100644 FinTrack/Dtos/SettingsDtos/UpdateProfilePictureDto.cs create mode 100644 FinTrack/Dtos/SettingsDtos/UpdateUserEmailDto.cs create mode 100644 FinTrack/Dtos/SettingsDtos/UpdateUserNameDto.cs create mode 100644 FinTrack/Dtos/SettingsDtos/UpdateUserPasswordDto.cs create mode 100644 FinTrack/Dtos/UserDtos/UserProfileDto.cs delete mode 100644 FinTrack/Dtos/UserProfileDto.cs create mode 100644 FinTrack/Enums/LanguageType.cs create mode 100644 FinTrack/Services/Dialog/IDialogService.cs create mode 100644 FinTrack/Services/Dialog/WpfDialogService.cs create mode 100644 FinTrack/Services/StoresRefresh/IStoresRefresh.cs create mode 100644 FinTrack/Services/StoresRefresh/StoresRefresh.cs create mode 100644 FinTrack/Views/OtpInputDialogWindow.xaml create mode 100644 FinTrack/Views/OtpInputDialogWindow.xaml.cs diff --git a/FinTrack/App.xaml.cs b/FinTrack/App.xaml.cs index c890ba3..6408b37 100644 --- a/FinTrack/App.xaml.cs +++ b/FinTrack/App.xaml.cs @@ -8,8 +8,10 @@ using FinTrackForWindows.Services.Camera; using FinTrackForWindows.Services.Currencies; using FinTrackForWindows.Services.Debts; +using FinTrackForWindows.Services.Dialog; using FinTrackForWindows.Services.Memberships; using FinTrackForWindows.Services.Reports; +using FinTrackForWindows.Services.StoresRefresh; using FinTrackForWindows.Services.Transactions; using FinTrackForWindows.Services.Users; using FinTrackForWindows.ViewModels; @@ -95,6 +97,8 @@ private void ConfigureServices(IServiceCollection services) services.AddSingleton(); services.AddTransient(); + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); } diff --git a/FinTrack/Dtos/SettingsDtos/ProfileSettingsDto.cs b/FinTrack/Dtos/SettingsDtos/ProfileSettingsDto.cs deleted file mode 100644 index 1a27a8f..0000000 --- a/FinTrack/Dtos/SettingsDtos/ProfileSettingsDto.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace FinTrackForWindows.Dtos.SettingsDtos -{ - public class ProfileSettingsDto - { - public int Id { get; set; } - public string FullName { get; set; } = string.Empty; - public string Email { get; set; } = string.Empty; - public string? ProfilePictureUrl { get; set; } - public DateTime CreatedAtUtc { get; set; } - public DateTime? UpdatedAtUtc { get; set; } - } -} diff --git a/FinTrack/Dtos/SettingsDtos/ProfileSettingsUpdateDto.cs b/FinTrack/Dtos/SettingsDtos/ProfileSettingsUpdateDto.cs deleted file mode 100644 index 01dc96b..0000000 --- a/FinTrack/Dtos/SettingsDtos/ProfileSettingsUpdateDto.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace FinTrackForWindows.Dtos.SettingsDtos -{ - public class ProfileSettingsUpdateDto - { - public string? FullName { get; set; } - public string? Email { get; set; } - public string? ProfilePictureUrl { get; set; } - } -} diff --git a/FinTrack/Dtos/SettingsDtos/UpdateProfilePictureDto.cs b/FinTrack/Dtos/SettingsDtos/UpdateProfilePictureDto.cs new file mode 100644 index 0000000..8ee9f4c --- /dev/null +++ b/FinTrack/Dtos/SettingsDtos/UpdateProfilePictureDto.cs @@ -0,0 +1,7 @@ +namespace FinTrackForWindows.Dtos.SettingsDtos +{ + public class UpdateProfilePictureDto + { + public string ProfilePictureUrl { get; set; } = string.Empty; + } +} diff --git a/FinTrack/Dtos/SettingsDtos/UpdateUserEmailDto.cs b/FinTrack/Dtos/SettingsDtos/UpdateUserEmailDto.cs new file mode 100644 index 0000000..f87a15c --- /dev/null +++ b/FinTrack/Dtos/SettingsDtos/UpdateUserEmailDto.cs @@ -0,0 +1,8 @@ +namespace FinTrackForWindows.Dtos.SettingsDtos +{ + public class UpdateUserEmailDto + { + public string NewEmail { get; set; } = string.Empty; + public string OtpCode { get; set; } = string.Empty; + } +} diff --git a/FinTrack/Dtos/SettingsDtos/UpdateUserNameDto.cs b/FinTrack/Dtos/SettingsDtos/UpdateUserNameDto.cs new file mode 100644 index 0000000..d1c5ca5 --- /dev/null +++ b/FinTrack/Dtos/SettingsDtos/UpdateUserNameDto.cs @@ -0,0 +1,8 @@ +namespace FinTrackForWindows.Dtos.SettingsDtos +{ + public class UpdateUserNameDto + { + public string FirstName { get; set; } = string.Empty; + public string LastName { get; set; } = string.Empty; + } +} diff --git a/FinTrack/Dtos/SettingsDtos/UpdateUserPasswordDto.cs b/FinTrack/Dtos/SettingsDtos/UpdateUserPasswordDto.cs new file mode 100644 index 0000000..ebed954 --- /dev/null +++ b/FinTrack/Dtos/SettingsDtos/UpdateUserPasswordDto.cs @@ -0,0 +1,8 @@ +namespace FinTrackForWindows.Dtos.SettingsDtos +{ + public class UpdateUserPasswordDto + { + public string CurrentPassword { get; set; } = string.Empty; + public string NewPassword { get; set; } = string.Empty; + } +} diff --git a/FinTrack/Dtos/UserDtos/UserProfileDto.cs b/FinTrack/Dtos/UserDtos/UserProfileDto.cs new file mode 100644 index 0000000..04bda0f --- /dev/null +++ b/FinTrack/Dtos/UserDtos/UserProfileDto.cs @@ -0,0 +1,42 @@ +using FinTrackForWindows.Enums; + +namespace FinTrackForWindows.Dtos.UserDtos +{ + public class UserProfileDto + { + // Temel Bilgiler + public int Id { get; set; } + public string UserName { get; set; } = string.Empty; + public string Email { get; set; } = string.Empty; + public string? ProfilePictureUrl { get; set; } + public DateTime CreatedAtUtc { get; set; } + + // Üyelik Bilgileri + public int CurrentMembershipPlanId { get; set; } + public string CurrentMembershipPlanType { get; set; } = string.Empty; + public DateTime MembershipStartDateUtc { get; set; } + public DateTime MembershipExpirationDateUtc { get; set; } + + // Ayarlar + public AppearanceType Thema { get; set; } + public LanguageType Language { get; set; } + public BaseCurrencyType Currency { get; set; } + public bool SpendingLimitWarning { get; set; } + public bool ExpectedBillReminder { get; set; } + public bool WeeklySpendingSummary { get; set; } + public bool NewFeaturesAndAnnouncements { get; set; } + public bool EnableDesktopNotifications { get; set; } + + // Kullanımlar + public List CurrentAccounts = new(); + public List CurrentBudgets = new(); + public List CurrentTransactions = new(); + public List CurrentBudgetsCategories = new(); + public List CurrentTransactionsCategories = new(); + public List CurrentLenderDebts = new(); + public List CurrentBorrowerDebts = new(); + public List CurrentNotifications = new(); + public List CurrentFeedbacks = new(); + public List CurrentVideos = new(); + } +} diff --git a/FinTrack/Dtos/UserProfileDto.cs b/FinTrack/Dtos/UserProfileDto.cs deleted file mode 100644 index 7a4c6fc..0000000 --- a/FinTrack/Dtos/UserProfileDto.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace FinTrackForWindows.Dtos -{ - public class UserProfileDto - { - public int Id { get; set; } - public string UserName { get; set; } = string.Empty; - public string Email { get; set; } = string.Empty; - public string? ProfilePicture { get; set; } - public string MembershipType { get; set; } = string.Empty; - } -} diff --git a/FinTrack/Enums/LanguageType.cs b/FinTrack/Enums/LanguageType.cs new file mode 100644 index 0000000..eb21cce --- /dev/null +++ b/FinTrack/Enums/LanguageType.cs @@ -0,0 +1,12 @@ +namespace FinTrackForWindows.Enums +{ + public enum LanguageType + { + Turkish, + English, + German, + French, + Spanish, + Italian + } +} diff --git a/FinTrack/Models/User/UserModel.cs b/FinTrack/Models/User/UserModel.cs index 89ccefe..939d415 100644 --- a/FinTrack/Models/User/UserModel.cs +++ b/FinTrack/Models/User/UserModel.cs @@ -1,12 +1,42 @@ -namespace FinTrackForWindows.Models.User +using FinTrackForWindows.Enums; + +namespace FinTrackForWindows.Models.User { public class UserModel { + // Temel Bilgiler public int Id { get; set; } public string UserName { get; set; } = string.Empty; public string Email { get; set; } = string.Empty; - public string ProfilePictureUrl { get; set; } = string.Empty; - public DateTime CreatedAt { get; set; } = DateTime.UtcNow; - public string MembershipPlan { get; set; } = string.Empty; + public string? ProfilePictureUrl { get; set; } + public DateTime CreatedAtUtc { get; set; } + + // Üyelik Bilgileri + public int CurrentMembershipPlanId { get; set; } + public string CurrentMembershipPlanType { get; set; } = string.Empty; + public DateTime MembershipStartDateUtc { get; set; } + public DateTime MembershipExpirationDateUtc { get; set; } + + // Ayarlar + public AppearanceType Thema { get; set; } + public LanguageType Language { get; set; } + public BaseCurrencyType Currency { get; set; } + public bool SpendingLimitWarning { get; set; } + public bool ExpectedBillReminder { get; set; } + public bool WeeklySpendingSummary { get; set; } + public bool NewFeaturesAndAnnouncements { get; set; } + public bool EnableDesktopNotifications { get; set; } + + // Kullanımlar + public List CurrentAccounts = new(); + public List CurrentBudgets = new(); + public List CurrentTransactions = new(); + public List CurrentBudgetsCategories = new(); + public List CurrentTransactionsCategories = new(); + public List CurrentLenderDebts = new(); + public List CurrentBorrowerDebts = new(); + public List CurrentNotifications = new(); + public List CurrentFeedbacks = new(); + public List CurrentVideos = new(); } } diff --git a/FinTrack/Services/Currencies/CurrenciesStore.cs b/FinTrack/Services/Currencies/CurrenciesStore.cs index 69ad05c..6a594a6 100644 --- a/FinTrack/Services/Currencies/CurrenciesStore.cs +++ b/FinTrack/Services/Currencies/CurrenciesStore.cs @@ -4,6 +4,7 @@ using FinTrackWebApi.Dtos.CurrencyDtos; using Microsoft.Extensions.Logging; using System.Collections.ObjectModel; +using System.Collections.Specialized; namespace FinTrackForWindows.Services.Currencies { @@ -15,6 +16,8 @@ public class CurrenciesStore : ICurrenciesStore public ReadOnlyObservableCollection Currencies { get; } + public event NotifyCollectionChangedEventHandler? CurrenciesChanged; + public CurrenciesStore(IApiService apiService, ILogger logger) { _apiService = apiService; @@ -22,6 +25,11 @@ public CurrenciesStore(IApiService apiService, ILogger logger) _currencies = new ObservableCollection(); Currencies = new ReadOnlyObservableCollection(_currencies); + + _currencies.CollectionChanged += (sender, e) => + { + CurrenciesChanged?.Invoke(this, e); + }; } public async Task LoadCurrenciesAsync() diff --git a/FinTrack/Services/Currencies/ICurrenciesStore.cs b/FinTrack/Services/Currencies/ICurrenciesStore.cs index 0e25ced..c93627c 100644 --- a/FinTrack/Services/Currencies/ICurrenciesStore.cs +++ b/FinTrack/Services/Currencies/ICurrenciesStore.cs @@ -1,6 +1,7 @@ using FinTrackForWindows.Models.Currency; using FinTrackWebApi.Dtos.CurrencyDtos; using System.Collections.ObjectModel; +using System.Collections.Specialized; namespace FinTrackForWindows.Services.Currencies { @@ -8,6 +9,7 @@ public interface ICurrenciesStore { ReadOnlyObservableCollection Currencies { get; } Task LoadCurrenciesAsync(); + event NotifyCollectionChangedEventHandler? CurrenciesChanged; Task GetHistoricalDataAsync(string targetCurrencyCode, string period); Task GetConvertCurrencies(string fromCurrencyCode, string toCurrencyCode, decimal amount); } diff --git a/FinTrack/Services/Dialog/IDialogService.cs b/FinTrack/Services/Dialog/IDialogService.cs new file mode 100644 index 0000000..afc8e13 --- /dev/null +++ b/FinTrack/Services/Dialog/IDialogService.cs @@ -0,0 +1,7 @@ +namespace FinTrackForWindows.Services.Dialog +{ + public interface IDialogService + { + string? ShowOtpDialog(string newEmail); + } +} diff --git a/FinTrack/Services/Dialog/WpfDialogService.cs b/FinTrack/Services/Dialog/WpfDialogService.cs new file mode 100644 index 0000000..bc98dd7 --- /dev/null +++ b/FinTrack/Services/Dialog/WpfDialogService.cs @@ -0,0 +1,17 @@ +using FinTrackForWindows.Views; + +namespace FinTrackForWindows.Services.Dialog +{ + public class WpfDialogService : IDialogService + { + public string? ShowOtpDialog(string newEmail) + { + var dialog = new OtpInputDialogWindow(newEmail); + if (dialog.ShowDialog() == true) + { + return dialog.OtpCode; + } + return null; + } + } +} diff --git a/FinTrack/Services/StoresRefresh/IStoresRefresh.cs b/FinTrack/Services/StoresRefresh/IStoresRefresh.cs new file mode 100644 index 0000000..8a6ce27 --- /dev/null +++ b/FinTrack/Services/StoresRefresh/IStoresRefresh.cs @@ -0,0 +1,9 @@ +namespace FinTrackForWindows.Services.StoresRefresh +{ + public interface IStoresRefresh + { + event Action? RefreshStarted; + event Action? RefreshCompleted; + Task RefreshAllStoresAsync(); + } +} diff --git a/FinTrack/Services/StoresRefresh/StoresRefresh.cs b/FinTrack/Services/StoresRefresh/StoresRefresh.cs new file mode 100644 index 0000000..b783b0a --- /dev/null +++ b/FinTrack/Services/StoresRefresh/StoresRefresh.cs @@ -0,0 +1,76 @@ +using FinTrackForWindows.Services.Accounts; +using FinTrackForWindows.Services.Budgets; +using FinTrackForWindows.Services.Currencies; +using FinTrackForWindows.Services.Debts; +using FinTrackForWindows.Services.Memberships; +using FinTrackForWindows.Services.Transactions; +using Microsoft.Extensions.Logging; + +namespace FinTrackForWindows.Services.StoresRefresh +{ + public class StoresRefresh : IStoresRefresh + { + private readonly IAccountStore _accountStore; + private readonly IBudgetStore _budgetStore; + private readonly ITransactionStore _transactionStore; + private readonly IDebtStore _debtStore; + private readonly IMembershipStore _membershipStore; + private readonly ICurrenciesStore _currenciesStore; + + private readonly ILogger _logger; + + public event Action? RefreshStarted; + public event Action? RefreshCompleted; + + public StoresRefresh(IAccountStore accountStore, + IBudgetStore budgetStore, + ITransactionStore transactionStore, + IDebtStore debtStore, + IMembershipStore membershipStore, + ICurrenciesStore currenciesStore, + ILogger logger) + { + _accountStore = accountStore; + _budgetStore = budgetStore; + _transactionStore = transactionStore; + _debtStore = debtStore; + _membershipStore = membershipStore; + _currenciesStore = currenciesStore; + _logger = logger; + } + + public async Task RefreshAllStoresAsync() + { + _logger.LogInformation("Refresh process starting for all data stores..."); + RefreshStarted?.Invoke(); + + try + { + Task loadAccountsTask = _accountStore.LoadAccountsAsync(); + Task loadBudgetsTask = _budgetStore.LoadBudgetsAsync(); + Task loadDebtsTask = _debtStore.LoadDebtsAsync(); + Task loadMembershipTask = _membershipStore.LoadAllMembershipDataAsync(); + Task loadTransactionsTask = _transactionStore.LoadTransactionsAsync(); + Task loadCurrenciesTask = _currenciesStore.LoadCurrenciesAsync(); + + await Task.WhenAll( + loadAccountsTask, + loadBudgetsTask, + loadDebtsTask, + loadMembershipTask, + loadCurrenciesTask, + loadTransactionsTask); + + _logger.LogInformation("All data stores have been successfully refreshed."); + RefreshCompleted?.Invoke(true); + return true; + } + catch (Exception ex) + { + _logger.LogError(ex, "An error occurred while refreshing the data stores."); + RefreshCompleted?.Invoke(false); + return false; + } + } + } +} diff --git a/FinTrack/Services/Users/IUserStore.cs b/FinTrack/Services/Users/IUserStore.cs index 547e2bc..34887b6 100644 --- a/FinTrack/Services/Users/IUserStore.cs +++ b/FinTrack/Services/Users/IUserStore.cs @@ -1,4 +1,5 @@ -using FinTrackForWindows.Models.User; +using FinTrackForWindows.Dtos.SettingsDtos; +using FinTrackForWindows.Models.User; namespace FinTrackForWindows.Services.Users { @@ -7,7 +8,15 @@ public interface IUserStore UserModel? CurrentUser { get; } event Action? UserChanged; Task LoadCurrentUserAsync(); - Task UpdateUserAsync(UserModel updatedUserData); + + Task UpdateProfilePictureAsync(UpdateProfilePictureDto pictureDto); + Task UpdateUserNameAsync(UpdateUserNameDto nameDto); + Task UpdateUserPasswordAsync(UpdateUserPasswordDto passwordDto); + Task RequestEmailChangeOtpAsync(); + Task UpdateUserEmailAsync(UpdateUserEmailDto emailDto); + + Task UpdateAppSettingsAsync(UserAppSettingsUpdateDto settingsDto); + Task UpdateNotificationSettingsAsync(UserNotificationSettingsUpdateDto settingsDto); void ClearCurrentUser(); } diff --git a/FinTrack/Services/Users/UserStore.cs b/FinTrack/Services/Users/UserStore.cs index 9ce1e96..4048b44 100644 --- a/FinTrack/Services/Users/UserStore.cs +++ b/FinTrack/Services/Users/UserStore.cs @@ -1,102 +1,214 @@ -using FinTrackForWindows.Dtos; +using FinTrackForWindows.Dtos.SettingsDtos; +using FinTrackForWindows.Dtos.UserDtos; using FinTrackForWindows.Models.User; using FinTrackForWindows.Services.Api; -using Microsoft.Extensions.Logging; namespace FinTrackForWindows.Services.Users { public class UserStore : IUserStore { private readonly IApiService _apiService; - private readonly ILogger _logger; private UserModel? _currentUser; - public UserModel? CurrentUser + public UserModel? CurrentUser => _currentUser; + public event Action? UserChanged; + + public UserStore(IApiService apiService) { - get => _currentUser; - private set + _apiService = apiService; + } + + public async Task LoadCurrentUserAsync() + { + try { - if (_currentUser != value) + var userProfile = await _apiService.GetAsync("user"); + + if (userProfile != null) { - _currentUser = value; - UserChanged?.Invoke(); + _currentUser = MapProfileDtoToUserModel(userProfile); + OnUserChanged(); } } + catch (Exception ex) + { + Console.WriteLine($"Error loading current user: {ex.Message}"); + ClearCurrentUser(); + } } - public event Action? UserChanged; + public void ClearCurrentUser() + { + if (_currentUser != null) + { + _currentUser = null; + OnUserChanged(); + } + } - public UserStore(IApiService apiService, ILogger logger) + public async Task UpdateProfilePictureAsync(UpdateProfilePictureDto pictureDto) { - _apiService = apiService; - _logger = logger; + if (CurrentUser == null) return false; + + try + { + await _apiService.PostAsync("usersettings/update-profile-picture", pictureDto); + + CurrentUser.ProfilePictureUrl = pictureDto.ProfilePictureUrl; + OnUserChanged(); + return true; + } + catch (Exception ex) + { + Console.WriteLine($"Error updating profile picture: {ex.Message}"); + return false; + } } - public async Task LoadCurrentUserAsync() + public async Task UpdateUserNameAsync(UpdateUserNameDto nameDto) { - _logger.LogInformation("Mevcut kullanıcı bilgileri yükleniyor..."); + if (CurrentUser == null) return false; + try { - var userDto = await _apiService.GetAsync("User"); - var user = new UserModel - { - Id = userDto.Id, - UserName = userDto.UserName, - Email = userDto.Email, - ProfilePictureUrl = userDto.ProfilePicture ?? string.Empty, - MembershipPlan = userDto.MembershipType, - }; - if (userDto != null) - { - CurrentUser = user; - _logger.LogInformation("Kullanıcı bilgileri başarıyla yüklendi: {UserName}", userDto.UserName); - } - else - { - _logger.LogWarning("API'den kullanıcı bilgileri alınamadı veya kullanıcı bulunamadı."); - ClearCurrentUser(); - } + await _apiService.PostAsync("usersettings/update-username", nameDto); + + CurrentUser.UserName = $"{nameDto.FirstName.Trim()}_{nameDto.LastName.Trim()}"; + OnUserChanged(); + return true; } catch (Exception ex) { - _logger.LogError(ex, "Kullanıcı bilgileri yüklenirken bir hata oluştu."); - ClearCurrentUser(); + Console.WriteLine($"Error updating username: {ex.Message}"); + return false; } } - public async Task UpdateUserAsync(UserModel updatedUserData) + public async Task UpdateUserPasswordAsync(UpdateUserPasswordDto passwordDto) { - //_logger.LogInformation("Kullanıcı bilgileri güncelleniyor: {UserName}", updatedUserData.UserName); - //try - //{ - // var updatedUserFromApi = await _apiService.PutAsync("User", updatedUserData); - - // if (updatedUserFromApi != null) - // { - // CurrentUser = updatedUserFromApi; - // _logger.LogInformation("Kullanıcı bilgileri başarıyla güncellendi."); - // return true; - // } - - // _logger.LogWarning("Kullanıcı güncelleme isteği API tarafından başarısız oldu veya geçersiz veri döndü."); - // return false; - //} - //catch (Exception ex) - //{ - // _logger.LogError(ex, "Kullanıcı bilgileri güncellenirken bir hata oluştu."); - // return false; - //} - await Task.Delay(1000); - return false; + try + { + await _apiService.PostAsync("usersettings/update-password", passwordDto); + return true; + } + catch (Exception ex) + { + Console.WriteLine($"Error updating password: {ex.Message}"); + return false; + } } - public void ClearCurrentUser() + public async Task RequestEmailChangeOtpAsync() { - if (CurrentUser != null) + try + { + await _apiService.PostAsync("usersettings/request-email-change", null); + } + catch (Exception ex) + { + Console.WriteLine($"Error requesting email change OTP: {ex.Message}"); + } + } + + public async Task UpdateUserEmailAsync(UpdateUserEmailDto emailDto) + { + if (CurrentUser == null) return false; + + try + { + await _apiService.PostAsync("usersettings/confirm-email-change", emailDto); + + CurrentUser.Email = emailDto.NewEmail; + OnUserChanged(); + return true; + } + catch (Exception ex) + { + Console.WriteLine($"Error confirming email change: {ex.Message}"); + return false; + } + } + + public async Task UpdateNotificationSettingsAsync(UserNotificationSettingsUpdateDto settingsDto) + { + if (CurrentUser == null) return false; + + try { - _logger.LogInformation("Mevcut kullanıcı bilgileri temizleniyor."); - CurrentUser = null; + await _apiService.PostAsync("usersettings/user-notificationettings", settingsDto); + + CurrentUser.SpendingLimitWarning = settingsDto.SpendingLimitWarning; + CurrentUser.ExpectedBillReminder = settingsDto.ExpectedBillReminder; + CurrentUser.WeeklySpendingSummary = settingsDto.WeeklySpendingSummary; + CurrentUser.NewFeaturesAndAnnouncements = settingsDto.NewFeaturesAndAnnouncements; + CurrentUser.EnableDesktopNotifications = settingsDto.EnableDesktopNotifications; + OnUserChanged(); + return true; + } + catch (Exception ex) + { + Console.WriteLine($"Error updating notification settings: {ex.Message}"); + return false; + } + } + + public async Task UpdateAppSettingsAsync(UserAppSettingsUpdateDto settingsDto) + { + if (CurrentUser == null) return false; + + try + { + await _apiService.PostAsync("usersettings/app-settings", settingsDto); + + CurrentUser.Thema = settingsDto.Appearance; + CurrentUser.Currency = settingsDto.Currency; + OnUserChanged(); + return true; + } + catch (Exception ex) + { + Console.WriteLine($"Error updating app settings: {ex.Message}"); + return false; } } + + private void OnUserChanged() + { + UserChanged?.Invoke(); + } + + private UserModel MapProfileDtoToUserModel(UserProfileDto dto) + { + return new UserModel + { + Id = dto.Id, + UserName = dto.UserName, + Email = dto.Email, + ProfilePictureUrl = dto.ProfilePictureUrl, + CreatedAtUtc = dto.CreatedAtUtc, + CurrentMembershipPlanId = dto.CurrentMembershipPlanId, + CurrentMembershipPlanType = dto.CurrentMembershipPlanType, + MembershipStartDateUtc = dto.MembershipStartDateUtc, + MembershipExpirationDateUtc = dto.MembershipExpirationDateUtc, + Thema = dto.Thema, + Language = dto.Language, + Currency = dto.Currency, + SpendingLimitWarning = dto.SpendingLimitWarning, + ExpectedBillReminder = dto.ExpectedBillReminder, + WeeklySpendingSummary = dto.WeeklySpendingSummary, + NewFeaturesAndAnnouncements = dto.NewFeaturesAndAnnouncements, + EnableDesktopNotifications = dto.EnableDesktopNotifications, + CurrentAccounts = dto.CurrentAccounts, + CurrentBudgets = dto.CurrentBudgets, + CurrentTransactions = dto.CurrentTransactions, + CurrentBudgetsCategories = dto.CurrentBudgetsCategories, + CurrentTransactionsCategories = dto.CurrentTransactionsCategories, + CurrentLenderDebts = dto.CurrentLenderDebts, + CurrentBorrowerDebts = dto.CurrentBorrowerDebts, + CurrentNotifications = dto.CurrentNotifications, + CurrentFeedbacks = dto.CurrentFeedbacks, + CurrentVideos = dto.CurrentVideos + }; + } } -} +} \ No newline at end of file diff --git a/FinTrack/ViewModels/AccountViewModel.cs b/FinTrack/ViewModels/AccountViewModel.cs index a2c99fc..5448463 100644 --- a/FinTrack/ViewModels/AccountViewModel.cs +++ b/FinTrack/ViewModels/AccountViewModel.cs @@ -93,7 +93,7 @@ private void InitializeEmptyChart() private async Task InitializeViewModel() { - await LoadData(); + ApplyFilters(); if (FilteredAccounts.Any()) { @@ -118,13 +118,6 @@ partial void OnSelectedAccountChanged(AccountModel? value) } } - private async Task LoadData() - { - await _accountStore.LoadAccountsAsync(); - - ApplyFilters(); - } - private async Task LoadTransactionHistory(int accountId, string accountName) { var transactions = await _apiService.GetAsync>($"Transactions/account-id/{accountId}"); diff --git a/FinTrack/ViewModels/AppSettingsContentViewModel.cs b/FinTrack/ViewModels/AppSettingsContentViewModel.cs index e3cb347..ba8cc32 100644 --- a/FinTrack/ViewModels/AppSettingsContentViewModel.cs +++ b/FinTrack/ViewModels/AppSettingsContentViewModel.cs @@ -2,10 +2,12 @@ using CommunityToolkit.Mvvm.Input; using FinTrackForWindows.Dtos.SettingsDtos; using FinTrackForWindows.Enums; -using FinTrackForWindows.Services.Api; +using FinTrackForWindows.Services.AppInNotifications; +using FinTrackForWindows.Services.Users; using Microsoft.Extensions.Logging; +using System; using System.Collections.ObjectModel; -using System.Windows; +using System.Threading.Tasks; namespace FinTrackForWindows.ViewModels { @@ -20,9 +22,6 @@ public partial class AppSettingsContentViewModel : ObservableObject [ObservableProperty] private BaseCurrencyType _selectedCurrencyType; - [ObservableProperty] - private bool _startWithWindows; - [ObservableProperty] private bool _isLoading; @@ -31,41 +30,36 @@ public partial class AppSettingsContentViewModel : ObservableObject private bool _isSaving; private readonly ILogger _logger; - private readonly IApiService _apiService; + private readonly IUserStore _userStore; + private readonly IAppInNotificationService _appInNotificationService; - public AppSettingsContentViewModel(ILogger logger, IApiService apiService) + public AppSettingsContentViewModel(ILogger logger, IUserStore userStore, IAppInNotificationService appInNotificationService) { _logger = logger; - _apiService = apiService; + _userStore = userStore; + _appInNotificationService = appInNotificationService; AppearanceTypes = new ObservableCollection(Enum.GetValues()); CurrencyTypes = new ObservableCollection(Enum.GetValues()); - _ = LoadAppSettings(); + _userStore.UserChanged += OnUserChanged; + LoadDataFromStore(); + } + + private void OnUserChanged() + { + LoadDataFromStore(); } - private async Task LoadAppSettings() + private void LoadDataFromStore() { IsLoading = true; - try - { - var settings = await _apiService.GetAsync("UserSettings/AppSettings"); - if (settings != null) - { - SelectedAppearanceType = settings.Appearance; - SelectedCurrencyType = settings.Currency; - // StartWithWindows = settings.StartWithWindows; // DTO'ya eklendiğinde bu satır açılmalı - } - } - catch (Exception ex) + if (_userStore.CurrentUser != null) { - _logger.LogError(ex, "Failed to load application settings."); - MessageBox.Show("Could not load application settings. Default values will be used.", "Error", MessageBoxButton.OK, MessageBoxImage.Warning); - } - finally - { - IsLoading = false; + SelectedAppearanceType = _userStore.CurrentUser.Thema; + SelectedCurrencyType = _userStore.CurrentUser.Currency; } + IsLoading = false; } [RelayCommand(CanExecute = nameof(CanSaveChanges))] @@ -78,21 +72,24 @@ private async Task SaveChanges() { Appearance = SelectedAppearanceType, Currency = SelectedCurrencyType - // StartWithWindows = this.StartWithWindows; // DTO'ya eklendiğinde bu satır açılmalı }; - await _apiService.PostAsync("UserSettings/AppSettings", settingsToUpdate); - - // TODO: StartWithWindows ayarını gerçekten sisteme uygulayacak bir servis çağrısı burada yapılmalı. - // Örneğin: _startupService.SetStartup(this.StartWithWindows); - - _logger.LogInformation("Application settings have been updated by the user."); - MessageBox.Show("Settings saved successfully!", "Application Settings", MessageBoxButton.OK, MessageBoxImage.Information); + bool success = await _userStore.UpdateAppSettingsAsync(settingsToUpdate); + if (success) + { + _logger.LogInformation("Application settings have been updated by the user."); + _appInNotificationService.ShowSuccess("Application settings saved successfully!"); + } + else + { + _logger.LogError("Failed to save application settings via UserStore."); + _appInNotificationService.ShowError("An error occurred while saving your settings. Please try again."); + } } catch (Exception ex) { - _logger.LogError(ex, "Failed to save application settings."); - MessageBox.Show("An error occurred while saving your settings. Please try again.", "Error", MessageBoxButton.OK, MessageBoxImage.Error); + _logger.LogError(ex, "An exception occurred while saving application settings."); + _appInNotificationService.ShowError("An unexpected error occurred. Please try again."); } finally { diff --git a/FinTrack/ViewModels/BottomBarViewModel.cs b/FinTrack/ViewModels/BottomBarViewModel.cs index 62b85c8..4382968 100644 --- a/FinTrack/ViewModels/BottomBarViewModel.cs +++ b/FinTrack/ViewModels/BottomBarViewModel.cs @@ -1,12 +1,13 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using FinTrackForWindows.Services.StoresRefresh; using Microsoft.Extensions.Logging; using System.Windows; namespace FinTrackForWindows.ViewModels { - public partial class BottomBarViewModel : ObservableObject + public partial class BottomBarViewModel : ObservableObject, IDisposable { [ObservableProperty] private string company = string.Empty; @@ -15,19 +16,60 @@ public partial class BottomBarViewModel : ObservableObject private string version = string.Empty; [ObservableProperty] - private string lastSyncStatus = string.Empty; + private string lastSyncStatus = "Awaiting first synchronization..."; private readonly ILogger _logger; + private readonly IStoresRefresh _storesRefresh; + private readonly Timer _refreshTimer; - public BottomBarViewModel(ILogger logger) + public BottomBarViewModel(ILogger logger, IStoresRefresh storesRefresh) { _logger = logger; + _storesRefresh = storesRefresh; int year = DateTime.Now.Year; Company = $"© {year} FinTrack Inc."; - Version = "v1.0.0"; - LastSyncStatus = "Last Synchronization: Successful"; + + _storesRefresh.RefreshStarted += OnRefreshStarted; + _storesRefresh.RefreshCompleted += OnRefreshCompleted; + + _refreshTimer = new Timer( + callback: RefreshStores, + state: null, + dueTime: TimeSpan.FromSeconds(5), + period: TimeSpan.FromMinutes(5)); + } + + private void OnRefreshStarted() + { + Application.Current.Dispatcher.Invoke(() => + { + LastSyncStatus = "Synchronization in progress..."; + }); + } + + private void OnRefreshCompleted(bool success) + { + Application.Current.Dispatcher.Invoke(() => + { + if (success) + { + LastSyncStatus = $"Last Synchronization: Successful at {DateTime.Now:HH:mm:ss}"; + _logger.LogInformation("Synchronization completed successfully."); + } + else + { + LastSyncStatus = $"Last Synchronization: Failed at {DateTime.Now:HH:mm:ss}"; + _logger.LogWarning("Synchronization failed."); + } + }); + } + + private async void RefreshStores(object? state) + { + _logger.LogInformation("Periodic synchronization triggered."); + await _storesRefresh.RefreshAllStoresAsync(); } [RelayCommand] @@ -36,5 +78,13 @@ private void AddNewTransaction() _logger.LogInformation("Adding new transaction..."); MessageBox.Show("The add new transaction feature is not implemented yet.", "Information", MessageBoxButton.OK, MessageBoxImage.Information); } + + public void Dispose() + { + _refreshTimer?.Dispose(); + _storesRefresh.RefreshStarted -= OnRefreshStarted; + _storesRefresh.RefreshCompleted -= OnRefreshCompleted; + GC.SuppressFinalize(this); + } } } diff --git a/FinTrack/ViewModels/BudgetViewModel.cs b/FinTrack/ViewModels/BudgetViewModel.cs index 806f7d9..0db22e8 100644 --- a/FinTrack/ViewModels/BudgetViewModel.cs +++ b/FinTrack/ViewModels/BudgetViewModel.cs @@ -72,7 +72,6 @@ public BudgetViewModel(IBudgetStore budgetStore, ILogger logger private async Task InitializeViewModelAsync() { await LoadCategoriesAsync(); - await _budgetStore.LoadBudgetsAsync(); PrepareForNewBudget(); } diff --git a/FinTrack/ViewModels/CurrenciesViewModel.cs b/FinTrack/ViewModels/CurrenciesViewModel.cs index 0098957..89e0d85 100644 --- a/FinTrack/ViewModels/CurrenciesViewModel.cs +++ b/FinTrack/ViewModels/CurrenciesViewModel.cs @@ -16,6 +16,7 @@ using Microsoft.Extensions.Logging; using SkiaSharp; using System.Collections.ObjectModel; +using System.Collections.Specialized; using System.Globalization; namespace FinTrackForWindows.ViewModels @@ -86,7 +87,9 @@ public CurrenciesViewModel(ILogger logger, ICurrenciesStore _currenciesStore = currenciesStore; InitializeEmptyChart(); - _ = InitializeDataAsync(); + InitializeData(); + + _currenciesStore.CurrenciesChanged += OnCurrenciesStoreChanged; } partial void OnCurrencySearchChanged(string value) @@ -104,14 +107,13 @@ async partial void OnSelectedCurrencyChanged(CurrencyModel? value) await LoadHistoricalDataAsync(value.ToCurrencyCode); } - private async Task InitializeDataAsync() + private void InitializeData() { try { - await _currenciesStore.LoadCurrenciesAsync(); FilterCurrencies(); - if (FilteredCurrencies.Any()) + if (FilteredCurrencies.Any() && SelectedCurrency == null) { SelectedCurrency = FilteredCurrencies.First(); } @@ -376,5 +378,26 @@ private void FilterCurrencies() } _logger.LogInformation("Currencies filtered by search text: '{SearchText}'.", CurrencySearch); } + + private void OnCurrenciesStoreChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + App.Current.Dispatcher.Invoke(() => + { + _logger.LogInformation("CurrenciesStore has been updated in the background. Re-filtering the list."); + var previouslySelectedCode = SelectedCurrency?.ToCurrencyCode; + FilterCurrencies(); + + if (previouslySelectedCode != null) + { + SelectedCurrency = FilteredCurrencies.FirstOrDefault(c => c.ToCurrencyCode == previouslySelectedCode); + } + + if (SelectedCurrency == null && FilteredCurrencies.Any()) + { + SelectedCurrency = FilteredCurrencies.FirstOrDefault(); + } + }); + } + } } \ No newline at end of file diff --git a/FinTrack/ViewModels/DashboardViewModel.cs b/FinTrack/ViewModels/DashboardViewModel.cs index 1860168..ad4d7c5 100644 --- a/FinTrack/ViewModels/DashboardViewModel.cs +++ b/FinTrack/ViewModels/DashboardViewModel.cs @@ -120,25 +120,17 @@ public DashboardViewModel( _transactionStore.TransactionsChanged += OnTransactionsChanged; _membershipStore.CurrentUserMembershipChanged += OnMembershipChanged; _debtStore.DebtsChanged += OnDebtsChanged; + _currenciesStore.CurrenciesChanged += OnCurrenciesChanged; if (SessionManager.IsLoggedIn) { - _logger.LogInformation("Kullanıcı zaten giriş yapmış. DashboardViewModel verileri yüklüyor."); + _logger.LogInformation("The user is already logged in. DashboardViewModel is loading data."); _ = LoadInitialDataAsync(); } } private async Task LoadInitialDataAsync() { - await Task.WhenAll( - _budgetStore.LoadBudgetsAsync(), - _accountStore.LoadAccountsAsync(), - _transactionStore.LoadTransactionsAsync(), - _currenciesStore.LoadCurrenciesAsync(), - _membershipStore.LoadAllMembershipDataAsync(), - _debtStore.LoadDebtsAsync() - ); - RefreshDashboardBudgets(); RefreshDashboardAccounts(); RefreshDashboardTransactions(); @@ -392,6 +384,16 @@ private void RefreshDashboardCurrencies() } } + private void OnCurrenciesChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + App.Current.Dispatcher.Invoke(() => + { + _logger.LogInformation("CurrenciesStore changed, refreshing dashboard currencies and total balance."); + RefreshDashboardCurrencies(); + _ = CalculateTotalBalance(); + }); + } + // ------ private void RefreshDashboardMembership() diff --git a/FinTrack/ViewModels/NotificationSettingsContentViewModel.cs b/FinTrack/ViewModels/NotificationSettingsContentViewModel.cs index 7cd5b53..cc42268 100644 --- a/FinTrack/ViewModels/NotificationSettingsContentViewModel.cs +++ b/FinTrack/ViewModels/NotificationSettingsContentViewModel.cs @@ -3,10 +3,10 @@ using FinTrackForWindows.Dtos.SettingsDtos; using FinTrackForWindows.Enums; using FinTrackForWindows.Models.Settings; -using FinTrackForWindows.Services.Api; -using FinTrackForWindows.Services.AppInNotifications; +using FinTrackForWindows.Services.Users; using Microsoft.Extensions.Logging; using System.Collections.ObjectModel; +using System.Linq; namespace FinTrackForWindows.ViewModels { @@ -14,106 +14,68 @@ public partial class NotificationSettingsContentViewModel : ObservableObject { public ObservableCollection EmailNotificationSettings { get; } - [ObservableProperty] - private bool _enableDesktopNotifications; - - [ObservableProperty] - private bool _isLoading; - - [ObservableProperty] - [NotifyCanExecuteChangedFor(nameof(SaveChangesCommand))] - private bool _isSaving; + [ObservableProperty] private bool _enableDesktopNotifications; + [ObservableProperty] private bool _isLoading; + [ObservableProperty, NotifyCanExecuteChangedFor(nameof(SaveChangesCommand))] private bool _isSaving; private readonly ILogger _logger; - private readonly IApiService _apiService; - private readonly IAppInNotificationService _appInNotificationService; + private readonly IUserStore _userStore; - public NotificationSettingsContentViewModel(ILogger logger, IApiService apiService, IAppInNotificationService appInNotificationService) + public NotificationSettingsContentViewModel(ILogger logger, IUserStore userStore) { _logger = logger; - _apiService = apiService; - _appInNotificationService = appInNotificationService; - + _userStore = userStore; EmailNotificationSettings = new ObservableCollection(); - _ = LoadSettings(); + + _userStore.UserChanged += OnUserChanged; + LoadDataFromStore(); } - private async Task LoadSettings() + private void OnUserChanged() { - IsLoading = true; - try - { - var settingsDto = await _apiService.GetAsync("UserSettings/UserNotificationSettings"); - EmailNotificationSettings.Clear(); + LoadDataFromStore(); + } - if (settingsDto != null) - { - // DTO'dan Model Listesine Mapping - EmailNotificationSettings.Add(new NotificationSettingItemModel(NotificationSettingsType.SpendingLimitWarning, settingsDto.SpendingLimitWarning)); - EmailNotificationSettings.Add(new NotificationSettingItemModel(NotificationSettingsType.ExpectedBillReminder, settingsDto.ExpectedBillReminder)); - EmailNotificationSettings.Add(new NotificationSettingItemModel(NotificationSettingsType.WeeklySpendingSummary, settingsDto.WeeklySpendingSummary)); - EmailNotificationSettings.Add(new NotificationSettingItemModel(NotificationSettingsType.NewFeaturesAndAnnouncements, settingsDto.NewFeaturesAndAnnouncements)); - EnableDesktopNotifications = settingsDto.EnableDesktopNotifications; - } - else - { - // API'den veri gelmezse varsayılan değerlerle doldur - InitializeDefaultSettings(); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to load notification settings."); - _appInNotificationService.ShowError("Failed to load notification settings. Default values will be used.", ex); - InitializeDefaultSettings(); - } - finally + private void LoadDataFromStore() + { + IsLoading = true; + EmailNotificationSettings.Clear(); + if (_userStore.CurrentUser != null) { - IsLoading = false; + EmailNotificationSettings.Add(new NotificationSettingItemModel(NotificationSettingsType.SpendingLimitWarning, _userStore.CurrentUser.SpendingLimitWarning)); + EmailNotificationSettings.Add(new NotificationSettingItemModel(NotificationSettingsType.ExpectedBillReminder, _userStore.CurrentUser.ExpectedBillReminder)); + EmailNotificationSettings.Add(new NotificationSettingItemModel(NotificationSettingsType.WeeklySpendingSummary, _userStore.CurrentUser.WeeklySpendingSummary)); + EmailNotificationSettings.Add(new NotificationSettingItemModel(NotificationSettingsType.NewFeaturesAndAnnouncements, _userStore.CurrentUser.NewFeaturesAndAnnouncements)); + EnableDesktopNotifications = _userStore.CurrentUser.EnableDesktopNotifications; } + IsLoading = false; } [RelayCommand(CanExecute = nameof(CanSaveChanges))] private async Task SaveChanges() { IsSaving = true; - try + var dto = new UserNotificationSettingsUpdateDto { - var settingsUpdateDto = new UserNotificationSettingsUpdateDto - { - EnableDesktopNotifications = this.EnableDesktopNotifications, - SpendingLimitWarning = EmailNotificationSettings.FirstOrDefault(s => s.SettingType == NotificationSettingsType.SpendingLimitWarning)?.IsEnabled ?? false, - ExpectedBillReminder = EmailNotificationSettings.FirstOrDefault(s => s.SettingType == NotificationSettingsType.ExpectedBillReminder)?.IsEnabled ?? false, - WeeklySpendingSummary = EmailNotificationSettings.FirstOrDefault(s => s.SettingType == NotificationSettingsType.WeeklySpendingSummary)?.IsEnabled ?? false, - NewFeaturesAndAnnouncements = EmailNotificationSettings.FirstOrDefault(s => s.SettingType == NotificationSettingsType.NewFeaturesAndAnnouncements)?.IsEnabled ?? false - }; + EnableDesktopNotifications = this.EnableDesktopNotifications, + SpendingLimitWarning = EmailNotificationSettings.FirstOrDefault(s => s.SettingType == NotificationSettingsType.SpendingLimitWarning)?.IsEnabled ?? false, + ExpectedBillReminder = EmailNotificationSettings.FirstOrDefault(s => s.SettingType == NotificationSettingsType.ExpectedBillReminder)?.IsEnabled ?? false, + WeeklySpendingSummary = EmailNotificationSettings.FirstOrDefault(s => s.SettingType == NotificationSettingsType.WeeklySpendingSummary)?.IsEnabled ?? false, + NewFeaturesAndAnnouncements = EmailNotificationSettings.FirstOrDefault(s => s.SettingType == NotificationSettingsType.NewFeaturesAndAnnouncements)?.IsEnabled ?? false + }; - await _apiService.PostAsync("UserSettings/UserNotificationSettings", settingsUpdateDto); - - _logger.LogInformation("User notification settings have been successfully saved."); - _appInNotificationService.ShowInfo("Your notification settings have been saved successfully."); - } - catch (Exception ex) + bool success = await _userStore.UpdateNotificationSettingsAsync(dto); + if (success) { - _logger.LogError(ex, "Failed to save notification settings."); - _appInNotificationService.ShowError("Failed to save notification settings.", ex); + _logger.LogInformation("Notification settings saved successfully."); } - finally + else { - IsSaving = false; + _logger.LogError("Failed to save notification settings."); } + IsSaving = false; } private bool CanSaveChanges() => !IsSaving; - - private void InitializeDefaultSettings() - { - EmailNotificationSettings.Clear(); - EmailNotificationSettings.Add(new NotificationSettingItemModel(NotificationSettingsType.SpendingLimitWarning, true)); - EmailNotificationSettings.Add(new NotificationSettingItemModel(NotificationSettingsType.ExpectedBillReminder, true)); - EmailNotificationSettings.Add(new NotificationSettingItemModel(NotificationSettingsType.WeeklySpendingSummary, false)); - EmailNotificationSettings.Add(new NotificationSettingItemModel(NotificationSettingsType.NewFeaturesAndAnnouncements, false)); - EnableDesktopNotifications = true; - } } } \ No newline at end of file diff --git a/FinTrack/ViewModels/ProfileSettingsContentViewModel.cs b/FinTrack/ViewModels/ProfileSettingsContentViewModel.cs index f4ecf28..6716bde 100644 --- a/FinTrack/ViewModels/ProfileSettingsContentViewModel.cs +++ b/FinTrack/ViewModels/ProfileSettingsContentViewModel.cs @@ -1,60 +1,107 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using FinTrackForWindows.Dtos.SettingsDtos; -using FinTrackForWindows.Services.Api; using FinTrackForWindows.Services.AppInNotifications; +using FinTrackForWindows.Services.Dialog; +using FinTrackForWindows.Services.Users; using Microsoft.Extensions.Logging; +using System.Linq; namespace FinTrackForWindows.ViewModels { public partial class ProfileSettingsContentViewModel : ObservableObject { - [ObservableProperty] - private string fullName = string.Empty; - - [ObservableProperty] - private string email = string.Empty; - - [ObservableProperty] - private string profilePhotoUrl = string.Empty; + [ObservableProperty] private string firstName = string.Empty; + [ObservableProperty] private string lastName = string.Empty; + [ObservableProperty] private string email = string.Empty; + [ObservableProperty] private string profilePhotoUrl = string.Empty; + [ObservableProperty, NotifyCanExecuteChangedFor(nameof(SaveChangesCommand))] private bool _isSaving; private readonly ILogger _logger; - - private readonly IApiService _apiService; - + private readonly IUserStore _userStore; + private readonly IDialogService _dialogService; private readonly IAppInNotificationService _appInNotificationService; - public ProfileSettingsContentViewModel(ILogger logger, IApiService apiService, IAppInNotificationService appInNotificationService) + public ProfileSettingsContentViewModel(ILogger logger, IUserStore userStore, IDialogService dialogService, IAppInNotificationService appInNotificationService) { _logger = logger; - _apiService = apiService; + _userStore = userStore; + _dialogService = dialogService; _appInNotificationService = appInNotificationService; - _ = LoadProfileData(); + _userStore.UserChanged += OnUserChanged; + LoadDataFromStore(); + } + + private void OnUserChanged() + { + LoadDataFromStore(); } - private async Task LoadProfileData() + private void LoadDataFromStore() { - var profile = await _apiService.GetAsync("UserSettings/ProfileSettings"); - if (profile != null) + if (_userStore.CurrentUser != null) { - FullName = profile.FullName; - Email = profile.Email; - ProfilePhotoUrl = profile.ProfilePictureUrl ?? "N/A"; + var nameParts = _userStore.CurrentUser.UserName.Split('_'); + FirstName = nameParts.FirstOrDefault() ?? string.Empty; + LastName = nameParts.Length > 1 ? nameParts.Last() : string.Empty; + Email = _userStore.CurrentUser.Email; + ProfilePhotoUrl = _userStore.CurrentUser.ProfilePictureUrl ?? string.Empty; } } [RelayCommand] - private async Task ProfileSettingsContentSaveChanges() + private async Task RequestEmailChange() { - await _apiService.PostAsync("UserSettings/ProfileSettings", new ProfileSettingsUpdateDto + string newEmail = this.Email; + if (newEmail == _userStore.CurrentUser?.Email) + { + _appInNotificationService.ShowWarning("The new email address cannot be the same as the current one."); + return; + } + + await _userStore.RequestEmailChangeOtpAsync(); + var otp = _dialogService.ShowOtpDialog(newEmail); + + if (!string.IsNullOrEmpty(otp)) { - FullName = FullName, - Email = Email, - ProfilePictureUrl = ProfilePhotoUrl - }); - _logger.LogInformation("New profile information saved: {FullName}, {Email}, {ProfilePhotoUrl}", FullName, Email, ProfilePhotoUrl); - _appInNotificationService.ShowSuccess("Your profile information has been successfully saved."); + var dto = new UpdateUserEmailDto { NewEmail = newEmail, OtpCode = otp }; + bool success = await _userStore.UpdateUserEmailAsync(dto); + if (success) + { + _appInNotificationService.ShowSuccess("Your email has been changed successfully."); + } + else + { + _appInNotificationService.ShowError("Failed to change your email. The OTP may be incorrect or expired."); + } + } } + + [RelayCommand(CanExecute = nameof(CanSaveChanges))] + private async Task SaveChanges() + { + IsSaving = true; + + var nameDto = new UpdateUserNameDto { FirstName = this.FirstName, LastName = this.LastName }; + var picDto = new UpdateProfilePictureDto { ProfilePictureUrl = this.ProfilePhotoUrl }; + + bool nameSuccess = await _userStore.UpdateUserNameAsync(nameDto); + bool picSuccess = await _userStore.UpdateProfilePictureAsync(picDto); + + if (nameSuccess && picSuccess) + { + _logger.LogInformation("Profile settings saved successfully."); + _appInNotificationService.ShowSuccess("Your profile has been updated."); + } + else + { + _logger.LogError("Failed to save one or more profile settings."); + _appInNotificationService.ShowError("There was an issue saving your profile."); + } + IsSaving = false; + } + + private bool CanSaveChanges() => !IsSaving; } -} +} \ No newline at end of file diff --git a/FinTrack/ViewModels/SecuritySettingsContentViewModel.cs b/FinTrack/ViewModels/SecuritySettingsContentViewModel.cs index 846c0bb..b09705b 100644 --- a/FinTrack/ViewModels/SecuritySettingsContentViewModel.cs +++ b/FinTrack/ViewModels/SecuritySettingsContentViewModel.cs @@ -1,33 +1,55 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using FinTrackForWindows.Dtos.SettingsDtos; using FinTrackForWindows.Services.AppInNotifications; +using FinTrackForWindows.Services.Users; using Microsoft.Extensions.Logging; namespace FinTrackForWindows.ViewModels { public partial class SecuritySettingsContentViewModel : ObservableObject { - [ObservableProperty] - private string currentPassword = string.Empty; - - [ObservableProperty] - private string newPassword = string.Empty; - - readonly ILogger _logger; + [ObservableProperty] private string currentPassword = string.Empty; + [ObservableProperty] private string newPassword = string.Empty; + [ObservableProperty, NotifyCanExecuteChangedFor(nameof(SaveChangesCommand))] private bool _isSaving; + private readonly ILogger _logger; private readonly IAppInNotificationService _appInNotificationService; + private readonly IUserStore _userStore; - public SecuritySettingsContentViewModel(ILogger logger, IAppInNotificationService appInNotificationService) + public SecuritySettingsContentViewModel(ILogger logger, IAppInNotificationService appInNotificationService, IUserStore userStore) { _logger = logger; _appInNotificationService = appInNotificationService; + _userStore = userStore; } - [RelayCommand] - private void SecuritySettingsContentSaveChanges() + [RelayCommand(CanExecute = nameof(CanSaveChanges))] + private async Task SaveChanges() { - _logger.LogInformation("Security settings saved."); - _appInNotificationService.ShowSuccess("Security settings saved."); + IsSaving = true; + var dto = new UpdateUserPasswordDto + { + CurrentPassword = this.CurrentPassword, + NewPassword = this.NewPassword + }; + + bool success = await _userStore.UpdateUserPasswordAsync(dto); + if (success) + { + _logger.LogInformation("Password updated successfully."); + _appInNotificationService.ShowSuccess("Your password has been changed successfully."); + CurrentPassword = string.Empty; + NewPassword = string.Empty; + } + else + { + _logger.LogError("Failed to update password."); + _appInNotificationService.ShowError("Failed to change your password. Please check your current password."); + } + IsSaving = false; } + + private bool CanSaveChanges() => !IsSaving; } -} +} \ No newline at end of file diff --git a/FinTrack/ViewModels/TopBarViewModel.cs b/FinTrack/ViewModels/TopBarViewModel.cs index c68dd78..d9e10a7 100644 --- a/FinTrack/ViewModels/TopBarViewModel.cs +++ b/FinTrack/ViewModels/TopBarViewModel.cs @@ -65,12 +65,12 @@ private async Task LoadProfile() if (userProfile != null) { - UserAvatar = userProfile.ProfilePictureUrl; + UserAvatar = userProfile.ProfilePictureUrl ?? string.Empty; UserFullName = userProfile.UserName; UserEmail = userProfile.Email; - UserMembershipType = $"{userProfile.MembershipPlan} Member"; + UserMembershipType = $"{userProfile.CurrentMembershipPlanType} Member"; _logger.LogInformation("User profile loaded successfully. Name: {UserName}, Email: {Email}, Membership Type: {MembershipType}", - userProfile.UserName, userProfile.Email, userProfile.MembershipPlan); + userProfile.UserName, userProfile.Email, userProfile.CurrentMembershipPlanType); } else { diff --git a/FinTrack/ViewModels/TransactionsViewModel.cs b/FinTrack/ViewModels/TransactionsViewModel.cs index d76bc89..abca109 100644 --- a/FinTrack/ViewModels/TransactionsViewModel.cs +++ b/FinTrack/ViewModels/TransactionsViewModel.cs @@ -523,9 +523,7 @@ private async Task AddNewCategory() private async Task LoadData() { - await _accountStore.LoadAccountsAsync(); await LoadCategories(); - await _transactionStore.LoadTransactionsAsync(); } } } \ No newline at end of file diff --git a/FinTrack/Views/AppSettingsContentView.xaml b/FinTrack/Views/AppSettingsContentView.xaml index 64ff1e6..19db546 100644 --- a/FinTrack/Views/AppSettingsContentView.xaml +++ b/FinTrack/Views/AppSettingsContentView.xaml @@ -37,11 +37,13 @@ - + + -