From bb35a68b1febdd96b17f0ac2f3c6ca1ba973cdef Mon Sep 17 00:00:00 2001 From: TekH Date: Mon, 5 Oct 2026 14:54:06 +0200 Subject: [PATCH] feat(sender-editor): consume bootstrap payload and auto-save draft Accept bootstrapToken query parameter and consume one-time payload from sessionStorage-backed service. Hydrate editor state with title, message, envelope type, receivers, and PDF document from bootstrap data. Refactor draft save into reusable core flow and trigger non-navigating auto-save after bootstrap is applied. Prevent stale token replay by clearing query token from URL and handling invalid tokens gracefully. --- .../Pages/SenderEditorPage.razor | 115 +++++++++++++++++- 1 file changed, 109 insertions(+), 6 deletions(-) diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Pages/SenderEditorPage.razor b/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Pages/SenderEditorPage.razor index 53d0a746..68186fe2 100644 --- a/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Pages/SenderEditorPage.razor +++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Pages/SenderEditorPage.razor @@ -21,6 +21,7 @@ @inject ReceiverService ReceiverService @inject EnvelopeService EnvelopeService @inject EnvelopeTypeService EnvelopeTypeService +@inject CreateEnvelopeBootstrapStorageService CreateEnvelopeBootstrapStorageService @inject AuthService AuthService @inject IStringLocalizer UiLocalizer @inject IStringLocalizer ModelLocalizer @@ -589,6 +590,9 @@ [SupplyParameterFromQuery(Name = "envelopeId")] public int? EnvelopeId { get; set; } + [SupplyParameterFromQuery(Name = "bootstrapToken")] + public string? BootstrapToken { get; set; } + // ── Constants ── // Signature field size in PDF points (fixed): 1.77" × 1.96" const double SigWidthPt = 1.77 * 72; // 127.44 pt @@ -609,6 +613,8 @@ int? _loadedEnvelopeId; int? _editingEnvelopeId; bool _isLoadingEnvelopeForEdit; + bool _bootstrapApplied; + string? _pendingBootstrapToken; List _signatureFields = []; ReceiverDraft? _pendingReceiverForPlacement; // Set when user clicks "Signatur hinzufügen" @@ -629,6 +635,7 @@ string _saveSuccessTitle = "Vorgang erfolgreich abgeschlossen."; string _saveSuccessBody = string.Empty; bool _saveNavigateToDashboard = false; + bool _saveDraftAfterBootstrap; // ── Envelope metadata ── string _envelopeTitle = string.Empty; @@ -697,6 +704,9 @@ if (EnvelopeId.HasValue && EnvelopeId.Value > 0) _editingEnvelopeId = EnvelopeId.Value; + if (!IsEditMode && !_bootstrapApplied && !string.IsNullOrWhiteSpace(BootstrapToken)) + _pendingBootstrapToken = BootstrapToken; + if (IsEditMode && _loadedEnvelopeId != EnvelopeId) { await LoadEnvelopeForEditAsync(EnvelopeId!.Value); @@ -707,15 +717,91 @@ return; } - protected override Task OnAfterRenderAsync(bool firstRender) + async Task TryApplyBootstrapAsync(string token) + { + var payload = await CreateEnvelopeBootstrapStorageService.ConsumeAsync(token); + _bootstrapApplied = true; + + if (payload is null) + { + NavigationManager.NavigateTo("/sender/editor", replace: true); + return; + } + + _envelopeTitle = payload.Title ?? string.Empty; + _envelopeMessage = payload.Message ?? string.Empty; + _selectedEnvelopeTypeId = payload.EnvelopeTypeId; + _language = string.IsNullOrWhiteSpace(payload.Language) ? "de-DE" : payload.Language; + + _receivers = payload.Receivers + .Select((receiver, index) => new ReceiverDraft( + Guid.NewGuid(), + receiver.FullName, + receiver.EmailAddress, + string.Empty, + ReceiverPalette[index % ReceiverPalette.Length])) + .ToList(); + + _originalPdfBytes = payload.DocumentBytes; + _fileName = string.IsNullOrWhiteSpace(payload.DocumentFileName) ? "document.pdf" : payload.DocumentFileName; + _pdfLoaded = _originalPdfBytes is { Length: > 0 }; + + _signatureFields.Clear(); + _pendingReceiverForPlacement = null; + + _saveDraftAfterBootstrap = true; + + if (_pdfLoaded && _originalPdfBytes is not null) + { + try + { + _pagePreviews = (await PdfRenderService.RenderPagesAsync(_originalPdfBytes)) + .OrderBy(p => p.PageNumber) + .ToList(); + } + catch (Exception ex) + { + Logger.LogWarning(ex, "Failed to render bootstrap PDF previews."); + _pagePreviews = []; + } + + _pageCount = _pagePreviews.Count > 0 + ? _pagePreviews.Count + : EstimatePdfPageCount(_originalPdfBytes) ?? 1; + } + else + { + _pagePreviews = []; + _pageCount = 0; + } + + NavigationManager.NavigateTo("/sender/editor", replace: true); + } + + protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { _isPageReady = true; - StateHasChanged(); + await InvokeAsync(StateHasChanged); } - return Task.CompletedTask; + if (!OperatingSystem.IsBrowser()) + return; + + if (_saveDraftAfterBootstrap && !_isSaving) + { + _saveDraftAfterBootstrap = false; + await SaveDraftAndStayOnEditorAsync(); + return; + } + + if (!IsEditMode && !_bootstrapApplied && !string.IsNullOrWhiteSpace(_pendingBootstrapToken)) + { + var token = _pendingBootstrapToken; + _pendingBootstrapToken = null; + await TryApplyBootstrapAsync(token); + } } async Task LoadEnvelopeForEditAsync(int envelopeId) @@ -1108,12 +1194,17 @@ } async Task SaveDraftAsync() + { + await SaveDraftCoreAsync(navigateToDashboard: true); + } + + async Task SaveDraftCoreAsync(bool navigateToDashboard) { if (!ValidateSettings(out var settingsError)) { _saveErrorMessage = settingsError; _savePopupVisible = true; - return; + return null; } _isSaving = true; @@ -1134,25 +1225,37 @@ _loadedEnvelopeId = result.Id; _editingEnvelopeId = result.Id; - if (previousEnvelopeId != result.Id) + if (navigateToDashboard) + { + NavigationManager.NavigateTo("/sender", forceLoad: false); + } + else if (previousEnvelopeId != result.Id) { NavigationManager.NavigateTo($"/sender/editor?envelopeId={result.Id}", forceLoad: false); } - NavigationManager.NavigateTo("/sender", forceLoad: false); + return result.Id; } catch (Exception ex) { Logger.LogError(ex, "[SenderEditor] Failed to save draft"); _saveErrorMessage = ex.Message; _savePopupVisible = true; + return null; } finally { _isSaving = false; + await InvokeAsync(StateHasChanged); } } + async Task SaveDraftAndStayOnEditorAsync() + { + Logger.LogInformation("[SenderEditor] Trigger bootstrap draft save."); + await SaveDraftCoreAsync(navigateToDashboard: false); + } + // ── Send envelope ── async Task SendAsync() {