Compare commits

...

25 Commits

Author SHA1 Message Date
89a4e789f1 Improve popup layout and scrolling behavior
Updated `CreateEnvelopeWizardPopup.razor` to enforce consistent popup dimensions by setting height properties to `85vh` and disabling scrolling.

Enhanced `sender-page.css` with new classes to manage flexible layouts and scrolling behavior for the wizard's stepper and step content. Removed fixed height and scrolling constraints from `.sender-create-wizard__receiver-existing-list` for better layout flexibility.

These changes improve the user experience by ensuring consistent dimensions, reducing unnecessary scrolling, and maintaining a structured layout.
2026-11-11 23:50:45 +01:00
94658b0d9b Simplify wizard flow and improve document upload UX
Reduced wizard steps from 5 to 4 by merging document upload into step 3. Added a new localized hint (`SenderWizard_FinalUploadHint`) to guide users during the final document upload. Removed redundant document preview functionality and associated UI elements.

Refactored the document upload area to always remain visible on the right side of the wizard, using the `PdfFileDropZone` component. Removed the "Skip" button for optional steps and cleaned up unused methods, properties, and CSS classes.

Updated step titles and descriptions to reflect the new flow. General code cleanup to simplify logic and improve maintainability.
2026-11-11 17:24:39 +01:00
6ad76fa4b0 Improve PDF preview UX in SenderPage.razor
Updated button labels and popup headers to reflect a change in functionality from "Preview" to "Show Original Document." Added an informational hint in the PDF preview viewer to notify users about the absence of signature fields, with auto-hide and dismiss functionality. Introduced state management for the hint display, including methods to show and dismiss it, and ensured proper reset on preview close. Enhanced localization and styling for better user clarity.
2026-11-11 16:58:47 +01:00
31933f5568 Add "Send Confirmation" feature for envelope sending
Introduced a "Send Confirmation" popup to improve user experience
when sending envelopes. The popup allows users to review recipients,
signature fields, and settings before confirming the send action.

- Added localized strings in `Resource.de-DE.resx`, `Resource.en-US.resx`,
  and `Resource.fr-FR.resx` for confirmation messages and actions.
- Updated `SenderEditorPage.razor` to include the confirmation popup
  (`DxPopup`) with body and footer templates.
- Added `RequestSendAsync` and `ConfirmSendAsync` methods to manage
  the confirmation process and sending logic.
- Replaced the old success popup logic with direct navigation to the
  sender dashboard after a successful send.
- Updated the toolbar button to trigger the confirmation popup.
- Styled the confirmation popup in `envelope-viewer.css`, including
  support for dark themes.
- Ensured proper state management for the popup and sending process.

These changes enhance usability by providing a clear confirmation
step and progress feedback during envelope sending.
2026-11-11 16:17:34 +01:00
47c4f33dfc Refactor and enhance SenderPage grid and UI behavior
Refactored the `DxGrid` component in `SenderPage.razor` to improve readability, maintainability, and user experience. Added new grid properties like `HighlightRowOnHover` and `DetailExpandButtonDisplayMode`. Simplified and reorganized column and template structures.

Enhanced detail row behavior to handle row expansion and collapse more intuitively. Updated receiver and history displays with progress bars, badges, and improved styling.

Performed code cleanup by removing redundant blocks, consolidating logic, and eliminating unused variables. Refactored `ThemeService.LoadFromServerAsync` for better readability.

Made minor adjustments to the `CreateEnvelopeWizardPopup` configuration and improved general UI elements for better alignment, spacing, and styling.
2026-11-11 14:07:19 +01:00
26d41d9178 Add AutoCollapseDetailRow and improve attribute layout
The `AutoCollapseDetailRow=true` attribute was added to enable automatic collapsing of detail rows in the grid. Additionally, the `LayoutAutoSaving` attribute was moved to its own line for improved readability.
2026-11-11 09:24:31 +01:00
238c938ce7 Optimize post-auth calls with parallel execution
Refactored the code to improve performance by parallelizing independent post-authentication service calls using `Task.WhenAll`. This reduces the initial page load time by avoiding redundant HTTP calls and reusing pre-fetched data.

- Moved `ThemeService.LoadFromServerAsync` to a separate task.
- Parallelized service calls for theme, logos, envelope types, and envelopes.
- Introduced `ApplyEnvelopeResultAsync` to process envelope data.
- Updated `LoadEnvelopesAsync` to reuse pre-fetched envelope data.
- Removed redundant HTTP calls to streamline initialization logic.
2026-11-11 09:15:46 +01:00
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
2c8b65d576 Add localized label to signature field button
A `<span>` element with the class `pdf-toolbar__btn-text` was added to the signature field button. This element displays localized text for "Signaturfeld" (Signature Field) using the `@L` localization function. The change improves clarity and accessibility by adding a visible label next to the button's icon.
2026-11-10 21:06:00 +01:00
e1f24249a9 Add confirmation popup for clearing signature fields
Added a confirmation popup to `SenderEditorPage.razor` for clearing all signature fields, requiring user confirmation before proceeding. Updated resource files (`Resource.de-DE.resx`, `Resource.en-US.resx`, `Resource.fr-FR.resx`) with localized confirmation messages. Modified the "Remove all signature fields" button to trigger the popup instead of directly invoking the clear action.

Introduced a new `_clearFieldsConfirmVisible` variable to manage popup visibility. Updated the `ClearAllFieldsAsync` method to close the popup before clearing fields. Added new CSS styles in `envelope-viewer.css` to style the confirmation popup and its elements.
2026-11-10 21:00:36 +01:00
f6dc454193 Adjust popup width for better responsiveness
Updated the `DxPopup` component in `CreateEnvelopeWizardPopup.razor` to use `90vw` for both width and max-width, replacing the previous values of `860px` and `96vw`. Corresponding changes were made in `sender-page.css` for `.sender-create-wizard-popup .dxbl-popup` under the media query for screens with a max width of `760px`. These changes improve layout consistency and responsiveness.
2026-11-10 19:37:46 +01:00
b65098fafb Improve document upload UX and refactor wizard layout
Enhanced the `CreateEnvelopeWizardPopup` component:
- Added a new grid layout (`sender-create-wizard__body-layout`) to separate main content and side panel.
- Introduced a collapsible side panel for document upload and preview.
- Replaced old upload logic with `PdfFileDropZone` and added support for displaying document metadata (name, size) and replacement hints.
- Added asynchronous document preview rendering with `EnsureDocumentPreviewPreparedAsync`.

Localization updates:
- Added new localized strings for document upload hints in `de-DE`, `en-US`, and `fr-FR`.

CSS updates:
- Styled the new layout and side panel with transitions.
- Updated `PdfFileDropZone` styles for different states (e.g., document uploaded).

Refactored code for better maintainability and improved user feedback during document upload and preview preparation.
2026-11-10 19:29:33 +01:00
c6fe7722b4 refactor(AGENTS.md): add todo 2026-11-10 17:58:58 +01:00
2065102fb3 Update default zoom level in PDF editor to 180%
The `_zoomPercent` variable was updated from 112 to 180, increasing the default zoom level for the PDF image editor. This change ensures that users start with a higher magnification when interacting with the editor.
2026-11-10 17:19:51 +01:00
858b748e65 Refactor receiver list UI and improve accessibility
Reorganized the receiver list in `CreateEnvelopeWizardPopup.razor`:
- Separated "existing receivers" and "new receiver" input rows.
- Added editable fields for `FullName` and `EmailAddress` with real-time updates.
- Introduced "Remove" and "Add Receiver" buttons with updated icons and accessibility attributes.

Updated `sender-page.css`:
- Styled `.sender-create-wizard__receiver-existing-list` for better usability and responsiveness.
- Improved button designs with hover effects and consistent sizing.
- Simplified layout and removed redundant styles.

Enhanced responsiveness and accessibility:
- Made receiver list scrollable and visually distinct.
- Improved localization support with updated titles and `aria-labels`.

Removed redundant code to streamline implementation.
2026-11-10 09:41:38 +01:00
25c03b350b Refactor: Remove unused receiver filtering and list UI
Removed the title and input field for filtering recent receivers
from the suggestions section. Also removed the label, tooltip,
and help icon for the receiver list, along with the container
used to display added receivers. These elements were no longer
needed in the updated UI flow.
2026-11-09 16:34:43 +01:00
ed9ea0ac54 Refactor receiver row layout in CreateEnvelopeWizard
Refactor the structure and class names in `CreateEnvelopeWizardPopup.razor` to improve the layout and alignment of receiver rows:
- Replace `sender-create-wizard__receiver-item` with `sender-create-wizard__receiver-row`.
- Replace `sender-create-wizard__form-grid` with `sender-create-wizard__receiver-fields-stack`.

Update `sender-page.css` to introduce new classes:
- Add `.sender-create-wizard__receiver-row` for flex layout with proper alignment and spacing.
- Add `.sender-create-wizard__receiver-fields-stack` for grid-based field organization.

Adjust styles for responsiveness, including column layouts for smaller contexts. These changes enhance the visual structure and responsiveness of the receiver rows.
2026-11-09 16:16:08 +01:00
18c1adb68a Refactor receiver management UI in wizard
Simplify and restructure the UI for managing receivers in the sender creation wizard. Replace separate fields for adding new receivers with an inline approach, allowing users to add receivers directly within the receiver list.

Introduce a new inline receiver item with fields for name and email, along with an "Add" button. Update the receiver list layout to accommodate the new design.

Add styles for the inline receiver item and "Add Receiver" button in `sender-page.css`, including hover effects, disabled states, and alignment adjustments. Ensure the new design integrates seamlessly with the existing layout.
2026-11-09 16:08:00 +01:00
ec8b9adb9c Refactor receiver management in wizard popup
Replaced `DxTagBox` with a table-like structure for managing receivers, enabling direct editing of `FullName` and `EmailAddress` fields. Added validation for required fields and duplicate email addresses. Updated `RemoveReceiver` functionality with a new button and improved accessibility.

Removed `_receiverTags` and related logic, simplifying state management. Introduced new methods for handling receiver updates. Updated CSS to style the new layout and components. Improved code clarity and user experience.
2026-11-09 15:51:07 +01:00
4a10db0865 Refactor recent suggestion handling to use async method
Replaced the synchronous `ApplyRecentSuggestion` method with the new asynchronous `ApplyRecentSuggestionAndAddAsync` method. Updated the `@onclick` event handler in the suggestion list to use the async method. Removed `_validationMessage = null;` from the logic, and added an `await AddReceiver()` call to handle receiver addition asynchronously. These changes improve responsiveness and support additional async operations.
2026-11-09 13:34:17 +01:00
ceb2860883 Add filter for recent receiver suggestions
Introduced a filter input box in `CreateEnvelopeWizardPopup.razor` to allow users to search recent receiver suggestions dynamically. Added `_receiverSuggestionFilterText` property and `OnReceiverSuggestionFilterChanged` method to handle filtering logic.

Refactored `ReceiverSuggestionFilterHandler` to simplify its API. Updated the `Filter` method to accept a single query string and removed redundant overloads. Improved filtering logic to match `FullName` or `EmailAddress` using case-sensitive comparison.

These changes enhance usability by enabling quick filtering of receiver suggestions.
2026-11-09 13:14:31 +01:00
11e72bac53 Update resource keys and labels for final emails
Updated "FinalEmailToCreator" and "FinalEmailToReceivers" text in `.resx` files to use "Completed Document" terminology in English, German, and French. Adjusted corresponding labels, tooltips, and aria-labels in `CreateEnvelopeWizardPopup.razor` to reflect these changes in German.
2026-11-09 11:33:44 +01:00
638b734648 Enhance UI with tooltips and MudBlazor integration
Added contextual tooltips (`MudTooltip` and `MudIconButton`) to form fields in `CreateEnvelopeWizardPopup.razor` for improved user guidance. Updated label layouts using the new `sender-create-wizard__label-row` class for better alignment with tooltips. Integrated MudBlazor components (`MudThemeProvider`, `MudPopoverProvider`, `MudDialogProvider`, `MudSnackbarProvider`) in `SenderPage.razor` to enable enhanced UI features.

Updated `_Imports.razor` to include `MudBlazor` for global availability. Enhanced accessibility by adding `aria-label` attributes to help icons. Improved user experience with detailed explanations for fields like "Title," "Envelope Type," and others. Updated checkbox labels and numeric input fields with tooltips for additional context.

Added new CSS styles in `sender-page.css` to ensure proper alignment and sizing of tooltips and icons. These changes improve the overall usability, accessibility, and design consistency of the application.
2026-11-09 11:22:53 +01:00
eec1b4926b Update localization for wizard step titles
Added a new localization key `SenderWizard_Step_Start` to resource files for German, English, and French locales. Updated `CreateEnvelopeWizardPopup.razor` to replace `SenderWizard_Step_Basics` with `SenderWizard_Step_Start` for step 0, ensuring consistent naming of the first wizard step across the application.
2026-11-09 09:35:10 +01:00
d7611a6b59 Enhance stepper functionality and visual design
Added new properties to `<MudStepper>` for customization and
visual feedback. Introduced `_completedSteps` to track step
completion dynamically. Updated logic to manage completed
steps and reset state. Improved CSS for stepper appearance,
including step labels, icons, and content differentiation.
These changes improve usability and flexibility of the
stepper component.
2026-11-09 09:13:58 +01:00
16 changed files with 1272 additions and 696 deletions

