|
44 | 44 | <!-- Signatures Details Section -->
|
45 | 45 | <div class="row mt-4">
|
46 | 46 | <div class="col">
|
47 |
| - @if (messagesReceived) |
| 47 | + @if (messagesReceived || isLoading) |
48 | 48 | {
|
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> |
50 | 58 | }
|
| 59 | + |
51 | 60 | else
|
52 | 61 | {
|
53 | 62 | <div class="col-md-12">
|
|
57 | 66 | <div class="col-6 d-flex align-items-center">
|
58 | 67 | <h6 class="mb-0">Signatures</h6>
|
59 | 68 | </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> |
60 | 83 | </div>
|
61 | 84 | </div>
|
62 | 85 | <div class="card-body">
|
| 86 | + |
| 87 | + |
63 | 88 | <div class="table-responsive form-control">
|
64 | 89 | <table class="table align-items-center mb-0">
|
65 | 90 | <thead>
|
|
77 | 102 | <td>@signature.TimeArrived.ToString("g")</td>
|
78 | 103 | @if (signature.TimeApproved is null)
|
79 | 104 | {
|
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> |
81 | 106 | }
|
82 | 107 | else
|
83 | 108 | {
|
|
110 | 135 | bool messagesReceived;
|
111 | 136 | bool scanedForApprovals;
|
112 | 137 |
|
| 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 | + |
113 | 149 | protected override async Task OnInitializedAsync()
|
114 | 150 | {
|
115 | 151 | Logger.LogDebug("OnInitializedAsync");
|
|
137 | 173 | }
|
138 | 174 | catch (JSException e)
|
139 | 175 | {
|
140 |
| - Console.WriteLine(e); |
| 176 | + Logger.LogError($"Error loading nostr tools: {e.Message}", e); |
141 | 177 | notificationComponent.ShowErrorMessage(e.Message);
|
142 | 178 | return;
|
143 | 179 | }
|
|
161 | 197 | }
|
162 | 198 | }
|
163 | 199 |
|
| 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 | + |
164 | 238 | protected async Task FetchSignatures()
|
165 | 239 | {
|
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}"); |
167 | 241 |
|
168 | 242 | if (signaturesRequests.Any(x => x.AmountToInvest == null))
|
169 | 243 | {
|
|
202 | 276 | }
|
203 | 277 | catch (Exception e)
|
204 | 278 | {
|
205 |
| - Console.WriteLine(e); |
206 |
| - Logger.LogDebug(pendingSignature.TransactionHex); |
| 279 | + Logger.LogError(pendingSignature.TransactionHex, e); |
207 | 280 | pendingSignature.TransactionHex = null;
|
208 | 281 | }
|
209 | 282 | }
|
|
213 | 286 | }
|
214 | 287 |
|
215 | 288 | Logger.LogDebug("OnAfterRenderAsync Completed");
|
| 289 | + Logger.LogDebug($"Signatures retrieved: {signaturesRequests.Count}"); |
216 | 290 | }
|
217 | 291 |
|
218 | 292 | private async Task FetchPendingSignatures(FounderProject project)
|
|
329 | 403 | {
|
330 | 404 | passwordComponent.ShowPassword(async () =>
|
331 | 405 | {
|
332 |
| - await ApproveSignature(signature); ; |
| 406 | + await ApproveSignature(signature); |
333 | 407 | });
|
334 | 408 | }
|
335 | 409 | }
|
336 | 410 |
|
337 |
| - private async Task ApproveSignature(SignatureRequest signature) |
| 411 | + private async Task<OperationResult> PerformSignatureApproval(SignatureRequest signature, WalletWords words) |
338 | 412 | {
|
339 |
| - var operationResult = await notificationComponent.LongOperation(async () => |
| 413 | + try |
340 | 414 | {
|
341 |
| - var words = await passwordComponent.GetWalletAsync(); |
| 415 | + if (words == null) |
| 416 | + { |
| 417 | + return new OperationResult { Success = false, Message = "Password retrieval failed." }; |
| 418 | + } |
342 | 419 |
|
343 | 420 | var key = DerivationOperations.DeriveFounderRecoveryPrivateKey(words, FounderProject.ProjectIndex);
|
344 |
| - |
345 | 421 | var signatureInfo = signProject(signature.TransactionHex, FounderProject.ProjectInfo, Encoders.Hex.EncodeData(key.ToBytes()));
|
346 | 422 |
|
347 | 423 | var sigJson = serializer.Serialize(signatureInfo);
|
348 | 424 |
|
349 | 425 | var nostrPrivateKey = await DerivationOperations.DeriveProjectNostrPrivateKeyAsync(words, FounderProject.ProjectIndex);
|
350 |
| - |
351 | 426 | var nostrPrivateKeyHex = Encoders.Hex.EncodeData(nostrPrivateKey.ToBytes());
|
352 | 427 |
|
353 | 428 | var encryptedContent = await javascriptNostrToolsModule.InvokeAsync<string>(
|
|
357 | 432 | sigJson);
|
358 | 433 |
|
359 | 434 | FounderProject.LastRequestForSignaturesTime = SignService.SendSignaturesToInvestor(encryptedContent, nostrPrivateKeyHex, signature.investorPubKey, signature.EventId);
|
360 |
| - |
361 | 435 | Storage.UpdateFounderProject(FounderProject);
|
362 | 436 |
|
363 | 437 | signaturesRequests.Single(_ => _.investorPubKey == signature.investorPubKey && _.TimeApproved is null)
|
364 | 438 | .TimeApproved = FounderProject.LastRequestForSignaturesTime;
|
365 | 439 |
|
366 | 440 | 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 | + } |
368 | 463 |
|
369 |
| - if (operationResult.Success) |
| 464 | + private async Task ApproveAllSignatures() |
| 465 | + { |
| 466 | + isLoading = true; |
| 467 | + StateHasChanged(); |
| 468 | + |
| 469 | + try |
370 | 470 | {
|
| 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; |
371 | 490 | passwordComponent.ClearPassword();
|
372 | 491 | StateHasChanged();
|
373 | 492 | }
|
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) |
375 | 504 | {
|
376 |
| - notificationComponent.ShowErrorMessage(operationResult.Message); |
| 505 | + await PerformSignatureApproval(signature, words); |
| 506 | + numOfSignaturesSigned++; |
| 507 | + StateHasChanged(); |
377 | 508 | }
|
378 | 509 | }
|
379 | 510 |
|
|
0 commit comments