diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/EnvelopeReceiverService.cs b/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/EnvelopeReceiverService.cs index 89917286..650f008c 100644 --- a/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/EnvelopeReceiverService.cs +++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/EnvelopeReceiverService.cs @@ -2,6 +2,7 @@ using System.Net; using System.Net.Http; using System.Net.Http.Json; using System.Text.Json; +using EnvelopeGenerator.Application.EnvelopeReceivers.Commands; using EnvelopeGenerator.Server.Client.Models; namespace EnvelopeGenerator.Server.Client.Services; @@ -9,6 +10,7 @@ namespace EnvelopeGenerator.Server.Client.Services; /// /// Retrieves the for the authenticated receiver /// from GET /api/EnvelopeReceiver/{envelopeKey}. +/// Also creates new envelopes via POST /api/EnvelopeReceiver. /// public class EnvelopeReceiverService(IHttpClientFactory httpClientFactory) { @@ -37,4 +39,28 @@ public class EnvelopeReceiverService(IHttpClientFactory httpClientFactory) return await response.Content.ReadFromJsonAsync(_jsonOptions, cancel); } + + /// + /// Creates a new envelope with document and receivers via POST /api/EnvelopeReceiver. + /// Requires sender authentication cookie to be present in the request. + /// + /// Thrown when the API request fails. + public async Task CreateAsync( + CreateEnvelopeReceiverCommand request, + CancellationToken cancel = default) + { + using var http = httpClientFactory.CreateClient("EnvelopeGenerator.Server"); + var response = await http.PostAsJsonAsync("/api/EnvelopeReceiver", request, _jsonOptions, cancel); + + if (!response.IsSuccessStatusCode) + { + var body = await response.Content.ReadAsStringAsync(cancel); + throw new HttpRequestException( + $"Fehler beim Erstellen des Umschlags. Status: {(int)response.StatusCode} – {body}", + null, + response.StatusCode); + } + + return await response.Content.ReadFromJsonAsync(_jsonOptions, cancel); + } } diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeSenderEditorPage.razor b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeSenderEditorPage.razor index a4de1d08..53ec6e0b 100644 --- a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeSenderEditorPage.razor +++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeSenderEditorPage.razor @@ -3,6 +3,7 @@ @using DevExpress.Blazor.PdfViewer @using DevExpress.Blazor.Reporting.Models @using DevExpress.Blazor +@using EnvelopeGenerator.Application.EnvelopeReceivers.Commands @using EnvelopeGenerator.Server.Client.Services @using EnvelopeGenerator.Server.Services @using Microsoft.AspNetCore.Components.Forms @@ -12,6 +13,7 @@ @inject AppVersionService AppVersion @inject ILogger Logger @inject EnvelopeReceiverPageDataService ReceiverPageDataService +@inject EnvelopeReceiverService EnvelopeReceiverService @inject IMemoryCache MemoryCache @@ -143,11 +145,20 @@ @* Save *@ - - - + @if (_isSaving) + { + + } + else + { + + + + } Speichern } @@ -303,6 +314,79 @@ +@* ── Save result popup (success + error) ── *@ + + + @if (_saveErrorMessage is null) + { + + + + + + + + + Umschlag wurde erfolgreich erstellt. + + + Der Umschlag wurde gespeichert und die Empfänger wurden benachrichtigt. + + + + } + else + { + + + + + + + + + + Fehler beim Erstellen des Umschlags + + + @_saveErrorMessage + + + + } + + + + @if (_saveErrorMessage is null) + { + + Zur Übersicht + + } + else + { + _savePopupVisible = false" + style="border-radius:6px;padding:0.5rem 1.25rem;font-weight:500;"> + Schließen + + } + + + + @code { // ── Session query param — persists across SignalR reconnects ── [SupplyParameterFromQuery(Name = "esid")] @@ -330,6 +414,11 @@ List _signatureFields = []; ReceiverDraft? _pendingReceiverForPlacement; // Set when user clicks "Signatur hinzufügen" + // ── Save state ── + bool _isSaving = false; + bool _savePopupVisible = false; + string? _saveErrorMessage = null; + List _receivers = []; bool _receiverPopupVisible; string _receiverDraftName = string.Empty; @@ -523,24 +612,111 @@ void Cancel() => NavigationManager.NavigateTo("/sender"); - // ── Save ── + void GoToDashboard() + { + // Clear session from cache on successful save so it is not restored if user returns + if (!string.IsNullOrWhiteSpace(Esid)) + MemoryCache.Remove(SessionKey); + + NavigationManager.NavigateTo("/sender"); + } + + // ── Save — POST /api/EnvelopeReceiver ── async Task SaveAsync() { + // ── Validation ── + if (!_pdfLoaded || _originalPdfBytes is null) + { + _saveErrorMessage = "Bitte laden Sie zuerst ein PDF-Dokument hoch."; + _savePopupVisible = true; + return; + } + if (_receivers.Count == 0) + { + _saveErrorMessage = "Bitte fügen Sie mindestens einen Empfänger hinzu."; + _savePopupVisible = true; + return; + } if (_signatureFields.Count == 0) { - await JSRuntime.InvokeVoidAsync("console.log", - "[SenderEditor] No signature fields to save."); + _saveErrorMessage = "Bitte platzieren Sie mindestens ein Signaturfeld."; + _savePopupVisible = true; return; } - foreach (var f in _signatureFields) + // Warn if any receiver has no signature field, but don't block + var receiversWithoutField = _receivers + .Where(r => !_signatureFields.Any(f => f.ReceiverName == r.FullName)) + .ToList(); + if (receiversWithoutField.Count > 0) { - await JSRuntime.InvokeVoidAsync("console.log", - $"[SenderEditor] Field: Page={f.Page} X={f.XPt:F2}pt ({f.XPt / 72:F3}in) Y={f.YPt:F2}pt ({f.YPt / 72:F3}in) Receiver={f.ReceiverName}"); + Logger.LogWarning( + "[SenderEditor] Receivers without signature field: {Names}", + string.Join(", ", receiversWithoutField.Select(r => r.FullName))); } - await JSRuntime.InvokeVoidAsync("console.log", - $"[SenderEditor] Total fields: {_signatureFields.Count}"); + _isSaving = true; + _saveErrorMessage = null; + await InvokeAsync(StateHasChanged); + + try + { + // ── Build request ── + // Document: use the ORIGINAL pdf (without placeholder burn-in) as base64 + var docBase64 = Convert.ToBase64String(_originalPdfBytes); + + // Build receivers list — each receiver gets their own signature fields + // Coordinate conversion: PDF points → inches (DB stores inches) + var receiversCmd = _receivers.Select(receiver => + { + var fields = _signatureFields + .Where(f => f.ReceiverName == receiver.FullName) + .Select(f => new DocReceiverElementCreateDto( + X: f.XPt / 72.0, + Y: f.YPt / 72.0, + Page: f.Page)) + .ToList(); + + return new ReceiverGetOrCreateCommand + { + EmailAddress = receiver.Email, + Salution = receiver.FullName, + PhoneNumber = string.IsNullOrWhiteSpace(receiver.PhoneNumber) + ? null + : receiver.PhoneNumber, + DocReceiverElements = fields, + }; + }).ToList(); + + var command = new CreateEnvelopeReceiverCommand + { + Title = "Neuer Umschlag", // placeholder — dedicated field will be added later + Message = "Bitte unterzeichnen Sie das beigefügte Dokument.", + TFAEnabled = false, + Document = new DocumentCreateCommand { DataAsBase64 = docBase64 }, + Receivers = receiversCmd, + }; + + var result = await EnvelopeReceiverService.CreateAsync(command); + + Logger.LogInformation( + "[SenderEditor] Envelope created. Id={Id} SentReceivers={Count}", + result?.Id, result?.SentReceiver.Count()); + + // Success — show popup; GoToDashboard clears cache and navigates + _saveErrorMessage = null; + _savePopupVisible = true; + } + catch (Exception ex) + { + Logger.LogError(ex, "[SenderEditor] Failed to create envelope"); + _saveErrorMessage = ex.Message; + _savePopupVisible = true; + } + finally + { + _isSaving = false; + } } // ── Cache persistence ──
+ Umschlag wurde erfolgreich erstellt. +
+ Der Umschlag wurde gespeichert und die Empfänger wurden benachrichtigt. +
+ Fehler beim Erstellen des Umschlags +
+ @_saveErrorMessage +