View File

@@ -287,6 +287,7 @@ Manual testing workflow:
- Audit `EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/` for unnecessary request/payload remapping and remove redundant mappings where direct request DTO usage is safe; review client HTTP methods end-to-end for similar avoidable transformation layers. - Audit `EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/` for unnecessary request/payload remapping and remove redundant mappings where direct request DTO usage is safe; review client HTTP methods end-to-end for similar avoidable transformation layers.
- Centralize dependency injection registrations into dedicated extension methods/modules (especially server + client service registrations in `Program.cs`) to reduce startup composition sprawl and improve maintainability. - Centralize dependency injection registrations into dedicated extension methods/modules (especially server + client service registrations in `Program.cs`) to reduce startup composition sprawl and improve maintainability.
- Refactor `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Controllers/PdfRenderController.cs` + matching client service contract to remove JSON/base64 `byte[]` transport for PDF rendering; prefer binary streaming (`application/octet-stream` or multipart), avoid `data:` URL bloat where possible, and validate end-to-end memory/latency behavior with large PDFs. - Refactor `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Controllers/PdfRenderController.cs` + matching client service contract to remove JSON/base64 `byte[]` transport for PDF rendering; prefer binary streaming (`application/octet-stream` or multipart), avoid `data:` URL bloat where possible, and validate end-to-end memory/latency behavior with large PDFs.
- Move sender PDF image editor initial zoom from hardcoded value to Options pattern configuration (bind from `appsettings.json` and inject via `IOptions`/`IOptionsMonitor`) so default zoom (for example `%180`) is environment-configurable.
- Establish a DevExpress toast-based notification architecture using `DxToastProvider` + `IToastNotificationService` (tab/page-scoped provider strategy, standardized `ToastOptions` factory, and severity-to-style mapping) so sender/receiver flows show consistent, non-duplicated, actionable feedback. - Establish a DevExpress toast-based notification architecture using `DxToastProvider` + `IToastNotificationService` (tab/page-scoped provider strategy, standardized `ToastOptions` factory, and severity-to-style mapping) so sender/receiver flows show consistent, non-duplicated, actionable feedback.
- Eliminate manual property-by-property request→domain mapping drift (example: `CreateEnvelopeCommandHandler`) by introducing a standardized mapping policy (AutoMapper/profile conventions or reflection/source-generator based mapper) and enforcing it across envelope workflows; this needs to be solved in the shared company Core library level so all services get the same guardrails by default. - Eliminate manual property-by-property request→domain mapping drift (example: `CreateEnvelopeCommandHandler`) by introducing a standardized mapping policy (AutoMapper/profile conventions or reflection/source-generator based mapper) and enforcing it across envelope workflows; this needs to be solved in the shared company Core library level so all services get the same guardrails by default.
- P1 security workstream: remove dynamic SQL string composition/interpolation from active save/create/update paths (`string.Format`, `$"...{input}..."`, `ToSqlParam`) and migrate to parameterized queries only; treat this as a potential SQL injection vulnerability (stability + security risk), execute repo-wide audit, and add CI guardrails to block reintroduction. - P1 security workstream: remove dynamic SQL string composition/interpolation from active save/create/update paths (`string.Format`, `$"...{input}..."`, `ToSqlParam`) and migrate to parameterized queries only; treat this as a potential SQL injection vulnerability (stability + security risk), execute repo-wide audit, and add CI guardrails to block reintroduction.

View File

@@ -622,10 +622,10 @@
<value>Zertifizierungstyp</value> <value>Zertifizierungstyp</value>
</data> </data>
<data name="FinalEmailToCreator" xml:space="preserve"> <data name="FinalEmailToCreator" xml:space="preserve">
<value>Finale E-Mail an Ersteller</value> <value>Abgeschlossenes Dokument an Ersteller</value>
</data> </data>
<data name="FinalEmailToReceivers" xml:space="preserve"> <data name="FinalEmailToReceivers" xml:space="preserve">
<value>Finale E-Mail an Empfänger</value> <value>Abgeschlossenes Dokument an Empfänger</value>
</data> </data>
<data name="SendReminderEmails" xml:space="preserve"> <data name="SendReminderEmails" xml:space="preserve">
<value>Erinnerungs-E-Mails senden</value> <value>Erinnerungs-E-Mails senden</value>
@@ -749,7 +749,14 @@
<data name="SenderEditor_PageLabel" xml:space="preserve"><value>Seite</value></data> <data name="SenderEditor_PageLabel" xml:space="preserve"><value>Seite</value></data>
<data name="SenderEditor_PlaceSignatureField" xml:space="preserve"><value>Signaturfeld platzieren</value></data> <data name="SenderEditor_PlaceSignatureField" xml:space="preserve"><value>Signaturfeld platzieren</value></data>
<data name="SenderEditor_RemoveAllSignatureFields" xml:space="preserve"><value>Alle Signaturfelder entfernen</value></data> <data name="SenderEditor_RemoveAllSignatureFields" xml:space="preserve"><value>Alle Signaturfelder entfernen</value></data>
<data name="SenderEditor_RemoveAllSignatureFieldsConfirm" xml:space="preserve"><value>Diese Aktion entfernt alle platzierten Signaturfelder unwiderruflich. Fortfahren?</value></data>
<data name="SenderEditor_SelectPdf" xml:space="preserve"><value>Bitte wählen Sie eine PDF-Datei aus.</value></data> <data name="SenderEditor_SelectPdf" xml:space="preserve"><value>Bitte wählen Sie eine PDF-Datei aus.</value></data>
<data name="SenderEditor_SendConfirm_Message" xml:space="preserve"><value>Wollen Sie den Umschlag jetzt versenden? Hinweis: Nach der Übergabe kann der Vorgang nicht mehr bearbeitet werden.</value></data>
<data name="SenderEditor_SendConfirm_Title" xml:space="preserve"><value>Umschlag jetzt versenden?</value></data>
<data name="SenderEditor_SendConfirm_Note" xml:space="preserve"><value>Bitte prüfen Sie Empfänger, Signaturfelder und Einstellungen vor dem Versenden.</value></data>
<data name="SenderEditor_SendConfirm_Action" xml:space="preserve"><value>Jetzt versenden</value></data>
<data name="SenderEditor_SendConfirm_InProgress" xml:space="preserve"><value>Umschlag wird versendet...</value></data>
<data name="SenderEditor_SendConfirm_InProgressHint" xml:space="preserve"><value>Bitte warten Sie einen Moment. Der Vorgang wird abgeschlossen.</value></data>
<data name="SenderEditor_Sent_Body" xml:space="preserve"><value>Der bestehende Entwurf wurde versendet.</value></data> <data name="SenderEditor_Sent_Body" xml:space="preserve"><value>Der bestehende Entwurf wurde versendet.</value></data>
<data name="SenderEditor_Sent_Header" xml:space="preserve"><value>Umschlag gesendet</value></data> <data name="SenderEditor_Sent_Header" xml:space="preserve"><value>Umschlag gesendet</value></data>
<data name="SenderEditor_Sent_Title" xml:space="preserve"><value>Umschlag wurde erfolgreich gesendet.</value></data> <data name="SenderEditor_Sent_Title" xml:space="preserve"><value>Umschlag wurde erfolgreich gesendet.</value></data>
@@ -777,6 +784,10 @@
<data name="Signed" xml:space="preserve"><value>Unterschrieben</value></data> <data name="Signed" xml:space="preserve"><value>Unterschrieben</value></data>
<data name="SignedDocument" xml:space="preserve"><value>Signiertes Dokument</value></data> <data name="SignedDocument" xml:space="preserve"><value>Signiertes Dokument</value></data>
<data name="System" xml:space="preserve"><value>System</value></data> <data name="System" xml:space="preserve"><value>System</value></data>
<data name="SenderWizard_Step_Start" xml:space="preserve"><value>Start</value></data>
<data name="SenderWizard_DocumentUploadSimpleHint" xml:space="preserve"><value>Dokument hinzufuegen: klicken oder per Drag-and-Drop ablegen</value></data>
<data name="SenderWizard_DocumentReplaceHint" xml:space="preserve"><value>Zum Ersetzen klicken oder eine Datei auf diese Flaeche ziehen.</value></data>
<data name="SenderWizard_FinalUploadHint" xml:space="preserve"><value>Letzte Moeglichkeit im Wizard: Bitte laden Sie jetzt Ihr PDF ueber den rechten Upload-Bereich hoch. Im Editor koennen Sie spaeter ebenfalls ein Dokument hochladen oder ersetzen.</value></data>
<data name="UnexpectedErrorTryAgainLater" xml:space="preserve"><value>Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.</value></data> <data name="UnexpectedErrorTryAgainLater" xml:space="preserve"><value>Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.</value></data>
<data name="UploadPdf" xml:space="preserve"><value>PDF hochladen</value></data> <data name="UploadPdf" xml:space="preserve"><value>PDF hochladen</value></data>
<data name="UserName" xml:space="preserve"><value>Benutzername</value></data> <data name="UserName" xml:space="preserve"><value>Benutzername</value></data>

View File

@@ -622,10 +622,10 @@
<value>Certification Type</value> <value>Certification Type</value>
</data> </data>
<data name="FinalEmailToCreator" xml:space="preserve"> <data name="FinalEmailToCreator" xml:space="preserve">
<value>Final Email to Creator</value> <value>Completed Document to Creator</value>
</data> </data>
<data name="FinalEmailToReceivers" xml:space="preserve"> <data name="FinalEmailToReceivers" xml:space="preserve">
<value>Final Email to Receivers</value> <value>Completed Document to Receivers</value>
</data> </data>
<data name="SendReminderEmails" xml:space="preserve"> <data name="SendReminderEmails" xml:space="preserve">
<value>Send Reminder Emails</value> <value>Send Reminder Emails</value>
@@ -749,7 +749,14 @@
<data name="SenderEditor_PageLabel" xml:space="preserve"><value>Page</value></data> <data name="SenderEditor_PageLabel" xml:space="preserve"><value>Page</value></data>
<data name="SenderEditor_PlaceSignatureField" xml:space="preserve"><value>Place signature field</value></data> <data name="SenderEditor_PlaceSignatureField" xml:space="preserve"><value>Place signature field</value></data>
<data name="SenderEditor_RemoveAllSignatureFields" xml:space="preserve"><value>Remove all signature fields</value></data> <data name="SenderEditor_RemoveAllSignatureFields" xml:space="preserve"><value>Remove all signature fields</value></data>
<data name="SenderEditor_RemoveAllSignatureFieldsConfirm" xml:space="preserve"><value>This action will permanently remove all placed signature fields. Continue?</value></data>
<data name="SenderEditor_SelectPdf" xml:space="preserve"><value>Please select a PDF file.</value></data> <data name="SenderEditor_SelectPdf" xml:space="preserve"><value>Please select a PDF file.</value></data>
<data name="SenderEditor_SendConfirm_Message" xml:space="preserve"><value>Do you want to send the envelope now? Note: After handover, the process can no longer be edited.</value></data>
<data name="SenderEditor_SendConfirm_Title" xml:space="preserve"><value>Send envelope now?</value></data>
<data name="SenderEditor_SendConfirm_Note" xml:space="preserve"><value>Please review recipients, signature fields, and settings before sending.</value></data>
<data name="SenderEditor_SendConfirm_Action" xml:space="preserve"><value>Send now</value></data>
<data name="SenderEditor_SendConfirm_InProgress" xml:space="preserve"><value>Sending envelope...</value></data>
<data name="SenderEditor_SendConfirm_InProgressHint" xml:space="preserve"><value>Please wait a moment while we complete the process.</value></data>
<data name="SenderEditor_Sent_Body" xml:space="preserve"><value>The existing draft has been sent.</value></data> <data name="SenderEditor_Sent_Body" xml:space="preserve"><value>The existing draft has been sent.</value></data>
<data name="SenderEditor_Sent_Header" xml:space="preserve"><value>Envelope sent</value></data> <data name="SenderEditor_Sent_Header" xml:space="preserve"><value>Envelope sent</value></data>
<data name="SenderEditor_Sent_Title" xml:space="preserve"><value>Envelope was sent successfully.</value></data> <data name="SenderEditor_Sent_Title" xml:space="preserve"><value>Envelope was sent successfully.</value></data>
@@ -777,6 +784,10 @@
<data name="Signed" xml:space="preserve"><value>Signed</value></data> <data name="Signed" xml:space="preserve"><value>Signed</value></data>
<data name="SignedDocument" xml:space="preserve"><value>Signed document</value></data> <data name="SignedDocument" xml:space="preserve"><value>Signed document</value></data>
<data name="System" xml:space="preserve"><value>System</value></data> <data name="System" xml:space="preserve"><value>System</value></data>
<data name="SenderWizard_Step_Start" xml:space="preserve"><value>Start</value></data>
<data name="SenderWizard_DocumentUploadSimpleHint" xml:space="preserve"><value>Add document: click or drag and drop</value></data>
<data name="SenderWizard_DocumentReplaceHint" xml:space="preserve"><value>Click to replace or drag a file onto this area.</value></data>
<data name="SenderWizard_FinalUploadHint" xml:space="preserve"><value>Last chance in the wizard: please upload your PDF now using the upload area on the right. In the editor, you can still upload or replace a document later.</value></data>
<data name="UnexpectedErrorTryAgainLater" xml:space="preserve"><value>An unexpected error occurred. Please try again later.</value></data> <data name="UnexpectedErrorTryAgainLater" xml:space="preserve"><value>An unexpected error occurred. Please try again later.</value></data>
<data name="UploadPdf" xml:space="preserve"><value>Upload PDF</value></data> <data name="UploadPdf" xml:space="preserve"><value>Upload PDF</value></data>
<data name="UserName" xml:space="preserve"><value>Username</value></data> <data name="UserName" xml:space="preserve"><value>Username</value></data>

