Skip to content

Commit 5b7a9bb

Browse files
Improve spend funds data (#100)
* added the table for stats need to still add more things * updated spend page * add approve all button and fix logic i added another function because in approve signature there is passwordComponent.ClearPassword(); each time. * fix for dan comments * Refactor signature approval workflow * add refresh button * encapsulate signature refresh process * Update commands.js recover wallet takes time to load * Update Invest.razor * add storage methods * add an option for multiple projects stats * Update Signatures.razor * fix the nesting in the methods for approve * Update Signatures.razor * Update Signatures.razor add fail safe log * remove storage for invested amount, and save it in the project * Update Signatures.razor * some minor changes * fix round down of long --------- Co-authored-by: dangershony <[email protected]>
1 parent 75d9d31 commit 5b7a9bb

File tree

7 files changed

+306
-51
lines changed

7 files changed

+306
-51
lines changed

src/Angor/Client/Components/FounderProjectItem.razor

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
<div class="card mb-3">
99
@if (InvestmentRequests)
1010
{
11-
<div class="card-header bg-success">
12-
<h4 class="d-flex justify-content-center align-items-center">Pending investment requests</h4>
13-
</div>
11+
<div class="card-header bg-success">
12+
<h4 class="d-flex justify-content-center align-items-center">Pending investment requests</h4>
13+
</div>
1414
}
1515
<div class="card-body">
1616
<h3 class="card-title">@FounderProject.Metadata?.Name</h3>

src/Angor/Client/Pages/Invest.razor

+26-7
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@
221221
}
222222
else
223223
{
224-
if (investorProject.ReceivedFounderSignatures() && !investorProject.InvestedInProject())
224+
@if (investorProject.ReceivedFounderSignatures() && !investorProject.InvestedInProject())
225225
{
226226
<div class="card mt-3">
227227
<div class="card-header d-flex justify-content-between align-items-center">
@@ -242,9 +242,22 @@
242242
<span>Invest</span>
243243
}
244244
</button>
245+
246+
@if (investorProject.SignaturesInfo != null)
247+
{
248+
<div class="mt-3">
249+
<h5>Transaction Details</h5>
250+
<div class="mb-1">
251+
<strong>Amount to invest:</strong> @Money.Satoshis((investorProject.AmountInvested ?? 0)).ToUnit(MoneyUnit.BTC) @network.CoinTicker
252+
<p><strong>Date:</strong> @investorProject?.SignaturesInfo?.TimeOfSignatureRequest?.ToString("MM/dd/yyyy HH:mm")</p>
253+
<p><strong>Project Name: </strong> @investorProject?.Metadata?.Name</p>
254+
</div>
255+
</div>
256+
}
245257
</div>
246258
</div>
247-
}
259+
}
260+
248261
}
249262
}
250263
</div>
@@ -271,12 +284,13 @@
271284
TransactionInfo? signedTransaction;
272285
Transaction unSignedTransaction;
273286

287+
274288
private FeeData feeData = new();
275289

276290
private IJSInProcessObjectReference? javascriptNostrToolsModule;
277291

278292
protected override async Task OnInitializedAsync()
279-
{
293+
{
280294
if (!hasWallet)
281295
{
282296
NavigationManager.NavigateTo($"/wallet");
@@ -287,8 +301,7 @@
287301
if (findProject != null)
288302
{
289303
var investmentProject = findProject as InvestorProject;
290-
project = investmentProject;
291-
304+
project = investmentProject;
292305
invested = investmentProject?.InvestedInProject() ?? false;
293306
}
294307
else
@@ -311,7 +324,6 @@
311324
}
312325
}
313326
}
314-
315327
await CheckIfSeederTimeHasPassed();
316328

317329
UpdateStagesBreakdown(new ChangeEventArgs { Value = Investment.InvestmentAmount });
@@ -410,6 +422,10 @@
410422
return Task.CompletedTask;
411423
}
412424

425+
426+
427+
428+
413429
private void UpdateStagesBreakdown(ChangeEventArgs e)
414430
{
415431
if (decimal.TryParse(e.Value.ToString(), out decimal amount))
@@ -573,7 +589,8 @@
573589
ProjectInfo = project.ProjectInfo,
574590
Metadata = project.Metadata,
575591
SignedTransactionHex = signedTransaction!.Transaction!.ToHex(),
576-
CreationTransactionId = project.CreationTransactionId
592+
CreationTransactionId = project.CreationTransactionId,
593+
AmountInvested = new Money(Investment.InvestmentAmount, MoneyUnit.BTC).Satoshi
577594
};
578595

579596
var investorProject = (InvestorProject)project;
@@ -613,6 +630,8 @@
613630
investorProject.SignaturesInfo!.SignatureRequestEventId = investmentSigsRequest.eventId;
614631

615632
storage.AddInvestmentProject(investorProject);
633+
634+
616635

