Skip to content

Commit c3176b3

Browse files
Merge pull request #442 from TransactionProcessing/copilot/add-merchant-creation-screen
Add merchant creation and management screens with tabbed interface
2 parents 436679e + 34f0f2f commit c3176b3

6 files changed

Lines changed: 1533 additions & 1 deletion

File tree

EstateManagementUI.BlazorServer/Components/Pages/Merchants/Edit.razor

Lines changed: 916 additions & 0 deletions
Large diffs are not rendered by default.

EstateManagementUI.BlazorServer/Components/Pages/Merchants/Index.razor

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
@page "/merchants"
2+
@rendermode InteractiveServer
23
@inject IMediator Mediator
34
@inject NavigationManager NavigationManager
45

Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
@page "/merchants/new"
2+
@rendermode InteractiveServer
3+
@using System.ComponentModel.DataAnnotations
4+
@inject IMediator Mediator
5+
@inject NavigationManager NavigationManager
6+
7+
<PageTitle>Create New Merchant</PageTitle>
8+
9+
<div class="space-y-6">
10+
<!-- Page Header -->
11+
<div class="flex items-center justify-between">
12+
<div>
13+
<h1 class="text-2xl font-bold text-gray-900">Create New Merchant</h1>
14+
<p class="text-gray-600 mt-1">Add a new merchant to the estate</p>
15+
</div>
16+
<button class="btn btn-secondary" @onclick="Cancel">
17+
Cancel
18+
</button>
19+
</div>
20+
21+
@if (!string.IsNullOrWhiteSpace(errorMessage))
22+
{
23+
<div class="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-lg">
24+
<p class="font-medium">Error</p>
25+
<p class="text-sm">@errorMessage</p>
26+
</div>
27+
}
28+
29+
<!-- Form -->
30+
<div class="bg-white rounded-lg shadow-md p-6">
31+
<EditForm Model="@model" OnValidSubmit="@HandleSubmit">
32+
<DataAnnotationsValidator />
33+
34+
<!-- Merchant Details Section -->
35+
<div class="mb-6">
36+
<h3 class="text-lg font-semibold text-gray-900 mb-4">Merchant Details</h3>
37+
<div class="space-y-4">
38+
<div>
39+
<label class="block text-sm font-medium text-gray-700 mb-1">
40+
Merchant Name <span class="text-red-500">*</span>
41+
</label>
42+
<InputText @bind-Value="model.MerchantName" class="input w-full" placeholder="Enter merchant name" />
43+
<ValidationMessage For="@(() => model.MerchantName)" class="text-red-600 text-sm mt-1" />
44+
</div>
45+
46+
<div>
47+
<label class="block text-sm font-medium text-gray-700 mb-1">
48+
Settlement Schedule <span class="text-red-500">*</span>
49+
</label>
50+
<InputSelect @bind-Value="model.SettlementSchedule" class="input w-full">
51+
<option value="">Select a schedule...</option>
52+
<option value="Immediate">Immediate</option>
53+
<option value="Weekly">Weekly</option>
54+
<option value="Monthly">Monthly</option>
55+
</InputSelect>
56+
<ValidationMessage For="@(() => model.SettlementSchedule)" class="text-red-600 text-sm mt-1" />
57+
</div>
58+
</div>
59+
</div>
60+
61+
<!-- Address Section -->
62+
<div class="mb-6">
63+
<h3 class="text-lg font-semibold text-gray-900 mb-4">Address</h3>
64+
<div class="space-y-4">
65+
<div>
66+
<label class="block text-sm font-medium text-gray-700 mb-1">
67+
Address Line 1 <span class="text-red-500">*</span>
68+
</label>
69+
<InputText @bind-Value="model.AddressLine1" class="input w-full" placeholder="Enter address line 1" />
70+
<ValidationMessage For="@(() => model.AddressLine1)" class="text-red-600 text-sm mt-1" />
71+
</div>
72+
73+
<div>
74+
<label class="block text-sm font-medium text-gray-700 mb-1">
75+
Address Line 2
76+
</label>
77+
<InputText @bind-Value="model.AddressLine2" class="input w-full" placeholder="Enter address line 2 (optional)" />
78+
</div>
79+
80+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
81+
<div>
82+
<label class="block text-sm font-medium text-gray-700 mb-1">
83+
Town <span class="text-red-500">*</span>
84+
</label>
85+
<InputText @bind-Value="model.Town" class="input w-full" placeholder="Enter town" />
86+
<ValidationMessage For="@(() => model.Town)" class="text-red-600 text-sm mt-1" />
87+
</div>
88+
89+
<div>
90+
<label class="block text-sm font-medium text-gray-700 mb-1">
91+
Region <span class="text-red-500">*</span>
92+
</label>
93+
<InputText @bind-Value="model.Region" class="input w-full" placeholder="Enter region" />
94+
<ValidationMessage For="@(() => model.Region)" class="text-red-600 text-sm mt-1" />
95+
</div>
96+
</div>
97+
98+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
99+
<div>
100+
<label class="block text-sm font-medium text-gray-700 mb-1">
101+
PostCode <span class="text-red-500">*</span>
102+
</label>
103+
<InputText @bind-Value="model.PostCode" class="input w-full" placeholder="Enter postcode" />
104+
<ValidationMessage For="@(() => model.PostCode)" class="text-red-600 text-sm mt-1" />
105+
</div>
106+
107+
<div>
108+
<label class="block text-sm font-medium text-gray-700 mb-1">
109+
Country <span class="text-red-500">*</span>
110+
</label>
111+
<InputText @bind-Value="model.Country" class="input w-full" placeholder="Enter country" />
112+
<ValidationMessage For="@(() => model.Country)" class="text-red-600 text-sm mt-1" />
113+
</div>
114+
</div>
115+
</div>
116+
</div>
117+
118+
<!-- Contact Section -->
119+
<div class="mb-6">
120+
<h3 class="text-lg font-semibold text-gray-900 mb-4">Contact</h3>
121+
<div class="space-y-4">
122+
<div>
123+
<label class="block text-sm font-medium text-gray-700 mb-1">
124+
Contact Name <span class="text-red-500">*</span>
125+
</label>
126+
<InputText @bind-Value="model.ContactName" class="input w-full" placeholder="Enter contact name" />
127+
<ValidationMessage For="@(() => model.ContactName)" class="text-red-600 text-sm mt-1" />
128+
</div>
129+
130+
<div>
131+
<label class="block text-sm font-medium text-gray-700 mb-1">
132+
Email Address <span class="text-red-500">*</span>
133+
</label>
134+
<InputText @bind-Value="model.EmailAddress" type="email" class="input w-full" placeholder="Enter email address" />
135+
<ValidationMessage For="@(() => model.EmailAddress)" class="text-red-600 text-sm mt-1" />
136+
</div>
137+
138+
<div>
139+
<label class="block text-sm font-medium text-gray-700 mb-1">
140+
Phone Number <span class="text-red-500">*</span>
141+
</label>
142+
<InputText @bind-Value="model.PhoneNumber" type="tel" class="input w-full" placeholder="Enter phone number" />
143+
<ValidationMessage For="@(() => model.PhoneNumber)" class="text-red-600 text-sm mt-1" />
144+
</div>
145+
</div>
146+
</div>
147+
148+
<!-- Submit Button -->
149+
<div class="flex justify-end space-x-4 pt-4 border-t">
150+
<button type="button" class="btn btn-secondary" @onclick="Cancel" disabled="@isSaving">
151+
Cancel
152+
</button>
153+
<button type="submit" class="btn btn-primary" disabled="@isSaving">
154+
@if (isSaving)
155+
{
156+
<span class="inline-block animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></span>
157+
<span>Saving...</span>
158+
}
159+
else
160+
{
161+
<span>Create Merchant</span>
162+
}
163+
</button>
164+
</div>
165+
</EditForm>
166+
</div>
167+
</div>
168+
169+
@code {
170+
private CreateMerchantModel model = new();
171+
private bool isSaving = false;
172+
private string? errorMessage;
173+
174+
private async Task HandleSubmit()
175+
{
176+
isSaving = true;
177+
errorMessage = null;
178+
179+
try
180+
{
181+
var correlationId = new CorrelationId(Guid.NewGuid());
182+
var estateId = Guid.Parse("11111111-1111-1111-1111-111111111111");
183+
var accessToken = "stubbed-token";
184+
185+
// Create merchant
186+
var createCommand = new Commands.CreateMerchantCommand(
187+
correlationId,
188+
accessToken,
189+
estateId,
190+
model.MerchantName!,
191+
model.ContactName!,
192+
model.EmailAddress!
193+
);
194+
195+
var createResult = await Mediator.Send(createCommand);
196+
197+
if (!createResult.IsSuccess)
198+
{
199+
errorMessage = createResult.Message ?? "Failed to create merchant";
200+
return;
201+
}
202+
203+
// Get the newly created merchant ID (in real implementation, this would be returned from the create command)
204+
var merchantId = Guid.NewGuid();
205+
206+
// Update address
207+
var addressCommand = new Commands.UpdateMerchantAddressCommand(
208+
correlationId,
209+
accessToken,
210+
estateId,
211+
merchantId,
212+
model.AddressLine1!,
213+
model.Town!,
214+
model.Region!,
215+
model.PostCode!,
216+
model.Country!
217+
);
218+
219+
await Mediator.Send(addressCommand);
220+
221+
// Update contact (including phone)
222+
var contactCommand = new Commands.UpdateMerchantContactCommand(
223+
correlationId,
224+
accessToken,
225+
estateId,
226+
merchantId,
227+
model.ContactName!,
228+
model.EmailAddress!,
229+
model.PhoneNumber!
230+
);
231+
232+
await Mediator.Send(contactCommand);
233+
234+
// Set settlement schedule
235+
var scheduleCommand = new Commands.SetMerchantSettlementScheduleCommand(
236+
correlationId,
237+
accessToken,
238+
estateId,
239+
merchantId,
240+
model.SettlementSchedule!
241+
);
242+
243+
await Mediator.Send(scheduleCommand);
244+
245+
// Navigate to edit page
246+
NavigationManager.NavigateTo($"/merchants/{merchantId}/edit");
247+
}
248+
catch (Exception ex)
249+
{
250+
errorMessage = $"An error occurred: {ex.Message}";
251+
}
252+
finally
253+
{
254+
isSaving = false;
255+
}
256+
}
257+
258+
private void Cancel()
259+
{
260+
NavigationManager.NavigateTo("/merchants");
261+
}
262+
263+
public class CreateMerchantModel
264+
{
265+
[Required(ErrorMessage = "Merchant name is required")]
266+
public string? MerchantName { get; set; }
267+
268+
[Required(ErrorMessage = "Settlement schedule is required")]
269+
public string? SettlementSchedule { get; set; }
270+
271+
[Required(ErrorMessage = "Address line 1 is required")]
272+
public string? AddressLine1 { get; set; }
273+
274+
public string? AddressLine2 { get; set; }
275+
276+
[Required(ErrorMessage = "Town is required")]
277+
public string? Town { get; set; }
278+
279+
[Required(ErrorMessage = "Region is required")]
280+
public string? Region { get; set; }
281+
282+
[Required(ErrorMessage = "PostCode is required")]
283+
public string? PostCode { get; set; }
284+
285+
[Required(ErrorMessage = "Country is required")]
286+
public string? Country { get; set; }
287+
288+
[Required(ErrorMessage = "Contact name is required")]
289+
public string? ContactName { get; set; }
290+
291+
[Required(ErrorMessage = "Email address is required")]
292+
[EmailAddress(ErrorMessage = "Invalid email address")]
293+
public string? EmailAddress { get; set; }
294+
295+
[Required(ErrorMessage = "Phone number is required")]
296+
public string? PhoneNumber { get; set; }
297+
}
298+
}

0 commit comments

Comments
 (0)