View File

@@ -622,10 +622,10 @@
<value>Type de certification</value> <value>Type de certification</value>
</data> </data>
<data name="FinalEmailToCreator" xml:space="preserve"> <data name="FinalEmailToCreator" xml:space="preserve">
<value>E-mail final au créateur</value> <value>Document terminé au créateur</value>
</data> </data>
<data name="FinalEmailToReceivers" xml:space="preserve"> <data name="FinalEmailToReceivers" xml:space="preserve">
<value>E-mail final aux destinataires</value> <value>Document terminé aux destinataires</value>
</data> </data>
<data name="SendReminderEmails" xml:space="preserve"> <data name="SendReminderEmails" xml:space="preserve">
<value>Envoyer des e-mails de rappel</value> <value>Envoyer des e-mails de rappel</value>
@@ -749,7 +749,14 @@
<data name="SenderEditor_PageLabel" xml:space="preserve"><value>Page</value></data> <data name="SenderEditor_PageLabel" xml:space="preserve"><value>Page</value></data>
<data name="SenderEditor_PlaceSignatureField" xml:space="preserve"><value>Placer un champ de signature</value></data> <data name="SenderEditor_PlaceSignatureField" xml:space="preserve"><value>Placer un champ de signature</value></data>
<data name="SenderEditor_RemoveAllSignatureFields" xml:space="preserve"><value>Supprimer tous les champs de signature</value></data> <data name="SenderEditor_RemoveAllSignatureFields" xml:space="preserve"><value>Supprimer tous les champs de signature</value></data>
<data name="SenderEditor_RemoveAllSignatureFieldsConfirm" xml:space="preserve"><value>Cette action supprimera définitivement tous les champs de signature placés. Continuer?</value></data>
<data name="SenderEditor_SelectPdf" xml:space="preserve"><value>Veuillez sélectionner un fichier PDF.</value></data> <data name="SenderEditor_SelectPdf" xml:space="preserve"><value>Veuillez sélectionner un fichier PDF.</value></data>
<data name="SenderEditor_SendConfirm_Message" xml:space="preserve"><value>Voulez-vous envoyer l'enveloppe maintenant ? Remarque : après la transmission, le processus ne peut plus être modifié.</value></data>
<data name="SenderEditor_SendConfirm_Title" xml:space="preserve"><value>Envoyer l'enveloppe maintenant ?</value></data>
<data name="SenderEditor_SendConfirm_Note" xml:space="preserve"><value>Veuillez vérifier les destinataires, les champs de signature et les paramètres avant l'envoi.</value></data>
<data name="SenderEditor_SendConfirm_Action" xml:space="preserve"><value>Envoyer maintenant</value></data>
<data name="SenderEditor_SendConfirm_InProgress" xml:space="preserve"><value>Envoi de l'enveloppe...</value></data>
<data name="SenderEditor_SendConfirm_InProgressHint" xml:space="preserve"><value>Veuillez patienter un instant pendant la finalisation du traitement.</value></data>
<data name="SenderEditor_Sent_Body" xml:space="preserve"><value>Le brouillon existant a été envoyé.</value></data> <data name="SenderEditor_Sent_Body" xml:space="preserve"><value>Le brouillon existant a été envoyé.</value></data>
<data name="SenderEditor_Sent_Header" xml:space="preserve"><value>Enveloppe envoyée</value></data> <data name="SenderEditor_Sent_Header" xml:space="preserve"><value>Enveloppe envoyée</value></data>
<data name="SenderEditor_Sent_Title" xml:space="preserve"><value>L'enveloppe a été envoyée avec succès.</value></data> <data name="SenderEditor_Sent_Title" xml:space="preserve"><value>L'enveloppe a été envoyée avec succès.</value></data>
@@ -777,6 +784,10 @@
<data name="Signed" xml:space="preserve"><value>Signé</value></data> <data name="Signed" xml:space="preserve"><value>Signé</value></data>
<data name="SignedDocument" xml:space="preserve"><value>Document signé</value></data> <data name="SignedDocument" xml:space="preserve"><value>Document signé</value></data>
<data name="System" xml:space="preserve"><value>Système</value></data> <data name="System" xml:space="preserve"><value>Système</value></data>
<data name="SenderWizard_Step_Start" xml:space="preserve"><value>Démarrer</value></data>
<data name="SenderWizard_DocumentUploadSimpleHint" xml:space="preserve"><value>Ajouter un document : cliquer ou glisser-deposer</value></data>
<data name="SenderWizard_DocumentReplaceHint" xml:space="preserve"><value>Cliquez pour remplacer ou glissez un fichier sur cette zone.</value></data>
<data name="SenderWizard_FinalUploadHint" xml:space="preserve"><value>Derniere possibilite dans l'assistant : veuillez televerser votre PDF maintenant via la zone d'upload a droite. Dans l'editeur, vous pourrez encore televerser ou remplacer un document plus tard.</value></data>
<data name="UnexpectedErrorTryAgainLater" xml:space="preserve"><value>Une erreur inattendue s'est produite. Veuillez réessayer plus tard.</value></data> <data name="UnexpectedErrorTryAgainLater" xml:space="preserve"><value>Une erreur inattendue s'est produite. Veuillez réessayer plus tard.</value></data>
<data name="UploadPdf" xml:space="preserve"><value>Téléverser un PDF</value></data> <data name="UploadPdf" xml:space="preserve"><value>Téléverser un PDF</value></data>
<data name="UserName" xml:space="preserve"><value>Nom d'utilisateur</value></data> <data name="UserName" xml:space="preserve"><value>Nom d'utilisateur</value></data>

View File

@@ -295,10 +295,10 @@
<value>Use Access Code</value> <value>Use Access Code</value>
</data> </data>
<data name="FinalEmailToCreator" xml:space="preserve"> <data name="FinalEmailToCreator" xml:space="preserve">
<value>Final Email to Creator</value> <value>Completed Document to Creator</value>
</data> </data>
<data name="FinalEmailToReceivers" xml:space="preserve"> <data name="FinalEmailToReceivers" xml:space="preserve">
<value>Final Email to Receivers</value> <value>Completed Document to Receivers</value>
</data> </data>
<data name="SendReminderEmails" xml:space="preserve"> <data name="SendReminderEmails" xml:space="preserve">
<value>Send Reminder Emails</value> <value>Send Reminder Emails</value>

View File

@@ -132,10 +132,10 @@
<value>Utiliser un code d'acces</value> <value>Utiliser un code d'acces</value>
</data> </data>
<data name="FinalEmailToCreator" xml:space="preserve"> <data name="FinalEmailToCreator" xml:space="preserve">
<value>E-mail final au createur</value> <value>Document termine au createur</value>
</data> </data>
<data name="FinalEmailToReceivers" xml:space="preserve"> <data name="FinalEmailToReceivers" xml:space="preserve">
<value>E-mail final aux destinataires</value> <value>Document termine aux destinataires</value>
</data> </data>
<data name="SendReminderEmails" xml:space="preserve"> <data name="SendReminderEmails" xml:space="preserve">
<value>Envoyer des e-mails de rappel</value> <value>Envoyer des e-mails de rappel</value>

View File

@@ -298,10 +298,10 @@
<value>Zugriffscode verwenden</value> <value>Zugriffscode verwenden</value>
</data> </data>
<data name="FinalEmailToCreator" xml:space="preserve"> <data name="FinalEmailToCreator" xml:space="preserve">
<value>Finale E-Mail an Ersteller</value> <value>Abgeschlossenes Dokument an Ersteller</value>
</data> </data>
<data name="FinalEmailToReceivers" xml:space="preserve"> <data name="FinalEmailToReceivers" xml:space="preserve">
<value>Finale E-Mail an Empfänger</value> <value>Abgeschlossenes Dokument an Empfänger</value>
</data> </data>
<data name="SendReminderEmails" xml:space="preserve"> <data name="SendReminderEmails" xml:space="preserve">
<value>Erinnerungs-E-Mails senden</value> <value>Erinnerungs-E-Mails senden</value>

View File

@@ -146,7 +146,7 @@
private double _grabOffsetXPt; private double _grabOffsetXPt;
private double _grabOffsetYPt; private double _grabOffsetYPt;
private int _currentPage = 1; private int _currentPage = 1;
private int _zoomPercent = 112; private int _zoomPercent = 180;
private bool _zoomOverrideInitialized; private bool _zoomOverrideInitialized;
private bool _isDownloading; private bool _isDownloading;
private bool _isPrinting; private bool _isPrinting;

View File