617636
foreach (var input in strippedInvestmentTransaction.Inputs)
618637
accountInfo.UtxoReservedForInvestment.Add(input.PrevOut.ToString());

src/Angor/Client/Pages/Signatures.razor

+149-18
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,19 @@
4444
<!-- Signatures Details Section -->
4545
<div class="row mt-4">
4646
<div class="col">
47-
@if (messagesReceived)
47+
@if (messagesReceived || isLoading)
4848
{
49-
<div class="loader"></div>
49+
<div class="d-flex justify-content-center align-items-center" style="min-height: 200px;">
50+
<div class="text-center mb-3">
51+
@if (isLoading)
52+
{
53+
<h6 class="mb-0">Approving Signatures... @numOfSignaturesSigned / @numOfSignatureToSign</h6>
54+
}
55+
<div class="loader"></div>
56+
</div>
57+
</div>
5058
}
59+
5160
else
5261
{
5362
<div class="col-md-12">
@@ -57,9 +66,25 @@
5766
<div class="col-6 d-flex align-items-center">
5867
<h6 class="mb-0">Signatures</h6>
5968
</div>
69+
<div class="col-6 d-flex justify-content-end">
70+
<button class="btn btn-sm @(ApproveButtonClass)" @onclick="ApproveAllSignatures" disabled="@ApproveButtonDisabled">Approve All</button>
71+
<button class="btn btn-sm btn-secondary ml-2" @onclick="RefreshSignaturesInternal" disabled="@refreshButtonSpinner">
72+
@if (refreshButtonSpinner)
73+
{
74+
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
75+
<span>Refreshing...</span>
76+
}
77+
else
78+
{
79+
<span>Refresh</span>
80+
}
81+
</button>
82+
</div>
6083
</div>
6184
</div>
6285
<div class="card-body">
86+
87+
6388
<div class="table-responsive form-control">
6489
<table class="table align-items-center mb-0">
6590
<thead>
@@ -77,7 +102,7 @@
77102
<td>@signature.TimeArrived.ToString("g")</td>
78103
@if (signature.TimeApproved is null)
79104
{
80-
<td><button class="btn btn-success" @onclick="() => ApproveSignature(signature)">Approve</button></td>
105+
<td><button class="btn btn-success" @onclick="() => ApproveSignatureCheckPassword(signature)">Approve</button></td>
81106
}
82107
else
83108
{
@@ -110,6 +135,17 @@
110135
bool messagesReceived;
111136
bool scanedForApprovals;
112137

138+
private bool CanApproveAllSignatures => signaturesRequests != null && signaturesRequests.Any(s => s.TransactionHex != null && s.AmountToInvest != null && s.TimeApproved == null);
139+
140+
private string ApproveButtonClass => CanApproveAllSignatures ? "btn-success" : "btn-primary";
141+
142+
private bool ApproveButtonDisabled => !CanApproveAllSignatures;
143+
144+
private bool isLoading = false;
145+
private bool refreshButtonSpinner = false;
146+
private int numOfSignatureToSign = 0;
147+
private int numOfSignaturesSigned = 0;
148+
113149
protected override async Task OnInitializedAsync()
114150
{
115151
Logger.LogDebug("OnInitializedAsync");
@@ -137,7 +173,7 @@
137173
}
138174
catch (JSException e)
139175
{
140-
Console.WriteLine(e);
176+
Logger.LogError($"Error loading nostr tools: {e.Message}", e);
141177
notificationComponent.ShowErrorMessage(e.Message);
142178
return;
143179
}
@@ -161,9 +197,47 @@
161197
}
162198
}
163199

200+
private async Task RefreshSignaturesInternal()
201+
{
202+
if (passwordComponent.HasPassword())
203+
{
204+
await RefreshSignatures();
205+
}
206+
else
207+
{
208+
passwordComponent.ShowPassword(async () =>
209+
{
210+
await RefreshSignatures();
211+
});
212+
}
213+
}
214+
215+
private async Task RefreshSignatures()
216+
{
217+
refreshButtonSpinner = true;
218+
StateHasChanged();
219+
220+
try
221+
{
222+
await FetchPendingSignatures(FounderProject);
223+
await FetchSignatures();
224+
await Task.Delay(1000);
225+
}
226+
catch (Exception e)
227+
{
228+
Logger.LogError($"Error fetching signatures: {e.Message}");
229+
}
230+
finally
231+
{
232+
233+
refreshButtonSpinner = false;
234+
StateHasChanged();
235+
}
236+
}
237+
164238
protected async Task FetchSignatures()
165239
{
166-
Logger.LogDebug("handled = {Count}, total = {SignaturesRequestsCount}", signaturesRequests.Count(x => x.AmountToInvest.HasValue), signaturesRequests.Count);
240+
Logger.LogDebug($"handled = {signaturesRequests.Count(x => x.AmountToInvest.HasValue)}, total = {signaturesRequests.Count}");
167241

168242
if (signaturesRequests.Any(x => x.AmountToInvest == null))
169243
{
@@ -202,8 +276,7 @@
202276
}
203277
catch (Exception e)
204278
{
205-
Console.WriteLine(e);
206-
Logger.LogDebug(pendingSignature.TransactionHex);
279+
Logger.LogError(pendingSignature.TransactionHex, e);
207280
pendingSignature.TransactionHex = null;
208281
}
209282
}
@@ -213,6 +286,7 @@
213286
}
214287

