From 681534ebf0ed8190276b2824635aee4e03b4bb20 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Mon, 26 Jan 2026 10:38:45 +0000 Subject: [PATCH] Refactor merchant deposit logic and improve UX Refactored the "Make Merchant Deposit" feature to move business logic and permission checks into a new code-behind file (Deposit.razor.cs), improving separation of concerns. Moved MakeMerchantDepositCommand to MerchantCommands and updated its usage throughout the codebase. Added API client support for deposits and improved user feedback by displaying a success message before redirecting. Updated test and request handlers to match the new structure. --- .../Components/Pages/Merchants/Deposit.razor | 143 ++---------------- .../Pages/Merchants/Deposit.razor.cs | 132 ++++++++++++++++ .../Client/MerchantMethods.cs | 16 ++ .../RequestHandlers/DateRequestHandler.cs | 6 +- .../Requests/Requests.cs | 3 +- .../Services/TestMediatorService.cs | 4 +- 6 files changed, 164 insertions(+), 140 deletions(-) create mode 100644 EstateManagementUI.BlazorServer/Components/Pages/Merchants/Deposit.razor.cs diff --git a/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Deposit.razor b/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Deposit.razor index a5859f89..c6faaa94 100644 --- a/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Deposit.razor +++ b/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Deposit.razor @@ -10,24 +10,6 @@ Make Merchant Deposit -@if (!hasPermission) -{ -
-
- - - -

Access Denied

-

You don't have permission to make deposits.

-

This operation requires the MakeDeposit permission for the Merchant section.

- -
-
-} -else -{
@@ -51,6 +33,14 @@ else
} + @if (!string.IsNullOrWhiteSpace(successMessage)) + { +
+

Success

+

@successMessage

+
+ } +
@@ -103,119 +93,4 @@ else
- -} - -@code { - [Parameter] - public Guid MerchantId { get; set; } - - private DepositModel model = new(); - private bool isSaving = false; - private string? errorMessage; - private string? merchantName; - private bool hasPermission = false; - - protected override async Task OnInitializedAsync() - { - await RequirePermission(PermissionSection.Merchant, PermissionFunction.MakeDeposit); - - // Set default date to today - model.Date = DateTime.Today; - - // Load merchant name - try - { - var correlationId = new CorrelationId(Guid.NewGuid()); - var estateId = Guid.Parse("11111111-1111-1111-1111-111111111111"); - var accessToken = "stubbed-token"; - - var result = await Mediator.Send(new MerchantQueries.GetMerchantQuery(correlationId, estateId, MerchantId)); - if (result.IsSuccess && result.Data != null) - { - merchantName = result.Data.MerchantName; - } - } - catch (Exception ex) - { - errorMessage = $"Failed to load merchant details: {ex.Message}"; - } - } - - private async Task HandleSubmit() - { - isSaving = true; - errorMessage = null; - - try - { - var correlationId = new CorrelationId(Guid.NewGuid()); - var estateId = Guid.Parse("11111111-1111-1111-1111-111111111111"); - var accessToken = "stubbed-token"; - - var command = new Commands.MakeMerchantDepositCommand( - correlationId, - accessToken, - estateId, - MerchantId, - model.Amount, - model.Date, - model.Reference! - ); - - var result = await Mediator.Send(command); - - if (!result.IsSuccess) - { - errorMessage = result.Message ?? "Failed to make deposit"; - return; - } - - // Navigate back to merchant list on success - NavigationManager.NavigateTo("/merchants"); - } - catch (Exception ex) - { - errorMessage = $"An error occurred: {ex.Message}"; - } - finally - { - isSaving = false; - } - } - - private void Cancel() - { - NavigationManager.NavigateTo("/merchants"); - } - - public class DepositModel - { - [Required(ErrorMessage = "Deposit amount is required")] - [Range(1, int.MaxValue, ErrorMessage = "Deposit amount must be greater than 0")] - public int Amount { get; set; } - - [Required(ErrorMessage = "Date of deposit is required")] - [DateNotInFuture(ErrorMessage = "Date cannot be in the future")] - public DateTime Date { get; set; } = DateTime.Today; - - [Required(ErrorMessage = "Reference is required")] - public string? Reference { get; set; } - } - - // Custom validation attribute for date not in future - private class DateNotInFutureAttribute : ValidationAttribute - { - protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) - { - if (value is DateTime date) - { - if (date.Date > DateTime.Today) - { - return new ValidationResult(ErrorMessage ?? "Date cannot be in the future"); - } - } - return ValidationResult.Success; - } - } -} + \ No newline at end of file diff --git a/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Deposit.razor.cs b/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Deposit.razor.cs new file mode 100644 index 00000000..b2ed5f03 --- /dev/null +++ b/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Deposit.razor.cs @@ -0,0 +1,132 @@ +using EstateManagementUI.BlazorServer.Permissions; +using EstateManagementUI.BusinessLogic.Requests; +using Microsoft.AspNetCore.Components; +using System.ComponentModel.DataAnnotations; + +namespace EstateManagementUI.BlazorServer.Components.Pages.Merchants +{ + public partial class Deposit + { + [Parameter] + public Guid MerchantId { get; set; } + + private DepositModel model = new(); + private bool isSaving = false; + private string? errorMessage; + private string? successMessage; + private string? merchantName; + private bool hasPermission = false; + + protected override async Task OnAfterRenderAsync(bool firstRender) { + if (!firstRender) { + await base.OnAfterRenderAsync(firstRender); + return; + } + + await RequirePermission(PermissionSection.Merchant, PermissionFunction.MakeDeposit); + + // Set default date to today + model.Date = DateTime.Today; + + // Load merchant name + try + { + var correlationId = new CorrelationId(Guid.NewGuid()); + var estateId = await this.GetEstateId(); + + var result = await Mediator.Send(new MerchantQueries.GetMerchantQuery(correlationId, estateId, MerchantId)); + if (result.IsSuccess && result.Data != null) + { + merchantName = result.Data.MerchantName; + } + } + catch (Exception ex) + { + errorMessage = $"Failed to load merchant details: {ex.Message}"; + } + } + + private async Task HandleSubmit() + { + isSaving = true; + errorMessage = null; + successMessage = null; + + try + { + var correlationId = new CorrelationId(Guid.NewGuid()); + var estateId = await this.GetEstateId(); + + var command = new MerchantCommands.MakeMerchantDepositCommand( + correlationId, + estateId, + MerchantId, + model.Amount, + model.Date, + model.Reference! + ); + + var result = await Mediator.Send(command); + + if (!result.IsSuccess) + { + errorMessage = result.Message ?? "Failed to make deposit"; + return; + } + + // Show success message briefly before navigating away + successMessage = "Deposit recorded successfully."; + StateHasChanged(); + + // Small delay so user sees confirmation (adjust duration as needed) + await Task.Delay(2500); + + // Navigate back to merchant list on success + NavigationManager.NavigateTo("/merchants"); + } + catch (Exception ex) + { + errorMessage = $"An error occurred: {ex.Message}"; + } + finally + { + isSaving = false; + } + } + + private void Cancel() + { + NavigationManager.NavigateTo("/merchants"); + } + + public class DepositModel + { + [Required(ErrorMessage = "Deposit amount is required")] + [Range(1, int.MaxValue, ErrorMessage = "Deposit amount must be greater than 0")] + public int Amount { get; set; } + + [Required(ErrorMessage = "Date of deposit is required")] + [DateNotInFuture(ErrorMessage = "Date cannot be in the future")] + public DateTime Date { get; set; } = DateTime.Today; + + [Required(ErrorMessage = "Reference is required")] + public string? Reference { get; set; } + } + + // Custom validation attribute for date not in future + private class DateNotInFutureAttribute : ValidationAttribute + { + protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) + { + if (value is DateTime date) + { + if (date.Date > DateTime.Today) + { + return new ValidationResult(ErrorMessage ?? "Date cannot be in the future"); + } + } + return ValidationResult.Success; + } + } + } +} diff --git a/EstateManagmentUI.BusinessLogic/Client/MerchantMethods.cs b/EstateManagmentUI.BusinessLogic/Client/MerchantMethods.cs index 5d3965e4..ac910242 100644 --- a/EstateManagmentUI.BusinessLogic/Client/MerchantMethods.cs +++ b/EstateManagmentUI.BusinessLogic/Client/MerchantMethods.cs @@ -28,6 +28,7 @@ public partial interface IApiClient Task AddContractToMerchant(MerchantCommands.AssignContractToMerchantCommand request, CancellationToken cancellationToken); Task AddDeviceToMerchant(MerchantCommands.AddMerchantDeviceCommand request, CancellationToken cancellationToken); Task SwapMerchantDevice(MerchantCommands.SwapMerchantDeviceCommand request, CancellationToken cancellationToken); + Task MakeMerchantDeposit(MerchantCommands.MakeMerchantDepositCommand request, CancellationToken cancellationToken); } public partial class ApiClient : IApiClient { @@ -106,6 +107,21 @@ public async Task SwapMerchantDevice(MerchantCommands.SwapMerchantDevice return Result.Success(); } + public async Task MakeMerchantDeposit(MerchantCommands.MakeMerchantDepositCommand request, + CancellationToken cancellationToken) { + var token = await this.GetToken(cancellationToken); + if (token.IsFailed) + return ResultHelpers.CreateFailure(token); + + MakeMerchantDepositRequest apiRequest = new() { Amount = request.Amount, DepositDateTime = request.Date, Reference = request.Reference}; + + var apiResult = await this.TransactionProcessorClient.MakeMerchantDeposit(token.Data, request.EstateId, request.MerchantId,apiRequest, cancellationToken); + if (apiResult.IsFailed) + return ResultHelpers.CreateFailure(apiResult); + + return Result.Success(); + } + public async Task RemoveOperatorFromMerchant(MerchantCommands.RemoveOperatorFromMerchantCommand request, CancellationToken cancellationToken) { var token = await this.GetToken(cancellationToken); diff --git a/EstateManagmentUI.BusinessLogic/RequestHandlers/DateRequestHandler.cs b/EstateManagmentUI.BusinessLogic/RequestHandlers/DateRequestHandler.cs index 2c806642..9f7d0dad 100644 --- a/EstateManagmentUI.BusinessLogic/RequestHandlers/DateRequestHandler.cs +++ b/EstateManagmentUI.BusinessLogic/RequestHandlers/DateRequestHandler.cs @@ -57,7 +57,7 @@ public class MerchantRequestHandler : IRequestHandler, IRequestHandler, IRequestHandler, - IRequestHandler, + IRequestHandler, IRequestHandler, IRequestHandler, IRequestHandler, @@ -97,9 +97,9 @@ public async Task Handle(Commands.CreateMerchantCommand request, return Result.Success(); } - public async Task Handle(Commands.MakeMerchantDepositCommand request, + public async Task Handle(MerchantCommands.MakeMerchantDepositCommand request, CancellationToken cancellationToken) { - return Result.Success(); + return await this.ApiClient.MakeMerchantDeposit(request, cancellationToken); } public async Task Handle(MerchantCommands.RemoveContractFromMerchantCommand request, diff --git a/EstateManagmentUI.BusinessLogic/Requests/Requests.cs b/EstateManagmentUI.BusinessLogic/Requests/Requests.cs index d0862972..32959f4f 100644 --- a/EstateManagmentUI.BusinessLogic/Requests/Requests.cs +++ b/EstateManagmentUI.BusinessLogic/Requests/Requests.cs @@ -84,6 +84,7 @@ public record RemoveContractFromMerchantCommand(CorrelationId CorrelationId, Gui public record AssignContractToMerchantCommand(CorrelationId CorrelationId, Guid EstateId, Guid MerchantId, Guid ContractId) : IRequest; public record AddMerchantDeviceCommand(CorrelationId CorrelationId, Guid EstateId, Guid MerchantId, string DeviceIdentifier) : IRequest; public record SwapMerchantDeviceCommand(CorrelationId CorrelationId, Guid EstateId, Guid MerchantId, string OldDevice, string NewDevice) : IRequest; + public record MakeMerchantDepositCommand(CorrelationId CorrelationId, Guid EstateId, Guid MerchantId, decimal Amount, DateTime Date, string Reference) : IRequest; } public static class Commands @@ -93,7 +94,7 @@ public record CreateContractCommand(CorrelationId CorrelationId, string AccessTo public record CreateMerchantCommand(CorrelationId CorrelationId, string AccessToken, Guid EstateId, string Name, string ContactName, string ContactEmail) : IRequest; public record CreateMerchantUserCommand(CorrelationId CorrelationId, string AccessToken, Guid EstateId, Guid MerchantId, string EmailAddress, string Password) : IRequest; public record CreateOperatorCommand(CorrelationId CorrelationId, string AccessToken, Guid EstateId, string Name, bool RequireCustomMerchantNumber, bool RequireCustomTerminalNumber) : IRequest; - public record MakeMerchantDepositCommand(CorrelationId CorrelationId, string AccessToken, Guid EstateId, Guid MerchantId, decimal Amount, DateTime Date, string Reference) : IRequest; + public record UpdateOperatorCommand(CorrelationId CorrelationId, string AccessToken, Guid EstateId, Guid OperatorId, string Name, bool RequireCustomMerchantNumber, bool RequireCustomTerminalNumber) : IRequest; diff --git a/EstateManagmentUI.BusinessLogic/Services/TestMediatorService.cs b/EstateManagmentUI.BusinessLogic/Services/TestMediatorService.cs index 33217582..62f44916 100644 --- a/EstateManagmentUI.BusinessLogic/Services/TestMediatorService.cs +++ b/EstateManagmentUI.BusinessLogic/Services/TestMediatorService.cs @@ -83,7 +83,7 @@ public Task Send(IRequest request, Cancellation MerchantCommands.AddMerchantDeviceCommand => Task.FromResult((TResponse)(object)Result.Success()), MerchantCommands.SwapMerchantDeviceCommand => Task.FromResult((TResponse)(object)Result.Success()), Commands.CreateMerchantUserCommand => Task.FromResult((TResponse)(object)Result.Success()), - Commands.MakeMerchantDepositCommand cmd => Task.FromResult((TResponse)(object)this.ExecuteMakeMerchantDeposit(cmd)), + MerchantCommands.MakeMerchantDepositCommand cmd => Task.FromResult((TResponse)(object)this.ExecuteMakeMerchantDeposit(cmd)), _ => throw new NotImplementedException($"Request type {request.GetType().Name} is not implemented in test mediator") }; @@ -299,7 +299,7 @@ private Result ExecuteRemoveOperatorFromMerchant(MerchantCommands.RemoveOperator return Result.Success(); } - private Result ExecuteMakeMerchantDeposit(Commands.MakeMerchantDepositCommand cmd) + private Result ExecuteMakeMerchantDeposit(MerchantCommands.MakeMerchantDepositCommand cmd) { var merchant = this._testDataStore.GetMerchant(cmd.EstateId, cmd.MerchantId); if (merchant == null)