@@ -11,13 +11,16 @@
@inject IStringLocalizer<Model> ModelLocalizer @inject IStringLocalizer<Model> ModelLocalizer
@inject IStringLocalizer<Resource> UiLocalizer @inject IStringLocalizer<Resource> UiLocalizer
@inject ReceiverService ReceiverService @inject ReceiverService ReceiverService
@inject PdfRenderService PdfRenderService
<DxPopup Visible="@_isVisible" <DxPopup Visible="@_isVisible"
VisibleChanged="OnPopupVisibleChangedAsync" VisibleChanged="OnPopupVisibleChangedAsync"
HeaderText="@L("SenderWizard_Header", "Neuen Umschlag erstellen")" HeaderText="@L("SenderWizard_Header", "Neuen Umschlag erstellen")"
Width="860px" Width="90vw"
MaxWidth="96vw" MaxWidth="90vw"
Height="85vh"
MinHeight="85vh"
MaxHeight="85vh"
Scrollable="false"
ShowFooter="true" ShowFooter="true"
CloseOnOutsideClick="false" CloseOnOutsideClick="false"
ShowCloseButton="true" ShowCloseButton="true"
@@ -28,10 +31,15 @@
<div class="sender-create-wizard" <div class="sender-create-wizard"
tabindex="0" tabindex="0"
@onkeydown="OnWizardKeyDownAsync"> @onkeydown="OnWizardKeyDownAsync">
<div class="sender-create-wizard__body-layout">
<MudPaper Class="sender-create-wizard__stepper-shell" Elevation="0"> <MudPaper Class="sender-create-wizard__stepper-shell" Elevation="0">
<MudStepper Vertical="true" <MudStepper Vertical="true"
NonLinear="false" NonLinear="false"
ShowResetButton="false" ShowResetButton="false"
CurrentStepColor="Color.Info"
CompletedStepColor="Color.Success"
ErrorStepColor="Color.Error"
StepCompleteIcon="@Icons.Material.Filled.CheckCircle"
ActiveIndex="@_currentStep" ActiveIndex="@_currentStep"
Class="sender-create-wizard__stepper"> Class="sender-create-wizard__stepper">
<ChildContent> <ChildContent>
@@ -39,18 +47,25 @@
{ {
var stepIndex = step; var stepIndex = step;
<MudStep Title="@GetStepTitle(stepIndex)"> <MudStep Title="@GetStepTitle(stepIndex)"
Completed="@_completedSteps.Contains(stepIndex)"
Skippable="false">
<div class="sender-create-wizard__stepper-description">@GetStepDescription(stepIndex)</div> <div class="sender-create-wizard__stepper-description">@GetStepDescription(stepIndex)</div>
@if (stepIndex == _currentStep) @if (stepIndex == _currentStep)
{ {
<div class="sender-create-wizard__step-content"> <div class="sender-create-wizard__step-content">
<div class="sender-create-wizard__step-layout">
<div class="sender-create-wizard__step-main">
@if (_currentStep == 0) @if (_currentStep == 0)
{ {
<div class="sender-create-wizard__form-grid"> <div class="sender-create-wizard__form-grid">
<div class="sender-create-wizard__field"> <div class="sender-create-wizard__field">
<label class="sender-create-wizard__label"> <label class="sender-create-wizard__label sender-create-wizard__label-row">
@L("Title", "Titel") <span class="sender-create-wizard__required">*</span> <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> </label>
<DxTextBox Text="@_title" <DxTextBox Text="@_title"
TextChanged="OnTitleChanged" TextChanged="OnTitleChanged"
@@ -60,8 +75,11 @@
</div> </div>
<div class="sender-create-wizard__field"> <div class="sender-create-wizard__field">
<label class="sender-create-wizard__label"> <label class="sender-create-wizard__label sender-create-wizard__label-row">
@L("EnvelopeType", "Umschlagtyp") <span class="sender-create-wizard__required">*</span> <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> </label>
<select value="@(_selectedEnvelopeTypeId?.ToString() ?? string.Empty)" <select value="@(_selectedEnvelopeTypeId?.ToString() ?? string.Empty)"
@onchange="OnEnvelopeTypeChanged" @onchange="OnEnvelopeTypeChanged"
@@ -78,8 +96,11 @@
{ {
<div class="sender-create-wizard__form-grid"> <div class="sender-create-wizard__form-grid">
<div class="sender-create-wizard__field sender-create-wizard__field--full"> <div class="sender-create-wizard__field sender-create-wizard__field--full">
<label class="sender-create-wizard__label"> <label class="sender-create-wizard__label sender-create-wizard__label-row">
@L("Message", "Nachricht") <span class="sender-create-wizard__optional">(@L("Optional", "optional"))</span> <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> </label>
<DxMemo Text="@_message" <DxMemo Text="@_message"
TextChanged="OnMessageChanged" TextChanged="OnMessageChanged"
@@ -90,8 +111,11 @@
</div> </div>
<div class="sender-create-wizard__field"> <div class="sender-create-wizard__field">
<label class="sender-create-wizard__label"> <label class="sender-create-wizard__label sender-create-wizard__label-row">
@L("Language", "Sprache") <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> </label>
<select @bind="_language" class="sender-create-wizard__select"> <select @bind="_language" class="sender-create-wizard__select">
<option value="de-DE">Deutsch (de-DE)</option> <option value="de-DE">Deutsch (de-DE)</option>
@@ -100,6 +124,12 @@
<option value="en">English (en)</option> <option value="en">English (en)</option>
</select> </select>
</div> </div>
<div class="sender-create-wizard__field sender-create-wizard__field--full">
<div class="sender-create-wizard__stage">
<p>@L("SenderWizard_FinalUploadHint", "Letzte Moeglichkeit im Wizard: Bitte laden Sie jetzt Ihr PDF ueber den rechten Upload-Bereich hoch. Im Editor koennen Sie spaeter ebenfalls ein Dokument hochladen oder ersetzen.")</p>
</div>
</div>
</div> </div>
} }
else if (_currentStep == 2) else if (_currentStep == 2)
@@ -112,15 +142,25 @@
<div class="sender-create-wizard__form-grid"> <div class="sender-create-wizard__form-grid">
<div class="sender-create-wizard__field"> <div class="sender-create-wizard__field">
<label class="sender-create-wizard__label">@L("CertificationType", "Zertifizierung")</label> <label class="sender-create-wizard__label sender-create-wizard__label-row">
<select @bind="_certificationType" class="sender-create-wizard__select"> <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.AdvancedElectronicSignature">@L("AdvancedElectronicSignature", "Advanced Electronic Signature")</option>
<option value="@CertificationType.ElectronicSignature">@L("ElectronicSignature", "Electronic Signature")</option> <option value="@CertificationType.ElectronicSignature">@L("ElectronicSignature", "Electronic Signature")</option>
</select> </select>
</div> </div>
<div class="sender-create-wizard__field"> <div class="sender-create-wizard__field">
<label class="sender-create-wizard__label">@L("FinalEmailToCreator", "Finale E-Mail an Ersteller")</label> <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"> <select @bind="_finalEmailToCreator" class="sender-create-wizard__select">
<option value="0">@L("No", "Nein")</option> <option value="0">@L("No", "Nein")</option>
<option value="1">@L("Yes", "Ja")</option> <option value="1">@L("Yes", "Ja")</option>
@@ -129,7 +169,12 @@
</div> </div>
<div class="sender-create-wizard__field"> <div class="sender-create-wizard__field">
<label class="sender-create-wizard__label">@L("FinalEmailToReceivers", "Finale E-Mail an Empfänger")</label> <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"> <select @bind="_finalEmailToReceivers" class="sender-create-wizard__select">
<option value="0">@L("No", "Nein")</option> <option value="0">@L("No", "Nein")</option>
<option value="1">@L("Yes", "Ja")</option> <option value="1">@L("Yes", "Ja")</option>
@@ -140,12 +185,18 @@
<div class="sender-create-wizard__field sender-create-wizard__field--full"> <div class="sender-create-wizard__field sender-create-wizard__field--full">
<div class="sender-create-wizard__settings-toggles"> <div class="sender-create-wizard__settings-toggles">
<label class="sender-create-wizard__settings-toggle"> <label class="sender-create-wizard__settings-toggle">
<input type="checkbox" checked="@_tfaEnabled" @onchange="OnTfaChanged" /> <input type="checkbox" checked="@_tfaEnabled" @onchange="OnTfaChanged" disabled="@IsFesSelected" />
@L("TFAEnabled", "2FA aktiviert") <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>
<label class="sender-create-wizard__settings-toggle"> <label class="sender-create-wizard__settings-toggle">
<input type="checkbox" checked="@_useAccessCode" @onchange="OnUseAccessCodeChanged" disabled="@_tfaEnabled" /> <input type="checkbox" checked="@_useAccessCode" @onchange="OnUseAccessCodeChanged" disabled="@_tfaEnabled" />
@L("UseAccessCode", "Zugriffscode verwenden") <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> </label>
</div> </div>
</div> </div>
@@ -161,27 +212,50 @@
<div class="sender-create-wizard__field sender-create-wizard__field--full"> <div class="sender-create-wizard__field sender-create-wizard__field--full">
<label class="sender-create-wizard__settings-toggle"> <label class="sender-create-wizard__settings-toggle">
<input type="checkbox" checked="@_sendReminderEmails" @onchange="OnSendReminderEmailsChanged" /> <input type="checkbox" checked="@_sendReminderEmails" @onchange="OnSendReminderEmailsChanged" />
@L("SendReminderEmails", "Erinnerungs-E-Mails senden") <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> </label>
</div> </div>
<div class="sender-create-wizard__field"> <div class="sender-create-wizard__field">
<label class="sender-create-wizard__label">@L("FirstReminderDays", "Erste Erinnerung (Tage)")</label> <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)" /> <InputNumber @bind-Value="_firstReminderDays" class="form-control form-control-sm" disabled="@(!_sendReminderEmails)" />
</div> </div>
<div class="sender-create-wizard__field"> <div class="sender-create-wizard__field">
<label class="sender-create-wizard__label">@L("ReminderIntervalDays", "Erinnerungsintervall (Tage)")</label> <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)" /> <InputNumber @bind-Value="_reminderIntervalDays" class="form-control form-control-sm" disabled="@(!_sendReminderEmails)" />
</div> </div>
<div class="sender-create-wizard__field"> <div class="sender-create-wizard__field">
<label class="sender-create-wizard__label">@L("ExpiresWhenDays", "Läuft ab nach (Tage)")</label> <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" /> <InputNumber @bind-Value="_expiresWhenDays" class="form-control form-control-sm" />
</div> </div>
<div class="sender-create-wizard__field"> <div class="sender-create-wizard__field">
<label class="sender-create-wizard__label">@L("ExpiresWarningWhenDays", "Ablaufwarnung (Tage)")</label> <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" /> <InputNumber @bind-Value="_expiresWarningWhenDays" class="form-control form-control-sm" />
</div> </div>
</div> </div>
@@ -191,7 +265,11 @@
else if (_currentStep == 1) else if (_currentStep == 1)
{ {
<div class="sender-create-wizard__suggestions"> <div class="sender-create-wizard__suggestions">
<div class="sender-create-wizard__suggestions-title">@L("SenderWizard_RecentReceivers", "Zuletzt verwendete Empfänger")</div> <DxTextBox Text="@_receiverSuggestionFilterText"
TextChanged="OnReceiverSuggestionFilterChanged"
BindValueMode="BindValueMode.OnInput"
NullText='@L("SenderWizard_FilterRecentReceivers_Placeholder", "Empfänger in Vorschlägen filtern...")'
CssClass="w-100" />
@if (_isLoadingRecentSuggestions) @if (_isLoadingRecentSuggestions)
{ {
@@ -204,7 +282,7 @@
{ {
<button type="button" <button type="button"
class="sender-create-wizard__suggestion-item" class="sender-create-wizard__suggestion-item"
@onclick="() => ApplyRecentSuggestion(suggestion)" @onclick="() => ApplyRecentSuggestionAndAddAsync(suggestion)"
disabled="@IsActionLocked"> disabled="@IsActionLocked">
<span class="sender-create-wizard__suggestion-name">@suggestion.FullName</span> <span class="sender-create-wizard__suggestion-name">@suggestion.FullName</span>
<span class="sender-create-wizard__suggestion-email">@suggestion.EmailAddress</span> <span class="sender-create-wizard__suggestion-email">@suggestion.EmailAddress</span>
@@ -214,142 +292,82 @@
} }
</div> </div>
<div class="sender-create-wizard__form-grid"> <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"> <div class="sender-create-wizard__field">
<label class="sender-create-wizard__label">
@L("FullName", "Vor- und Nachname") <span class="sender-create-wizard__required">*</span>
</label>
<DxTextBox Text="@_receiverName" <DxTextBox Text="@_receiverName"
TextChanged="OnReceiverNameChanged" TextChanged="OnReceiverNameChanged"
BindValueMode="BindValueMode.OnInput" BindValueMode="BindValueMode.OnInput"
NullText="Max Mustermann" NullText="Max Mustermann"
CssClass="w-100" /> CssClass="w-100"
Enabled="@(!IsActionLocked)" />
</div> </div>
<div class="sender-create-wizard__field"> <div class="sender-create-wizard__field">
<label class="sender-create-wizard__label">
@L("EmailAddress", "E-Mail-Adresse") <span class="sender-create-wizard__required">*</span>
</label>
<DxTextBox Text="@_receiverEmail" <DxTextBox Text="@_receiverEmail"
TextChanged="OnReceiverEmailChanged" TextChanged="OnReceiverEmailChanged"
BindValueMode="BindValueMode.OnInput" BindValueMode="BindValueMode.OnInput"
NullText="name@beispiel.de" NullText="name@beispiel.de"
CssClass="w-100" /> CssClass="w-100"
Enabled="@(!IsActionLocked)" />
</div> </div>
</div> </div>
<div class="sender-create-wizard__receiver-actions">
<button type="button" <button type="button"
class="sender-create-wizard__add-receiver-btn" class="sender-create-wizard__receiver-add-inline-btn"
@onclick="AddReceiver" @onclick="AddReceiver"
disabled="@IsActionLocked"> disabled="@IsActionLocked"
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true"> title='@L("AddReceiver", "Empfänger hinzufügen")'
<path d="M8 4a.5.5 0 0 1 .5.5V7.5H11.5a.5.5 0 0 1 0 1H8.5v3a.5.5 0 0 1-1 0v-3H4.5a.5.5 0 0 1 0-1H7.5v-3A.5.5 0 0 1 8 4z" /> 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> </svg>
<span>@L("AddReceiver", "Empfänger hinzufügen")</span>
</button> </button>
</div> </div>
@if (_receivers.Count == 0)
{
<div class="sender-create-wizard__empty-list">
@L("SenderEditor_NoReceiversAdded", "Noch keine Empfänger hinzugefügt.")
</div> </div>
}
else
{
<div class="sender-create-wizard__receiver-tagbox-wrap">
<label class="sender-create-wizard__label" for="wizardReceiversTagBox">
@L("Receiver", "Empfänger")
</label>
<DxTagBox Data="@_receivers"
TData="CreateEnvelopeWizardReceiver"
TValue="CreateEnvelopeWizardReceiver"
AllowCustomTags="true"
@bind-Tags="@_receiverTags"
@bind-Tags:after="OnReceiverTagsChanged"
HideSelectedItems="false"
TextFieldName="@nameof(CreateEnvelopeWizardReceiver.EmailAddress)"
ListRenderMode="ListRenderMode.Virtual"
SearchMode="ListSearchMode.AutoSearch"
SearchFilterCondition="ListSearchFilterCondition.Contains"
SearchTextParseMode="ListSearchTextParseMode.GroupWordsByAnd"
CssClass="sender-create-wizard__receiver-tagbox"
InputId="wizardReceiversTagBox"
Enabled="@(!IsActionLocked)">
<TagDisplayTemplate Context="tagContext">
<div class="sender-create-wizard__receiver-tag">
@if (tagContext.DataItem is CreateEnvelopeWizardReceiver receiver)
{
<span class="sender-create-wizard__receiver-tag-name">@receiver.FullName</span>
<span class="sender-create-wizard__receiver-tag-email">@receiver.EmailAddress</span>
}
else
{
<span class="sender-create-wizard__receiver-tag-email">@tagContext.DisplayText</span>
}
<button type="button"
class="sender-create-wizard__receiver-tag-remove"
@onclick="tagContext.RemoveTagAction"
aria-label="Remove receiver"
disabled="@IsActionLocked">x</button>
</div>
</TagDisplayTemplate>
<EmptyDataAreaTemplate Context="emptyContext">
<div class="sender-create-wizard__tagbox-empty"></div>
</EmptyDataAreaTemplate>
</DxTagBox>
</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>
</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> </div>
} }
</MudStep> </MudStep>
@@ -358,6 +376,18 @@
</MudStepper> </MudStepper>
</MudPaper> </MudPaper>
<aside class="sender-create-wizard__wizard-side">
<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)) @if (!string.IsNullOrWhiteSpace(_validationMessage))
{ {
<div class="sender-create-wizard__validation">@_validationMessage</div> <div class="sender-create-wizard__validation">@_validationMessage</div>
@@ -372,14 +402,6 @@
Click="CloseAsync" Click="CloseAsync"
Enabled="@(!IsActionLocked)" /> Enabled="@(!IsActionLocked)" />
@if (CanSkipCurrentStep)
{
<DxButton Text="@L("Skip", "Überspringen")"
RenderStyle="ButtonRenderStyle.Secondary"
Click="SkipOptionalStep"
Enabled="@(!IsActionLocked)" />
}
<DxButton Text="@L("Back", "Zurück")" <DxButton Text="@L("Back", "Zurück")"
RenderStyle="ButtonRenderStyle.Secondary" RenderStyle="ButtonRenderStyle.Secondary"
Click="MoveBack" Click="MoveBack"
@@ -416,12 +438,13 @@
[Parameter] [Parameter]
public IReadOnlyList<EnvelopeTypeDto> EnvelopeTypes { get; set; } = []; public IReadOnlyList<EnvelopeTypeDto> EnvelopeTypes { get; set; } = [];
private const int TotalSteps = 5; private const int TotalSteps = 4;
private static readonly System.ComponentModel.DataAnnotations.EmailAddressAttribute EmailValidator = new(); private static readonly System.ComponentModel.DataAnnotations.EmailAddressAttribute EmailValidator = new();
private bool _isVisible; private bool _isVisible;
private bool _previousVisible; private bool _previousVisible;
private int _currentStep; private int _currentStep;
private readonly HashSet<int> _completedSteps = [];
private string _title = string.Empty; private string _title = string.Empty;
private int? _selectedEnvelopeTypeId; private int? _selectedEnvelopeTypeId;
@@ -440,8 +463,8 @@
private string _receiverName = string.Empty; private string _receiverName = string.Empty;
private string _receiverEmail = string.Empty; private string _receiverEmail = string.Empty;
private string _receiverSuggestionFilterText = string.Empty;
private readonly List<CreateEnvelopeWizardReceiver> _receivers = []; private readonly List<CreateEnvelopeWizardReceiver> _receivers = [];
private IEnumerable<string> _receiverTags = [];
private readonly List<RecentReceiverSuggestionDto> _recentReceiverSuggestions = []; private readonly List<RecentReceiverSuggestionDto> _recentReceiverSuggestions = [];
private bool _isLoadingRecentSuggestions; private bool _isLoadingRecentSuggestions;
private bool _recentSuggestionsLoaded; private bool _recentSuggestionsLoaded;
@@ -451,41 +474,27 @@
private byte[]? _documentBytes; private byte[]? _documentBytes;
private string? _documentFileName; private string? _documentFileName;
private string _documentSizeText = string.Empty; private string _documentSizeText = string.Empty;
private int _documentPreviewPageCount;
private List<PdfPageImageDto> _documentPreviewPages = [];
private bool _isDocumentUploading; private bool _isDocumentUploading;
private bool _isStepActionRunning; private bool _isStepActionRunning;
private bool _isFinishing; private bool _isFinishing;
private readonly IReadOnlyList<SenderPdfFieldOverlay> _documentPreviewFieldOverlays = [];
private string? _validationMessage; private string? _validationMessage;
private bool IsActionLocked => _isDocumentUploading || _isStepActionRunning || _isFinishing; private bool IsActionLocked => _isDocumentUploading || _isStepActionRunning || _isFinishing;
private bool IsFesSelected => _certificationType == CertificationType.AdvancedElectronicSignature;
private bool IsFirstStep => _currentStep <= 0; private bool IsFirstStep => _currentStep <= 0;
private bool IsLastStep => _currentStep >= TotalSteps - 1; private bool IsLastStep => _currentStep >= TotalSteps - 1;
private bool CanSkipCurrentStep => _currentStep == 3;
private IReadOnlyList<RecentReceiverSuggestionDto> FilteredRecentSuggestions => private IReadOnlyList<RecentReceiverSuggestionDto> FilteredRecentSuggestions =>
BuildFilteredRecentSuggestions(); 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 private string GetStepTitle(int step) => step switch
{ {
0 => L("SenderWizard_Step_Basics", "Grunddaten"), 0 => L("SenderWizard_Step_Start", "Start"),
1 => L("SenderWizard_Step_Receivers", "Empfänger"), 1 => L("SenderWizard_Step_Receivers", "Empfänger"),
2 => L("SenderEditor_EnvelopeSettings", "Envelope-Einstellungen"), 2 => L("SenderEditor_EnvelopeSettings", "Envelope-Einstellungen"),
3 => L("SenderWizard_Step_Message", "Nachricht und Optionen"), 3 => L("SenderWizard_Step_Message", "Nachricht und letzter Dokument-Upload"),
4 => L("SenderWizard_Step_Document", "Dokument"), _ => L("SenderWizard_Step_Start", "Start")
_ => L("SenderWizard_Step_Basics", "Grunddaten")
}; };
private string GetStepDescription(int step) => step switch private string GetStepDescription(int step) => step switch
@@ -493,8 +502,7 @@
0 => L("SenderWizard_Step_Basics_Desc", "Starten Sie mit den Pflichtangaben für den Umschlag."), 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."), 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."), 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."), 3 => L("SenderWizard_Step_Message_Desc", "Fuegen Sie optionale Nachricht hinzu und nutzen Sie rechts die letzte Upload-Moeglichkeit im Wizard."),
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.") _ => L("SenderWizard_Step_Basics_Desc", "Starten Sie mit den Pflichtangaben für den Umschlag.")
}; };
@@ -539,6 +547,13 @@
return Task.CompletedTask; return Task.CompletedTask;
} }
private Task OnReceiverSuggestionFilterChanged(string? value)
{
_receiverSuggestionFilterText = value ?? string.Empty;
_validationMessage = null;
return Task.CompletedTask;
}
private Task OnEnvelopeTypeChanged(ChangeEventArgs args) private Task OnEnvelopeTypeChanged(ChangeEventArgs args)
{ {
_validationMessage = null; _validationMessage = null;
@@ -590,7 +605,6 @@
} }
_receivers.Add(new CreateEnvelopeWizardReceiver(fullName, email)); _receivers.Add(new CreateEnvelopeWizardReceiver(fullName, email));
_receiverTags = _receivers.Select(x => x.EmailAddress).ToList();
_receiverName = string.Empty; _receiverName = string.Empty;
_receiverEmail = string.Empty; _receiverEmail = string.Empty;
_validationMessage = null; _validationMessage = null;
@@ -603,17 +617,28 @@
return Task.CompletedTask; return Task.CompletedTask;
_receivers.Remove(receiver); _receivers.Remove(receiver);
_receiverTags = _receivers.Select(x => x.EmailAddress).ToList();
_validationMessage = null; _validationMessage = null;
return Task.CompletedTask; return Task.CompletedTask;
} }
private Task OnReceiverTagsChanged() private Task OnReceiverListNameChanged(int index, string? value)
{ {
var selectedEmails = _receiverTags.ToHashSet(StringComparer.OrdinalIgnoreCase); if (IsActionLocked || index < 0 || index >= _receivers.Count)
return Task.CompletedTask;
_receivers.RemoveAll(x => !selectedEmails.Contains(x.EmailAddress)); var current = _receivers[index];
_receiverTags = _receivers.Select(x => x.EmailAddress).ToList(); _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; _validationMessage = null;
return Task.CompletedTask; return Task.CompletedTask;
} }
@@ -624,7 +649,10 @@
return []; return [];
var handler = new ReceiverSuggestionFilterHandler(_recentReceiverSuggestions); var handler = new ReceiverSuggestionFilterHandler(_recentReceiverSuggestions);
var filtered = handler.Filter(_receiverName, _receiverEmail); var filterText = _receiverSuggestionFilterText.Trim();
var filtered = string.IsNullOrWhiteSpace(filterText)
? _recentReceiverSuggestions
: handler.Filter(filterText);
return filtered return filtered
.GroupBy(x => x.EmailAddress, StringComparer.OrdinalIgnoreCase) .GroupBy(x => x.EmailAddress, StringComparer.OrdinalIgnoreCase)
@@ -664,21 +692,6 @@
_documentFileName = file.Name; _documentFileName = file.Name;
_documentSizeText = FormatSize(_documentBytes.Length); _documentSizeText = FormatSize(_documentBytes.Length);
_documentPreviewPages = [];
try
{
_documentPreviewPages = (await PdfRenderService.RenderPagesAsync(_documentBytes))
.OrderBy(x => x.PageNumber)
.ToList();
}
catch
{
_documentPreviewPages = [];
}
_documentPreviewPageCount = _documentPreviewPages.Count > 0
? _documentPreviewPages.Count
: EstimatePdfPageCount(_documentBytes) ?? 1;
} }
finally finally
{ {
@@ -714,10 +727,12 @@
if (!IsLastStep) if (!IsLastStep)
{ {
_completedSteps.Add(_currentStep);
_currentStep++; _currentStep++;
if (_currentStep == 1) if (_currentStep == 1)
await EnsureRecentSuggestionsLoadedAsync(); await EnsureRecentSuggestionsLoadedAsync();
} }
} }
finally finally
@@ -728,19 +743,6 @@
await Task.CompletedTask; await Task.CompletedTask;
} }
private async Task SkipOptionalStep()
{
if (IsActionLocked)
return;
if (!CanSkipCurrentStep)
return;
_message = string.Empty;
_validationMessage = null;
await MoveNextAsync();
}
private async Task CloseAsync() private async Task CloseAsync()
{ {
if (IsActionLocked) if (IsActionLocked)
@@ -864,14 +866,14 @@
} }
} }
private void ApplyRecentSuggestion(RecentReceiverSuggestionDto suggestion) private async Task ApplyRecentSuggestionAndAddAsync(RecentReceiverSuggestionDto suggestion)
{ {
if (IsActionLocked) if (IsActionLocked)
return; return;
_receiverName = suggestion.FullName; _receiverName = suggestion.FullName;
_receiverEmail = suggestion.EmailAddress; _receiverEmail = suggestion.EmailAddress;
_validationMessage = null; await AddReceiver();
} }
private bool ValidateCurrentStep() private bool ValidateCurrentStep()
@@ -915,14 +917,38 @@
return false; return false;
} }
return true; 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 (_currentStep == 4) if (string.IsNullOrWhiteSpace(email))
{ {
if (_documentBytes is null || _documentBytes.Length == 0) _validationMessage = L("SenderEditor_Validation_EmailRequired", "Bitte geben Sie eine E-Mail-Adresse ein.");
return false;
}
if (!EmailValidator.IsValid(email))
{ {
_validationMessage = L("SenderEditor_Validation_UploadPdf", "Bitte laden Sie zuerst ein PDF-Dokument hoch."); _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 false;
} }
@@ -946,59 +972,29 @@
return $"{bytes} B"; 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) private void OnTfaChanged(ChangeEventArgs args)
{ {
_validationMessage = null; _validationMessage = null;
if (IsFesSelected)
{
_tfaEnabled = true;
_useAccessCode = true;
return;
}
_tfaEnabled = ParseCheckboxValue(args); _tfaEnabled = ParseCheckboxValue(args);
if (_tfaEnabled) if (_tfaEnabled)
_useAccessCode = true; _useAccessCode = true;
} }
private Task OnCertificationTypeChanged()
{
_validationMessage = null;
NormalizeSettings();
return Task.CompletedTask;
}
private void OnUseAccessCodeChanged(ChangeEventArgs args) private void OnUseAccessCodeChanged(ChangeEventArgs args)
{ {
_validationMessage = null; _validationMessage = null;
@@ -1068,6 +1064,9 @@
{ {
_language = string.IsNullOrWhiteSpace(_language) ? "de-DE" : _language.Trim(); _language = string.IsNullOrWhiteSpace(_language) ? "de-DE" : _language.Trim();
if (IsFesSelected)
_tfaEnabled = true;
if (_tfaEnabled) if (_tfaEnabled)
_useAccessCode = true; _useAccessCode = true;
@@ -1120,6 +1119,7 @@
private void ResetWizardState() private void ResetWizardState()
{ {
_currentStep = 0; _currentStep = 0;
_completedSteps.Clear();
_validationMessage = null; _validationMessage = null;
_title = string.Empty; _title = string.Empty;
@@ -1139,8 +1139,8 @@
_receiverName = string.Empty; _receiverName = string.Empty;
_receiverEmail = string.Empty; _receiverEmail = string.Empty;
_receiverSuggestionFilterText = string.Empty;
_receivers.Clear(); _receivers.Clear();
_receiverTags = [];
_recentReceiverSuggestions.Clear(); _recentReceiverSuggestions.Clear();
_isLoadingRecentSuggestions = false; _isLoadingRecentSuggestions = false;
_recentSuggestionsLoaded = false; _recentSuggestionsLoaded = false;
@@ -1148,10 +1148,9 @@
_documentBytes = null; _documentBytes = null;
_documentFileName = null; _documentFileName = null;
_documentSizeText = string.Empty; _documentSizeText = string.Empty;
_documentPreviewPageCount = 0;
_documentPreviewPages = [];
ApplyDefaultEnvelopeType(); ApplyDefaultEnvelopeType();
NormalizeSettings();
} }
private int? GetDefaultEnvelopeTypeId() private int? GetDefaultEnvelopeTypeId()

View File

@@ -2,10 +2,24 @@
<div class="sender-pdf-dropzone @DropZoneClass"> <div class="sender-pdf-dropzone @DropZoneClass">
<div class="sender-pdf-dropzone__content"> <div class="sender-pdf-dropzone__content">
<div class="sender-pdf-dropzone__title">@CurrentActionText</div> @if (HasDocument)
@if (!string.IsNullOrWhiteSpace(DragDropHintText))
{ {
<div class="sender-pdf-dropzone__hint">@DragDropHintText</div> @if (!string.IsNullOrWhiteSpace(DocumentName))
{
<div class="sender-pdf-dropzone__doc-name">@DocumentName</div>
}
@if (!string.IsNullOrWhiteSpace(DocumentSizeText))
{
<div class="sender-pdf-dropzone__doc-size">@DocumentSizeText</div>
}
@if (!string.IsNullOrWhiteSpace(UploadedHintText))
{
<div class="sender-pdf-dropzone__hint">@UploadedHintText</div>
}
}
else if (!string.IsNullOrWhiteSpace(CurrentActionText))
{
<div class="sender-pdf-dropzone__hint">@CurrentActionText</div>
} }
</div> </div>
@@ -38,6 +52,15 @@
[Parameter] [Parameter]
public string DragDropHintText { get; set; } = "Datei hierhin ziehen oder klicken"; public string DragDropHintText { get; set; } = "Datei hierhin ziehen oder klicken";
[Parameter]
public string? DocumentName { get; set; }
[Parameter]
public string? DocumentSizeText { get; set; }
[Parameter]
public string UploadedHintText { get; set; } = "Zum Ersetzen klicken oder eine Datei hierher ziehen.";
[Parameter] [Parameter]
public EventCallback<InputFileChangeEventArgs> FileSelected { get; set; } public EventCallback<InputFileChangeEventArgs> FileSelected { get; set; }
@@ -53,6 +76,9 @@
if (Disabled) if (Disabled)
return "sender-pdf-dropzone--disabled"; return "sender-pdf-dropzone--disabled";
if (HasDocument)
return "sender-pdf-dropzone--has-document";
return _isDragActive ? "sender-pdf-dropzone--drag-active" : string.Empty; return _isDragActive ? "sender-pdf-dropzone--drag-active" : string.Empty;
} }
} }