215288
Logger.LogDebug("OnAfterRenderAsync Completed");
289+
Logger.LogDebug($"Signatures retrieved: {signaturesRequests.Count}");
216290
}
217291

218292
private async Task FetchPendingSignatures(FounderProject project)
@@ -329,25 +403,26 @@
329403
{
330404
passwordComponent.ShowPassword(async () =>
331405
{
332-
await ApproveSignature(signature); ;
406+
await ApproveSignature(signature);
333407
});
334408
}
335409
}
336410

337-
private async Task ApproveSignature(SignatureRequest signature)
411+
private async Task<OperationResult> PerformSignatureApproval(SignatureRequest signature, WalletWords words)
338412
{
339-
var operationResult = await notificationComponent.LongOperation(async () =>
413+
try
340414
{
341-
var words = await passwordComponent.GetWalletAsync();
415+
if (words == null)
416+
{
417+
return new OperationResult { Success = false, Message = "Password retrieval failed." };
418+
}
342419

343420
var key = DerivationOperations.DeriveFounderRecoveryPrivateKey(words, FounderProject.ProjectIndex);
344-
345421
var signatureInfo = signProject(signature.TransactionHex, FounderProject.ProjectInfo, Encoders.Hex.EncodeData(key.ToBytes()));
346422

347423
var sigJson = serializer.Serialize(signatureInfo);
348424

349425
var nostrPrivateKey = await DerivationOperations.DeriveProjectNostrPrivateKeyAsync(words, FounderProject.ProjectIndex);
350-
351426
var nostrPrivateKeyHex = Encoders.Hex.EncodeData(nostrPrivateKey.ToBytes());
352427

353428
var encryptedContent = await javascriptNostrToolsModule.InvokeAsync<string>(
@@ -357,23 +432,79 @@
357432
sigJson);
358433

359434
FounderProject.LastRequestForSignaturesTime = SignService.SendSignaturesToInvestor(encryptedContent, nostrPrivateKeyHex, signature.investorPubKey, signature.EventId);
360-
361435
Storage.UpdateFounderProject(FounderProject);
362436

363437
signaturesRequests.Single(_ => _.investorPubKey == signature.investorPubKey && _.TimeApproved is null)
364438
.TimeApproved = FounderProject.LastRequestForSignaturesTime;
365439

366440
return new OperationResult { Success = true };
367-
});
441+
}
442+
catch (Exception ex)
443+
{
444+
return new OperationResult { Success = false, Message = $"An error occurred: {ex.Message}" };
445+
}
446+
}
447+
448+
private async Task ApproveSignature(SignatureRequest signature)
449+
{
450+
var words = await passwordComponent.GetWalletAsync();
451+
452+
var operationResult = await PerformSignatureApproval(signature, words);
453+
454+
if (!operationResult.Success)
455+
{
456+
notificationComponent.ShowErrorMessage(operationResult.Message);
457+
}
458+
else
459+
{
460+
StateHasChanged();
461+
}
462+
}
368463

369-
if (operationResult.Success)
464+
private async Task ApproveAllSignatures()
465+
{
466+
isLoading = true;
467+
StateHasChanged();
468+
469+
try
370470
{
471+
if (passwordComponent.HasPassword())
472+
{
473+
await ProcessSignatures();
474+
}
475+
else
476+
{
477+
passwordComponent.ShowPassword(async () =>
478+
{
479+
await ProcessSignatures();
480+
});
481+
}
482+
}
483+
catch (Exception e)
484+
{
485+
Logger.LogError("Error approving signatures: {ErrorMessage}", e.Message, e);
486+
}
487+
finally
488+
{
489+
isLoading = false;
371490
passwordComponent.ClearPassword();
372491
StateHasChanged();
373492
}
374-
else
493+
}
494+
495+
private async Task ProcessSignatures()
496+
{
497+
var pendingSignatures = signaturesRequests.Where(s => s.TransactionHex != null && s.AmountToInvest != null && s.TimeApproved == null).ToList();
498+
numOfSignatureToSign = pendingSignatures.Count;
499+
numOfSignaturesSigned = 0;
500+
501+
var words = await passwordComponent.GetWalletAsync();
502+
503+
foreach (var signature in pendingSignatures)
375504
{
376-
notificationComponent.ShowErrorMessage(operationResult.Message);
505+
await PerformSignatureApproval(signature, words);
506+
numOfSignaturesSigned++;
507+
StateHasChanged();
377508
}
378509
}
379510

0 commit comments

Comments
 (0)