Files
EnvelopeGenerator/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Components/Sender/CreateEnvelopeWizardPopup.razor
TekH 5fb7b5a79e Enforce behavior for AdvancedElectronicSignature
Added logic to enforce automatic enabling of Two-Factor Authentication (2FA) and access code usage when the `AdvancedElectronicSignature` certification type is selected.

- Added `@bind:after` event handler to `<select>` for `_certificationType` to handle changes.
- Introduced `IsFesSelected` property to check if `AdvancedElectronicSignature` is selected.
- Updated `OnTfaChanged` to enforce `_tfaEnabled` and `_useAccessCode` when `IsFesSelected`.
- Added `OnCertificationTypeChanged` method to reset validation and normalize settings.
- Updated `NormalizeSettings` to ensure `_tfaEnabled` and `_useAccessCode` are set appropriately.
- Called `NormalizeSettings` during component initialization to ensure consistent state.

These changes ensure the UI and logic reflect the enforced behavior for the `AdvancedElectronicSignature` certification type.
2026-11-10 21:17:30 +01:00

1349 lines
71 KiB
Plaintext

@using EnvelopeGenerator.Application.Common.Dto
@using EnvelopeGenerator.Application.Common.Dto.Receiver
@using EnvelopeGenerator.Application.Resources
@using EnvelopeGenerator.Domain.Constants
@using EnvelopeGenerator.Domain.Resources
@using EnvelopeGenerator.Server.Client.Components.Sender.ReceiverSuggestionFilter
@using EnvelopeGenerator.Server.Client.Services
@using MudBlazor
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.Extensions.Localization
@inject IStringLocalizer<Model> ModelLocalizer
@inject IStringLocalizer<Resource> UiLocalizer
@inject ReceiverService ReceiverService
@inject PdfRenderService PdfRenderService
<DxPopup Visible="@_isVisible"
VisibleChanged="OnPopupVisibleChangedAsync"
HeaderText="@L("SenderWizard_Header", "Neuen Umschlag erstellen")"
Width="90vw"
MaxWidth="90vw"
ShowFooter="true"
CloseOnOutsideClick="false"
ShowCloseButton="true"
CloseOnEscape="true"
ShowHeader="true"
CssClass="sender-create-wizard-popup">
<BodyContentTemplate>
<div class="sender-create-wizard"
tabindex="0"
@onkeydown="OnWizardKeyDownAsync">
<div class="sender-create-wizard__body-layout @(_currentStep == 4 ? "sender-create-wizard__body-layout--document" : string.Empty)">
<MudPaper Class="sender-create-wizard__stepper-shell" Elevation="0">
<MudStepper Vertical="true"
NonLinear="false"
ShowResetButton="false"
CurrentStepColor="Color.Info"
CompletedStepColor="Color.Success"
ErrorStepColor="Color.Error"
StepCompleteIcon="@Icons.Material.Filled.CheckCircle"
ActiveIndex="@_currentStep"
Class="sender-create-wizard__stepper">
<ChildContent>
@for (var step = 0; step < TotalSteps; step++)
{
var stepIndex = step;
<MudStep Title="@GetStepTitle(stepIndex)"
Completed="@_completedSteps.Contains(stepIndex)"
Skippable="@(stepIndex == 3)">
<div class="sender-create-wizard__stepper-description">@GetStepDescription(stepIndex)</div>
@if (stepIndex == _currentStep)
{
<div class="sender-create-wizard__step-content">
<div class="sender-create-wizard__step-layout">
<div class="sender-create-wizard__step-main">
@if (_currentStep == 0)
{
<div class="sender-create-wizard__form-grid">
<div class="sender-create-wizard__field">
<label class="sender-create-wizard__label sender-create-wizard__label-row">
<span>@L("Title", "Titel") <span class="sender-create-wizard__required">*</span></span>
<MudTooltip Text='@L("SenderWizard_Help_Title", "Ein kurzer, eindeutiger Titel hilft beim schnellen Wiederfinden des Umschlags.")'>
<MudIconButton Icon="@Icons.Material.Filled.Info" Color="Color.Tertiary" Size="Size.Small" Class="sender-create-wizard__help-icon" aria-label='@L("SenderWizard_HelpAria_Title", "Hilfe zum Feld Titel")' />
</MudTooltip>
</label>
<DxTextBox Text="@_title"
TextChanged="OnTitleChanged"
BindValueMode="BindValueMode.OnInput"
NullText='@L("SenderEditor_Title_Placeholder", "z.B. Arbeitsvertrag 2025")'
CssClass="w-100" />
</div>
<div class="sender-create-wizard__field">
<label class="sender-create-wizard__label sender-create-wizard__label-row">
<span>@L("EnvelopeType", "Umschlagtyp") <span class="sender-create-wizard__required">*</span></span>
<MudTooltip Text='@L("SenderWizard_Help_EnvelopeType", "Der Umschlagtyp kann Standardwerte wie Sprache, Erinnerungen und Sicherheit vorbelegen.")'>
<MudIconButton Icon="@Icons.Material.Filled.Info" Color="Color.Tertiary" Size="Size.Small" Class="sender-create-wizard__help-icon" aria-label='@L("SenderWizard_HelpAria_EnvelopeType", "Hilfe zum Feld Umschlagtyp")' />
</MudTooltip>
</label>
<select value="@(_selectedEnvelopeTypeId?.ToString() ?? string.Empty)"
@onchange="OnEnvelopeTypeChanged"
class="sender-create-wizard__select">
@foreach (var type in EnvelopeTypes)
{
<option value="@type.Id">@type.Title</option>
}
</select>
</div>
</div>
}
else if (_currentStep == 3)
{
<div class="sender-create-wizard__form-grid">
<div class="sender-create-wizard__field sender-create-wizard__field--full">
<label class="sender-create-wizard__label sender-create-wizard__label-row">
<span>@L("Message", "Nachricht") <span class="sender-create-wizard__optional">(@L("Optional", "optional"))</span></span>
<MudTooltip Text='@L("SenderWizard_Help_Message", "Diese Nachricht wird den Empfaengern in der E-Mail und im Signaturprozess angezeigt.")'>
<MudIconButton Icon="@Icons.Material.Filled.Info" Color="Color.Tertiary" Size="Size.Small" Class="sender-create-wizard__help-icon" aria-label='@L("SenderWizard_HelpAria_Message", "Hilfe zum Feld Nachricht")' />
</MudTooltip>
</label>
<DxMemo Text="@_message"
TextChanged="OnMessageChanged"
Rows="5"
MaxLength="500"
NullText='@L("SenderEditor_Message_Placeholder", "Bitte unterzeichnen Sie das beigefügte Dokument.")'
CssClass="w-100" />
</div>
<div class="sender-create-wizard__field">
<label class="sender-create-wizard__label sender-create-wizard__label-row">
<span>@L("Language", "Sprache")</span>
<MudTooltip Text='@L("SenderWizard_Help_Language", "Legt die Sprache fuer Hinweise, E-Mails und Oberflaechentexte fest.")'>
<MudIconButton Icon="@Icons.Material.Filled.Info" Color="Color.Tertiary" Size="Size.Small" Class="sender-create-wizard__help-icon" aria-label='@L("SenderWizard_HelpAria_Language", "Hilfe zum Feld Sprache")' />
</MudTooltip>
</label>
<select @bind="_language" class="sender-create-wizard__select">
<option value="de-DE">Deutsch (de-DE)</option>
<option value="en-US">English (en-US)</option>
<option value="de">Deutsch (de)</option>
<option value="en">English (en)</option>
</select>
</div>
</div>
}
else if (_currentStep == 2)
{
<div class="sender-create-wizard__settings-grid">
<div class="sender-create-wizard__settings-group">
<div class="sender-create-wizard__settings-title">
@L("SenderEditor_EnvelopeSettings", "Envelope-Einstellungen")
</div>
<div class="sender-create-wizard__form-grid">
<div class="sender-create-wizard__field">
<label class="sender-create-wizard__label sender-create-wizard__label-row">
<span>@L("CertificationType", "Zertifizierung")</span>
<MudTooltip Text='@L("SenderWizard_Help_CertificationType", "Bestimmt das Signaturniveau fuer diesen Umschlag.")'>
<MudIconButton Icon="@Icons.Material.Filled.Info" Color="Color.Tertiary" Size="Size.Small" Class="sender-create-wizard__help-icon" aria-label='@L("SenderWizard_HelpAria_CertificationType", "Hilfe zum Feld Zertifizierung")' />
</MudTooltip>
</label>
<select @bind="_certificationType" @bind:after="OnCertificationTypeChanged" class="sender-create-wizard__select">
<option value="@CertificationType.AdvancedElectronicSignature">@L("AdvancedElectronicSignature", "Advanced Electronic Signature")</option>
<option value="@CertificationType.ElectronicSignature">@L("ElectronicSignature", "Electronic Signature")</option>
</select>
</div>
<div class="sender-create-wizard__field">
<label class="sender-create-wizard__label sender-create-wizard__label-row">
<span>@L("FinalEmailToCreator", "Abgeschlossenes Dokument an Ersteller")</span>
<MudTooltip Text='@L("SenderWizard_Help_FinalEmailToCreator", "Steuert, ob der Ersteller nach Abschluss eine finale E-Mail erhaelt und ob das Dokument angehaengt wird.")'>
<MudIconButton Icon="@Icons.Material.Filled.Info" Color="Color.Tertiary" Size="Size.Small" Class="sender-create-wizard__help-icon" aria-label='@L("SenderWizard_HelpAria_FinalEmailToCreator", "Hilfe zum Feld Abgeschlossenes Dokument an Ersteller")' />
</MudTooltip>
</label>
<select @bind="_finalEmailToCreator" class="sender-create-wizard__select">
<option value="0">@L("No", "Nein")</option>
<option value="1">@L("Yes", "Ja")</option>
<option value="2">@L("YesWithAttachment", "Ja, mit Anhang")</option>
</select>
</div>
<div class="sender-create-wizard__field">
<label class="sender-create-wizard__label sender-create-wizard__label-row">
<span>@L("FinalEmailToReceivers", "Abgeschlossenes Dokument an Empfänger")</span>
<MudTooltip Text='@L("SenderWizard_Help_FinalEmailToReceivers", "Steuert, ob Empfaenger nach Abschluss eine finale E-Mail erhalten und ob das Dokument angehaengt wird.")'>
<MudIconButton Icon="@Icons.Material.Filled.Info" Color="Color.Tertiary" Size="Size.Small" Class="sender-create-wizard__help-icon" aria-label='@L("SenderWizard_HelpAria_FinalEmailToReceivers", "Hilfe zum Feld Abgeschlossenes Dokument an Empfaenger")' />
</MudTooltip>
</label>
<select @bind="_finalEmailToReceivers" class="sender-create-wizard__select">
<option value="0">@L("No", "Nein")</option>
<option value="1">@L("Yes", "Ja")</option>
<option value="2">@L("YesWithAttachment", "Ja, mit Anhang")</option>
</select>
</div>
<div class="sender-create-wizard__field sender-create-wizard__field--full">
<div class="sender-create-wizard__settings-toggles">
<label class="sender-create-wizard__settings-toggle">
<input type="checkbox" checked="@_tfaEnabled" @onchange="OnTfaChanged" disabled="@IsFesSelected" />
<span>@L("TFAEnabled", "2FA aktiviert")</span>
<MudTooltip Text='@L("SenderWizard_Help_TfaEnabled", "Wenn aktiviert, ist die Signaturfreigabe nur mit zusaetzlicher Verifizierung moeglich.")'>
<MudIconButton Icon="@Icons.Material.Filled.Info" Color="Color.Tertiary" Size="Size.Small" Class="sender-create-wizard__help-icon" aria-label='@L("SenderWizard_HelpAria_TfaEnabled", "Hilfe zur Option 2FA")' />
</MudTooltip>
</label>
<label class="sender-create-wizard__settings-toggle">
<input type="checkbox" checked="@_useAccessCode" @onchange="OnUseAccessCodeChanged" disabled="@_tfaEnabled" />
<span>@L("UseAccessCode", "Zugriffscode verwenden")</span>
<MudTooltip Text='@L("SenderWizard_Help_UseAccessCode", "Empfaenger muessen einen Zugriffscode eingeben. Bei aktivem 2FA bleibt diese Option immer aktiv.")'>
<MudIconButton Icon="@Icons.Material.Filled.Info" Color="Color.Tertiary" Size="Size.Small" Class="sender-create-wizard__help-icon" aria-label='@L("SenderWizard_HelpAria_UseAccessCode", "Hilfe zur Option Zugriffscode verwenden")' />
</MudTooltip>
</label>
</div>
</div>
</div>
</div>
<div class="sender-create-wizard__settings-group">
<div class="sender-create-wizard__settings-title">
@L("SendReminderEmails", "Erinnerungs-E-Mails senden")
</div>
<div class="sender-create-wizard__form-grid">
<div class="sender-create-wizard__field sender-create-wizard__field--full">
<label class="sender-create-wizard__settings-toggle">
<input type="checkbox" checked="@_sendReminderEmails" @onchange="OnSendReminderEmailsChanged" />
<span>@L("SendReminderEmails", "Erinnerungs-E-Mails senden")</span>
<MudTooltip Text='@L("SenderWizard_Help_SendReminderEmails", "Aktiviert automatische Erinnerungsmails fuer noch offene Signaturen.")'>
<MudIconButton Icon="@Icons.Material.Filled.Info" Color="Color.Tertiary" Size="Size.Small" Class="sender-create-wizard__help-icon" aria-label='@L("SenderWizard_HelpAria_SendReminderEmails", "Hilfe zur Option Erinnerungs-E-Mails senden")' />
</MudTooltip>
</label>
</div>
<div class="sender-create-wizard__field">
<label class="sender-create-wizard__label sender-create-wizard__label-row">
<span>@L("FirstReminderDays", "Erste Erinnerung (Tage)")</span>
<MudTooltip Text='@L("SenderWizard_Help_FirstReminderDays", "Anzahl Tage nach Versand bis zur ersten Erinnerung.")'>
<MudIconButton Icon="@Icons.Material.Filled.Info" Color="Color.Tertiary" Size="Size.Small" Class="sender-create-wizard__help-icon" aria-label='@L("SenderWizard_HelpAria_FirstReminderDays", "Hilfe zum Feld Erste Erinnerung")' />
</MudTooltip>
</label>
<InputNumber @bind-Value="_firstReminderDays" class="form-control form-control-sm" disabled="@(!_sendReminderEmails)" />
</div>
<div class="sender-create-wizard__field">
<label class="sender-create-wizard__label sender-create-wizard__label-row">
<span>@L("ReminderIntervalDays", "Erinnerungsintervall (Tage)")</span>
<MudTooltip Text='@L("SenderWizard_Help_ReminderIntervalDays", "Abstand in Tagen zwischen den weiteren Erinnerungen.")'>
<MudIconButton Icon="@Icons.Material.Filled.Info" Color="Color.Tertiary" Size="Size.Small" Class="sender-create-wizard__help-icon" aria-label='@L("SenderWizard_HelpAria_ReminderIntervalDays", "Hilfe zum Feld Erinnerungsintervall")' />
</MudTooltip>
</label>
<InputNumber @bind-Value="_reminderIntervalDays" class="form-control form-control-sm" disabled="@(!_sendReminderEmails)" />
</div>
<div class="sender-create-wizard__field">
<label class="sender-create-wizard__label sender-create-wizard__label-row">
<span>@L("ExpiresWhenDays", "Laeuft ab nach (Tage)")</span>
<MudTooltip Text='@L("SenderWizard_Help_ExpiresWhenDays", "Legt fest, nach wie vielen Tagen der Umschlag automatisch ablaeuft.")'>
<MudIconButton Icon="@Icons.Material.Filled.Info" Color="Color.Tertiary" Size="Size.Small" Class="sender-create-wizard__help-icon" aria-label='@L("SenderWizard_HelpAria_ExpiresWhenDays", "Hilfe zum Feld Laeuft ab nach")' />
</MudTooltip>
</label>
<InputNumber @bind-Value="_expiresWhenDays" class="form-control form-control-sm" />
</div>
<div class="sender-create-wizard__field">
<label class="sender-create-wizard__label sender-create-wizard__label-row">
<span>@L("ExpiresWarningWhenDays", "Ablaufwarnung (Tage)")</span>
<MudTooltip Text='@L("SenderWizard_Help_ExpiresWarningWhenDays", "Tage vor Ablauf, an denen eine Warnung versendet wird.")'>
<MudIconButton Icon="@Icons.Material.Filled.Info" Color="Color.Tertiary" Size="Size.Small" Class="sender-create-wizard__help-icon" aria-label='@L("SenderWizard_HelpAria_ExpiresWarningWhenDays", "Hilfe zum Feld Ablaufwarnung")' />
</MudTooltip>
</label>
<InputNumber @bind-Value="_expiresWarningWhenDays" class="form-control form-control-sm" />
</div>
</div>
</div>
</div>
}
else if (_currentStep == 1)
{
<div class="sender-create-wizard__suggestions">
<DxTextBox Text="@_receiverSuggestionFilterText"
TextChanged="OnReceiverSuggestionFilterChanged"
BindValueMode="BindValueMode.OnInput"
NullText='@L("SenderWizard_FilterRecentReceivers_Placeholder", "Empfänger in Vorschlägen filtern...")'
CssClass="w-100" />
@if (_isLoadingRecentSuggestions)
{
<div class="sender-create-wizard__empty-list">@L("Loading", "Lädt...")</div>
}
else if (_recentReceiverSuggestions.Count > 0)
{
<div class="sender-create-wizard__suggestion-list" role="listbox" aria-label="Recent receivers">
@foreach (var suggestion in FilteredRecentSuggestions)
{
<button type="button"
class="sender-create-wizard__suggestion-item"
@onclick="() => ApplyRecentSuggestionAndAddAsync(suggestion)"
disabled="@IsActionLocked">
<span class="sender-create-wizard__suggestion-name">@suggestion.FullName</span>
<span class="sender-create-wizard__suggestion-email">@suggestion.EmailAddress</span>
</button>
}
</div>
}
</div>
<div class="sender-create-wizard__receiver-tagbox-wrap">
<div class="sender-create-wizard__receiver-list" role="table" aria-label="Added receivers">
<div class="sender-create-wizard__receiver-existing-list">
@for (var i = 0; i < _receivers.Count; i++)
{
var rowIndex = i;
var receiver = _receivers[rowIndex];
<div class="sender-create-wizard__receiver-row" role="row">
<div class="sender-create-wizard__receiver-fields-stack sender-create-wizard__field--full">
<div class="sender-create-wizard__field">
<DxTextBox Text="@receiver.FullName"
TextChanged="@(value => OnReceiverListNameChanged(rowIndex, value))"
BindValueMode="BindValueMode.OnInput"
NullText='@L("FullName", "Vor- und Nachname")'
CssClass="w-100"
Enabled="@(!IsActionLocked)" />
</div>
<div class="sender-create-wizard__field">
<DxTextBox Text="@receiver.EmailAddress"
TextChanged="@(value => OnReceiverListEmailChanged(rowIndex, value))"
BindValueMode="BindValueMode.OnInput"
NullText='@L("EmailAddress", "E-Mail-Adresse")'
CssClass="w-100"
Enabled="@(!IsActionLocked)" />
</div>
</div>
<button type="button"
class="sender-create-wizard__receiver-remove-btn"
@onclick="() => RemoveReceiver(receiver)"
disabled="@IsActionLocked"
title='@L("Remove", "Entfernen")'
aria-label='@L("Remove", "Entfernen")'>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true">
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z" />
<path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z" />
</svg>
</button>
</div>
}
<div class="sender-create-wizard__receiver-row sender-create-wizard__receiver-row--new" role="row">
<div class="sender-create-wizard__receiver-fields-stack sender-create-wizard__field--full">
<div class="sender-create-wizard__field">
<DxTextBox Text="@_receiverName"
TextChanged="OnReceiverNameChanged"
BindValueMode="BindValueMode.OnInput"
NullText="Max Mustermann"
CssClass="w-100"
Enabled="@(!IsActionLocked)" />
</div>
<div class="sender-create-wizard__field">
<DxTextBox Text="@_receiverEmail"
TextChanged="OnReceiverEmailChanged"
BindValueMode="BindValueMode.OnInput"
NullText="name@beispiel.de"
CssClass="w-100"
Enabled="@(!IsActionLocked)" />
</div>
</div>
<button type="button"
class="sender-create-wizard__receiver-add-inline-btn"
@onclick="AddReceiver"
disabled="@IsActionLocked"
title='@L("AddReceiver", "Empfänger hinzufügen")'
aria-label='@L("AddReceiver", "Empfänger hinzufügen")'>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true">
<path d="M8 1a.5.5 0 0 1 .5.5V7.5h6a.5.5 0 0 1 0 1h-6v6a.5.5 0 0 1-1 0v-6h-6a.5.5 0 0 1 0-1h6v-6A.5.5 0 0 1 8 1z" />
</svg>
</button>
</div>
</div>
</div>
</div>
}
else
{
<div class="sender-create-wizard__upload-stage">
<label class="sender-create-wizard__upload-btn @(IsActionLocked ? "sender-create-wizard__upload-btn--disabled" : string.Empty)">
@if (_documentBytes is null)
{
<span>@L("UploadPdf", "PDF hochladen")</span>
}
else
{
<span>@L("SenderWizard_ReplacePdf", "PDF ersetzen")</span>
}
<InputFile OnChange="OnDocumentSelectedAsync"
accept=".pdf"
disabled="@IsActionLocked"
class="sender-create-wizard__hidden-input" />
</label>
@if (_documentBytes is not null)
{
<div class="sender-create-wizard__document-preview-card">
<div class="sender-create-wizard__document-meta">
<div class="sender-create-wizard__document-name">@_documentFileName</div>
<div class="sender-create-wizard__document-size">@_documentSizeText</div>
</div>
<div class="sender-create-wizard__document-preview-wrap">
<SenderPdfImageEditor IsPlacementMode="false"
PageCount="@_documentPreviewPageCount"
Pages="@_documentPreviewPages"
PdfDataUrl="@DocumentPreviewDataUrl"
PdfFileName="@_documentFileName"
Fields="@_documentPreviewFieldOverlays"
EditorClick="OnDocumentPreviewEditorClickAsync"
RemoveField="OnDocumentPreviewRemoveFieldAsync"
MoveField="OnDocumentPreviewMoveFieldAsync" />
</div>
</div>
}
else
{
<PdfFileDropZone Disabled="@IsActionLocked"
HasDocument="false"
UploadText='@L("SenderWizard_DocumentRequiredHint", "Bitte laden Sie ein PDF-Dokument hoch, um fortzufahren.")'
DragDropHintText='@L("SenderWizard_DragDropPdfHint", "Oder ziehen Sie die PDF-Datei hierher.")'
FileSelected="OnDocumentSelectedAsync" />
}
</div>
}
</div>
</div>
</div>
}
</MudStep>
}
</ChildContent>
</MudStepper>
</MudPaper>
<aside class="sender-create-wizard__wizard-side @(_currentStep == 4 ? "sender-create-wizard__wizard-side--hidden" : string.Empty)" aria-hidden="@(_currentStep == 4)">
<PdfFileDropZone Disabled="@IsActionLocked"
HasDocument="@(_documentBytes is { Length: > 0 })"
DocumentName="@_documentFileName"
DocumentSizeText="@_documentSizeText"
UploadText='@L("SenderWizard_DocumentUploadSimpleHint", "Dokument hinzufuegen: klicken oder per Drag-and-Drop ablegen")'
ReplaceText='@L("SenderWizard_DocumentUploadSimpleHint", "Dokument hinzufuegen: klicken oder per Drag-and-Drop ablegen")'
UploadedHintText='@L("SenderWizard_DocumentReplaceHint", "Zum Ersetzen klicken oder eine Datei auf diese Fläche ziehen.")'
FileSelected="OnDocumentSelectedAsync" />
</aside>
</div>
@if (!string.IsNullOrWhiteSpace(_validationMessage))
{
<div class="sender-create-wizard__validation">@_validationMessage</div>
}
</div>
</BodyContentTemplate>
<FooterContentTemplate>
<div class="sender-create-wizard__footer">
<DxButton Text="@L("Cancel", "Abbrechen")"
RenderStyle="ButtonRenderStyle.Secondary"
Click="CloseAsync"
Enabled="@(!IsActionLocked)" />
@if (CanSkipCurrentStep)
{
<DxButton Text="@L("Skip", "Überspringen")"
RenderStyle="ButtonRenderStyle.Secondary"
Click="SkipOptionalStep"
Enabled="@(!IsActionLocked)" />
}
<DxButton Text="@L("Back", "Zurück")"
RenderStyle="ButtonRenderStyle.Secondary"
Click="MoveBack"
Enabled="@(!IsFirstStep && !IsActionLocked)" />
@if (!IsLastStep)
{
<DxButton Text="@L("Next", "Weiter")"
RenderStyle="ButtonRenderStyle.Primary"
Click="MoveNextAsync"
Enabled="@(!IsActionLocked)" />
}
else
{
<DxButton Text="@L("Start", "Editor öffnen")"
RenderStyle="ButtonRenderStyle.Primary"
Click="FinishAsync"
Enabled="@(!IsActionLocked)" />
}
</div>
</FooterContentTemplate>
</DxPopup>
@code {
[Parameter]
public bool Visible { get; set; }
[Parameter]
public EventCallback<bool> VisibleChanged { get; set; }
[Parameter]
public EventCallback<CreateEnvelopeWizardResult> StartEditorRequested { get; set; }
[Parameter]
public IReadOnlyList<EnvelopeTypeDto> EnvelopeTypes { get; set; } = [];
private const int TotalSteps = 5;
private static readonly System.ComponentModel.DataAnnotations.EmailAddressAttribute EmailValidator = new();
private bool _isVisible;
private bool _previousVisible;
private int _currentStep;
private readonly HashSet<int> _completedSteps = [];
private string _title = string.Empty;
private int? _selectedEnvelopeTypeId;
private string _message = string.Empty;
private string _language = "de-DE";
private bool _useAccessCode;
private bool _tfaEnabled;
private CertificationType _certificationType = CertificationType.AdvancedElectronicSignature;
private int _finalEmailToCreator = (int)FinalEmailType.No;
private int _finalEmailToReceivers = (int)FinalEmailType.No;
private bool _sendReminderEmails;
private int _firstReminderDays;
private int _reminderIntervalDays;
private int _expiresWhenDays;
private int _expiresWarningWhenDays;
private string _receiverName = string.Empty;
private string _receiverEmail = string.Empty;
private string _receiverSuggestionFilterText = string.Empty;
private readonly List<CreateEnvelopeWizardReceiver> _receivers = [];
private readonly List<RecentReceiverSuggestionDto> _recentReceiverSuggestions = [];
private bool _isLoadingRecentSuggestions;
private bool _recentSuggestionsLoaded;
private const int SuggestionLimit = 20;
private byte[]? _documentBytes;
private string? _documentFileName;
private string _documentSizeText = string.Empty;
private int _documentPreviewPageCount;
private List<PdfPageImageDto> _documentPreviewPages = [];
private bool _isDocumentUploading;
private bool _isDocumentPreviewPrepared;
private bool _isStepActionRunning;
private bool _isFinishing;
private readonly IReadOnlyList<SenderPdfFieldOverlay> _documentPreviewFieldOverlays = [];
private string? _validationMessage;
private bool IsActionLocked => _isDocumentUploading || _isStepActionRunning || _isFinishing;
private bool IsFesSelected => _certificationType == CertificationType.AdvancedElectronicSignature;
private bool IsFirstStep => _currentStep <= 0;
private bool IsLastStep => _currentStep >= TotalSteps - 1;
private bool CanSkipCurrentStep => _currentStep == 3;
private IReadOnlyList<RecentReceiverSuggestionDto> FilteredRecentSuggestions =>
BuildFilteredRecentSuggestions();
private string? DocumentPreviewDataUrl => _documentBytes is { Length: > 0 }
? $"data:application/pdf;base64,{Convert.ToBase64String(_documentBytes)}"
: null;
private string CurrentStepTitle => GetStepTitle(_currentStep);
private string CurrentStepDescription => GetStepDescription(_currentStep);
private string GetStepTitle(int step) => step switch
{
0 => L("SenderWizard_Step_Start", "Start"),
1 => L("SenderWizard_Step_Receivers", "Empfänger"),
2 => L("SenderEditor_EnvelopeSettings", "Envelope-Einstellungen"),
3 => L("SenderWizard_Step_Message", "Nachricht und Optionen"),
4 => L("SenderWizard_Step_Document", "Dokument"),
_ => L("SenderWizard_Step_Start", "Start")
};
private string GetStepDescription(int step) => step switch
{
0 => L("SenderWizard_Step_Basics_Desc", "Starten Sie mit den Pflichtangaben für den Umschlag."),
1 => L("SenderWizard_Step_Receivers_Desc", "Definieren Sie die Empfänger für den Signaturprozess."),
2 => L("SenderWizard_Step_Settings_Desc", "Konfigurieren Sie Sicherheits-, Erinnerungs- und Ablaufoptionen."),
3 => L("SenderWizard_Step_Message_Desc", "Fügen Sie optionale Nachricht und Einstellungen hinzu."),
4 => L("SenderWizard_Step_Document_Desc", "Laden Sie das Dokument hoch und platzieren Sie Signaturfelder."),
_ => L("SenderWizard_Step_Basics_Desc", "Starten Sie mit den Pflichtangaben für den Umschlag.")
};
protected override void OnParametersSet()
{
_isVisible = Visible;
if (_selectedEnvelopeTypeId is null && EnvelopeTypes.Count > 0)
ApplyDefaultEnvelopeType();
if (Visible && !_previousVisible)
ResetWizardState();
_previousVisible = Visible;
}
private Task OnTitleChanged(string? value)
{
_title = value ?? string.Empty;
_validationMessage = null;
return Task.CompletedTask;
}
private Task OnMessageChanged(string? value)
{
_message = value ?? string.Empty;
_validationMessage = null;
return Task.CompletedTask;
}
private Task OnReceiverNameChanged(string? value)
{
_receiverName = value ?? string.Empty;
_validationMessage = null;
return Task.CompletedTask;
}
private Task OnReceiverEmailChanged(string? value)
{
_receiverEmail = value ?? string.Empty;
_validationMessage = null;
return Task.CompletedTask;
}
private Task OnReceiverSuggestionFilterChanged(string? value)
{
_receiverSuggestionFilterText = value ?? string.Empty;
_validationMessage = null;
return Task.CompletedTask;
}
private Task OnEnvelopeTypeChanged(ChangeEventArgs args)
{
_validationMessage = null;
var raw = args.Value?.ToString();
if (int.TryParse(raw, out var id) && id > 0)
{
_selectedEnvelopeTypeId = id;
var selectedType = EnvelopeTypes.FirstOrDefault(x => x.Id == id);
if (selectedType is not null)
ApplyEnvelopeTypeDefaults(selectedType);
return Task.CompletedTask;
}
_selectedEnvelopeTypeId = null;
return Task.CompletedTask;
}
private Task AddReceiver()
{
if (IsActionLocked)
return Task.CompletedTask;
var fullName = _receiverName.Trim();
var email = _receiverEmail.Trim();
if (string.IsNullOrWhiteSpace(fullName))
{
_validationMessage = L("SenderEditor_Validation_FullNameRequired", "Bitte geben Sie einen Vor- und Nachnamen ein.");
return Task.CompletedTask;
}
if (string.IsNullOrWhiteSpace(email))
{
_validationMessage = L("SenderEditor_Validation_EmailRequired", "Bitte geben Sie eine E-Mail-Adresse ein.");
return Task.CompletedTask;
}
if (!EmailValidator.IsValid(email))
{
_validationMessage = L("SenderEditor_Validation_EmailInvalid", "Bitte geben Sie eine gültige E-Mail-Adresse ein.");
return Task.CompletedTask;
}
if (_receivers.Any(x => string.Equals(x.EmailAddress, email, StringComparison.OrdinalIgnoreCase)))
{
_validationMessage = L("SenderEditor_Validation_EmailDuplicate", "Diese E-Mail-Adresse wurde bereits hinzugefügt.");
return Task.CompletedTask;
}
_receivers.Add(new CreateEnvelopeWizardReceiver(fullName, email));
_receiverName = string.Empty;
_receiverEmail = string.Empty;
_validationMessage = null;
return Task.CompletedTask;
}
private Task RemoveReceiver(CreateEnvelopeWizardReceiver receiver)
{
if (IsActionLocked)
return Task.CompletedTask;
_receivers.Remove(receiver);
_validationMessage = null;
return Task.CompletedTask;
}
private Task OnReceiverListNameChanged(int index, string? value)
{
if (IsActionLocked || index < 0 || index >= _receivers.Count)
return Task.CompletedTask;
var current = _receivers[index];
_receivers[index] = current with { FullName = value ?? string.Empty };
_validationMessage = null;
return Task.CompletedTask;
}
private Task OnReceiverListEmailChanged(int index, string? value)
{
if (IsActionLocked || index < 0 || index >= _receivers.Count)
return Task.CompletedTask;
var current = _receivers[index];
_receivers[index] = current with { EmailAddress = value ?? string.Empty };
_validationMessage = null;
return Task.CompletedTask;
}
private IReadOnlyList<RecentReceiverSuggestionDto> BuildFilteredRecentSuggestions()
{
if (_recentReceiverSuggestions.Count == 0)
return [];
var handler = new ReceiverSuggestionFilterHandler(_recentReceiverSuggestions);
var filterText = _receiverSuggestionFilterText.Trim();
var filtered = string.IsNullOrWhiteSpace(filterText)
? _recentReceiverSuggestions
: handler.Filter(filterText);
return filtered
.GroupBy(x => x.EmailAddress, StringComparer.OrdinalIgnoreCase)
.Select(g => g.OrderByDescending(x => x.LastUsedAt).First())
.OrderByDescending(x => x.LastUsedAt)
.ThenBy(x => x.EmailAddress, StringComparer.OrdinalIgnoreCase)
.Take(SuggestionLimit)
.ToList();
}
private async Task OnDocumentSelectedAsync(InputFileChangeEventArgs args)
{
if (IsActionLocked)
return;
_validationMessage = null;
var file = args.File;
if (file is null)
return;
if (!file.Name.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase))
{
_validationMessage = L("SenderEditor_SelectPdf", "Bitte wählen Sie eine PDF-Datei aus.");
return;
}
try
{
_isDocumentUploading = true;
const long maxBytes = 50 * 1024 * 1024;
await using var stream = file.OpenReadStream(maxBytes);
using var ms = new System.IO.MemoryStream();
await stream.CopyToAsync(ms);
_documentBytes = ms.ToArray();
_documentFileName = file.Name;
_documentSizeText = FormatSize(_documentBytes.Length);
_documentPreviewPages = [];
_documentPreviewPageCount = 0;
_isDocumentPreviewPrepared = false;
if (_currentStep == 4)
await EnsureDocumentPreviewPreparedAsync();
}
finally
{
_isDocumentUploading = false;
}
}
private Task MoveBack()
{
if (IsActionLocked)
return Task.CompletedTask;
_validationMessage = null;
if (!IsFirstStep)
_currentStep--;
return Task.CompletedTask;
}
private async Task MoveNextAsync()
{
if (IsActionLocked)
return;
_isStepActionRunning = true;
try
{
if (!ValidateCurrentStep())
return;
_validationMessage = null;
if (!IsLastStep)
{
_completedSteps.Add(_currentStep);
_currentStep++;
if (_currentStep == 1)
await EnsureRecentSuggestionsLoadedAsync();
if (_currentStep == 4)
await EnsureDocumentPreviewPreparedAsync();
}
}
finally
{
_isStepActionRunning = false;
}
await Task.CompletedTask;
}
private async Task SkipOptionalStep()
{
if (IsActionLocked)
return;
if (!CanSkipCurrentStep)
return;
_message = string.Empty;
_validationMessage = null;
await MoveNextAsync();
}
private async Task CloseAsync()
{
if (IsActionLocked)
return;
await CloseCoreAsync();
}
private async Task CloseCoreAsync()
{
ResetWizardState();
await OnPopupVisibleChangedAsync(false);
}
private async Task FinishAsync()
{
if (IsActionLocked)
return;
_isFinishing = true;
try
{
if (!ValidateCurrentStep())
return;
var result = new CreateEnvelopeWizardResult(
Title: _title.Trim(),
Message: _message.Trim(),
EnvelopeTypeId: _selectedEnvelopeTypeId,
Language: string.IsNullOrWhiteSpace(_language) ? "de-DE" : _language,
UseAccessCode: _useAccessCode,
TFAEnabled: _tfaEnabled,
CertificationType: _certificationType,
FinalEmailToCreator: _finalEmailToCreator,
FinalEmailToReceivers: _finalEmailToReceivers,
SendReminderEmails: _sendReminderEmails,
FirstReminderDays: _firstReminderDays,
ReminderIntervalDays: _reminderIntervalDays,
ExpiresWhenDays: _expiresWhenDays,
ExpiresWarningWhenDays: _expiresWarningWhenDays,
Receivers: _receivers.ToList(),
DocumentBytes: _documentBytes,
DocumentFileName: _documentFileName);
await CloseCoreAsync();
if (StartEditorRequested.HasDelegate)
await StartEditorRequested.InvokeAsync(result);
}
finally
{
_isFinishing = false;
}
}
private async Task OnWizardKeyDownAsync(KeyboardEventArgs args)
{
if (!_isVisible)
return;
if (string.Equals(args.Key, "Escape", StringComparison.OrdinalIgnoreCase))
{
await CloseAsync();
return;
}
if (!string.Equals(args.Key, "Enter", StringComparison.OrdinalIgnoreCase))
return;
if (_currentStep == 3)
return;
if (_currentStep == 1 && (!string.IsNullOrWhiteSpace(_receiverName) || !string.IsNullOrWhiteSpace(_receiverEmail)))
{
await AddReceiver();
return;
}
if (IsLastStep)
{
await FinishAsync();
return;
}
await MoveNextAsync();
}
private async Task OnPopupVisibleChangedAsync(bool value)
{
_isVisible = value;
if (!value)
ResetWizardState();
if (VisibleChanged.HasDelegate)
await VisibleChanged.InvokeAsync(value);
}
private async Task EnsureRecentSuggestionsLoadedAsync()
{
if (_recentSuggestionsLoaded || _isLoadingRecentSuggestions)
return;
_isLoadingRecentSuggestions = true;
try
{
var suggestions = await ReceiverService.GetRecentReceiverSuggestionsAsync(SuggestionLimit);
_recentReceiverSuggestions.Clear();
_recentReceiverSuggestions.AddRange(suggestions);
_recentSuggestionsLoaded = true;
}
catch
{
_recentReceiverSuggestions.Clear();
_recentSuggestionsLoaded = true;
}
finally
{
_isLoadingRecentSuggestions = false;
await InvokeAsync(StateHasChanged);
}
}
private async Task ApplyRecentSuggestionAndAddAsync(RecentReceiverSuggestionDto suggestion)
{
if (IsActionLocked)
return;
_receiverName = suggestion.FullName;
_receiverEmail = suggestion.EmailAddress;
await AddReceiver();
}
private async Task EnsureDocumentPreviewPreparedAsync()
{
if (_documentBytes is null || _documentBytes.Length == 0 || _isDocumentPreviewPrepared)
return;
try
{
_isDocumentUploading = true;
_documentPreviewPages = (await PdfRenderService.RenderPagesAsync(_documentBytes))
.OrderBy(x => x.PageNumber)
.ToList();
}
catch
{
_documentPreviewPages = [];
}
finally
{
_documentPreviewPageCount = _documentPreviewPages.Count > 0
? _documentPreviewPages.Count
: EstimatePdfPageCount(_documentBytes) ?? 1;
_isDocumentPreviewPrepared = true;
_isDocumentUploading = false;
}
}
private bool ValidateCurrentStep()
{
if (_currentStep == 0)
{
if (_selectedEnvelopeTypeId is null && EnvelopeTypes.Count > 0)
_selectedEnvelopeTypeId = GetDefaultEnvelopeTypeId();
if (string.IsNullOrWhiteSpace(_title))
{
_validationMessage = L("SenderEditor_Validation_TitleRequired", "Bitte geben Sie einen Titel ein.");
return false;
}
if (!_selectedEnvelopeTypeId.HasValue || _selectedEnvelopeTypeId.Value <= 0)
{
_validationMessage = L("SenderWizard_Validation_EnvelopeTypeRequired", "Bitte wählen Sie einen Umschlagtyp aus.");
return false;
}
return true;
}
if (_currentStep == 2)
{
if (!ValidateSettings(out var settingsError))
{
_validationMessage = settingsError;
return false;
}
return true;
}
if (_currentStep == 1)
{
if (_receivers.Count == 0)
{
_validationMessage = L("SenderEditor_Validation_ReceiverRequired", "Bitte fügen Sie mindestens einen Empfänger hinzu.");
return false;
}
for (var i = 0; i < _receivers.Count; i++)
{
var receiver = _receivers[i];
var fullName = receiver.FullName.Trim();
var email = receiver.EmailAddress.Trim();
if (string.IsNullOrWhiteSpace(fullName))
{
_validationMessage = L("SenderEditor_Validation_FullNameRequired", "Bitte geben Sie einen Vor- und Nachnamen ein.");
return false;
}
if (string.IsNullOrWhiteSpace(email))
{
_validationMessage = L("SenderEditor_Validation_EmailRequired", "Bitte geben Sie eine E-Mail-Adresse ein.");
return false;
}
if (!EmailValidator.IsValid(email))
{
_validationMessage = L("SenderEditor_Validation_EmailInvalid", "Bitte geben Sie eine gültige E-Mail-Adresse ein.");
return false;
}
}
var duplicateEmailExists = _receivers
.GroupBy(x => x.EmailAddress.Trim(), StringComparer.OrdinalIgnoreCase)
.Any(group => group.Count() > 1);
if (duplicateEmailExists)
{
_validationMessage = L("SenderEditor_Validation_EmailDuplicate", "Diese E-Mail-Adresse wurde bereits hinzugefügt.");
return false;
}
return true;
}
if (_currentStep == 4)
{
if (_documentBytes is null || _documentBytes.Length == 0)
{
_validationMessage = L("SenderEditor_Validation_UploadPdf", "Bitte laden Sie zuerst ein PDF-Dokument hoch.");
return false;
}
return true;
}
return true;
}
private static string FormatSize(long bytes)
{
const double kb = 1024;
const double mb = kb * 1024;
if (bytes >= mb)
return $"{bytes / mb:0.##} MB";
if (bytes >= kb)
return $"{bytes / kb:0.##} KB";
return $"{bytes} B";
}
private static int? EstimatePdfPageCount(byte[]? documentBytes)
{
if (documentBytes is null || documentBytes.Length == 0)
return null;
try
{
var text = System.Text.Encoding.ASCII.GetString(documentBytes);
const string marker = "/Type /Page";
var count = 0;
var index = 0;
while (true)
{
index = text.IndexOf(marker, index, StringComparison.Ordinal);
if (index < 0)
break;
count++;
index += marker.Length;
}
return count > 0 ? count : null;
}
catch
{
return null;
}
}
private Task OnDocumentPreviewEditorClickAsync(MouseEventArgs _)
{
return Task.CompletedTask;
}
private Task OnDocumentPreviewRemoveFieldAsync(int _)
{
return Task.CompletedTask;
}
private Task OnDocumentPreviewMoveFieldAsync(SenderPdfFieldMoveRequest _)
{
return Task.CompletedTask;
}
private void OnTfaChanged(ChangeEventArgs args)
{
_validationMessage = null;
if (IsFesSelected)
{
_tfaEnabled = true;
_useAccessCode = true;
return;
}
_tfaEnabled = ParseCheckboxValue(args);
if (_tfaEnabled)
_useAccessCode = true;
}
private Task OnCertificationTypeChanged()
{
_validationMessage = null;
NormalizeSettings();
return Task.CompletedTask;
}
private void OnUseAccessCodeChanged(ChangeEventArgs args)
{
_validationMessage = null;
if (_tfaEnabled)
{
_useAccessCode = true;
return;
}
_useAccessCode = ParseCheckboxValue(args);
}
private void OnSendReminderEmailsChanged(ChangeEventArgs args)
{
_validationMessage = null;
_sendReminderEmails = ParseCheckboxValue(args);
}
private static bool ParseCheckboxValue(ChangeEventArgs args)
{
if (args.Value is bool boolValue)
return boolValue;
return bool.TryParse(args.Value?.ToString(), out var parsed) && parsed;
}
private void ApplyEnvelopeTypeDefaults(EnvelopeTypeDto envelopeType)
{
if (!string.IsNullOrWhiteSpace(envelopeType.Language))
_language = envelopeType.Language;
if (envelopeType.UseAccessCode.HasValue)
_useAccessCode = envelopeType.UseAccessCode.Value;
if (_tfaEnabled)
_useAccessCode = true;
if (envelopeType.CertificationType.HasValue)
_certificationType = envelopeType.CertificationType.Value;
if (envelopeType.FinalEmailToCreator.HasValue)
_finalEmailToCreator = envelopeType.FinalEmailToCreator.Value;
if (envelopeType.FinalEmailToReceivers.HasValue)
_finalEmailToReceivers = envelopeType.FinalEmailToReceivers.Value;
if (envelopeType.SendReminderEmails.HasValue)
_sendReminderEmails = envelopeType.SendReminderEmails.Value;
if (envelopeType.FirstReminderDays.HasValue)
_firstReminderDays = envelopeType.FirstReminderDays.Value;
if (envelopeType.ReminderIntervalDays.HasValue)
_reminderIntervalDays = envelopeType.ReminderIntervalDays.Value;
if (envelopeType.ExpiresDays.HasValue)
_expiresWhenDays = envelopeType.ExpiresDays.Value;
if (envelopeType.ExpiresWarningDays.HasValue)
_expiresWarningWhenDays = envelopeType.ExpiresWarningDays.Value;
NormalizeSettings();
}
private void NormalizeSettings()
{
_language = string.IsNullOrWhiteSpace(_language) ? "de-DE" : _language.Trim();
if (IsFesSelected)
_tfaEnabled = true;
if (_tfaEnabled)
_useAccessCode = true;
_firstReminderDays = Math.Max(0, _firstReminderDays);
_reminderIntervalDays = Math.Max(0, _reminderIntervalDays);
_expiresWhenDays = Math.Max(0, _expiresWhenDays);
_expiresWarningWhenDays = Math.Max(0, _expiresWarningWhenDays);
if (_expiresWhenDays > 0 && _expiresWarningWhenDays > _expiresWhenDays)
_expiresWarningWhenDays = _expiresWhenDays;
}
private bool ValidateSettings(out string? message)
{
NormalizeSettings();
if (_sendReminderEmails && _reminderIntervalDays <= 0)
{
message = L("SenderEditor_Validation_ReminderInterval", "Bitte geben Sie ein Erinnerungsintervall groesser als 0 Tage ein.");
return false;
}
if (_sendReminderEmails && _firstReminderDays < 0)
{
message = L("SenderEditor_Validation_FirstReminder", "Bitte geben Sie gueltige Tage fuer die erste Erinnerung ein.");
return false;
}
if (_expiresWhenDays > 0 && _expiresWarningWhenDays > _expiresWhenDays)
{
message = L("SenderEditor_Validation_ExpiryWarning", "Die Ablaufwarnung darf nicht nach dem Ablaufdatum liegen.");
return false;
}
message = null;
return true;
}
private void ApplyDefaultEnvelopeType()
{
_selectedEnvelopeTypeId = GetDefaultEnvelopeTypeId();
if (!_selectedEnvelopeTypeId.HasValue)
return;
var selectedType = EnvelopeTypes.FirstOrDefault(x => x.Id == _selectedEnvelopeTypeId.Value);
if (selectedType is not null)
ApplyEnvelopeTypeDefaults(selectedType);
}
private void ResetWizardState()
{
_currentStep = 0;
_completedSteps.Clear();
_validationMessage = null;
_title = string.Empty;
_selectedEnvelopeTypeId = null;
_message = string.Empty;
_language = "de-DE";
_useAccessCode = false;
_tfaEnabled = false;
_certificationType = CertificationType.AdvancedElectronicSignature;
_finalEmailToCreator = (int)FinalEmailType.No;
_finalEmailToReceivers = (int)FinalEmailType.No;
_sendReminderEmails = false;
_firstReminderDays = 0;
_reminderIntervalDays = 0;
_expiresWhenDays = 0;
_expiresWarningWhenDays = 0;
_receiverName = string.Empty;
_receiverEmail = string.Empty;
_receiverSuggestionFilterText = string.Empty;
_receivers.Clear();
_recentReceiverSuggestions.Clear();
_isLoadingRecentSuggestions = false;
_recentSuggestionsLoaded = false;
_documentBytes = null;
_documentFileName = null;
_documentSizeText = string.Empty;
_documentPreviewPageCount = 0;
_documentPreviewPages = [];
_isDocumentPreviewPrepared = false;
ApplyDefaultEnvelopeType();
NormalizeSettings();
}
private int? GetDefaultEnvelopeTypeId()
{
if (EnvelopeTypes.Count == 0)
return null;
var handwrittenType = EnvelopeTypes.FirstOrDefault(x =>
!string.IsNullOrWhiteSpace(x.Title)
&& x.Title.Contains("Handschriftliche Unterschrift", StringComparison.OrdinalIgnoreCase));
return handwrittenType?.Id ?? EnvelopeTypes[0].Id;
}
private string L(string key, string fallback)
{
var modelValue = ModelLocalizer[key];
if (!modelValue.ResourceNotFound)
return modelValue.Value;
var uiValue = UiLocalizer[key];
return uiValue.ResourceNotFound ? fallback : uiValue.Value;
}
}