View File

@@ -4,23 +4,19 @@ namespace EnvelopeGenerator.Server.Client.Components.Sender.ReceiverSuggestionFi
public class ReceiverSuggestionFilterHandler(IEnumerable<RecentReceiverSuggestionDto> suggestions) public class ReceiverSuggestionFilterHandler(IEnumerable<RecentReceiverSuggestionDto> suggestions)
{ {
private readonly IReadOnlyList<Entry> _entries = suggestions private readonly IReadOnlyList<Entry> _entries = [.. suggestions
.Where(x => !string.IsNullOrWhiteSpace(x.FullName) && !string.IsNullOrWhiteSpace(x.EmailAddress)) .Where(x => !string.IsNullOrWhiteSpace(x.FullName) && !string.IsNullOrWhiteSpace(x.EmailAddress))
.Select(x => new Entry(new ReceiverFilterCriteria(x.FullName, x.EmailAddress), x)) .Select(x => new Entry(new ReceiverFilterCriteria(x.FullName, x.EmailAddress), x))];
.ToList();
private readonly record struct Entry(ReceiverFilterCriteria Criteria, RecentReceiverSuggestionDto Suggestion); private readonly record struct Entry(ReceiverFilterCriteria Criteria, RecentReceiverSuggestionDto Suggestion);
public IEnumerable<RecentReceiverSuggestionDto> Filter(ReceiverFilterCriteria criteria) public IEnumerable<RecentReceiverSuggestionDto> Filter(string query)
{ {
var normalizedQuery = new ReceiverFilterCriteria(query, query);
return _entries return _entries
.Where(x => x.Criteria.Matches(criteria)) .Where(x =>
x.Criteria.FullName.Contains(normalizedQuery.FullName, StringComparison.Ordinal)
|| x.Criteria.EmailAddress.Contains(normalizedQuery.EmailAddress, StringComparison.Ordinal))
.Select(x => x.Suggestion); .Select(x => x.Suggestion);
} }
public IEnumerable<RecentReceiverSuggestionDto> Filter(string fullName, string emailAddress)
{
var criteria = new ReceiverFilterCriteria(fullName, emailAddress);
return Filter(criteria);
}
} }

View File

@@ -289,12 +289,13 @@
@if (_pdfLoaded && _signatureFields.Count > 0) @if (_pdfLoaded && _signatureFields.Count > 0)
{ {
<button class="pdf-toolbar__btn pdf-toolbar__btn--reset" <button class="pdf-toolbar__btn pdf-toolbar__btn--reset"
@onclick="ClearAllFieldsAsync" @onclick='() => _clearFieldsConfirmVisible = true'
title='@L("SenderEditor_RemoveAllSignatureFields", "Alle Signaturfelder entfernen")'> title='@L("SenderEditor_RemoveAllSignatureFields", "Alle Signaturfelder entfernen")'>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" viewBox="0 0 16 16">
<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 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 0-2h3a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1h3a1 1 0 0 1 1 1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3h11V2h-11v1z" /> <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 0-2h3a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1h3a1 1 0 0 1 1 1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3h11V2h-11v1z" />
</svg> </svg>
<span class="pdf-toolbar__btn-text"> @L("SenderEditor_SignatureField", "Signaturfeld")</span>
</button> </button>
} }
@@ -317,7 +318,7 @@
</button> </button>
<button class="pdf-toolbar__btn pdf-toolbar__btn--success" <button class="pdf-toolbar__btn pdf-toolbar__btn--success"
@onclick="SendAsync" @onclick="RequestSendAsync"
disabled="@_isSaving" disabled="@_isSaving"
title='@L("SendEnvelope", "Umschlag senden")'> title='@L("SendEnvelope", "Umschlag senden")'>
@if (_isSaving) @if (_isSaving)
@@ -506,6 +507,69 @@
</FooterContentTemplate> </FooterContentTemplate>
</DxPopup> </DxPopup>
@* ── Clear all fields confirmation popup ── *@
<DxPopup @bind-Visible="_clearFieldsConfirmVisible"
Width="400px"
MaxWidth="95vw"
ShowHeader="false"
ShowFooter="false"
CloseOnEscape="true"
CloseOnOutsideClick="true">
<BodyContentTemplate>
<div class="sender-confirm-popup">
<p class="sender-confirm-popup__message">@L("SenderEditor_RemoveAllSignatureFieldsConfirm", "Diese Aktion entfernt alle platzierten Signaturfelder unwiderruflich. Fortfahren?")</p>
<div class="sender-confirm-popup__actions">
<DxButton Text='@L("Cancel", "Abbrechen")'
RenderStyle="ButtonRenderStyle.Secondary"
Click='() => _clearFieldsConfirmVisible = false' />
<DxButton Text='@L("Delete", "Löschen")'
RenderStyle="ButtonRenderStyle.Danger"
Click="ClearAllFieldsAsync" />
</div>
</div>
</BodyContentTemplate>
</DxPopup>
@* ── Send confirmation popup ── *@
<DxPopup @bind-Visible="_sendConfirmVisible"
HeaderText='@L("SenderEditor_SendConfirm_Title", "Umschlag jetzt versenden?")'
SizeMode="SizeMode.Medium"
Width="460px"
MaxWidth="95vw"
ShowFooter="@(!_isSendConfirmSubmitting)"
ShowCloseButton="@(!_isSendConfirmSubmitting)"
CloseOnEscape="@(!_isSendConfirmSubmitting)"
CloseOnOutsideClick="false">
<BodyContentTemplate>
@if (_isSendConfirmSubmitting)
{
<div class="sender-send-progress">
<DxWaitIndicator Visible="true" />
<div>
<p class="sender-send-progress__title">@L("SenderEditor_SendConfirm_InProgress", "Umschlag wird versendet...")</p>
<p class="sender-send-progress__hint">@L("SenderEditor_SendConfirm_InProgressHint", "Bitte warten Sie einen Moment. Der Vorgang wird abgeschlossen.")</p>
</div>
</div>
}
else
{
<div class="sender-confirm-popup sender-confirm-popup--send">
<p class="sender-confirm-popup__note">
@L("SenderEditor_SendConfirm_Message", "Wollen Sie den Umschlag jetzt versenden? Hinweis: Nach der Übergabe kann der Vorgang nicht mehr bearbeitet werden.")
</p>
</div>
}
</BodyContentTemplate>
<FooterContentTemplate>
<DxButton Text='@L("No", "Nein")'
RenderStyle="ButtonRenderStyle.Secondary"
Click='() => _sendConfirmVisible = false' />
<DxButton Text='@L("SenderEditor_SendConfirm_Action", "Ja, versenden")'
RenderStyle="ButtonRenderStyle.Primary"
Click="ConfirmSendAsync" />
</FooterContentTemplate>
</DxPopup>
@* ── Save result popup (success + error) ── *@ @* ── Save result popup (success + error) ── *@
<DxPopup @bind-Visible="_savePopupVisible" <DxPopup @bind-Visible="_savePopupVisible"
HeaderText='@(_saveErrorMessage is null ? _saveSuccessHeader : L("Error", "Fehler"))' HeaderText='@(_saveErrorMessage is null ? _saveSuccessHeader : L("Error", "Fehler"))'
@@ -620,6 +684,9 @@
// ── Save state ── // ── Save state ──
bool _isSaving = false; bool _isSaving = false;
bool _savePopupVisible = false; bool _savePopupVisible = false;
bool _clearFieldsConfirmVisible = false;
bool _sendConfirmVisible = false;
bool _isSendConfirmSubmitting = false;
string? _saveErrorMessage = null; string? _saveErrorMessage = null;
string _saveSuccessHeader = "Erfolg"; string _saveSuccessHeader = "Erfolg";
string _saveSuccessTitle = "Vorgang erfolgreich abgeschlossen."; string _saveSuccessTitle = "Vorgang erfolgreich abgeschlossen.";
@@ -1060,6 +1127,7 @@
// ── Clear all fields ── // ── Clear all fields ──
async Task ClearAllFieldsAsync() async Task ClearAllFieldsAsync()
{ {
_clearFieldsConfirmVisible = false;
_signatureFields.Clear(); _signatureFields.Clear();
_pendingReceiverForPlacement = null; _pendingReceiverForPlacement = null;
await Task.CompletedTask; await Task.CompletedTask;
@@ -1215,13 +1283,7 @@
Logger.LogInformation("[SenderEditor] Envelope sent from draft. Id={Id} Status={Status}", sendResult.Id, sendResult.Status); Logger.LogInformation("[SenderEditor] Envelope sent from draft. Id={Id} Status={Status}", sendResult.Id, sendResult.Status);
// Success — show popup; GoToDashboard clears cache and navigates NavigationManager.NavigateTo("/sender", forceLoad: false);
_saveErrorMessage = null;
_saveSuccessHeader = L("SenderEditor_Sent_Header", "Umschlag gesendet");
_saveSuccessTitle = L("SenderEditor_Sent_Title", "Umschlag wurde erfolgreich gesendet.");
_saveSuccessBody = L("SenderEditor_Sent_Body", "Der bestehende Entwurf wurde versendet.");
_saveNavigateToDashboard = true;
_savePopupVisible = true;
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -1235,6 +1297,38 @@
} }
} }
Task RequestSendAsync()
{
if (_isSaving)
return Task.CompletedTask;
_sendConfirmVisible = true;
return Task.CompletedTask;
}
async Task ConfirmSendAsync()
{
if (_isSendConfirmSubmitting)
return;
_isSendConfirmSubmitting = true;
await InvokeAsync(StateHasChanged);
try
{
await SendAsync();
}
finally
{
_isSendConfirmSubmitting = false;
if (_savePopupVisible)
_sendConfirmVisible = false;
await InvokeAsync(StateHasChanged);
}
}
CreateEnvelopeCommand BuildCreateEnvelopeCommand(bool send) CreateEnvelopeCommand BuildCreateEnvelopeCommand(bool send)
{ {
NormalizeSettings(); NormalizeSettings();

View File

@@ -32,6 +32,11 @@
<link href="@AppVersion.GetVersionedUrl("css/envelope-viewer.css")" rel="stylesheet" /> <link href="@AppVersion.GetVersionedUrl("css/envelope-viewer.css")" rel="stylesheet" />
<link href="@AppVersion.GetVersionedUrl("css/sender-page.css")" rel="stylesheet" /> <link href="@AppVersion.GetVersionedUrl("css/sender-page.css")" rel="stylesheet" />
<MudThemeProvider />
<MudPopoverProvider />
<MudDialogProvider />
<MudSnackbarProvider />
<div class="sender-dashboard-layout"> <div class="sender-dashboard-layout">
<div class="sender-action-bar"> <div class="sender-action-bar">
<div class="sender-action-bar__inner"> <div class="sender-action-bar__inner">
@@ -108,12 +113,12 @@
} }
</button> </button>
<button class="sender-btn" @onclick="ShowPreview" disabled="@(_selectedEnvelope == null)" title="@L("Sender_Preview_Title", "Ausgewählten Umschlag als PDF anzeigen")"> <button class="sender-btn" @onclick="ShowPreview" disabled="@(_selectedEnvelope == null)" title="@L("Sender_ShowOriginalDocument_Title", "Originaldokument anzeigen")">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path d="M10.5 8a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0z"/> <path d="M10.5 8a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0z"/>
<path d="M0 8s3-5.5 8-5.5S16 8 16 8s-3 5.5-8 5.5S0 8 0 8zm8 3.5a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7z"/> <path d="M0 8s3-5.5 8-5.5S16 8 16 8s-3 5.5-8 5.5S0 8 0 8zm8 3.5a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7z"/>
</svg> </svg>
@L("Preview", "Vorschau") @L("Sender_ShowOriginalDocument", "Original Dokument anzeigen")
</button> </button>
<div class="sender-export-group" title="@L("Sender_ExportView_Title", "Export der aktuellen Tabellenansicht")"> <div class="sender-export-group" title="@L("Sender_ExportView_Title", "Export der aktuellen Tabellenansicht")">
@@ -237,7 +242,10 @@
RowDoubleClick="OnRowDoubleClick" RowDoubleClick="OnRowDoubleClick"
CustomizeElement="OnCustomizeElement" CustomizeElement="OnCustomizeElement"
LayoutAutoLoading="OnGridLayoutAutoLoading" LayoutAutoLoading="OnGridLayoutAutoLoading"
LayoutAutoSaving="OnGridLayoutAutoSaving"> LayoutAutoSaving="OnGridLayoutAutoSaving"
HighlightRowOnHover="true"
AutoCollapseDetailRow=true
DetailExpandButtonDisplayMode="GridDetailExpandButtonDisplayMode.Auto">
<Columns> <Columns>
<DxGridDataColumn FieldName="Id" Caption="ID" <DxGridDataColumn FieldName="Id" Caption="ID"
DisplayFormat="0" DisplayFormat="0"
@@ -310,6 +318,12 @@
<h6 style="font-weight: 600; color: #374151; margin-bottom: 0.75rem;">@L("Receiver", "Empfänger")</h6> <h6 style="font-weight: 600; color: #374151; margin-bottom: 0.75rem;">@L("Receiver", "Empfänger")</h6>
@{ @{
var envelope = detailContext.DataItem as EnvelopeDto; var envelope = detailContext.DataItem as EnvelopeDto;
if(envelope is not null && _grid is not null)
{
_grid.SelectDataItem(envelope);
// _grid!.SelectedDataItem = envelope!;
}
if (envelope?.EnvelopeReceivers?.Any() == true) if (envelope?.EnvelopeReceivers?.Any() == true)
{ {
<div style="display: flex; flex-direction: column; gap: 0.5rem;"> <div style="display: flex; flex-direction: column; gap: 0.5rem;">
@@ -440,7 +454,7 @@
@* ── PDF Vorschau Popup ────────────────────────────────────────────────── *@ @* ── PDF Vorschau Popup ────────────────────────────────────────────────── *@
<DxPopup @bind-Visible="@_previewVisible" <DxPopup @bind-Visible="@_previewVisible"
HeaderText="@($"{(_activeTab == "completed" && _selectedEnvelope?.Status == EnvelopeStatus.EnvelopeCompletelySigned ? L("SignedPdf", "Signiertes PDF") : L("Preview", "Vorschau"))} — {_selectedEnvelope?.Title} (#{_selectedEnvelope?.Id})")" HeaderText="@($"{(_activeTab == "completed" && _selectedEnvelope?.Status == EnvelopeStatus.EnvelopeCompletelySigned ? L("SignedPdf", "Signiertes PDF") : L("Sender_OriginalDocument", "Originaldokument"))} — {_selectedEnvelope?.Title} (#{_selectedEnvelope?.Id})")"
Width="80vw" Width="80vw"
MaxWidth="80vw" MaxWidth="80vw"
Height="90vh" Height="90vh"
@@ -489,6 +503,22 @@
else if (_previewPdfBytes is not null && _previewViewerMounted) else if (_previewPdfBytes is not null && _previewViewerMounted)
{ {
<div class="sender-preview-viewer-wrap"> <div class="sender-preview-viewer-wrap">
@if (_showPreviewHint)
{
<div class="sender-preview-info" style="display:flex;align-items:center;justify-content:space-between;gap:0.5rem;padding:0.625rem 0.75rem;margin:0 0 0.5rem 0;background:#eff6ff;border:1px solid #bfdbfe;border-radius:8px;color:#1e3a8a;font-size:0.85rem;">
<span>@L("Sender_OriginalDocument_NoSignatureFieldsHint", "Hinweis: In dieser Ansicht werden keine Unterschriftsfelder angezeigt.")</span>
<button type="button"
class="sender-preview-info__close"
@onclick="DismissPreviewHint"
title="@L("Close", "Schliessen")"
aria-label="@L("Close", "Schliessen")"
style="display:inline-flex;align-items:center;justify-content:center;padding:0.125rem;border:none;background:transparent;box-shadow:none;appearance:none;cursor:pointer;color:#1e3a8a;line-height:1;">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true" focusable="false">
<path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.75.75 0 1 1 1.06 1.06L9.06 8l3.22 3.22a.75.75 0 1 1-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 1 1-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06z"/>
</svg>
</button>
</div>
}
<SenderPdfImageEditor IsPlacementMode="false" <SenderPdfImageEditor IsPlacementMode="false"
PageCount="@_previewPageCount" PageCount="@_previewPageCount"
Pages="@_previewPagePreviews" Pages="@_previewPagePreviews"
@@ -577,6 +607,9 @@
private string _previewFileName = "document.pdf"; private string _previewFileName = "document.pdf";
private string? _previewErrorMessage = null; private string? _previewErrorMessage = null;
private readonly IReadOnlyList<SenderPdfFieldOverlay> _previewFieldOverlays = []; private readonly IReadOnlyList<SenderPdfFieldOverlay> _previewFieldOverlays = [];
private bool _showPreviewHint = false;
private int _previewHintVersion = 0;
private const int PreviewHintAutoHideMs = 4000;
private string? PreviewPdfDataUrl => _previewPdfBytes is { Length: > 0 } private string? PreviewPdfDataUrl => _previewPdfBytes is { Length: > 0 }
? $"data:application/pdf;base64,{Convert.ToBase64String(_previewPdfBytes)}" ? $"data:application/pdf;base64,{Convert.ToBase64String(_previewPdfBytes)}"
@@ -613,22 +646,29 @@
return; return;
} }
// Load theme + DisplayName from server after authentication. // Fan out all independent post-auth calls in parallel to reduce initial load time.
// Non-fatal: if this fails the page still loads with default theme. var themeTask = Task.Run(async () =>
// ThemeService.ScheduleSaveAsync() already logs errors internally. {
try { await ThemeService.LoadFromServerAsync(); } try { await ThemeService.LoadFromServerAsync(); }
catch { /* swallow — default theme remains active */ } catch { /* swallow — default theme remains active */ }
});
_canUseReceiverEnvelopeUrl = await EnvelopeReceiverService.GetReceiverEnvelopeUrlFeatureEnabledAsync(); var featureTask = EnvelopeReceiverService.GetReceiverEnvelopeUrlFeatureEnabledAsync();
var logosTask = CustomImagesService.GetAsync();
var typesTask = EnvelopeTypeService.GetAsync();
var envelopesTask = EnvelopeService.GetAsync(includeDocAndDocRes: false);
// Load logo config — authenticated, so this should succeed await Task.WhenAll(themeTask, featureTask, logosTask, typesTask, envelopesTask);
var logos = await CustomImagesService.GetAsync();
_canUseReceiverEnvelopeUrl = await featureTask;
var logos = await logosTask;
_appLogo = logos.App; _appLogo = logos.App;
_companyLogo = logos.Company; _companyLogo = logos.Company;
try try
{ {
_envelopeTypes = (await EnvelopeTypeService.GetAsync()).ToList(); _envelopeTypes = (await typesTask).ToList();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -636,7 +676,8 @@
_envelopeTypes = []; _envelopeTypes = [];
} }
await LoadEnvelopesAsync(); // Use already-fetched envelope data instead of a second HTTP call.
await ApplyEnvelopeResultAsync(envelopesTask);
} }
async Task LoadEnvelopesAsync() async Task LoadEnvelopesAsync()
@@ -644,10 +685,15 @@
_isLoading = true; _isLoading = true;
_errorMessage = null; _errorMessage = null;
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
await ApplyEnvelopeResultAsync(EnvelopeService.GetAsync(includeDocAndDocRes: false));
}
async Task ApplyEnvelopeResultAsync(Task<IEnumerable<EnvelopeDto>?> envelopesTask)
{
_isLoading = true;
try try
{ {
_allEnvelopes = await EnvelopeService.GetAsync(includeDocAndDocRes: false) ?? []; _allEnvelopes = (await envelopesTask) ?? [];
// Split into active and completed based on effective status // Split into active and completed based on effective status
// (history-aware: rejected envelopes should not stay in active tab). // (history-aware: rejected envelopes should not stay in active tab).
@@ -1061,13 +1107,19 @@
var clicked = e.Grid.GetDataItem(e.VisibleIndex) as EnvelopeDto; var clicked = e.Grid.GetDataItem(e.VisibleIndex) as EnvelopeDto;
if (clicked == null) return; if (clicked == null) return;
if (_selectedEnvelope?.Id == clicked.Id) if (e.Grid.IsDetailRowExpanded(e.VisibleIndex))
{ {
// Same row clicked again → deselect // Already expanded → collapse and deselect
e.Grid.CollapseDetailRow(e.VisibleIndex);
_selectedEnvelope = null; _selectedEnvelope = null;
e.Grid.DeselectDataItem(clicked); e.Grid.DeselectDataItem(clicked);
} }
// Different row: @bind-SelectedDataItem already updated _selectedEnvelope else
{
// Collapsed → expand and select
e.Grid.SelectDataItem(clicked);
e.Grid.ExpandDetailRow(e.VisibleIndex);
}
} }
// Double-click: select the row and open preview popup // Double-click: select the row and open preview popup
@@ -1132,6 +1184,7 @@
// PDF ready — mount the viewer now // PDF ready — mount the viewer now
_previewViewerMounted = true; _previewViewerMounted = true;
ShowPreviewHint();
} }
catch (HttpRequestException ex) when ((int?)ex.StatusCode == 404) catch (HttpRequestException ex) when ((int?)ex.StatusCode == 404)
{ {
@@ -1156,6 +1209,9 @@
void OnPreviewClosed() void OnPreviewClosed()
{ {
_previewHintVersion++;
_showPreviewHint = false;
// Unmount the viewer first — prevent DevExpress async render from // Unmount the viewer first — prevent DevExpress async render from
// accessing the already-disposed byte array // accessing the already-disposed byte array
_previewViewerMounted = false; _previewViewerMounted = false;
@@ -1187,6 +1243,28 @@
return Task.CompletedTask; return Task.CompletedTask;
} }
void DismissPreviewHint()
{
_previewHintVersion++;
_showPreviewHint = false;
}
void ShowPreviewHint()
{
_showPreviewHint = true;
var currentVersion = ++_previewHintVersion;
_ = Task.Run(async () =>
{
await Task.Delay(PreviewHintAutoHideMs);
if (currentVersion != _previewHintVersion)
return;
_showPreviewHint = false;
await InvokeAsync(StateHasChanged);
});
}
static int? EstimatePdfPageCount(byte[]? documentBytes) static int? EstimatePdfPageCount(byte[]? documentBytes)
{ {
if (documentBytes is null || documentBytes.Length == 0) if (documentBytes is null || documentBytes.Length == 0)

View File

@@ -12,6 +12,7 @@
@using EnvelopeGenerator.Server.Client.Models @using EnvelopeGenerator.Server.Client.Models
@using EnvelopeGenerator.Server.Client.Options @using EnvelopeGenerator.Server.Client.Options
@using EnvelopeGenerator.Server.Client.Components.PdfEditor @using EnvelopeGenerator.Server.Client.Components.PdfEditor
@using MudBlazor
@using DevExpress.Blazor @using DevExpress.Blazor
@using DevExpress.Blazor.PdfViewer @using DevExpress.Blazor.PdfViewer
@using DevExpress.Blazor.Reporting @using DevExpress.Blazor.Reporting

View File

@@ -2085,3 +2085,79 @@ body.resizing {
[data-sf-theme="blazing-dark"] .pdf-text-layer ::-moz-selection { [data-sf-theme="blazing-dark"] .pdf-text-layer ::-moz-selection {
background: rgba(99, 102, 241, 0.35); background: rgba(99, 102, 241, 0.35);
} }
/* ── Confirm popup (headerless, footerless) ── */
.sender-confirm-popup {
padding: 0.25rem 0.25rem 0.5rem;
}
.sender-confirm-popup__message {
margin: 0 0 1.25rem;
font-size: 0.9rem;
line-height: 1.5;
color: var(--bs-body-color, #e2e8f0);
}
.sender-confirm-popup__actions {
display: flex;
gap: 0.5rem;
justify-content: flex-end;
}
.sender-confirm-popup--send {
padding: 0.5rem 0.25rem;
}
.sender-confirm-popup--send .sender-confirm-popup__note {
margin: 0;
text-align: center;
}
.sender-confirm-popup__title {
margin: 0 0 0.4rem;
font-size: 1.02rem;
font-weight: 600;
color: #0f172a;
}
.sender-confirm-popup__note {
margin: 0 0 1rem;
font-size: 0.84rem;
color: #475569;
}
.sender-send-progress {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.5rem 0.25rem;
}
.sender-send-progress__spinner {
width: 1.15rem;
height: 1.15rem;
color: #2563eb;
}
.sender-send-progress__title {
margin: 0 0 0.2rem;
font-size: 0.95rem;
font-weight: 600;
color: #0f172a;
}
.sender-send-progress__hint {
margin: 0;
font-size: 0.83rem;
color: #475569;
}
[data-sf-theme="blazing-dark"] .sender-confirm-popup__title,
[data-sf-theme="blazing-dark"] .sender-send-progress__title {
color: #e2e8f0;
}
[data-sf-theme="blazing-dark"] .sender-confirm-popup__note,
[data-sf-theme="blazing-dark"] .sender-send-progress__hint {
color: #94a3b8;
}

View File

@@ -1103,9 +1103,90 @@
outline: none; outline: none;
} }
.sender-create-wizard__body-layout {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(240px, 280px);
gap: 0.85rem;
align-items: stretch;
transition: grid-template-columns 220ms ease 0.2s, gap 220ms ease 0.2s;
}
.sender-create-wizard-popup.dxbl-modal-root,
.sender-create-wizard-popup .dxbl-modal-root,
.sender-create-wizard-popup .dxbl-modal,
.sender-create-wizard-popup .dxbl-modal-dialog,
.sender-create-wizard-popup .dxbl-modal-content,
.sender-create-wizard-popup .dxbl-popup,
.sender-create-wizard-popup.dxbl-popup {
height: 85vh !important;
max-height: 85vh !important;
display: flex !important;
flex-direction: column !important;
}
.sender-create-wizard-popup .dxbl-modal-body,
.sender-create-wizard-popup .dxbl-popup-content { .sender-create-wizard-popup .dxbl-popup-content {
max-height: min(78vh, 760px); flex: 1 1 auto !important;
overflow: auto; height: auto !important;
min-height: 0 !important;
max-height: none !important;
overflow: hidden !important;
display: flex !important;
flex-direction: column !important;
}
.sender-create-wizard-popup .dxbl-popup-content > .sender-create-wizard,
.sender-create-wizard-popup .sender-create-wizard,
.sender-create-wizard-popup .sender-create-wizard__body-layout {
flex: 1 1 auto !important;
height: auto !important;
min-height: 0 !important;
overflow: hidden !important;
}
/* Scroll only inside the stepper content, so the body never resizes. */
.sender-create-wizard__stepper-shell {
min-height: 0 !important;
overflow: hidden !important;
display: flex !important;
flex-direction: column !important;
}
.sender-create-wizard__stepper {
display: flex !important;
flex-direction: column !important;
min-height: 0 !important;
height: 100% !important;
overflow: hidden !important;
}
/* MudStepper internal wrappers must pass height down without scrolling. */
.sender-create-wizard__stepper .mud-stepper-content,
.sender-create-wizard__stepper .mud-stepper-nav-content,
.sender-create-wizard__stepper .mud-stepper-content-wrapper {
flex: 1 1 auto !important;
min-height: 0 !important;
display: flex !important;
flex-direction: column !important;
overflow: hidden !important;
}
.sender-create-wizard__step-content,
.sender-create-wizard__step-layout {
flex: 1 1 auto !important;
min-height: 0 !important;
display: flex !important;
flex-direction: column !important;
}
/* The actual scroll area for every step, mirroring the receiver list behavior. */
.sender-create-wizard__step-main {
flex: 1 1 auto !important;
min-height: 0 !important;
overflow-y: auto !important;
overflow-x: hidden !important;
scrollbar-gutter: stable;
padding-right: 0.2rem;
} }
.sender-create-wizard-popup .dxbl-popup-footer { .sender-create-wizard-popup .dxbl-popup-footer {
@@ -1121,6 +1202,7 @@
border-radius: 10px; border-radius: 10px;
background: rgba(126, 34, 206, 0.04); background: rgba(126, 34, 206, 0.04);
padding: 0.75rem; padding: 0.75rem;
height: 100%;
} }
.sender-create-wizard__stepper { .sender-create-wizard__stepper {
@@ -1137,7 +1219,7 @@
} }
.sender-create-wizard__stepper .mud-step-label { .sender-create-wizard__stepper .mud-step-label {
color: #1f2937; color: #334155;
font-size: 0.86rem; font-size: 0.86rem;
font-weight: 700; font-weight: 700;
} }
@@ -1153,22 +1235,53 @@
.sender-create-wizard__stepper .mud-step-icon { .sender-create-wizard__stepper .mud-step-icon {
color: #64748b; color: #64748b;
background: #f8fafc; background: #f8fafc;
border: 1px solid rgba(126, 34, 206, 0.25); border: 1px solid #cbd5e1;
} }
.sender-create-wizard__stepper .mud-step-completed .mud-step-icon, .sender-create-wizard__stepper .mud-step:not(.mud-step-completed) .mud-step-icon {
.sender-create-wizard__stepper .mud-step-current .mud-step-icon { position: relative;
color: #ffffff; color: transparent;
border-color: transparent; background: #e2e8f0;
border-color: #94a3b8;
}
.sender-create-wizard__stepper .mud-step:not(.mud-step-completed) .mud-step-icon::before {
content: "";
position: absolute;
top: 50%;
left: 50%;
width: 0.55rem;
height: 0.55rem;
border-radius: 50%;
border: 2px solid #64748b;
background: #64748b;
transform: translate(-50%, -50%);
}
.sender-create-wizard__stepper .mud-step-current:not(.mud-step-completed) .mud-step-icon::before {
border-color: #0e7490;
background: #22d3ee;
}
.sender-create-wizard__stepper .mud-step-current .mud-step-label {
color: #0f172a;
} }
.sender-create-wizard__stepper .mud-step-current .mud-step-icon { .sender-create-wizard__stepper .mud-step-current .mud-step-icon {
background: linear-gradient(135deg, #7e22ce 0%, #2a5298 100%); box-shadow: 0 0 0 3px rgba(14, 116, 144, 0.2);
box-shadow: 0 0 0 3px rgba(126, 34, 206, 0.15);
} }
.sender-create-wizard__stepper .mud-step-completed .mud-step-icon { .sender-create-wizard__stepper .mud-step-completed .mud-step-icon {
background: #2a5298; box-shadow: 0 0 0 2px rgba(22, 163, 74, 0.2);
}
.sender-create-wizard__stepper .mud-step-completed .mud-step-label {
color: #14532d;
}
.sender-create-wizard__stepper .mud-step-completed .mud-step-content,
.sender-create-wizard__stepper .mud-step-current .mud-step-content {
color: #475569;
} }
.sender-create-wizard__stepper-description { .sender-create-wizard__stepper-description {
@@ -1183,6 +1296,33 @@
margin-top: 0.7rem; margin-top: 0.7rem;
} }
.sender-create-wizard__step-layout {
display: block;
}
.sender-create-wizard__step-main {
min-width: 0;
}
.sender-create-wizard__wizard-side {
min-width: 0;
max-width: 280px;
border: 1px solid rgba(126, 34, 206, 0.12);
border-radius: 10px;
background: rgba(126, 34, 206, 0.04);
padding: 0.75rem;
display: flex;
flex-direction: column;
justify-content: stretch;
opacity: 1;
transition: opacity 220ms ease 0.2s, transform 220ms ease 0.2s;
}
.sender-create-wizard__wizard-side .sender-pdf-dropzone {
height: 100%;
min-height: 100%;
}
.sender-create-wizard__meta { .sender-create-wizard__meta {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -1296,6 +1436,25 @@
color: #334155; color: #334155;
} }
.sender-create-wizard__label-row {
display: inline-flex;
align-items: center;
justify-content: space-between;
gap: 0.3rem;
}
.sender-create-wizard__help-icon {
width: 1.2rem;
height: 1.2rem;
padding: 0;
min-width: 1.2rem;
min-height: 1.2rem;
}
.sender-create-wizard__help-icon .mud-icon-root {
font-size: 1rem;
}
.sender-create-wizard__required { .sender-create-wizard__required {
color: #dc2626; color: #dc2626;
} }
@@ -1452,6 +1611,31 @@
gap: 0.5rem; gap: 0.5rem;
} }
.sender-create-wizard__receiver-existing-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
padding-right: 0.2rem;
}
.sender-create-wizard__receiver-existing-list > .sender-create-wizard__receiver-row--new {
flex: 0 0 auto;
}
.sender-create-wizard__receiver-row {
display: flex;
align-items: flex-start;
gap: 0.55rem;
}
.sender-create-wizard__receiver-fields-stack {
flex: 1 1 auto;
min-width: 0;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.35rem;
}
.sender-create-wizard__receiver-item { .sender-create-wizard__receiver-item {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -1463,6 +1647,11 @@
gap: 0.75rem; gap: 0.75rem;
} }
.sender-create-wizard__receiver-item-fields {
flex: 1 1 auto;
min-width: 0;
}
.sender-create-wizard__receiver-name { .sender-create-wizard__receiver-name {
font-size: 0.86rem; font-size: 0.86rem;
font-weight: 700; font-weight: 700;
@@ -1484,6 +1673,41 @@
padding: 0.25rem 0.55rem; padding: 0.25rem 0.55rem;
} }
.sender-create-wizard__receiver-remove-btn {
flex: 0 0 auto;
width: auto;
height: auto;
align-self: stretch;
aspect-ratio: 1 / 1;
display: inline-flex;
align-items: center;
justify-content: center;
background: transparent;
color: #b91c1c;
padding: 0;
line-height: 1;
}
.sender-create-wizard__receiver-remove-btn:hover {
transform: scale(1.05);
}
.sender-create-wizard__receiver-add-inline-btn {
flex: 0 0 auto;
width: auto;
height: auto;
align-self: stretch;
aspect-ratio: 1 / 1;
display: inline-flex;
align-items: center;
justify-content: center;
background: transparent;
color: #0f766e;
padding: 0;
line-height: 1;
transition: transform 0.2s ease;
}
.sender-create-wizard__upload-stage { .sender-create-wizard__upload-stage {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -1539,6 +1763,11 @@
box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.16); box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.16);
} }
.sender-pdf-dropzone--has-document {
border-color: rgba(16, 185, 129, 0.55);
background: linear-gradient(180deg, rgba(16, 185, 129, 0.16), rgba(16, 185, 129, 0.07));
}
.sender-pdf-dropzone--disabled { .sender-pdf-dropzone--disabled {
opacity: 0.55; opacity: 0.55;
cursor: not-allowed; cursor: not-allowed;
@@ -1561,6 +1790,18 @@
color: #64748b; color: #64748b;
} }
.sender-pdf-dropzone__doc-name {
font-size: 0.82rem;
font-weight: 700;
color: #065f46;
line-height: 1.25;
}
.sender-pdf-dropzone__doc-size {
font-size: 0.76rem;
color: #047857;
}
.sender-pdf-dropzone__input { .sender-pdf-dropzone__input {
position: absolute; position: absolute;
inset: 0; inset: 0;
@@ -1625,8 +1866,8 @@
@media (max-width: 760px) { @media (max-width: 760px) {
.sender-create-wizard-popup .dxbl-popup { .sender-create-wizard-popup .dxbl-popup {
width: 96vw !important; width: 90vw !important;
max-width: 96vw !important; max-width: 90vw !important;
} }
.sender-create-wizard__progress { .sender-create-wizard__progress {
@@ -1650,10 +1891,41 @@
align-items: flex-start; align-items: flex-start;
} }
.sender-create-wizard__receiver-row {
flex-direction: column;
align-items: stretch;
}
.sender-create-wizard__receiver-existing-list {
max-height: min(20vh, 180px);
}
.sender-create-wizard__receiver-fields-stack {
grid-template-columns: 1fr;
}
.sender-create-wizard__receiver-remove { .sender-create-wizard__receiver-remove {
width: 100%; width: 100%;
} }
.sender-create-wizard__body-layout {
grid-template-columns: 1fr;
gap: 0.7rem;
min-height: 0;
}
.sender-create-wizard__wizard-side {
max-width: none;
}
.sender-create-wizard__receiver-remove-btn {
align-self: flex-end;
}
.sender-create-wizard__receiver-add-inline-btn {
align-self: flex-end;
}
.sender-pdf-dropzone { .sender-pdf-dropzone {
width: 100%; width: 100%;
} }