Compare commits
19 Commits
519a8bf3f9
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 940d129c11 | |||
| 44bbdbb835 | |||
| 223017006b | |||
| ad9250c7b1 | |||
| 5cc47ca9c7 | |||
| 156988403a | |||
| e960c02cd4 | |||
| a0e2492757 | |||
| 380a5f4c0c | |||
| 28f8b829e3 | |||
| d00c459124 | |||
| 287c14e114 | |||
| 212ba9d569 | |||
| 735f072dd1 | |||
| 0f53d0509e | |||
| 48cfd3f416 | |||
| 0d2e43b2d6 | |||
| 5aaa37b8ec | |||
| 051a8ce211 |
@@ -60,6 +60,11 @@ public record EnvelopeReceiverDto
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string? PrivateMessage { get; init; }
|
public string? PrivateMessage { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public string? AccessCode { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ public sealed record Signature
|
|||||||
public string? Position
|
public string? Position
|
||||||
{
|
{
|
||||||
get => _position;
|
get => _position;
|
||||||
init => _position = string.IsNullOrWhiteSpace(value) ? value : null;
|
init => _position = string.IsNullOrWhiteSpace(value) ? null : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -65,15 +65,18 @@ public static class DependencyInjection
|
|||||||
cfg.AddBehavior<IPipelineBehavior<SigningCommand, Unit>, EnvelopeReceiverResolutionBehavior>();
|
cfg.AddBehavior<IPipelineBehavior<SigningCommand, Unit>, EnvelopeReceiverResolutionBehavior>();
|
||||||
|
|
||||||
// 1. AnnotationBehavior - Saves annotations (executes second)
|
// 1. AnnotationBehavior - Saves annotations (executes second)
|
||||||
|
cfg.AddBehavior<IPipelineBehavior<SigningCommand, Unit>, SaveSignatureBehavior>();
|
||||||
|
|
||||||
|
// 2. AnnotationBehavior - Saves annotations for legacy web flow
|
||||||
cfg.AddBehavior<IPipelineBehavior<SigningCommand, Unit>, AnnotationBehavior>();
|
cfg.AddBehavior<IPipelineBehavior<SigningCommand, Unit>, AnnotationBehavior>();
|
||||||
|
|
||||||
// 2. DocStatusBehavior - Creates document status (executes third)
|
// 3. DocStatusBehavior - Creates document status
|
||||||
cfg.AddBehavior<IPipelineBehavior<SigningCommand, Unit>, DocStatusBehavior>();
|
cfg.AddBehavior<IPipelineBehavior<SigningCommand, Unit>, DocStatusBehavior>();
|
||||||
|
|
||||||
// 3. HistoryBehavior - Records history (executes fourth)
|
// 4. HistoryBehavior - Records history
|
||||||
cfg.AddBehavior<IPipelineBehavior<SigningCommand, Unit>, HistoryBehavior>();
|
cfg.AddBehavior<IPipelineBehavior<SigningCommand, Unit>, HistoryBehavior>();
|
||||||
|
|
||||||
// 4. SendSignedMailBehavior - Sends notification email (executes LAST, only if all previous succeed)
|
// 5. SendSignedMailBehavior - Sends notification email (executes LAST)
|
||||||
cfg.AddBehavior<IPipelineBehavior<SigningCommand, Unit>, SendSignedMailBehavior>();
|
cfg.AddBehavior<IPipelineBehavior<SigningCommand, Unit>, SendSignedMailBehavior>();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,11 @@ public class EnvelopeReceiverResolutionBehavior : IPipelineBehavior<SigningComma
|
|||||||
// If EnvelopeReceiver is not provided, query it from database
|
// If EnvelopeReceiver is not provided, query it from database
|
||||||
if (request.EnvelopeReceiver is null)
|
if (request.EnvelopeReceiver is null)
|
||||||
{
|
{
|
||||||
var er = await _erRepo.Query.Where(request, notnull: true).SingleOrDefaultAsync(cancellationToken)
|
var er = await _erRepo.Query
|
||||||
|
.Where(request, notnull: true)
|
||||||
|
.Include(x => x.Envelope)
|
||||||
|
.Include(x => x.Receiver)
|
||||||
|
.SingleOrDefaultAsync(cancellationToken)
|
||||||
?? throw new NotFoundException("EnvelopeReceiver not found");
|
?? throw new NotFoundException("EnvelopeReceiver not found");
|
||||||
|
|
||||||
request.SetEnvelopeReceiver(_mapper.Map<EnvelopeReceiverDto>(er));
|
request.SetEnvelopeReceiver(_mapper.Map<EnvelopeReceiverDto>(er));
|
||||||
|
|||||||
@@ -50,8 +50,8 @@ public class SaveSignatureBehavior : IPipelineBehavior<SigningCommand, Unit>
|
|||||||
throw new BadRequestException($"Signatures are required for saving signature behavior.");
|
throw new BadRequestException($"Signatures are required for saving signature behavior.");
|
||||||
|
|
||||||
var elements = await _elementRepo
|
var elements = await _elementRepo
|
||||||
.Where(e => e.Document.EnvelopeId == request.Envelope.Id)
|
.Where(e => e.Document.EnvelopeId == request.EnvelopeReceiver.EnvelopeId)
|
||||||
.Where(e => e.ReceiverId == request.Receiver.Id)
|
.Where(e => e.ReceiverId == request.EnvelopeReceiver.ReceiverId)
|
||||||
.ToListAsync(cancel);
|
.ToListAsync(cancel);
|
||||||
|
|
||||||
foreach (var element in elements)
|
foreach (var element in elements)
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using DigitalData.Core.Abstraction.Application.Repository;
|
using DigitalData.Core.Abstraction.Application.Repository;
|
||||||
|
using DigitalData.EmailProfilerDispatcher.Abstraction.Entities;
|
||||||
using DigitalData.Core.Exceptions;
|
using DigitalData.Core.Exceptions;
|
||||||
using EnvelopeGenerator.Domain.Entities;
|
using EnvelopeGenerator.Domain.Entities;
|
||||||
using EnvelopeGenerator.Application.Common.Dto;
|
using EnvelopeGenerator.Application.Common.Dto;
|
||||||
|
using EnvelopeGenerator.Application.Common.Configurations;
|
||||||
|
using EnvelopeGenerator.Application.Common.Extensions;
|
||||||
|
using EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||||
using EnvelopeGenerator.Application.Common.Interfaces.SQLExecutor;
|
using EnvelopeGenerator.Application.Common.Interfaces.SQLExecutor;
|
||||||
using EnvelopeGenerator.Application.Histories.Commands;
|
using EnvelopeGenerator.Application.Histories.Commands;
|
||||||
using EnvelopeGenerator.Domain.Constants;
|
using EnvelopeGenerator.Domain.Constants;
|
||||||
@@ -32,6 +37,16 @@ public class CreateEnvelopeCommandHandler : IRequestHandler<CreateEnvelopeComman
|
|||||||
|
|
||||||
private IRepository<DocReceiverElement> DocReceiverElementRepository => _provider.GetRequiredService<IRepository<DocReceiverElement>>();
|
private IRepository<DocReceiverElement> DocReceiverElementRepository => _provider.GetRequiredService<IRepository<DocReceiverElement>>();
|
||||||
|
|
||||||
|
private IRepository<EmailTemplate> EmailTemplateRepository => _provider.GetRequiredService<IRepository<EmailTemplate>>();
|
||||||
|
|
||||||
|
private IRepository<EmailOut> EmailOutRepository => _provider.GetRequiredService<IRepository<EmailOut>>();
|
||||||
|
|
||||||
|
private MailParams MailParams => _provider.GetRequiredService<IOptions<MailParams>>().Value;
|
||||||
|
|
||||||
|
private DispatcherParams DispatcherParams => _provider.GetRequiredService<IOptions<DispatcherParams>>().Value;
|
||||||
|
|
||||||
|
private IConfigService ConfigService => _provider.GetRequiredService<IConfigService>();
|
||||||
|
|
||||||
private IMediator Mediator => _provider.GetRequiredService<IMediator>();
|
private IMediator Mediator => _provider.GetRequiredService<IMediator>();
|
||||||
|
|
||||||
private readonly IMapper _mapper;
|
private readonly IMapper _mapper;
|
||||||
@@ -370,6 +385,70 @@ public class CreateEnvelopeCommandHandler : IRequestHandler<CreateEnvelopeComman
|
|||||||
Status = EnvelopeStatus.EnvelopeQueued
|
Status = EnvelopeStatus.EnvelopeQueued
|
||||||
}, cancel);
|
}, cancel);
|
||||||
|
|
||||||
|
await QueueInvitationEmailsAsync(envelope, links, userReference, cancel);
|
||||||
|
|
||||||
envelope.Status = EnvelopeStatus.EnvelopeQueued;
|
envelope.Status = EnvelopeStatus.EnvelopeQueued;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task QueueInvitationEmailsAsync(
|
||||||
|
Envelope envelope,
|
||||||
|
IReadOnlyCollection<EnvelopeReceiver> links,
|
||||||
|
string senderReference,
|
||||||
|
CancellationToken cancel)
|
||||||
|
{
|
||||||
|
var template = await EmailTemplateRepository.Query
|
||||||
|
.SingleOrDefaultAsync(x => x.Name == EmailTemplateType.DocumentReceived.ToString(), cancel)
|
||||||
|
?? throw new InvalidOperationException($"Email template not found: {EmailTemplateType.DocumentReceived}");
|
||||||
|
|
||||||
|
var signatureHost = await ConfigService.ReadDefaultSignatureHost();
|
||||||
|
var senderName = senderReference;
|
||||||
|
|
||||||
|
foreach (var link in links)
|
||||||
|
{
|
||||||
|
var receiver = link.Receiver;
|
||||||
|
if (receiver is null || string.IsNullOrWhiteSpace(receiver.EmailAddress) || string.IsNullOrWhiteSpace(receiver.Signature))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var envelopeKey = (envelope.Uuid, receiver.Signature).ToEnvelopeKey();
|
||||||
|
var documentLink = $"{signatureHost.TrimEnd('/')}/envelope/login/{Uri.EscapeDataString(envelopeKey)}";
|
||||||
|
|
||||||
|
var placeholders = new Dictionary<string, string>(MailParams.Placeholders)
|
||||||
|
{
|
||||||
|
["[NAME_RECEIVER]"] = link.Name ?? string.Empty,
|
||||||
|
["[NAME_SENDER]"] = senderName ?? string.Empty,
|
||||||
|
["[DOCUMENT_TITLE]"] = envelope.Title ?? string.Empty,
|
||||||
|
["[MESSAGE]"] = envelope.Message ?? string.Empty,
|
||||||
|
["[LINK_TO_DOCUMENT]"] = documentLink,
|
||||||
|
["[LINK_TO_DOCUMENT_TEXT]"] = documentLink[..Math.Min(40, documentLink.Length)] + ".."
|
||||||
|
};
|
||||||
|
|
||||||
|
var subject = ReplacePlaceHolders(template.Subject, placeholders);
|
||||||
|
var body = ReplacePlaceHolders(template.Body, placeholders);
|
||||||
|
|
||||||
|
var emailOut = new EmailOut
|
||||||
|
{
|
||||||
|
EmailAddress = receiver.EmailAddress,
|
||||||
|
EmailSubj = subject,
|
||||||
|
EmailBody = body,
|
||||||
|
AddedWhen = DateTime.Now,
|
||||||
|
AddedWho = DispatcherParams.AddedWho,
|
||||||
|
SendingProfile = DispatcherParams.SendingProfile,
|
||||||
|
ReminderTypeId = DispatcherParams.ReminderTypeId,
|
||||||
|
EmailAttmt1 = DispatcherParams.EmailAttmt1,
|
||||||
|
WfId = (int)EnvelopeStatus.MessageInvitationSent,
|
||||||
|
ReferenceString = receiver.EmailAddress,
|
||||||
|
ReferenceId = link.ReceiverId
|
||||||
|
};
|
||||||
|
|
||||||
|
await EmailOutRepository.CreateAsync(emailOut, cancel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ReplacePlaceHolders(string text, Dictionary<string, string> placeHolders)
|
||||||
|
{
|
||||||
|
foreach (var placeHolder in placeHolders)
|
||||||
|
text = text.Replace(placeHolder.Key, placeHolder.Value);
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,7 +149,9 @@ public class ReadEnvelopeQueryHandler : IRequestHandler<ReadEnvelopeQuery, IEnum
|
|||||||
query = query.Where(e => Status.Completed.Contains(e.Status));
|
query = query.Where(e => Status.Completed.Contains(e.Status));
|
||||||
|
|
||||||
var envelopes = await query
|
var envelopes = await query
|
||||||
|
.Include(e => e.Type)
|
||||||
.Include(e => e.EnvelopeReceivers).ThenInclude(er => er.Receiver)
|
.Include(e => e.EnvelopeReceivers).ThenInclude(er => er.Receiver)
|
||||||
|
.Include(e => e.Histories)
|
||||||
.ToListAsync(cancel);
|
.ToListAsync(cancel);
|
||||||
|
|
||||||
return _mapper.Map<IEnumerable<EnvelopeDto>>(envelopes);
|
return _mapper.Map<IEnumerable<EnvelopeDto>>(envelopes);
|
||||||
|
|||||||
@@ -651,4 +651,123 @@
|
|||||||
<data name="Status" xml:space="preserve">
|
<data name="Status" xml:space="preserve">
|
||||||
<value>Status</value>
|
<value>Status</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ChangedAt" xml:space="preserve"><value>Geändert am</value></data>
|
||||||
|
<data name="Close" xml:space="preserve"><value>Schließen</value></data>
|
||||||
|
<data name="CloseLanguageMenu" xml:space="preserve"><value>Sprachmenü schließen</value></data>
|
||||||
|
<data name="ConfirmSignature" xml:space="preserve"><value>Unterschrift bestätigen</value></data>
|
||||||
|
<data name="Created" xml:space="preserve"><value>Erstellt</value></data>
|
||||||
|
<data name="Delete" xml:space="preserve"><value>Löschen</value></data>
|
||||||
|
<data name="Document" xml:space="preserve"><value>Dokument</value></data>
|
||||||
|
<data name="DocumentCouldNotBeLoaded" xml:space="preserve"><value>Dokument konnte nicht geladen werden</value></data>
|
||||||
|
<data name="DocumentLoading" xml:space="preserve"><value>Dokument wird geladen...</value></data>
|
||||||
|
<data name="EmailAddress" xml:space="preserve"><value>E-Mail-Adresse</value></data>
|
||||||
|
<data name="Envelope" xml:space="preserve"><value>Umschlag</value></data>
|
||||||
|
<data name="Error" xml:space="preserve"><value>Fehler</value></data>
|
||||||
|
<data name="ErrorLoadingDocument_WithReason" xml:space="preserve"><value>Fehler beim Laden des Dokuments: {0}</value></data>
|
||||||
|
<data name="ErrorPage_DevelopmentMode" xml:space="preserve"><value>Entwicklungsmodus</value></data>
|
||||||
|
<data name="ErrorPage_DevelopmentMode_Detail" xml:space="preserve"><value>Dies kann dazu führen, dass vertrauliche Informationen aus Ausnahmen Endbenutzern angezeigt werden. Für lokales Debugging setzen Sie ASPNETCORE_ENVIRONMENT auf Development und starten die App neu.</value></data>
|
||||||
|
<data name="ErrorPage_DevelopmentMode_Info" xml:space="preserve"><value>Im Entwicklungsmodus werden detailliertere Fehlerinformationen angezeigt.</value></data>
|
||||||
|
<data name="ErrorPage_DevelopmentMode_Warning" xml:space="preserve"><value>Der Entwicklungsmodus sollte in bereitgestellten Anwendungen nicht aktiviert sein.</value></data>
|
||||||
|
<data name="ErrorPage_ProcessingRequest" xml:space="preserve"><value>Bei der Verarbeitung Ihrer Anfrage ist ein Fehler aufgetreten.</value></data>
|
||||||
|
<data name="ErrorPage_RequestId" xml:space="preserve"><value>Anfrage-ID</value></data>
|
||||||
|
<data name="ErrorWithReason" xml:space="preserve"><value>Fehler: {0}</value></data>
|
||||||
|
<data name="From" xml:space="preserve"><value>Von</value></data>
|
||||||
|
<data name="FullName" xml:space="preserve"><value>Vor- und Nachname</value></data>
|
||||||
|
<data name="History" xml:space="preserve"><value>Historie</value></data>
|
||||||
|
<data name="Index_DigitalSignature" xml:space="preserve"><value>Digitale Unterschrift</value></data>
|
||||||
|
<data name="Index_PdfExport" xml:space="preserve"><value>PDF-Export</value></data>
|
||||||
|
<data name="Index_SecureAccess" xml:space="preserve"><value>Sicherer Zugang</value></data>
|
||||||
|
<data name="Index_Subtitle" xml:space="preserve"><value>Willkommen im eSign-Portal</value></data>
|
||||||
|
<data name="Index_ToEnvelopeOverview" xml:space="preserve"><value>Zur Umschlag-Übersicht</value></data>
|
||||||
|
<data name="Languages" xml:space="preserve"><value>Sprachen</value></data>
|
||||||
|
<data name="Loading" xml:space="preserve"><value>Lädt...</value></data>
|
||||||
|
<data name="Login" xml:space="preserve"><value>Anmelden</value></data>
|
||||||
|
<data name="LoginInProgress" xml:space="preserve"><value>Anmelden ...</value></data>
|
||||||
|
<data name="NotAuthenticatedPleaseLoginAgain" xml:space="preserve"><value>Nicht authentifiziert. Bitte melden Sie sich erneut an.</value></data>
|
||||||
|
<data name="Only" xml:space="preserve"><value>Nur</value></data>
|
||||||
|
<data name="OpenDocument" xml:space="preserve"><value>Dokument öffnen</value></data>
|
||||||
|
<data name="Optional" xml:space="preserve"><value>optional</value></data>
|
||||||
|
<data name="Pending" xml:space="preserve"><value>Ausstehend</value></data>
|
||||||
|
<data name="Preview" xml:space="preserve"><value>Vorschau</value></data>
|
||||||
|
<data name="PrivacyPolicy" xml:space="preserve"><value>Datenschutz</value></data>
|
||||||
|
<data name="Receiver" xml:space="preserve"><value>Empfänger</value></data>
|
||||||
|
<data name="ReceiverLogin_AccessCode_Placeholder" xml:space="preserve"><value>Zugangscode eingeben</value></data>
|
||||||
|
<data name="ReceiverLogin_Description" xml:space="preserve"><value>Bitte geben Sie den Zugangscode ein, den Sie per E-Mail erhalten haben, um das Dokument sicher zu öffnen.</value></data>
|
||||||
|
<data name="ReceiverLogin_FooterHelp" xml:space="preserve"><value>Bei Problemen wenden Sie sich bitte an den Absender des Dokuments.</value></data>
|
||||||
|
<data name="ReceiverLogin_InvalidCode_Body" xml:space="preserve"><value>Der eingegebene Code ist falsch. Bitte versuchen Sie es erneut.</value></data>
|
||||||
|
<data name="ReceiverLogin_InvalidCode_Title" xml:space="preserve"><value>Ungültiger Zugangscode.</value></data>
|
||||||
|
<data name="ReceiverLogin_NotFound_Body" xml:space="preserve"><value>Der angegebene Zugangscode konnte keinem Dokument zugeordnet werden. Bitte prüfen Sie den Link in Ihrer E-Mail.</value></data>
|
||||||
|
<data name="ReceiverLogin_NotFound_Title" xml:space="preserve"><value>Dokument nicht gefunden.</value></data>
|
||||||
|
<data name="ReceiverLogin_Subtitle" xml:space="preserve"><value>Sicherer Zugang mit Zugangscode</value></data>
|
||||||
|
<data name="ReceiverSigned_ConfirmBody" xml:space="preserve"><value>Diese Aktion kann nicht rückgängig gemacht werden. Mit der Bestätigung erklären Sie, das oben angezeigte Dokument elektronisch unterzeichnet zu haben. Das unterzeichnete Dokument wird anschließend an alle beteiligten Parteien übermittelt.</value></data>
|
||||||
|
<data name="ReceiverSigned_ConfirmQuestion" xml:space="preserve"><value>Möchten Sie das Dokument verbindlich unterschreiben?</value></data>
|
||||||
|
<data name="ReceiverSigned_EnvelopeKeyMissing" xml:space="preserve"><value>Envelope-Schlüssel fehlt.</value></data>
|
||||||
|
<data name="Refresh" xml:space="preserve"><value>Aktualisieren</value></data>
|
||||||
|
<data name="RequiredField" xml:space="preserve"><value>Pflichtfeld</value></data>
|
||||||
|
<data name="Selected" xml:space="preserve"><value>ausgewählte</value></data>
|
||||||
|
<data name="Send" xml:space="preserve"><value>Senden</value></data>
|
||||||
|
<data name="Sender_ActiveEnvelopes" xml:space="preserve"><value>Aktive Umschläge</value></data>
|
||||||
|
<data name="Sender_CompletedEnvelopes" xml:space="preserve"><value>Abgeschlossene Umschläge</value></data>
|
||||||
|
<data name="Sender_CreateEnvelope" xml:space="preserve"><value>Neuer Umschlag</value></data>
|
||||||
|
<data name="Sender_CreateEnvelope_Title" xml:space="preserve"><value>Neuen Umschlag erstellen</value></data>
|
||||||
|
<data name="Sender_DeleteEnvelope_Header" xml:space="preserve"><value>Umschlag löschen</value></data>
|
||||||
|
<data name="Sender_DeleteEnvelope_ReasonPlaceholder" xml:space="preserve"><value>Begründung eingeben...</value></data>
|
||||||
|
<data name="Sender_DeleteEnvelope_ReasonPrompt" xml:space="preserve"><value>Bitte geben Sie einen Grund an, warum der Umschlag gelöscht/zurückgezogen werden soll.</value></data>
|
||||||
|
<data name="Sender_DeleteEnvelope_ReasonRequired" xml:space="preserve"><value>Bitte geben Sie einen Löschgrund an.</value></data>
|
||||||
|
<data name="Sender_DeleteEnvelope_Title" xml:space="preserve"><value>Ausgewählten Umschlag löschen</value></data>
|
||||||
|
<data name="Sender_Edit" xml:space="preserve"><value>Bearbeiten</value></data>
|
||||||
|
<data name="Sender_EditEnvelope_Title" xml:space="preserve"><value>Ausgewählten Umschlag bearbeiten</value></data>
|
||||||
|
<data name="Sender_ExportCurrentView_Title" xml:space="preserve"><value>Aktuelle Ansicht exportieren</value></data>
|
||||||
|
<data name="Sender_ExportFormat_Aria" xml:space="preserve"><value>Exportformat auswählen</value></data>
|
||||||
|
<data name="Sender_ExportView_Title" xml:space="preserve"><value>Export der aktuellen Tabellenansicht</value></data>
|
||||||
|
<data name="Sender_LoadError_Title" xml:space="preserve"><value>Fehler beim Laden der Umschläge</value></data>
|
||||||
|
<data name="Sender_LoadingEnvelopes" xml:space="preserve"><value>Umschläge werden geladen...</value></data>
|
||||||
|
<data name="Sender_NoHistory" xml:space="preserve"><value>Keine Historie vorhanden</value></data>
|
||||||
|
<data name="Sender_NoReceivers" xml:space="preserve"><value>Keine Empfänger</value></data>
|
||||||
|
<data name="Sender_Preview_Title" xml:space="preserve"><value>Ausgewählten Umschlag als PDF anzeigen</value></data>
|
||||||
|
<data name="Sender_PreviewDocumentNotFound" xml:space="preserve"><value>Das Dokument für Umschlag #{0} wurde nicht gefunden oder Sie haben keinen Zugriff darauf.</value></data>
|
||||||
|
<data name="SenderEditor_AddSignature" xml:space="preserve"><value>Signatur hinzufügen</value></data>
|
||||||
|
<data name="SenderEditor_ClickDocumentFor" xml:space="preserve"><value>Klicken Sie auf die Stelle im Dokument für</value></data>
|
||||||
|
<data name="SenderEditor_EmailSuggestionsHint" xml:space="preserve"><value>Bereits verwendete E-Mail-Adressen werden bei der Eingabe vorgeschlagen.</value></data>
|
||||||
|
<data name="SenderEditor_EmailSuggestionsLoading" xml:space="preserve"><value>E-Mail-Vorschläge werden geladen...</value></data>
|
||||||
|
<data name="SenderEditor_ErrorCreatingEnvelope" xml:space="preserve"><value>Fehler beim Erstellen des Umschlags</value></data>
|
||||||
|
<data name="SenderEditor_LoadEnvelopeError" xml:space="preserve"><value>Fehler beim Laden des Umschlags #{0}: {1}</value></data>
|
||||||
|
<data name="SenderEditor_LoadFileError" xml:space="preserve"><value>Fehler beim Laden der Datei: {0}</value></data>
|
||||||
|
<data name="SenderEditor_Message_Placeholder" xml:space="preserve"><value>Bitte unterzeichnen Sie das beigefügte Dokument.</value></data>
|
||||||
|
<data name="SenderEditor_NoDocumentLoaded" xml:space="preserve"><value>Kein Dokument geladen</value></data>
|
||||||
|
<data name="SenderEditor_NoReceiversAdded" xml:space="preserve"><value>Noch keine Empfänger hinzugefügt.</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_SelectPdf" xml:space="preserve"><value>Bitte wählen Sie eine PDF-Datei aus.</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_Title" xml:space="preserve"><value>Umschlag wurde erfolgreich gesendet.</value></data>
|
||||||
|
<data name="SenderEditor_SignatureField" xml:space="preserve"><value>Signaturfeld</value></data>
|
||||||
|
<data name="SenderEditor_Title_Placeholder" xml:space="preserve"><value>z.B. Arbeitsvertrag 2025</value></data>
|
||||||
|
<data name="SenderEditor_ToOverview" xml:space="preserve"><value>Zur Übersicht</value></data>
|
||||||
|
<data name="SenderEditor_UploadPdfToPlaceFields" xml:space="preserve"><value>Laden Sie eine PDF-Datei hoch, um Signaturfelder zu platzieren.</value></data>
|
||||||
|
<data name="SenderEditor_Validation_EmailDuplicate" xml:space="preserve"><value>Diese E-Mail-Adresse wurde bereits hinzugefügt.</value></data>
|
||||||
|
<data name="SenderEditor_Validation_EmailInvalid" xml:space="preserve"><value>Bitte geben Sie eine gültige E-Mail-Adresse ein.</value></data>
|
||||||
|
<data name="SenderEditor_Validation_EmailRequired" xml:space="preserve"><value>Bitte geben Sie eine E-Mail-Adresse ein.</value></data>
|
||||||
|
<data name="SenderEditor_Validation_FullNameRequired" xml:space="preserve"><value>Bitte geben Sie einen Vor- und Nachnamen ein.</value></data>
|
||||||
|
<data name="SenderEditor_Validation_ReceiverRequired" xml:space="preserve"><value>Bitte fügen Sie mindestens einen Empfänger hinzu.</value></data>
|
||||||
|
<data name="SenderEditor_Validation_SignatureFieldRequired" xml:space="preserve"><value>Bitte platzieren Sie mindestens ein Signaturfeld.</value></data>
|
||||||
|
<data name="SenderEditor_Validation_TitleRequired" xml:space="preserve"><value>Bitte geben Sie einen Titel ein.</value></data>
|
||||||
|
<data name="SenderEditor_Validation_UploadPdf" xml:space="preserve"><value>Bitte laden Sie zuerst ein PDF-Dokument hoch.</value></data>
|
||||||
|
<data name="SenderLogin_Description" xml:space="preserve"><value>Bitte melden Sie sich mit Ihren Zugangsdaten an, um auf das Sender-Dashboard zuzugreifen.</value></data>
|
||||||
|
<data name="SenderLogin_FooterHelp" xml:space="preserve"><value>Bei Problemen wenden Sie sich bitte an den Administrator.</value></data>
|
||||||
|
<data name="SenderLogin_InvalidCredentials_Body" xml:space="preserve"><value>Benutzername oder Passwort ist falsch. Bitte versuchen Sie es erneut.</value></data>
|
||||||
|
<data name="SenderLogin_InvalidCredentials_Title" xml:space="preserve"><value>Ungültige Anmeldedaten.</value></data>
|
||||||
|
<data name="SenderLogin_Password_Placeholder" xml:space="preserve"><value>Passwort eingeben</value></data>
|
||||||
|
<data name="SenderLogin_Subtitle" xml:space="preserve"><value>Sicherer Zugang zum Sender-Dashboard</value></data>
|
||||||
|
<data name="SenderLogin_Title" xml:space="preserve"><value>Sender Anmeldung</value></data>
|
||||||
|
<data name="SenderLogin_Username_Placeholder" xml:space="preserve"><value>Benutzername eingeben</value></data>
|
||||||
|
<data name="ServerError" xml:space="preserve"><value>Serverfehler.</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="System" xml:space="preserve"><value>System</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="UserName" xml:space="preserve"><value>Benutzername</value></data>
|
||||||
|
<data name="VerifyInProgress" xml:space="preserve"><value>Überprüfen ...</value></data>
|
||||||
</root>
|
</root>
|
||||||
@@ -651,4 +651,123 @@
|
|||||||
<data name="Status" xml:space="preserve">
|
<data name="Status" xml:space="preserve">
|
||||||
<value>Status</value>
|
<value>Status</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ChangedAt" xml:space="preserve"><value>Changed at</value></data>
|
||||||
|
<data name="Close" xml:space="preserve"><value>Close</value></data>
|
||||||
|
<data name="CloseLanguageMenu" xml:space="preserve"><value>Close language menu</value></data>
|
||||||
|
<data name="ConfirmSignature" xml:space="preserve"><value>Confirm signature</value></data>
|
||||||
|
<data name="Created" xml:space="preserve"><value>Created</value></data>
|
||||||
|
<data name="Delete" xml:space="preserve"><value>Delete</value></data>
|
||||||
|
<data name="Document" xml:space="preserve"><value>Document</value></data>
|
||||||
|
<data name="DocumentCouldNotBeLoaded" xml:space="preserve"><value>Document could not be loaded</value></data>
|
||||||
|
<data name="DocumentLoading" xml:space="preserve"><value>Document is loading...</value></data>
|
||||||
|
<data name="EmailAddress" xml:space="preserve"><value>Email address</value></data>
|
||||||
|
<data name="Envelope" xml:space="preserve"><value>Envelope</value></data>
|
||||||
|
<data name="Error" xml:space="preserve"><value>Error</value></data>
|
||||||
|
<data name="ErrorLoadingDocument_WithReason" xml:space="preserve"><value>Error loading document: {0}</value></data>
|
||||||
|
<data name="ErrorPage_DevelopmentMode" xml:space="preserve"><value>Development Mode</value></data>
|
||||||
|
<data name="ErrorPage_DevelopmentMode_Detail" xml:space="preserve"><value>This can result in sensitive exception information being shown to end users. For local debugging, set ASPNETCORE_ENVIRONMENT to Development and restart the app.</value></data>
|
||||||
|
<data name="ErrorPage_DevelopmentMode_Info" xml:space="preserve"><value>Switching to Development will display more detailed error information.</value></data>
|
||||||
|
<data name="ErrorPage_DevelopmentMode_Warning" xml:space="preserve"><value>The Development environment should not be enabled in deployed applications.</value></data>
|
||||||
|
<data name="ErrorPage_ProcessingRequest" xml:space="preserve"><value>An error occurred while processing your request.</value></data>
|
||||||
|
<data name="ErrorPage_RequestId" xml:space="preserve"><value>Request ID</value></data>
|
||||||
|
<data name="ErrorWithReason" xml:space="preserve"><value>Error: {0}</value></data>
|
||||||
|
<data name="From" xml:space="preserve"><value>From</value></data>
|
||||||
|
<data name="FullName" xml:space="preserve"><value>First and last name</value></data>
|
||||||
|
<data name="History" xml:space="preserve"><value>History</value></data>
|
||||||
|
<data name="Index_DigitalSignature" xml:space="preserve"><value>Digital signature</value></data>
|
||||||
|
<data name="Index_PdfExport" xml:space="preserve"><value>PDF export</value></data>
|
||||||
|
<data name="Index_SecureAccess" xml:space="preserve"><value>Secure access</value></data>
|
||||||
|
<data name="Index_Subtitle" xml:space="preserve"><value>Welcome to the eSign portal</value></data>
|
||||||
|
<data name="Index_ToEnvelopeOverview" xml:space="preserve"><value>Go to envelope overview</value></data>
|
||||||
|
<data name="Languages" xml:space="preserve"><value>Languages</value></data>
|
||||||
|
<data name="Loading" xml:space="preserve"><value>Loading...</value></data>
|
||||||
|
<data name="Login" xml:space="preserve"><value>Sign in</value></data>
|
||||||
|
<data name="LoginInProgress" xml:space="preserve"><value>Signing in ...</value></data>
|
||||||
|
<data name="NotAuthenticatedPleaseLoginAgain" xml:space="preserve"><value>Not authenticated. Please sign in again.</value></data>
|
||||||
|
<data name="Only" xml:space="preserve"><value>Only</value></data>
|
||||||
|
<data name="OpenDocument" xml:space="preserve"><value>Open document</value></data>
|
||||||
|
<data name="Optional" xml:space="preserve"><value>optional</value></data>
|
||||||
|
<data name="Pending" xml:space="preserve"><value>Pending</value></data>
|
||||||
|
<data name="Preview" xml:space="preserve"><value>Preview</value></data>
|
||||||
|
<data name="PrivacyPolicy" xml:space="preserve"><value>Privacy policy</value></data>
|
||||||
|
<data name="Receiver" xml:space="preserve"><value>Receiver</value></data>
|
||||||
|
<data name="ReceiverLogin_AccessCode_Placeholder" xml:space="preserve"><value>Enter access code</value></data>
|
||||||
|
<data name="ReceiverLogin_Description" xml:space="preserve"><value>Please enter the access code you received by email to securely open the document.</value></data>
|
||||||
|
<data name="ReceiverLogin_FooterHelp" xml:space="preserve"><value>If you have issues, please contact the document sender.</value></data>
|
||||||
|
<data name="ReceiverLogin_InvalidCode_Body" xml:space="preserve"><value>The entered code is incorrect. Please try again.</value></data>
|
||||||
|
<data name="ReceiverLogin_InvalidCode_Title" xml:space="preserve"><value>Invalid access code.</value></data>
|
||||||
|
<data name="ReceiverLogin_NotFound_Body" xml:space="preserve"><value>The provided access code could not be matched to a document. Please verify the link in your email.</value></data>
|
||||||
|
<data name="ReceiverLogin_NotFound_Title" xml:space="preserve"><value>Document not found.</value></data>
|
||||||
|
<data name="ReceiverLogin_Subtitle" xml:space="preserve"><value>Secure access with access code</value></data>
|
||||||
|
<data name="ReceiverSigned_ConfirmBody" xml:space="preserve"><value>This action cannot be undone. By confirming, you declare that you have electronically signed the document shown above. The signed document will then be delivered to all involved parties.</value></data>
|
||||||
|
<data name="ReceiverSigned_ConfirmQuestion" xml:space="preserve"><value>Do you want to sign this document as binding?</value></data>
|
||||||
|
<data name="ReceiverSigned_EnvelopeKeyMissing" xml:space="preserve"><value>Envelope key is missing.</value></data>
|
||||||
|
<data name="Refresh" xml:space="preserve"><value>Refresh</value></data>
|
||||||
|
<data name="RequiredField" xml:space="preserve"><value>Required field</value></data>
|
||||||
|
<data name="Selected" xml:space="preserve"><value>selected</value></data>
|
||||||
|
<data name="Send" xml:space="preserve"><value>Send</value></data>
|
||||||
|
<data name="Sender_ActiveEnvelopes" xml:space="preserve"><value>Active envelopes</value></data>
|
||||||
|
<data name="Sender_CompletedEnvelopes" xml:space="preserve"><value>Completed envelopes</value></data>
|
||||||
|
<data name="Sender_CreateEnvelope" xml:space="preserve"><value>New envelope</value></data>
|
||||||
|
<data name="Sender_CreateEnvelope_Title" xml:space="preserve"><value>Create new envelope</value></data>
|
||||||
|
<data name="Sender_DeleteEnvelope_Header" xml:space="preserve"><value>Delete envelope</value></data>
|
||||||
|
<data name="Sender_DeleteEnvelope_ReasonPlaceholder" xml:space="preserve"><value>Enter reason...</value></data>
|
||||||
|
<data name="Sender_DeleteEnvelope_ReasonPrompt" xml:space="preserve"><value>Please provide a reason why the envelope should be deleted/withdrawn.</value></data>
|
||||||
|
<data name="Sender_DeleteEnvelope_ReasonRequired" xml:space="preserve"><value>Please provide a deletion reason.</value></data>
|
||||||
|
<data name="Sender_DeleteEnvelope_Title" xml:space="preserve"><value>Delete selected envelope</value></data>
|
||||||
|
<data name="Sender_Edit" xml:space="preserve"><value>Edit</value></data>
|
||||||
|
<data name="Sender_EditEnvelope_Title" xml:space="preserve"><value>Edit selected envelope</value></data>
|
||||||
|
<data name="Sender_ExportCurrentView_Title" xml:space="preserve"><value>Export current view</value></data>
|
||||||
|
<data name="Sender_ExportFormat_Aria" xml:space="preserve"><value>Select export format</value></data>
|
||||||
|
<data name="Sender_ExportView_Title" xml:space="preserve"><value>Export current table view</value></data>
|
||||||
|
<data name="Sender_LoadError_Title" xml:space="preserve"><value>Error loading envelopes</value></data>
|
||||||
|
<data name="Sender_LoadingEnvelopes" xml:space="preserve"><value>Loading envelopes...</value></data>
|
||||||
|
<data name="Sender_NoHistory" xml:space="preserve"><value>No history available</value></data>
|
||||||
|
<data name="Sender_NoReceivers" xml:space="preserve"><value>No receivers</value></data>
|
||||||
|
<data name="Sender_Preview_Title" xml:space="preserve"><value>Show selected envelope as PDF</value></data>
|
||||||
|
<data name="Sender_PreviewDocumentNotFound" xml:space="preserve"><value>The document for envelope #{0} was not found or you do not have access.</value></data>
|
||||||
|
<data name="SenderEditor_AddSignature" xml:space="preserve"><value>Add signature</value></data>
|
||||||
|
<data name="SenderEditor_ClickDocumentFor" xml:space="preserve"><value>Click on the location in the document for</value></data>
|
||||||
|
<data name="SenderEditor_EmailSuggestionsHint" xml:space="preserve"><value>Previously used email addresses are suggested while typing.</value></data>
|
||||||
|
<data name="SenderEditor_EmailSuggestionsLoading" xml:space="preserve"><value>Loading email suggestions...</value></data>
|
||||||
|
<data name="SenderEditor_ErrorCreatingEnvelope" xml:space="preserve"><value>Error creating envelope</value></data>
|
||||||
|
<data name="SenderEditor_LoadEnvelopeError" xml:space="preserve"><value>Error loading envelope #{0}: {1}</value></data>
|
||||||
|
<data name="SenderEditor_LoadFileError" xml:space="preserve"><value>Error loading file: {0}</value></data>
|
||||||
|
<data name="SenderEditor_Message_Placeholder" xml:space="preserve"><value>Please sign the attached document.</value></data>
|
||||||
|
<data name="SenderEditor_NoDocumentLoaded" xml:space="preserve"><value>No document loaded</value></data>
|
||||||
|
<data name="SenderEditor_NoReceiversAdded" xml:space="preserve"><value>No receivers added yet.</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_SelectPdf" xml:space="preserve"><value>Please select a PDF file.</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_Title" xml:space="preserve"><value>Envelope was sent successfully.</value></data>
|
||||||
|
<data name="SenderEditor_SignatureField" xml:space="preserve"><value>Signature field</value></data>
|
||||||
|
<data name="SenderEditor_Title_Placeholder" xml:space="preserve"><value>e.g. Employment contract 2025</value></data>
|
||||||
|
<data name="SenderEditor_ToOverview" xml:space="preserve"><value>To overview</value></data>
|
||||||
|
<data name="SenderEditor_UploadPdfToPlaceFields" xml:space="preserve"><value>Upload a PDF file to place signature fields.</value></data>
|
||||||
|
<data name="SenderEditor_Validation_EmailDuplicate" xml:space="preserve"><value>This email address has already been added.</value></data>
|
||||||
|
<data name="SenderEditor_Validation_EmailInvalid" xml:space="preserve"><value>Please enter a valid email address.</value></data>
|
||||||
|
<data name="SenderEditor_Validation_EmailRequired" xml:space="preserve"><value>Please enter an email address.</value></data>
|
||||||
|
<data name="SenderEditor_Validation_FullNameRequired" xml:space="preserve"><value>Please enter first and last name.</value></data>
|
||||||
|
<data name="SenderEditor_Validation_ReceiverRequired" xml:space="preserve"><value>Please add at least one receiver.</value></data>
|
||||||
|
<data name="SenderEditor_Validation_SignatureFieldRequired" xml:space="preserve"><value>Please place at least one signature field.</value></data>
|
||||||
|
<data name="SenderEditor_Validation_TitleRequired" xml:space="preserve"><value>Please enter a title.</value></data>
|
||||||
|
<data name="SenderEditor_Validation_UploadPdf" xml:space="preserve"><value>Please upload a PDF document first.</value></data>
|
||||||
|
<data name="SenderLogin_Description" xml:space="preserve"><value>Please sign in with your credentials to access the sender dashboard.</value></data>
|
||||||
|
<data name="SenderLogin_FooterHelp" xml:space="preserve"><value>If you have issues, please contact the administrator.</value></data>
|
||||||
|
<data name="SenderLogin_InvalidCredentials_Body" xml:space="preserve"><value>Username or password is incorrect. Please try again.</value></data>
|
||||||
|
<data name="SenderLogin_InvalidCredentials_Title" xml:space="preserve"><value>Invalid credentials.</value></data>
|
||||||
|
<data name="SenderLogin_Password_Placeholder" xml:space="preserve"><value>Enter password</value></data>
|
||||||
|
<data name="SenderLogin_Subtitle" xml:space="preserve"><value>Secure access to sender dashboard</value></data>
|
||||||
|
<data name="SenderLogin_Title" xml:space="preserve"><value>Sender login</value></data>
|
||||||
|
<data name="SenderLogin_Username_Placeholder" xml:space="preserve"><value>Enter username</value></data>
|
||||||
|
<data name="ServerError" xml:space="preserve"><value>Server error.</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="System" xml:space="preserve"><value>System</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="UserName" xml:space="preserve"><value>Username</value></data>
|
||||||
|
<data name="VerifyInProgress" xml:space="preserve"><value>Verifying ...</value></data>
|
||||||
</root>
|
</root>
|
||||||
@@ -651,4 +651,123 @@
|
|||||||
<data name="Status" xml:space="preserve">
|
<data name="Status" xml:space="preserve">
|
||||||
<value>Statut</value>
|
<value>Statut</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ChangedAt" xml:space="preserve"><value>Modifié le</value></data>
|
||||||
|
<data name="Close" xml:space="preserve"><value>Fermer</value></data>
|
||||||
|
<data name="CloseLanguageMenu" xml:space="preserve"><value>Fermer le menu des langues</value></data>
|
||||||
|
<data name="ConfirmSignature" xml:space="preserve"><value>Confirmer la signature</value></data>
|
||||||
|
<data name="Created" xml:space="preserve"><value>Créé</value></data>
|
||||||
|
<data name="Delete" xml:space="preserve"><value>Supprimer</value></data>
|
||||||
|
<data name="Document" xml:space="preserve"><value>Document</value></data>
|
||||||
|
<data name="DocumentCouldNotBeLoaded" xml:space="preserve"><value>Le document n'a pas pu être chargé</value></data>
|
||||||
|
<data name="DocumentLoading" xml:space="preserve"><value>Chargement du document...</value></data>
|
||||||
|
<data name="EmailAddress" xml:space="preserve"><value>Adresse e-mail</value></data>
|
||||||
|
<data name="Envelope" xml:space="preserve"><value>Enveloppe</value></data>
|
||||||
|
<data name="Error" xml:space="preserve"><value>Erreur</value></data>
|
||||||
|
<data name="ErrorLoadingDocument_WithReason" xml:space="preserve"><value>Erreur lors du chargement du document : {0}</value></data>
|
||||||
|
<data name="ErrorPage_DevelopmentMode" xml:space="preserve"><value>Mode développement</value></data>
|
||||||
|
<data name="ErrorPage_DevelopmentMode_Detail" xml:space="preserve"><value>Cela peut afficher des informations sensibles d'exception aux utilisateurs finaux. Pour le débogage local, définissez ASPNETCORE_ENVIRONMENT sur Development et redémarrez l'application.</value></data>
|
||||||
|
<data name="ErrorPage_DevelopmentMode_Info" xml:space="preserve"><value>Le mode développement affiche des informations d'erreur plus détaillées.</value></data>
|
||||||
|
<data name="ErrorPage_DevelopmentMode_Warning" xml:space="preserve"><value>Le mode développement ne doit pas être activé dans les applications déployées.</value></data>
|
||||||
|
<data name="ErrorPage_ProcessingRequest" xml:space="preserve"><value>Une erreur s'est produite lors du traitement de votre demande.</value></data>
|
||||||
|
<data name="ErrorPage_RequestId" xml:space="preserve"><value>ID de requête</value></data>
|
||||||
|
<data name="ErrorWithReason" xml:space="preserve"><value>Erreur : {0}</value></data>
|
||||||
|
<data name="From" xml:space="preserve"><value>De</value></data>
|
||||||
|
<data name="FullName" xml:space="preserve"><value>Nom et prénom</value></data>
|
||||||
|
<data name="History" xml:space="preserve"><value>Historique</value></data>
|
||||||
|
<data name="Index_DigitalSignature" xml:space="preserve"><value>Signature numérique</value></data>
|
||||||
|
<data name="Index_PdfExport" xml:space="preserve"><value>Export PDF</value></data>
|
||||||
|
<data name="Index_SecureAccess" xml:space="preserve"><value>Accès sécurisé</value></data>
|
||||||
|
<data name="Index_Subtitle" xml:space="preserve"><value>Bienvenue sur le portail eSign</value></data>
|
||||||
|
<data name="Index_ToEnvelopeOverview" xml:space="preserve"><value>Aller à l'aperçu des enveloppes</value></data>
|
||||||
|
<data name="Languages" xml:space="preserve"><value>Langues</value></data>
|
||||||
|
<data name="Loading" xml:space="preserve"><value>Chargement...</value></data>
|
||||||
|
<data name="Login" xml:space="preserve"><value>Connexion</value></data>
|
||||||
|
<data name="LoginInProgress" xml:space="preserve"><value>Connexion ...</value></data>
|
||||||
|
<data name="NotAuthenticatedPleaseLoginAgain" xml:space="preserve"><value>Non authentifié. Veuillez vous reconnecter.</value></data>
|
||||||
|
<data name="Only" xml:space="preserve"><value>Uniquement</value></data>
|
||||||
|
<data name="OpenDocument" xml:space="preserve"><value>Ouvrir le document</value></data>
|
||||||
|
<data name="Optional" xml:space="preserve"><value>optionnel</value></data>
|
||||||
|
<data name="Pending" xml:space="preserve"><value>En attente</value></data>
|
||||||
|
<data name="Preview" xml:space="preserve"><value>Aperçu</value></data>
|
||||||
|
<data name="PrivacyPolicy" xml:space="preserve"><value>Politique de confidentialité</value></data>
|
||||||
|
<data name="Receiver" xml:space="preserve"><value>Destinataire</value></data>
|
||||||
|
<data name="ReceiverLogin_AccessCode_Placeholder" xml:space="preserve"><value>Saisir le code d'accès</value></data>
|
||||||
|
<data name="ReceiverLogin_Description" xml:space="preserve"><value>Veuillez saisir le code d'accès reçu par e-mail pour ouvrir le document en toute sécurité.</value></data>
|
||||||
|
<data name="ReceiverLogin_FooterHelp" xml:space="preserve"><value>En cas de problème, veuillez contacter l'expéditeur du document.</value></data>
|
||||||
|
<data name="ReceiverLogin_InvalidCode_Body" xml:space="preserve"><value>Le code saisi est incorrect. Veuillez réessayer.</value></data>
|
||||||
|
<data name="ReceiverLogin_InvalidCode_Title" xml:space="preserve"><value>Code d'accès invalide.</value></data>
|
||||||
|
<data name="ReceiverLogin_NotFound_Body" xml:space="preserve"><value>Le code d'accès indiqué ne correspond à aucun document. Veuillez vérifier le lien dans votre e-mail.</value></data>
|
||||||
|
<data name="ReceiverLogin_NotFound_Title" xml:space="preserve"><value>Document introuvable.</value></data>
|
||||||
|
<data name="ReceiverLogin_Subtitle" xml:space="preserve"><value>Accès sécurisé avec code d'accès</value></data>
|
||||||
|
<data name="ReceiverSigned_ConfirmBody" xml:space="preserve"><value>Cette action est irréversible. En confirmant, vous déclarez avoir signé électroniquement le document affiché ci-dessus. Le document signé sera ensuite transmis à toutes les parties concernées.</value></data>
|
||||||
|
<data name="ReceiverSigned_ConfirmQuestion" xml:space="preserve"><value>Voulez-vous signer ce document de manière contraignante ?</value></data>
|
||||||
|
<data name="ReceiverSigned_EnvelopeKeyMissing" xml:space="preserve"><value>La clé d'enveloppe est manquante.</value></data>
|
||||||
|
<data name="Refresh" xml:space="preserve"><value>Actualiser</value></data>
|
||||||
|
<data name="RequiredField" xml:space="preserve"><value>Champ obligatoire</value></data>
|
||||||
|
<data name="Selected" xml:space="preserve"><value>sélectionnées</value></data>
|
||||||
|
<data name="Send" xml:space="preserve"><value>Envoyer</value></data>
|
||||||
|
<data name="Sender_ActiveEnvelopes" xml:space="preserve"><value>Enveloppes actives</value></data>
|
||||||
|
<data name="Sender_CompletedEnvelopes" xml:space="preserve"><value>Enveloppes terminées</value></data>
|
||||||
|
<data name="Sender_CreateEnvelope" xml:space="preserve"><value>Nouvelle enveloppe</value></data>
|
||||||
|
<data name="Sender_CreateEnvelope_Title" xml:space="preserve"><value>Créer une nouvelle enveloppe</value></data>
|
||||||
|
<data name="Sender_DeleteEnvelope_Header" xml:space="preserve"><value>Supprimer l'enveloppe</value></data>
|
||||||
|
<data name="Sender_DeleteEnvelope_ReasonPlaceholder" xml:space="preserve"><value>Saisir la raison...</value></data>
|
||||||
|
<data name="Sender_DeleteEnvelope_ReasonPrompt" xml:space="preserve"><value>Veuillez indiquer la raison pour laquelle l'enveloppe doit être supprimée/retirée.</value></data>
|
||||||
|
<data name="Sender_DeleteEnvelope_ReasonRequired" xml:space="preserve"><value>Veuillez indiquer un motif de suppression.</value></data>
|
||||||
|
<data name="Sender_DeleteEnvelope_Title" xml:space="preserve"><value>Supprimer l'enveloppe sélectionnée</value></data>
|
||||||
|
<data name="Sender_Edit" xml:space="preserve"><value>Modifier</value></data>
|
||||||
|
<data name="Sender_EditEnvelope_Title" xml:space="preserve"><value>Modifier l'enveloppe sélectionnée</value></data>
|
||||||
|
<data name="Sender_ExportCurrentView_Title" xml:space="preserve"><value>Exporter la vue actuelle</value></data>
|
||||||
|
<data name="Sender_ExportFormat_Aria" xml:space="preserve"><value>Sélectionner le format d'export</value></data>
|
||||||
|
<data name="Sender_ExportView_Title" xml:space="preserve"><value>Exporter la vue actuelle du tableau</value></data>
|
||||||
|
<data name="Sender_LoadError_Title" xml:space="preserve"><value>Erreur lors du chargement des enveloppes</value></data>
|
||||||
|
<data name="Sender_LoadingEnvelopes" xml:space="preserve"><value>Chargement des enveloppes...</value></data>
|
||||||
|
<data name="Sender_NoHistory" xml:space="preserve"><value>Aucun historique disponible</value></data>
|
||||||
|
<data name="Sender_NoReceivers" xml:space="preserve"><value>Aucun destinataire</value></data>
|
||||||
|
<data name="Sender_Preview_Title" xml:space="preserve"><value>Afficher l'enveloppe sélectionnée en PDF</value></data>
|
||||||
|
<data name="Sender_PreviewDocumentNotFound" xml:space="preserve"><value>Le document de l'enveloppe #{0} est introuvable ou vous n'avez pas l'autorisation d'y accéder.</value></data>
|
||||||
|
<data name="SenderEditor_AddSignature" xml:space="preserve"><value>Ajouter une signature</value></data>
|
||||||
|
<data name="SenderEditor_ClickDocumentFor" xml:space="preserve"><value>Cliquez sur l'emplacement dans le document pour</value></data>
|
||||||
|
<data name="SenderEditor_EmailSuggestionsHint" xml:space="preserve"><value>Les adresses e-mail déjà utilisées sont proposées pendant la saisie.</value></data>
|
||||||
|
<data name="SenderEditor_EmailSuggestionsLoading" xml:space="preserve"><value>Chargement des suggestions d'e-mail...</value></data>
|
||||||
|
<data name="SenderEditor_ErrorCreatingEnvelope" xml:space="preserve"><value>Erreur lors de la création de l'enveloppe</value></data>
|
||||||
|
<data name="SenderEditor_LoadEnvelopeError" xml:space="preserve"><value>Erreur lors du chargement de l'enveloppe #{0} : {1}</value></data>
|
||||||
|
<data name="SenderEditor_LoadFileError" xml:space="preserve"><value>Erreur lors du chargement du fichier : {0}</value></data>
|
||||||
|
<data name="SenderEditor_Message_Placeholder" xml:space="preserve"><value>Veuillez signer le document ci-joint.</value></data>
|
||||||
|
<data name="SenderEditor_NoDocumentLoaded" xml:space="preserve"><value>Aucun document chargé</value></data>
|
||||||
|
<data name="SenderEditor_NoReceiversAdded" xml:space="preserve"><value>Aucun destinataire ajouté pour le moment.</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_SelectPdf" xml:space="preserve"><value>Veuillez sélectionner un fichier PDF.</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_Title" xml:space="preserve"><value>L'enveloppe a été envoyée avec succès.</value></data>
|
||||||
|
<data name="SenderEditor_SignatureField" xml:space="preserve"><value>Champ de signature</value></data>
|
||||||
|
<data name="SenderEditor_Title_Placeholder" xml:space="preserve"><value>p. ex. Contrat de travail 2025</value></data>
|
||||||
|
<data name="SenderEditor_ToOverview" xml:space="preserve"><value>Vers l'aperçu</value></data>
|
||||||
|
<data name="SenderEditor_UploadPdfToPlaceFields" xml:space="preserve"><value>Téléversez un fichier PDF pour placer des champs de signature.</value></data>
|
||||||
|
<data name="SenderEditor_Validation_EmailDuplicate" xml:space="preserve"><value>Cette adresse e-mail a déjà été ajoutée.</value></data>
|
||||||
|
<data name="SenderEditor_Validation_EmailInvalid" xml:space="preserve"><value>Veuillez saisir une adresse e-mail valide.</value></data>
|
||||||
|
<data name="SenderEditor_Validation_EmailRequired" xml:space="preserve"><value>Veuillez saisir une adresse e-mail.</value></data>
|
||||||
|
<data name="SenderEditor_Validation_FullNameRequired" xml:space="preserve"><value>Veuillez saisir le nom et le prénom.</value></data>
|
||||||
|
<data name="SenderEditor_Validation_ReceiverRequired" xml:space="preserve"><value>Veuillez ajouter au moins un destinataire.</value></data>
|
||||||
|
<data name="SenderEditor_Validation_SignatureFieldRequired" xml:space="preserve"><value>Veuillez placer au moins un champ de signature.</value></data>
|
||||||
|
<data name="SenderEditor_Validation_TitleRequired" xml:space="preserve"><value>Veuillez saisir un titre.</value></data>
|
||||||
|
<data name="SenderEditor_Validation_UploadPdf" xml:space="preserve"><value>Veuillez d'abord téléverser un document PDF.</value></data>
|
||||||
|
<data name="SenderLogin_Description" xml:space="preserve"><value>Veuillez vous connecter avec vos identifiants pour accéder au tableau de bord expéditeur.</value></data>
|
||||||
|
<data name="SenderLogin_FooterHelp" xml:space="preserve"><value>En cas de problème, veuillez contacter l'administrateur.</value></data>
|
||||||
|
<data name="SenderLogin_InvalidCredentials_Body" xml:space="preserve"><value>Le nom d'utilisateur ou le mot de passe est incorrect. Veuillez réessayer.</value></data>
|
||||||
|
<data name="SenderLogin_InvalidCredentials_Title" xml:space="preserve"><value>Identifiants invalides.</value></data>
|
||||||
|
<data name="SenderLogin_Password_Placeholder" xml:space="preserve"><value>Saisir le mot de passe</value></data>
|
||||||
|
<data name="SenderLogin_Subtitle" xml:space="preserve"><value>Accès sécurisé au tableau de bord expéditeur</value></data>
|
||||||
|
<data name="SenderLogin_Title" xml:space="preserve"><value>Connexion expéditeur</value></data>
|
||||||
|
<data name="SenderLogin_Username_Placeholder" xml:space="preserve"><value>Saisir le nom d'utilisateur</value></data>
|
||||||
|
<data name="ServerError" xml:space="preserve"><value>Erreur serveur.</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="System" xml:space="preserve"><value>Système</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="UserName" xml:space="preserve"><value>Nom d'utilisateur</value></data>
|
||||||
|
<data name="VerifyInProgress" xml:space="preserve"><value>Vérification ...</value></data>
|
||||||
</root>
|
</root>
|
||||||
7
EnvelopeGenerator.Domain/Resources/Model.cs
Normal file
7
EnvelopeGenerator.Domain/Resources/Model.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace EnvelopeGenerator.Domain.Resources
|
||||||
|
{
|
||||||
|
// Marker type for IStringLocalizer lookup of Resources/Model.resx.
|
||||||
|
public class Model
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
116
EnvelopeGenerator.Domain/Resources/Model.fr-FR.resx
Normal file
116
EnvelopeGenerator.Domain/Resources/Model.fr-FR.resx
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
|
||||||
|
<data name="AccessCodeIncorrect" xml:space="preserve">
|
||||||
|
<value>Code d'acces incorrect</value>
|
||||||
|
</data>
|
||||||
|
<data name="DocumentRejected" xml:space="preserve">
|
||||||
|
<value>Document rejete</value>
|
||||||
|
</data>
|
||||||
|
<data name="DocumentSigned" xml:space="preserve">
|
||||||
|
<value>Document signe</value>
|
||||||
|
</data>
|
||||||
|
<data name="Draft" xml:space="preserve">
|
||||||
|
<value>Brouillon</value>
|
||||||
|
</data>
|
||||||
|
<data name="EnvelopeCompletelySigned" xml:space="preserve">
|
||||||
|
<value>Entierement signe</value>
|
||||||
|
</data>
|
||||||
|
<data name="EnvelopeCreated" xml:space="preserve">
|
||||||
|
<value>Enveloppe creee</value>
|
||||||
|
</data>
|
||||||
|
<data name="EnvelopeDeleted" xml:space="preserve">
|
||||||
|
<value>Enveloppe supprimee</value>
|
||||||
|
</data>
|
||||||
|
<data name="EnvelopePartlySigned" xml:space="preserve">
|
||||||
|
<value>Partiellement signee</value>
|
||||||
|
</data>
|
||||||
|
<data name="EnvelopeQueued" xml:space="preserve">
|
||||||
|
<value>Enveloppe en file d'attente</value>
|
||||||
|
</data>
|
||||||
|
<data name="EnvelopeRejected" xml:space="preserve">
|
||||||
|
<value>Enveloppe rejetee</value>
|
||||||
|
</data>
|
||||||
|
<data name="EnvelopeSaved" xml:space="preserve">
|
||||||
|
<value>Enregistree</value>
|
||||||
|
</data>
|
||||||
|
<data name="EnvelopeSent" xml:space="preserve">
|
||||||
|
<value>Envoyee</value>
|
||||||
|
</data>
|
||||||
|
<data name="EnvelopeWithdrawn" xml:space="preserve">
|
||||||
|
<value>Enveloppe retiree</value>
|
||||||
|
</data>
|
||||||
|
<data name="MessageAccessCodeSent" xml:space="preserve">
|
||||||
|
<value>Code d'acces envoye</value>
|
||||||
|
</data>
|
||||||
|
<data name="MessageCompletionSent" xml:space="preserve">
|
||||||
|
<value>E-mail de cloture envoye</value>
|
||||||
|
</data>
|
||||||
|
<data name="MessageConfirmationSent" xml:space="preserve">
|
||||||
|
<value>Confirmation finale envoyee</value>
|
||||||
|
</data>
|
||||||
|
<data name="MessageDeletionSent" xml:space="preserve">
|
||||||
|
<value>Notification de suppression envoyee</value>
|
||||||
|
</data>
|
||||||
|
<data name="MessageInvitationSent" xml:space="preserve">
|
||||||
|
<value>Invitation envoyee</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
@@ -204,16 +204,24 @@ public abstract class EGDbContextBase : DbContext
|
|||||||
|
|
||||||
#region Trigger
|
#region Trigger
|
||||||
// Configure entities to handle database triggers
|
// Configure entities to handle database triggers
|
||||||
void AddTrigger<T>() where T : class => _triggers
|
void AddTrigger<T>() where T : class
|
||||||
.Where(t => t.Key == typeof(T).Name)
|
{
|
||||||
.SelectMany(t => t.Value)
|
var entityName = typeof(T).Name;
|
||||||
.ForEach(tName =>
|
|
||||||
|
var configuredTriggerNames = _triggers
|
||||||
|
.Where(t => t.Key == entityName)
|
||||||
|
.SelectMany(t => t.Value)
|
||||||
|
.Distinct()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var triggerName in configuredTriggerNames)
|
||||||
{
|
{
|
||||||
#if NET
|
#if NET
|
||||||
modelBuilder.Entity<T>().ToTable(tb => tb.HasTrigger(tName));
|
modelBuilder.Entity<T>().ToTable(tb => tb.HasTrigger(triggerName));
|
||||||
#endif
|
#endif
|
||||||
_logger?.LogInformation("Trigger '{triggerName}' has been added to the '{entityName}' entity.", tName, typeof(T).Name);
|
_logger?.LogInformation("Trigger '{triggerName}' has been added to the '{entityName}' entity.", triggerName, entityName);
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: call add trigger methods with attributes and reflection
|
// TODO: call add trigger methods with attributes and reflection
|
||||||
AddTrigger<Config>();
|
AddTrigger<Config>();
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
@using EnvelopeGenerator.Server.Client.Services
|
||||||
|
@using EnvelopeGenerator.Application.Resources
|
||||||
|
@using Microsoft.Extensions.Localization
|
||||||
|
@rendermode InteractiveWebAssembly
|
||||||
|
|
||||||
|
@inject LocalizationService LocalizationService
|
||||||
|
@inject NavigationManager Navigation
|
||||||
|
@inject IStringLocalizer<Resource> UiLocalizer
|
||||||
|
|
||||||
|
<div class="sf-lang-combo">
|
||||||
|
<button type="button"
|
||||||
|
class="sf-lang-trigger"
|
||||||
|
@onclick="ToggleMenu"
|
||||||
|
aria-label="@L("Language", "Language")"
|
||||||
|
aria-expanded="@_isOpen">
|
||||||
|
<span class="fi @_selected.FlagClass"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
@if (_isOpen)
|
||||||
|
{
|
||||||
|
<button type="button"
|
||||||
|
class="sf-lang-backdrop"
|
||||||
|
aria-label="@L("CloseLanguageMenu", "Close language menu")"
|
||||||
|
@onclick="CloseMenu">
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="sf-lang-menu" role="listbox" aria-label="@L("Languages", "Languages")">
|
||||||
|
@foreach (var option in _options)
|
||||||
|
{
|
||||||
|
<button type="button"
|
||||||
|
class="sf-lang-option @(_selected.Code == option.Code ? "sf-lang-option--active" : string.Empty)"
|
||||||
|
role="option"
|
||||||
|
aria-selected="@(_selected.Code == option.Code)"
|
||||||
|
@onmousedown:preventDefault="true"
|
||||||
|
@onclick="() => SelectLanguageAsync(option)">
|
||||||
|
<span class="fi @option.FlagClass"></span>
|
||||||
|
<span>@option.ShortName</span>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private readonly IReadOnlyList<LanguageOption> _options =
|
||||||
|
[
|
||||||
|
new("de-DE", "fi-de", "DE"),
|
||||||
|
new("en-US", "fi-us", "EN"),
|
||||||
|
new("fr-FR", "fi-fr", "FR")
|
||||||
|
];
|
||||||
|
|
||||||
|
private LanguageOption _selected = new("de-DE", "fi-de", "DE");
|
||||||
|
private bool _isOpen;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
var currentLanguage = await LocalizationService.GetLanguageAsync();
|
||||||
|
_selected = _options.FirstOrDefault(x => x.Code.Equals(currentLanguage, StringComparison.OrdinalIgnoreCase))
|
||||||
|
?? _options[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleMenu()
|
||||||
|
{
|
||||||
|
_isOpen = !_isOpen;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CloseMenu()
|
||||||
|
{
|
||||||
|
_isOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SelectLanguageAsync(LanguageOption option)
|
||||||
|
{
|
||||||
|
if (_selected.Code.Equals(option.Code, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
_isOpen = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_selected = option;
|
||||||
|
_isOpen = false;
|
||||||
|
await LocalizationService.SetLanguageAsync(option.Code);
|
||||||
|
Navigation.NavigateTo(Navigation.Uri, forceLoad: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed record LanguageOption(string Code, string FlagClass, string ShortName);
|
||||||
|
|
||||||
|
string L(string key, string fallback)
|
||||||
|
{
|
||||||
|
var value = UiLocalizer[key];
|
||||||
|
return value.ResourceNotFound ? fallback : value.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,14 @@
|
|||||||
@using EnvelopeGenerator.Server.Client.Services
|
@using EnvelopeGenerator.Server.Client.Services
|
||||||
|
@using EnvelopeGenerator.Application.Resources
|
||||||
|
@using Microsoft.AspNetCore.Components.Routing
|
||||||
|
@using Microsoft.Extensions.Localization
|
||||||
|
@using System.Globalization
|
||||||
@inherits LayoutComponentBase
|
@inherits LayoutComponentBase
|
||||||
@implements IDisposable
|
@implements IDisposable
|
||||||
|
|
||||||
@inject ThemeService ThemeService
|
@inject ThemeService ThemeService
|
||||||
|
@inject IStringLocalizer<Resource> UiLocalizer
|
||||||
|
@inject NavigationManager Navigation
|
||||||
|
|
||||||
<HeadContent>
|
<HeadContent>
|
||||||
<link href="@ThemeService.ThemeCssHref" rel="stylesheet" />
|
<link href="@ThemeService.ThemeCssHref" rel="stylesheet" />
|
||||||
@@ -18,10 +24,13 @@
|
|||||||
<div class="receiver-footer__left">
|
<div class="receiver-footer__left">
|
||||||
<span>© SignFlow 2023-2024 <a href="https://digitaldata.works" target="_blank" rel="noopener">Digital Data GmbH</a></span>
|
<span>© SignFlow 2023-2024 <a href="https://digitaldata.works" target="_blank" rel="noopener">Digital Data GmbH</a></span>
|
||||||
<span class="receiver-footer__sep">|</span>
|
<span class="receiver-footer__sep">|</span>
|
||||||
<a href="docs/privacy-policy.de-DE.html" target="_blank" rel="noopener">Datenschutz</a>
|
<a href="@GetPrivacyPolicyPath()" target="_blank" rel="noopener">@L("PrivacyPolicy", "Datenschutz")</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ThemeControls />
|
<div class="receiver-footer__right">
|
||||||
|
<LanguageControls />
|
||||||
|
<ThemeControls />
|
||||||
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -32,6 +41,7 @@
|
|||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
|
Navigation.LocationChanged += OnLocationChanged;
|
||||||
await FontLoader.LoadFonts(HttpClientFactory, ["opensans.ttf"]);
|
await FontLoader.LoadFonts(HttpClientFactory, ["opensans.ttf"]);
|
||||||
_themeChangeHandler = () => InvokeAsync(StateHasChanged);
|
_themeChangeHandler = () => InvokeAsync(StateHasChanged);
|
||||||
ThemeService.OnChange += _themeChangeHandler;
|
ThemeService.OnChange += _themeChangeHandler;
|
||||||
@@ -39,5 +49,31 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
=> ThemeService.OnChange -= _themeChangeHandler;
|
{
|
||||||
|
ThemeService.OnChange -= _themeChangeHandler;
|
||||||
|
Navigation.LocationChanged -= OnLocationChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLocationChanged(object? sender, LocationChangedEventArgs e)
|
||||||
|
=> _ = InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
|
private string GetPrivacyPolicyPath()
|
||||||
|
{
|
||||||
|
var current = CultureInfo.CurrentUICulture.Name;
|
||||||
|
var code = current switch
|
||||||
|
{
|
||||||
|
"de-DE" => "de-DE",
|
||||||
|
"en-US" => "en-US",
|
||||||
|
"fr-FR" => "fr-FR",
|
||||||
|
_ => "de-DE"
|
||||||
|
};
|
||||||
|
|
||||||
|
return $"docs/privacy-policy.{code}.html";
|
||||||
|
}
|
||||||
|
|
||||||
|
string L(string key, string fallback)
|
||||||
|
{
|
||||||
|
var value = UiLocalizer[key];
|
||||||
|
return value.ResourceNotFound ? fallback : value.Value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace EnvelopeGenerator.Server.Client.Models;
|
||||||
|
|
||||||
|
public sealed record SignatureSubmitDto
|
||||||
|
{
|
||||||
|
public required int Id { get; init; }
|
||||||
|
public required string DataUrl { get; init; }
|
||||||
|
public required string FullName { get; init; }
|
||||||
|
public string? Position { get; init; }
|
||||||
|
public required string Place { get; init; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
@page "/envelope/signed-result"
|
||||||
|
@rendermode InteractiveWebAssembly
|
||||||
|
@using EnvelopeGenerator.Application.Resources
|
||||||
|
@using Microsoft.Extensions.Localization
|
||||||
|
@inject IStringLocalizer<Resource> UiLocalizer
|
||||||
|
|
||||||
|
<div class="receiver-signed-result-page">
|
||||||
|
<div class="receiver-signed-result-card">
|
||||||
|
<div class="receiver-signed-result-icon" aria-hidden="true">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="72" height="72" fill="currentColor" viewBox="0 0 16 16">
|
||||||
|
<path d="M2.5 8a5.5 5.5 0 0 1 8.25-4.764.5.5 0 0 0 .5-.866A6.5 6.5 0 1 0 14.5 8a.5.5 0 0 0-1 0 5.5 5.5 0 1 1-11 0z" />
|
||||||
|
<path d="M15.354 3.354a.5.5 0 0 0-.708-.708L8 9.293 5.354 6.646a.5.5 0 1 0-.708.708l3 3a.5.5 0 0 0 .708 0l7-7z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 class="receiver-signed-result-title">@L("DocumentSuccessfullySigned", "Dokument erfolgreich unterzeichnet")</h1>
|
||||||
|
<p class="receiver-signed-result-message">
|
||||||
|
@L("DocumentSignedConfirmationMessage", "Vielen Dank. Ihre Unterschrift wurde erfolgreich gespeichert und an die beteiligten Parteien übermittelt.")
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
string L(string key, string fallback)
|
||||||
|
{
|
||||||
|
var value = UiLocalizer[key];
|
||||||
|
return value.ResourceNotFound ? fallback : value.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
@page "/"
|
@page "/"
|
||||||
|
@using EnvelopeGenerator.Application.Resources
|
||||||
|
@using Microsoft.Extensions.Localization
|
||||||
@inject IJSRuntime JS
|
@inject IJSRuntime JS
|
||||||
@inject NavigationManager Navigation
|
@inject NavigationManager Navigation
|
||||||
@inject EnvelopeGenerator.Server.Client.Services.CustomImagesService CustomImagesService
|
@inject EnvelopeGenerator.Server.Client.Services.CustomImagesService CustomImagesService
|
||||||
|
@inject IStringLocalizer<Resource> UiLocalizer
|
||||||
@rendermode InteractiveWebAssembly
|
@rendermode InteractiveWebAssembly
|
||||||
|
|
||||||
<div class="home-page-wrapper">
|
<div class="home-page-wrapper">
|
||||||
@@ -24,7 +27,7 @@
|
|||||||
}
|
}
|
||||||
<div>
|
<div>
|
||||||
<h1 class="home-hero-header__title">SignFlow</h1>
|
<h1 class="home-hero-header__title">SignFlow</h1>
|
||||||
<p class="home-hero-header__subtitle">Willkommen im eSign-Portal</p>
|
<p class="home-hero-header__subtitle">@L("Index_Subtitle", "Willkommen im eSign-Portal")</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -43,7 +46,7 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16">
|
||||||
<path d="M0 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V4Zm2-1a1 1 0 0 0-1 1v.217l7 4.2 7-4.2V4a1 1 0 0 0-1-1H2Zm13 2.383-4.708 2.825L15 11.105V5.383Zm-.034 6.876-5.64-3.471L8 9.583l-1.326-.795-5.64 3.47A1 1 0 0 0 2 13h12a1 1 0 0 0 .966-.741ZM1 11.105l4.708-2.897L1 5.383v5.722Z"/>
|
<path d="M0 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V4Zm2-1a1 1 0 0 0-1 1v.217l7 4.2 7-4.2V4a1 1 0 0 0-1-1H2Zm13 2.383-4.708 2.825L15 11.105V5.383Zm-.034 6.876-5.64-3.471L8 9.583l-1.326-.795-5.64 3.47A1 1 0 0 0 2 13h12a1 1 0 0 0 .966-.741ZM1 11.105l4.708-2.897L1 5.383v5.722Z"/>
|
||||||
</svg>
|
</svg>
|
||||||
Zur Umschlag-Übersicht
|
@L("Index_ToEnvelopeOverview", "Zur Umschlag-Übersicht")
|
||||||
<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 fill-rule="evenodd" d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z"/>
|
<path fill-rule="evenodd" d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z"/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -54,20 +57,20 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" fill="currentColor" class="me-1" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" fill="currentColor" class="me-1" viewBox="0 0 16 16">
|
||||||
<path d="M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2z"/>
|
<path d="M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2z"/>
|
||||||
</svg>
|
</svg>
|
||||||
Sicherer Zugang
|
@L("Index_SecureAccess", "Sicherer Zugang")
|
||||||
</div>
|
</div>
|
||||||
<div class="home-feature-badge">
|
<div class="home-feature-badge">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" fill="currentColor" class="me-1" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" fill="currentColor" class="me-1" viewBox="0 0 16 16">
|
||||||
<path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"/>
|
<path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"/>
|
||||||
</svg>
|
</svg>
|
||||||
Digitale Unterschrift
|
@L("Index_DigitalSignature", "Digitale Unterschrift")
|
||||||
</div>
|
</div>
|
||||||
<div class="home-feature-badge">
|
<div class="home-feature-badge">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" fill="currentColor" class="me-1" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" fill="currentColor" class="me-1" viewBox="0 0 16 16">
|
||||||
<path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/>
|
<path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/>
|
||||||
<path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/>
|
<path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/>
|
||||||
</svg>
|
</svg>
|
||||||
PDF-Export
|
@L("Index_PdfExport", "PDF-Export")
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -80,10 +83,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private const string HomePageDescription =
|
private string HomePageDescription => L(
|
||||||
"Das digitale Unterschriftenportal ist eine Plattform, die entwickelt wurde, um Ihre Dokumente sicher zu unterschreiben und zu verwalten. " +
|
"HomePageDescription",
|
||||||
"Mit seiner benutzerfreundlichen Oberfläche können Sie Ihre Dokumente schnell hochladen, die Unterschriftsprozesse verfolgen und Ihre digitalen Unterschriftenanwendungen einfach durchführen. " +
|
"Das digitale Unterschriftenportal ist eine Plattform, die entwickelt wurde, um Ihre Dokumente sicher zu unterschreiben und zu verwalten. Mit seiner benutzerfreundlichen Oberfläche können Sie Ihre Dokumente schnell hochladen, die Unterschriftsprozesse verfolgen und Ihre digitalen Unterschriftenanwendungen einfach durchführen. Dieses Portal beschleunigt Ihren Arbeitsablauf mit rechtlich gültigen Unterschriften und erhöht gleichzeitig die Sicherheit Ihrer Dokumente.");
|
||||||
"Dieses Portal beschleunigt Ihren Arbeitsablauf mit rechtlich gültigen Unterschriften und erhöht gleichzeitig die Sicherheit Ihrer Dokumente.";
|
|
||||||
|
|
||||||
private EnvelopeGenerator.Server.Client.Services.CustomImageDto? _appLogo;
|
private EnvelopeGenerator.Server.Client.Services.CustomImageDto? _appLogo;
|
||||||
|
|
||||||
@@ -99,4 +101,10 @@
|
|||||||
if (!firstRender) return;
|
if (!firstRender) return;
|
||||||
await JS.InvokeVoidAsync("receiverSignature.startTyped", "home-description", HomePageDescription, 15);
|
await JS.InvokeVoidAsync("receiverSignature.startTyped", "home-description", HomePageDescription, 15);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string L(string key, string fallback)
|
||||||
|
{
|
||||||
|
var value = UiLocalizer[key];
|
||||||
|
return value.ResourceNotFound ? fallback : value.Value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
@page "/envelope/login/{EnvelopeKey}"
|
@page "/envelope/login/{EnvelopeKey}"
|
||||||
@rendermode InteractiveWebAssembly
|
@rendermode InteractiveWebAssembly
|
||||||
@using EnvelopeGenerator.Server.Client.Services
|
@using EnvelopeGenerator.Server.Client.Services
|
||||||
|
@using EnvelopeGenerator.Application.Resources
|
||||||
|
@using Microsoft.Extensions.Localization
|
||||||
@inject AuthService AuthService
|
@inject AuthService AuthService
|
||||||
@inject NavigationManager Navigation
|
@inject NavigationManager Navigation
|
||||||
|
@inject IStringLocalizer<Resource> UiLocalizer
|
||||||
|
|
||||||
<div class="login-page-wrapper d-flex align-items-center justify-content-center min-vh-100">
|
<div class="login-page-wrapper d-flex align-items-center justify-content-center min-vh-100">
|
||||||
<div class="login-card card shadow border-0" style="max-width: 440px; width: 100%;">
|
<div class="login-card card shadow border-0" style="max-width: 440px; width: 100%;">
|
||||||
@@ -13,14 +16,14 @@
|
|||||||
<path d="M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2z" />
|
<path d="M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2z" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h5 class="mb-0 fw-semibold">Dokument öffnen</h5>
|
<h5 class="mb-0 fw-semibold">@L("OpenDocument", "Dokument öffnen")</h5>
|
||||||
<p class="mb-0 mt-1 opacity-75" style="font-size: 0.85rem;">Sicherer Zugang mit Zugangscode</p>
|
<p class="mb-0 mt-1 opacity-75" style="font-size: 0.85rem;">@L("ReceiverLogin_Subtitle", "Sicherer Zugang mit Zugangscode")</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
|
|
||||||
<p class="text-muted mb-4" style="font-size: 0.875rem; line-height: 1.5;">
|
<p class="text-muted mb-4" style="font-size: 0.875rem; line-height: 1.5;">
|
||||||
Bitte geben Sie den Zugangscode ein, den Sie per E-Mail erhalten haben, um das Dokument sicher zu öffnen.
|
@L("ReceiverLogin_Description", "Bitte geben Sie den Zugangscode ein, den Sie per E-Mail erhalten haben, um das Dokument sicher zu öffnen.")
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@if (LoginResult == EnvelopeLoginResult.NotFound)
|
@if (LoginResult == EnvelopeLoginResult.NotFound)
|
||||||
@@ -30,8 +33,8 @@
|
|||||||
<path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z" />
|
<path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z" />
|
||||||
</svg>
|
</svg>
|
||||||
<div>
|
<div>
|
||||||
<strong>Dokument nicht gefunden.</strong><br />
|
<strong>@L("ReceiverLogin_NotFound_Title", "Dokument nicht gefunden.")</strong><br />
|
||||||
<span style="font-size:0.85rem;">Der angegebene Zugangscode konnte keinem Dokument zugeordnet werden. Bitte prüfen Sie den Link in Ihrer E-Mail.</span>
|
<span style="font-size:0.85rem;">@L("ReceiverLogin_NotFound_Body", "Der angegebene Zugangscode konnte keinem Dokument zugeordnet werden. Bitte prüfen Sie den Link in Ihrer E-Mail.")</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -42,8 +45,8 @@
|
|||||||
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z" />
|
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z" />
|
||||||
</svg>
|
</svg>
|
||||||
<div>
|
<div>
|
||||||
<strong>Ungültiger Zugangscode.</strong><br />
|
<strong>@L("ReceiverLogin_InvalidCode_Title", "Ungültiger Zugangscode.")</strong><br />
|
||||||
<span style="font-size:0.85rem;">Der eingegebene Code ist falsch. Bitte versuchen Sie es erneut.</span>
|
<span style="font-size:0.85rem;">@L("ReceiverLogin_InvalidCode_Body", "Der eingegebene Code ist falsch. Bitte versuchen Sie es erneut.")</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -55,15 +58,15 @@
|
|||||||
<path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z" />
|
<path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z" />
|
||||||
</svg>
|
</svg>
|
||||||
<div>
|
<div>
|
||||||
<strong>Serverfehler.</strong><br />
|
<strong>@L("ServerError", "Serverfehler.")</strong><br />
|
||||||
<span style="font-size:0.85rem;">Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.</span>
|
<span style="font-size:0.85rem;">@L("UnexpectedErrorTryAgainLater", "Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.")</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label class="form-label fw-medium" for="login-access-code">
|
<label class="form-label fw-medium" for="login-access-code">
|
||||||
Zugangscode
|
@L("AccessCode", "Zugangscode")
|
||||||
<span class="text-danger ms-1">*</span>
|
<span class="text-danger ms-1">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
@@ -75,7 +78,7 @@
|
|||||||
<input id="login-access-code"
|
<input id="login-access-code"
|
||||||
type="@(ShowCode ? "text" : "password")"
|
type="@(ShowCode ? "text" : "password")"
|
||||||
class="form-control border-start-0 border-end-0 @(LoginResult == EnvelopeLoginResult.InvalidCode ? "is-invalid" : null)"
|
class="form-control border-start-0 border-end-0 @(LoginResult == EnvelopeLoginResult.InvalidCode ? "is-invalid" : null)"
|
||||||
placeholder="Zugangscode eingeben"
|
placeholder="@L("ReceiverLogin_AccessCode_Placeholder", "Zugangscode eingeben")"
|
||||||
@bind="AccessCode"
|
@bind="AccessCode"
|
||||||
@bind:event="oninput"
|
@bind:event="oninput"
|
||||||
@onkeydown="OnKeyDownAsync"
|
@onkeydown="OnKeyDownAsync"
|
||||||
@@ -113,21 +116,21 @@
|
|||||||
@if (IsLoading)
|
@if (IsLoading)
|
||||||
{
|
{
|
||||||
<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
|
<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
|
||||||
<span>Überprüfen …</span>
|
<span>@L("VerifyInProgress", "Überprüfen …")</span>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="me-2" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="me-2" viewBox="0 0 16 16">
|
||||||
<path d="M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2z" />
|
<path d="M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2z" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>Dokument öffnen</span>
|
<span>@L("OpenDocument", "Dokument öffnen")</span>
|
||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-footer text-center text-muted py-3 border-0 bg-transparent" style="font-size: 0.78rem;">
|
<div class="card-footer text-center text-muted py-3 border-0 bg-transparent" style="font-size: 0.78rem;">
|
||||||
Bei Problemen wenden Sie sich bitte an den Absender des Dokuments.
|
@L("ReceiverLogin_FooterHelp", "Bei Problemen wenden Sie sich bitte an den Absender des Dokuments.")
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -141,6 +144,14 @@
|
|||||||
bool IsLoading;
|
bool IsLoading;
|
||||||
EnvelopeLoginResult? LoginResult;
|
EnvelopeLoginResult? LoginResult;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(EnvelopeKey))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_ = await AuthService.RequestEnvelopeReceiverAccessCodeAsync(EnvelopeKey);
|
||||||
|
}
|
||||||
|
|
||||||
async Task OnKeyDownAsync(Microsoft.AspNetCore.Components.Web.KeyboardEventArgs e)
|
async Task OnKeyDownAsync(Microsoft.AspNetCore.Components.Web.KeyboardEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.Key == "Enter")
|
if (e.Key == "Enter")
|
||||||
@@ -167,5 +178,11 @@
|
|||||||
IsLoading = false;
|
IsLoading = false;
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string L(string key, string fallback)
|
||||||
|
{
|
||||||
|
var value = UiLocalizer[key];
|
||||||
|
return value.ResourceNotFound ? fallback : value.Value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
@page "/sender/login"
|
@page "/sender/login"
|
||||||
@rendermode InteractiveWebAssembly
|
@rendermode InteractiveWebAssembly
|
||||||
@using EnvelopeGenerator.Server.Client.Services
|
@using EnvelopeGenerator.Server.Client.Services
|
||||||
|
@using EnvelopeGenerator.Application.Resources
|
||||||
|
@using Microsoft.Extensions.Localization
|
||||||
@inject AuthService AuthService
|
@inject AuthService AuthService
|
||||||
@inject NavigationManager Navigation
|
@inject NavigationManager Navigation
|
||||||
|
@inject IStringLocalizer<Resource> UiLocalizer
|
||||||
|
|
||||||
<div class="login-page-wrapper d-flex align-items-center justify-content-center min-vh-100">
|
<div class="login-page-wrapper d-flex align-items-center justify-content-center min-vh-100">
|
||||||
<div class="login-card card shadow border-0" style="max-width: 440px; width: 100%;">
|
<div class="login-card card shadow border-0" style="max-width: 440px; width: 100%;">
|
||||||
@@ -14,14 +17,14 @@
|
|||||||
<path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1z"/>
|
<path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1z"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h5 class="mb-0 fw-semibold">Sender Anmeldung</h5>
|
<h5 class="mb-0 fw-semibold">@L("SenderLogin_Title", "Sender Anmeldung")</h5>
|
||||||
<p class="mb-0 mt-1 opacity-75" style="font-size: 0.85rem;">Sicherer Zugang zum Sender-Dashboard</p>
|
<p class="mb-0 mt-1 opacity-75" style="font-size: 0.85rem;">@L("SenderLogin_Subtitle", "Sicherer Zugang zum Sender-Dashboard")</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
|
|
||||||
<p class="text-muted mb-4" style="font-size: 0.875rem; line-height: 1.5;">
|
<p class="text-muted mb-4" style="font-size: 0.875rem; line-height: 1.5;">
|
||||||
Bitte melden Sie sich mit Ihren Zugangsdaten an, um auf das Sender-Dashboard zuzugreifen.
|
@L("SenderLogin_Description", "Bitte melden Sie sich mit Ihren Zugangsdaten an, um auf das Sender-Dashboard zuzugreifen.")
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@if (LoginResult == SenderLoginResult.InvalidCredentials) {
|
@if (LoginResult == SenderLoginResult.InvalidCredentials) {
|
||||||
@@ -30,8 +33,8 @@
|
|||||||
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z"/>
|
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z"/>
|
||||||
</svg>
|
</svg>
|
||||||
<div>
|
<div>
|
||||||
<strong>Ungültige Anmeldedaten.</strong><br />
|
<strong>@L("SenderLogin_InvalidCredentials_Title", "Ungültige Anmeldedaten.")</strong><br />
|
||||||
<span style="font-size:0.85rem;">Benutzername oder Passwort ist falsch. Bitte versuchen Sie es erneut.</span>
|
<span style="font-size:0.85rem;">@L("SenderLogin_InvalidCredentials_Body", "Benutzername oder Passwort ist falsch. Bitte versuchen Sie es erneut.")</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
} else if (LoginResult == SenderLoginResult.Error) {
|
} else if (LoginResult == SenderLoginResult.Error) {
|
||||||
@@ -41,15 +44,15 @@
|
|||||||
<path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z"/>
|
<path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z"/>
|
||||||
</svg>
|
</svg>
|
||||||
<div>
|
<div>
|
||||||
<strong>Serverfehler.</strong><br />
|
<strong>@L("ServerError", "Serverfehler.")</strong><br />
|
||||||
<span style="font-size:0.85rem;">Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.</span>
|
<span style="font-size:0.85rem;">@L("UnexpectedErrorTryAgainLater", "Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.")</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label fw-medium" for="login-username">
|
<label class="form-label fw-medium" for="login-username">
|
||||||
Benutzername
|
@L("UserName", "Benutzername")
|
||||||
<span class="text-danger ms-1">*</span>
|
<span class="text-danger ms-1">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
@@ -61,7 +64,7 @@
|
|||||||
<input id="login-username"
|
<input id="login-username"
|
||||||
type="text"
|
type="text"
|
||||||
class="form-control @(LoginResult == SenderLoginResult.InvalidCredentials ? "is-invalid" : null)"
|
class="form-control @(LoginResult == SenderLoginResult.InvalidCredentials ? "is-invalid" : null)"
|
||||||
placeholder="Benutzername eingeben"
|
placeholder="@L("SenderLogin_Username_Placeholder", "Benutzername eingeben")"
|
||||||
@bind="Username"
|
@bind="Username"
|
||||||
@bind:event="oninput"
|
@bind:event="oninput"
|
||||||
@onkeydown="OnKeyDownAsync"
|
@onkeydown="OnKeyDownAsync"
|
||||||
@@ -72,7 +75,7 @@
|
|||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label class="form-label fw-medium" for="login-password">
|
<label class="form-label fw-medium" for="login-password">
|
||||||
Passwort
|
@L("Password", "Passwort")
|
||||||
<span class="text-danger ms-1">*</span>
|
<span class="text-danger ms-1">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
@@ -84,7 +87,7 @@
|
|||||||
<input id="login-password"
|
<input id="login-password"
|
||||||
type="@(ShowPassword ? "text" : "password")"
|
type="@(ShowPassword ? "text" : "password")"
|
||||||
class="form-control border-start-0 border-end-0 @(LoginResult == SenderLoginResult.InvalidCredentials ? "is-invalid" : null)"
|
class="form-control border-start-0 border-end-0 @(LoginResult == SenderLoginResult.InvalidCredentials ? "is-invalid" : null)"
|
||||||
placeholder="Passwort eingeben"
|
placeholder="@L("SenderLogin_Password_Placeholder", "Passwort eingeben")"
|
||||||
@bind="Password"
|
@bind="Password"
|
||||||
@bind:event="oninput"
|
@bind:event="oninput"
|
||||||
@onkeydown="OnKeyDownAsync"
|
@onkeydown="OnKeyDownAsync"
|
||||||
@@ -118,20 +121,20 @@
|
|||||||
disabled="@(IsLoading || string.IsNullOrWhiteSpace(Username) || string.IsNullOrWhiteSpace(Password))">
|
disabled="@(IsLoading || string.IsNullOrWhiteSpace(Username) || string.IsNullOrWhiteSpace(Password))">
|
||||||
@if (IsLoading) {
|
@if (IsLoading) {
|
||||||
<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
|
<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
|
||||||
<span>Anmelden …</span>
|
<span>@L("LoginInProgress", "Anmelden …")</span>
|
||||||
} else {
|
} else {
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="me-2" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="me-2" viewBox="0 0 16 16">
|
||||||
<path fill-rule="evenodd" d="M10 3.5a.5.5 0 0 0-.5-.5h-8a.5.5 0 0 0-.5.5v9a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5v-2a.5.5 0 0 1 1 0v2A1.5 1.5 0 0 1 9.5 14h-8A1.5 1.5 0 0 1 0 12.5v-9A1.5 1.5 0 0 1 1.5 2h8A1.5 1.5 0 0 1 11 3.5v2a.5.5 0 0 1-1 0v-2z"/>
|
<path fill-rule="evenodd" d="M10 3.5a.5.5 0 0 0-.5-.5h-8a.5.5 0 0 0-.5.5v9a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5v-2a.5.5 0 0 1 1 0v2A1.5 1.5 0 0 1 9.5 14h-8A1.5 1.5 0 0 1 0 12.5v-9A1.5 1.5 0 0 1 1.5 2h8A1.5 1.5 0 0 1 11 3.5v2a.5.5 0 0 1-1 0v-2z"/>
|
||||||
<path fill-rule="evenodd" d="M4.146 8.354a.5.5 0 0 1 0-.708l3-3a.5.5 0 1 1 .708.708L5.707 7.5H14.5a.5.5 0 0 1 0 1H5.707l2.147 2.146a.5.5 0 0 1-.708.708l-3-3z"/>
|
<path fill-rule="evenodd" d="M4.146 8.354a.5.5 0 0 1 0-.708l3-3a.5.5 0 1 1 .708.708L5.707 7.5H14.5a.5.5 0 0 1 0 1H5.707l2.147 2.146a.5.5 0 0 1-.708.708l-3-3z"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span>Anmelden</span>
|
<span>@L("Login", "Anmelden")</span>
|
||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-footer text-center text-muted py-3 border-0 bg-transparent" style="font-size: 0.78rem;">
|
<div class="card-footer text-center text-muted py-3 border-0 bg-transparent" style="font-size: 0.78rem;">
|
||||||
Bei Problemen wenden Sie sich bitte an den Administrator.
|
@L("SenderLogin_FooterHelp", "Bei Problemen wenden Sie sich bitte an den Administrator.")
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -167,4 +170,10 @@
|
|||||||
IsLoading = false;
|
IsLoading = false;
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string L(string key, string fallback)
|
||||||
|
{
|
||||||
|
var value = UiLocalizer[key];
|
||||||
|
return value.ResourceNotFound ? fallback : value.Value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,17 @@ using DevExpress.Blazor.Reporting;
|
|||||||
using DevExpress.XtraReports.Web.Extensions;
|
using DevExpress.XtraReports.Web.Extensions;
|
||||||
using DevExpress.DataAccess.Web;
|
using DevExpress.DataAccess.Web;
|
||||||
using DevExpress.XtraReports.Services;
|
using DevExpress.XtraReports.Services;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||||
|
|
||||||
|
var initialCulture = await ResolveInitialCultureAsync(builder.HostEnvironment.BaseAddress);
|
||||||
|
var cultureInfo = new CultureInfo(initialCulture);
|
||||||
|
CultureInfo.DefaultThreadCurrentCulture = cultureInfo;
|
||||||
|
CultureInfo.DefaultThreadCurrentUICulture = cultureInfo;
|
||||||
|
|
||||||
|
builder.Services.AddLocalization();
|
||||||
|
|
||||||
// Named HttpClient for API calls (both for services and DevExpress components)
|
// Named HttpClient for API calls (both for services and DevExpress components)
|
||||||
builder.Services.AddHttpClient("EnvelopeGenerator.Server", client =>
|
builder.Services.AddHttpClient("EnvelopeGenerator.Server", client =>
|
||||||
{
|
{
|
||||||
@@ -33,6 +41,7 @@ builder.Services.AddScoped<SignatureService>();
|
|||||||
builder.Services.AddScoped<SignatureCacheService>();
|
builder.Services.AddScoped<SignatureCacheService>();
|
||||||
builder.Services.AddSingleton<AppVersionService>();
|
builder.Services.AddSingleton<AppVersionService>();
|
||||||
builder.Services.AddScoped<EnvelopeService>();
|
builder.Services.AddScoped<EnvelopeService>();
|
||||||
|
builder.Services.AddScoped<LocalizationService>();
|
||||||
|
|
||||||
// UI Preferences & Theme
|
// UI Preferences & Theme
|
||||||
builder.Services.AddScoped<UserPreferencesService>();
|
builder.Services.AddScoped<UserPreferencesService>();
|
||||||
@@ -66,3 +75,23 @@ ReportStorageWebExtension.RegisterExtensionGlobal(new InMemoryReportStorageWebEx
|
|||||||
var host = builder.Build();
|
var host = builder.Build();
|
||||||
await FontLoader.LoadFonts(host.Services.GetRequiredService<IHttpClientFactory>(), new List<string> { "opensans.ttf" });
|
await FontLoader.LoadFonts(host.Services.GetRequiredService<IHttpClientFactory>(), new List<string> { "opensans.ttf" });
|
||||||
await host.RunAsync();
|
await host.RunAsync();
|
||||||
|
|
||||||
|
static async Task<string> ResolveInitialCultureAsync(string baseAddress)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var http = new HttpClient { BaseAddress = new Uri(baseAddress) };
|
||||||
|
var response = await http.GetAsync("/api/Localization/lang");
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
return "de-DE";
|
||||||
|
|
||||||
|
var value = await response.Content.ReadAsStringAsync();
|
||||||
|
var culture = value.Trim().Trim('"');
|
||||||
|
return string.IsNullOrWhiteSpace(culture) ? "de-DE" : culture;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return "de-DE";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -33,6 +33,20 @@ public class AuthService(IHttpClientFactory httpClientFactory)
|
|||||||
return response.StatusCode == HttpStatusCode.OK;
|
return response.StatusCode == HttpStatusCode.OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggers access code e-mail request flow for the receiver login page.
|
||||||
|
/// Calls POST /api/Auth/envelope-receiver/{key}/request-access-code.
|
||||||
|
/// </summary>
|
||||||
|
public async Task<bool> RequestEnvelopeReceiverAccessCodeAsync(string envelopeKey, CancellationToken cancel = default)
|
||||||
|
{
|
||||||
|
using var http = CreateDefaultClient();
|
||||||
|
var response = await http.PostAsync(
|
||||||
|
$"/api/Auth/envelope-receiver/{Uri.EscapeDataString(envelopeKey)}/request-access-code",
|
||||||
|
null,
|
||||||
|
cancel);
|
||||||
|
return response.IsSuccessStatusCode;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Submits the access code for the given envelope key.
|
/// Submits the access code for the given envelope key.
|
||||||
/// Calls POST /api/Auth/envelope-receiver/{key} with multipart/form-data.
|
/// Calls POST /api/Auth/envelope-receiver/{key} with multipart/form-data.
|
||||||
@@ -72,6 +86,18 @@ public class AuthService(IHttpClientFactory httpClientFactory)
|
|||||||
return response.IsSuccessStatusCode;
|
return response.IsSuccessStatusCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shares the current envelope in read-only mode for the specified receiver and expiration date.
|
||||||
|
/// Calls POST /api/readonly/{envelopeKey}.
|
||||||
|
/// </summary>
|
||||||
|
public async Task<bool> ShareEnvelopeReadOnlyAsync(string envelopeKey, string receiverMail, DateTime dateValid, CancellationToken cancel = default)
|
||||||
|
{
|
||||||
|
using var http = CreateDefaultClient();
|
||||||
|
var payload = new { receiverMail, dateValid };
|
||||||
|
var response = await http.PostAsJsonAsync($"/api/readonly/{Uri.EscapeDataString(envelopeKey)}", payload, cancel);
|
||||||
|
return response.IsSuccessStatusCode;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removes the per-envelope receiver cookie for the given envelope key.
|
/// Removes the per-envelope receiver cookie for the given envelope key.
|
||||||
/// Calls POST /api/auth/logout/envelope/{envelopeKey}.
|
/// Calls POST /api/auth/logout/envelope/{envelopeKey}.
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace EnvelopeGenerator.Server.Client.Services;
|
||||||
|
|
||||||
|
public class LocalizationService(IHttpClientFactory httpClientFactory)
|
||||||
|
{
|
||||||
|
private HttpClient CreateDefaultClient() => httpClientFactory.CreateClient("EnvelopeGenerator.Server");
|
||||||
|
|
||||||
|
public async Task<string> GetLanguageAsync(CancellationToken cancel = default)
|
||||||
|
{
|
||||||
|
using var http = CreateDefaultClient();
|
||||||
|
var response = await http.GetAsync("/api/Localization/lang", cancel);
|
||||||
|
|
||||||
|
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||||
|
return "de-DE";
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
return "de-DE";
|
||||||
|
|
||||||
|
var language = await response.Content.ReadAsStringAsync(cancel);
|
||||||
|
return string.IsNullOrWhiteSpace(language) ? "de-DE" : language.Trim('"');
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetLanguageAsync(string language, CancellationToken cancel = default)
|
||||||
|
{
|
||||||
|
using var http = CreateDefaultClient();
|
||||||
|
var response = await http.PostAsync($"/api/Localization/lang?language={Uri.EscapeDataString(language)}", null, cancel);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using EnvelopeGenerator.Application.Common.Dto;
|
||||||
using EnvelopeGenerator.Server.Client.Models;
|
using EnvelopeGenerator.Server.Client.Models;
|
||||||
|
|
||||||
namespace EnvelopeGenerator.Server.Client.Services;
|
namespace EnvelopeGenerator.Server.Client.Services;
|
||||||
@@ -21,4 +22,32 @@ public class SignatureService(IHttpClientFactory httpClientFactory)
|
|||||||
var result = await response.Content.ReadFromJsonAsync<List<SignatureDto>>(_jsonOptions, cancel);
|
var result = await response.Content.ReadFromJsonAsync<List<SignatureDto>>(_jsonOptions, cancel);
|
||||||
return result ?? [];
|
return result ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task SubmitAsync(string envelopeKey, IEnumerable<SignatureSubmitDto> signatures, CancellationToken cancel = default)
|
||||||
|
{
|
||||||
|
using var http = httpClientFactory.CreateClient("EnvelopeGenerator.Server");
|
||||||
|
|
||||||
|
var payload = signatures
|
||||||
|
.Select(signature => new Signature
|
||||||
|
{
|
||||||
|
Id = signature.Id,
|
||||||
|
DataUrl = signature.DataUrl,
|
||||||
|
FullName = signature.FullName,
|
||||||
|
Position = signature.Position,
|
||||||
|
Place = signature.Place
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var response = await http.PostAsJsonAsync(
|
||||||
|
$"/api/Signature/submit/{Uri.EscapeDataString(envelopeKey)}",
|
||||||
|
payload,
|
||||||
|
cancel);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var body = await response.Content.ReadAsStringAsync(cancel);
|
||||||
|
throw new HttpRequestException(
|
||||||
|
$"Failed to submit signature for envelope {envelopeKey}: {(int)response.StatusCode} {response.StatusCode} - {body}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<base href="/" />
|
<base href="/" />
|
||||||
<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
|
<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flag-icons@7.2.3/css/flag-icons.min.css" />
|
||||||
<link rel="stylesheet" href="css/app.css" />
|
<link rel="stylesheet" href="css/app.css" />
|
||||||
<link rel="stylesheet" href="css/envelope-viewer.css" />
|
<link rel="stylesheet" href="css/envelope-viewer.css" />
|
||||||
<link rel="stylesheet" href="EnvelopeGenerator.Server.styles.css" />
|
<link rel="stylesheet" href="EnvelopeGenerator.Server.styles.css" />
|
||||||
|
|||||||
@@ -5,11 +5,13 @@
|
|||||||
@using DevExpress.Blazor
|
@using DevExpress.Blazor
|
||||||
@using EnvelopeGenerator.Application.Common.Dto
|
@using EnvelopeGenerator.Application.Common.Dto
|
||||||
@using EnvelopeGenerator.Application.Envelopes.Commands
|
@using EnvelopeGenerator.Application.Envelopes.Commands
|
||||||
|
@using EnvelopeGenerator.Application.Resources
|
||||||
@using EnvelopeGenerator.Server.Client.Services
|
@using EnvelopeGenerator.Server.Client.Services
|
||||||
@using EnvelopeGenerator.Server.Services
|
@using EnvelopeGenerator.Server.Services
|
||||||
@using Microsoft.AspNetCore.WebUtilities
|
@using Microsoft.AspNetCore.WebUtilities
|
||||||
@using Microsoft.AspNetCore.Components.Forms
|
@using Microsoft.AspNetCore.Components.Forms
|
||||||
@using Microsoft.Extensions.Caching.Memory
|
@using Microsoft.Extensions.Caching.Memory
|
||||||
|
@using Microsoft.Extensions.Localization
|
||||||
@inject IJSRuntime JSRuntime
|
@inject IJSRuntime JSRuntime
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject AppVersionService AppVersion
|
@inject AppVersionService AppVersion
|
||||||
@@ -18,6 +20,7 @@
|
|||||||
@inject EnvelopeService EnvelopeService
|
@inject EnvelopeService EnvelopeService
|
||||||
@inject IEnvelopeAuthService EnvelopeAuthService
|
@inject IEnvelopeAuthService EnvelopeAuthService
|
||||||
@inject IMemoryCache MemoryCache
|
@inject IMemoryCache MemoryCache
|
||||||
|
@inject IStringLocalizer<Resource> UiLocalizer
|
||||||
|
|
||||||
<link href="_content/DevExpress.Blazor.Themes/blazing-berry.bs5.min.css" rel="stylesheet" />
|
<link href="_content/DevExpress.Blazor.Themes/blazing-berry.bs5.min.css" rel="stylesheet" />
|
||||||
<link href="_content/DevExpress.Blazor.Viewer/css/dx-blazor-viewer-components.bs5.css" rel="stylesheet" />
|
<link href="_content/DevExpress.Blazor.Viewer/css/dx-blazor-viewer-components.bs5.css" rel="stylesheet" />
|
||||||
@@ -41,12 +44,12 @@
|
|||||||
<div style="display: flex; flex-direction: column; gap: 0.2rem; flex: 1 1 180px; min-width: 140px;">
|
<div style="display: flex; flex-direction: column; gap: 0.2rem; flex: 1 1 180px; min-width: 140px;">
|
||||||
<label style="font-size: 0.65rem; font-weight: 600; color: #6b7280;
|
<label style="font-size: 0.65rem; font-weight: 600; color: #6b7280;
|
||||||
text-transform: uppercase; letter-spacing: 0.05em;">
|
text-transform: uppercase; letter-spacing: 0.05em;">
|
||||||
Titel <span style="color:#dc2626;">*</span>
|
@L("Title", "Titel") <span style="color:#dc2626;">*</span>
|
||||||
</label>
|
</label>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
value="@_envelopeTitle"
|
value="@_envelopeTitle"
|
||||||
@oninput="e => _envelopeTitle = e.Value?.ToString() ?? string.Empty"
|
@oninput="e => _envelopeTitle = e.Value?.ToString() ?? string.Empty"
|
||||||
placeholder="z.B. Arbeitsvertrag 2025"
|
placeholder='@L("SenderEditor_Title_Placeholder", "z.B. Arbeitsvertrag 2025")'
|
||||||
maxlength="120"
|
maxlength="120"
|
||||||
style="font-size: 0.78rem; font-weight: 500; color: #1f2937;
|
style="font-size: 0.78rem; font-weight: 500; color: #1f2937;
|
||||||
border: 1px solid #d1d5db; border-radius: 5px;
|
border: 1px solid #d1d5db; border-radius: 5px;
|
||||||
@@ -55,7 +58,7 @@
|
|||||||
@(_titleTouched && string.IsNullOrWhiteSpace(_envelopeTitle) ? "border-color:#dc2626;" : "")" />
|
@(_titleTouched && string.IsNullOrWhiteSpace(_envelopeTitle) ? "border-color:#dc2626;" : "")" />
|
||||||
@if (_titleTouched && string.IsNullOrWhiteSpace(_envelopeTitle))
|
@if (_titleTouched && string.IsNullOrWhiteSpace(_envelopeTitle))
|
||||||
{
|
{
|
||||||
<span style="font-size:0.6rem;color:#dc2626;">Pflichtfeld</span>
|
<span style="font-size:0.6rem;color:#dc2626;">@L("RequiredField", "Pflichtfeld")</span>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -63,12 +66,12 @@
|
|||||||
<div style="display: flex; flex-direction: column; gap: 0.2rem; flex: 2 1 260px; min-width: 180px;">
|
<div style="display: flex; flex-direction: column; gap: 0.2rem; flex: 2 1 260px; min-width: 180px;">
|
||||||
<label style="font-size: 0.65rem; font-weight: 600; color: #6b7280;
|
<label style="font-size: 0.65rem; font-weight: 600; color: #6b7280;
|
||||||
text-transform: uppercase; letter-spacing: 0.05em;">
|
text-transform: uppercase; letter-spacing: 0.05em;">
|
||||||
Nachricht <span style="color:#6b7280;font-weight:400;">(optional)</span>
|
@L("Message", "Nachricht") <span style="color:#6b7280;font-weight:400;">(@L("Optional", "optional"))</span>
|
||||||
</label>
|
</label>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
value="@_envelopeMessage"
|
value="@_envelopeMessage"
|
||||||
@oninput="e => _envelopeMessage = e.Value?.ToString() ?? string.Empty"
|
@oninput="e => _envelopeMessage = e.Value?.ToString() ?? string.Empty"
|
||||||
placeholder="Bitte unterzeichnen Sie das beigefügte Dokument."
|
placeholder='@L("SenderEditor_Message_Placeholder", "Bitte unterzeichnen Sie das beigefügte Dokument.")'
|
||||||
maxlength="500"
|
maxlength="500"
|
||||||
style="font-size: 0.78rem; color: #1f2937;
|
style="font-size: 0.78rem; color: #1f2937;
|
||||||
border: 1px solid #d1d5db; border-radius: 5px;
|
border: 1px solid #d1d5db; border-radius: 5px;
|
||||||
@@ -86,7 +89,7 @@
|
|||||||
<span style="display: inline-flex; align-items: center; padding: 0.125rem 0.4rem;
|
<span style="display: inline-flex; align-items: center; padding: 0.125rem 0.4rem;
|
||||||
background: #ede9fe; border-radius: 0.25rem; color: #6d28d9;
|
background: #ede9fe; border-radius: 0.25rem; color: #6d28d9;
|
||||||
font-weight: 500; font-size: 0.7rem; white-space: nowrap;">
|
font-weight: 500; font-size: 0.7rem; white-space: nowrap;">
|
||||||
@_signatureFields.Count Signaturfeld@(_signatureFields.Count != 1 ? "er" : "")
|
@_signatureFields.Count @L("SenderEditor_SignatureField", "Signaturfeld")@(_signatureFields.Count != 1 ? "er" : "")
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -96,24 +99,24 @@
|
|||||||
<div class="sender-receivers-panel">
|
<div class="sender-receivers-panel">
|
||||||
<div class="sender-receivers-panel__header">
|
<div class="sender-receivers-panel__header">
|
||||||
<div class="sender-receivers-panel__title-wrap">
|
<div class="sender-receivers-panel__title-wrap">
|
||||||
<span class="sender-receivers-panel__title">Empfänger</span>
|
<span class="sender-receivers-panel__title">@L("Receiver", "Empfänger")</span>
|
||||||
<span class="sender-receivers-panel__count">@_receivers.Count</span>
|
<span class="sender-receivers-panel__count">@_receivers.Count</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="pdf-toolbar__btn pdf-toolbar__btn--signature-change sender-toolbar-action-btn"
|
<button class="pdf-toolbar__btn pdf-toolbar__btn--signature-change sender-toolbar-action-btn"
|
||||||
@onclick="OpenAddReceiverPopup"
|
@onclick="OpenAddReceiverPopup"
|
||||||
title="Empfänger hinzufügen">
|
title='@L("AddReceiver", "Empfänger hinzufügen")'>
|
||||||
<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="M8 1a.5.5 0 0 1 .5.5v6h6a.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" />
|
<path d="M8 1a.5.5 0 0 1 .5.5v6h6a.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 class="pdf-toolbar__btn-text">Empfänger hinzufügen</span>
|
<span class="pdf-toolbar__btn-text">@L("AddReceiver", "Empfänger hinzufügen")</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (_receivers.Count == 0)
|
@if (_receivers.Count == 0)
|
||||||
{
|
{
|
||||||
<div class="sender-receivers-panel__empty">
|
<div class="sender-receivers-panel__empty">
|
||||||
Noch keine Empfänger hinzugefügt.
|
@L("SenderEditor_NoReceiversAdded", "Noch keine Empfänger hinzugefügt.")
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -136,11 +139,11 @@
|
|||||||
@(_pendingReceiverForPlacement?.Id == receiver.Id ? "pdf-toolbar__btn--signature-change-active" : "")"
|
@(_pendingReceiverForPlacement?.Id == receiver.Id ? "pdf-toolbar__btn--signature-change-active" : "")"
|
||||||
@onclick="() => ActivatePlacementForReceiver(receiver)"
|
@onclick="() => ActivatePlacementForReceiver(receiver)"
|
||||||
style="@(_pendingReceiverForPlacement?.Id == receiver.Id ? $"background:{receiver.Color};color:#fff;border-color:{receiver.Color};" : $"color:{receiver.Color};border-color:{receiver.Color};")"
|
style="@(_pendingReceiverForPlacement?.Id == receiver.Id ? $"background:{receiver.Color};color:#fff;border-color:{receiver.Color};" : $"color:{receiver.Color};border-color:{receiver.Color};")"
|
||||||
title="Signaturfeld platzieren">
|
title='@L("SenderEditor_PlaceSignatureField", "Signaturfeld platzieren")'>
|
||||||
<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="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10z" />
|
<path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10z" />
|
||||||
</svg>
|
</svg>
|
||||||
<span class="pdf-toolbar__btn-text">Signatur hinzufügen</span>
|
<span class="pdf-toolbar__btn-text">@L("SenderEditor_AddSignature", "Signatur hinzufügen")</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -154,19 +157,19 @@
|
|||||||
|
|
||||||
<button class="pdf-toolbar__btn pdf-toolbar__btn--reset"
|
<button class="pdf-toolbar__btn pdf-toolbar__btn--reset"
|
||||||
@onclick="Cancel"
|
@onclick="Cancel"
|
||||||
title="Abbrechen">
|
title='@L("Cancel", "Abbrechen")'>
|
||||||
Abbrechen
|
@L("Cancel", "Abbrechen")
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@* PDF Upload *@
|
@* PDF Upload *@
|
||||||
<label class="pdf-toolbar__btn"
|
<label class="pdf-toolbar__btn"
|
||||||
title="PDF hochladen"
|
title='@L("UploadPdf", "PDF hochladen")'
|
||||||
style="cursor: pointer; display: inline-flex; align-items: center; gap: 0.3rem; padding: 0.3rem 0.75rem; font-size: 0.75rem; font-weight: 500;">
|
style="cursor: pointer; display: inline-flex; align-items: center; gap: 0.3rem; padding: 0.3rem 0.75rem; font-size: 0.75rem; font-weight: 500;">
|
||||||
<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="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z" />
|
<path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z" />
|
||||||
<path d="M7.646 1.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 2.707V11.5a.5.5 0 0 1-1 0V2.707L5.354 4.854a.5.5 0 1 1-.708-.708l3-3z" />
|
<path d="M7.646 1.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 2.707V11.5a.5.5 0 0 1-1 0V2.707L5.354 4.854a.5.5 0 1 1-.708-.708l3-3z" />
|
||||||
</svg>
|
</svg>
|
||||||
PDF hochladen
|
@L("UploadPdf", "PDF hochladen")
|
||||||
<InputFile OnChange="OnPdfFileSelectedAsync"
|
<InputFile OnChange="OnPdfFileSelectedAsync"
|
||||||
accept=".pdf"
|
accept=".pdf"
|
||||||
style="display: none;" />
|
style="display: none;" />
|
||||||
@@ -176,7 +179,7 @@
|
|||||||
{
|
{
|
||||||
<button class="pdf-toolbar__btn pdf-toolbar__btn--reset"
|
<button class="pdf-toolbar__btn pdf-toolbar__btn--reset"
|
||||||
@onclick="ClearAllFieldsAsync"
|
@onclick="ClearAllFieldsAsync"
|
||||||
title="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" />
|
||||||
@@ -187,7 +190,7 @@
|
|||||||
<button class="pdf-toolbar__btn pdf-toolbar__btn--draft"
|
<button class="pdf-toolbar__btn pdf-toolbar__btn--draft"
|
||||||
@onclick="SaveDraftAsync"
|
@onclick="SaveDraftAsync"
|
||||||
disabled="@_isSaving"
|
disabled="@_isSaving"
|
||||||
title="Speichern">
|
title='@L("Save", "Speichern")'>
|
||||||
@if (_isSaving)
|
@if (_isSaving)
|
||||||
{
|
{
|
||||||
<span class="spinner-border spinner-border-sm me-1" role="status"
|
<span class="spinner-border spinner-border-sm me-1" role="status"
|
||||||
@@ -200,13 +203,13 @@
|
|||||||
<path d="M5.5 1.5v4a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-4h-4zM4 9a1 1 0 0 0-1 1v3h10v-3a1 1 0 0 0-1-1H4z"/>
|
<path d="M5.5 1.5v4a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-4h-4zM4 9a1 1 0 0 0-1 1v3h10v-3a1 1 0 0 0-1-1H4z"/>
|
||||||
</svg>
|
</svg>
|
||||||
}
|
}
|
||||||
<span class="pdf-toolbar__btn-text">Speichern</span>
|
<span class="pdf-toolbar__btn-text">@L("Save", "Speichern")</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="pdf-toolbar__btn pdf-toolbar__btn--success"
|
<button class="pdf-toolbar__btn pdf-toolbar__btn--success"
|
||||||
@onclick="SendAsync"
|
@onclick="SendAsync"
|
||||||
disabled="@_isSaving"
|
disabled="@_isSaving"
|
||||||
title="Umschlag senden"
|
title='@L("SendEnvelope", "Umschlag senden")'
|
||||||
style="background: linear-gradient(135deg,#059669,#047857); color:#fff; border-radius:6px; padding:0.3rem 0.75rem; font-size:0.75rem; font-weight:600; border:none;">
|
style="background: linear-gradient(135deg,#059669,#047857); color:#fff; border-radius:6px; padding:0.3rem 0.75rem; font-size:0.75rem; font-weight:600; border:none;">
|
||||||
@if (_isSaving)
|
@if (_isSaving)
|
||||||
{
|
{
|
||||||
@@ -219,7 +222,7 @@
|
|||||||
<path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z" />
|
<path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z" />
|
||||||
</svg>
|
</svg>
|
||||||
}
|
}
|
||||||
Senden
|
@L("Send", "Senden")
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -229,10 +232,10 @@
|
|||||||
{
|
{
|
||||||
<div style="background: @_pendingReceiverForPlacement.Color; color: white; font-size: 0.75rem; font-weight: 500;
|
<div style="background: @_pendingReceiverForPlacement.Color; color: white; font-size: 0.75rem; font-weight: 500;
|
||||||
padding: 0.3rem 1.5rem; text-align: center; letter-spacing: 0.01em;">
|
padding: 0.3rem 1.5rem; text-align: center; letter-spacing: 0.01em;">
|
||||||
📌 Klicken Sie auf die Stelle im Dokument für <strong>@_pendingReceiverForPlacement.FullName</strong>.
|
📌 @L("SenderEditor_ClickDocumentFor", "Klicken Sie auf die Stelle im Dokument für") <strong>@_pendingReceiverForPlacement.FullName</strong>.
|
||||||
<button @onclick="CancelPlacement"
|
<button @onclick="CancelPlacement"
|
||||||
style="background:none;border:none;color:white;cursor:pointer;font-size:0.75rem;text-decoration:underline;padding:0;">
|
style="background:none;border:none;color:white;cursor:pointer;font-size:0.75rem;text-decoration:underline;padding:0;">
|
||||||
Abbrechen
|
@L("Cancel", "Abbrechen")
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -252,16 +255,16 @@
|
|||||||
<path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z" />
|
<path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h5 class="text-white mb-2">Kein Dokument geladen</h5>
|
<h5 class="text-white mb-2">@L("SenderEditor_NoDocumentLoaded", "Kein Dokument geladen")</h5>
|
||||||
<p class="text-white mb-4" style="opacity: 0.75; font-size: 0.85rem;">
|
<p class="text-white mb-4" style="opacity: 0.75; font-size: 0.85rem;">
|
||||||
Laden Sie eine PDF-Datei hoch, um Signaturfelder zu platzieren.
|
@L("SenderEditor_UploadPdfToPlaceFields", "Laden Sie eine PDF-Datei hoch, um Signaturfelder zu platzieren.")
|
||||||
</p>
|
</p>
|
||||||
<label class="btn btn-light btn-sm" style="cursor: pointer; font-weight: 500;">
|
<label class="btn btn-light btn-sm" style="cursor: pointer; font-weight: 500;">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="me-1" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="me-1" viewBox="0 0 16 16">
|
||||||
<path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z" />
|
<path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z" />
|
||||||
<path d="M7.646 1.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 2.707V11.5a.5.5 0 0 1-1 0V2.707L5.354 4.854a.5.5 0 1 1-.708-.708l3-3z" />
|
<path d="M7.646 1.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 2.707V11.5a.5.5 0 0 1-1 0V2.707L5.354 4.854a.5.5 0 1 1-.708-.708l3-3z" />
|
||||||
</svg>
|
</svg>
|
||||||
PDF hochladen
|
@L("UploadPdf", "PDF hochladen")
|
||||||
<InputFile OnChange="OnPdfFileSelectedAsync" accept=".pdf" style="display: none;" />
|
<InputFile OnChange="OnPdfFileSelectedAsync" accept=".pdf" style="display: none;" />
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -271,7 +274,7 @@
|
|||||||
{
|
{
|
||||||
<div class="error-container">
|
<div class="error-container">
|
||||||
<div class="alert alert-danger shadow-lg m-4">
|
<div class="alert alert-danger shadow-lg m-4">
|
||||||
<strong>Fehler:</strong> @_errorMessage
|
<strong>@L("Error", "Fehler"):</strong> @_errorMessage
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -290,7 +293,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DxPopup @bind-Visible="_receiverPopupVisible"
|
<DxPopup @bind-Visible="_receiverPopupVisible"
|
||||||
HeaderText="Empfänger hinzufügen"
|
HeaderText='@L("AddReceiver", "Empfänger hinzufügen")'
|
||||||
ShowFooter="true"
|
ShowFooter="true"
|
||||||
CloseOnEscape="true"
|
CloseOnEscape="true"
|
||||||
Width="720px"
|
Width="720px"
|
||||||
@@ -299,7 +302,7 @@
|
|||||||
<div class="sender-receiver-popup__body">
|
<div class="sender-receiver-popup__body">
|
||||||
<div class="sender-receiver-popup__form-grid">
|
<div class="sender-receiver-popup__form-grid">
|
||||||
<div class="sender-receiver-popup__field">
|
<div class="sender-receiver-popup__field">
|
||||||
<div class="sender-receiver-popup__label">Vor- und Nachname</div>
|
<div class="sender-receiver-popup__label">@L("FullName", "Vor- und Nachname")</div>
|
||||||
<DxTextBox Text="@_receiverDraftName"
|
<DxTextBox Text="@_receiverDraftName"
|
||||||
TextChanged="OnReceiverNameChanged"
|
TextChanged="OnReceiverNameChanged"
|
||||||
NullText="Max Mustermann"
|
NullText="Max Mustermann"
|
||||||
@@ -309,7 +312,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sender-receiver-popup__field">
|
<div class="sender-receiver-popup__field">
|
||||||
<div class="sender-receiver-popup__label">Telefonnummer (optional)</div>
|
<div class="sender-receiver-popup__label">@L("PhoneNumber", "Telefonnummer") (@L("Optional", "optional"))</div>
|
||||||
<DxTextBox Text="@_receiverDraftPhoneNumber"
|
<DxTextBox Text="@_receiverDraftPhoneNumber"
|
||||||
TextChanged="OnReceiverPhoneNumberChanged"
|
TextChanged="OnReceiverPhoneNumberChanged"
|
||||||
NullText="+49 170 1234567"
|
NullText="+49 170 1234567"
|
||||||
@@ -319,7 +322,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sender-receiver-popup__field">
|
<div class="sender-receiver-popup__field">
|
||||||
<div class="sender-receiver-popup__label">E-Mail-Adresse</div>
|
<div class="sender-receiver-popup__label">@L("EmailAddress", "E-Mail-Adresse")</div>
|
||||||
<div @onkeydown="OnReceiverEmailKeyDownAsync">
|
<div @onkeydown="OnReceiverEmailKeyDownAsync">
|
||||||
<DxTextBox Text="@_receiverDraftEmail"
|
<DxTextBox Text="@_receiverDraftEmail"
|
||||||
TextChanged="OnReceiverEmailTextChangedAsync"
|
TextChanged="OnReceiverEmailTextChangedAsync"
|
||||||
@@ -347,12 +350,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sender-receiver-popup__hint">
|
<div class="sender-receiver-popup__hint">
|
||||||
Bereits verwendete E-Mail-Adressen werden bei der Eingabe vorgeschlagen.
|
@L("SenderEditor_EmailSuggestionsHint", "Bereits verwendete E-Mail-Adressen werden bei der Eingabe vorgeschlagen.")
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (_isReceiverEmailSearchRunning)
|
@if (_isReceiverEmailSearchRunning)
|
||||||
{
|
{
|
||||||
<div class="sender-receiver-popup__loading">E-Mail-Vorschläge werden geladen…</div>
|
<div class="sender-receiver-popup__loading">@L("SenderEditor_EmailSuggestionsLoading", "E-Mail-Vorschläge werden geladen…")</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (!string.IsNullOrWhiteSpace(_receiverPopupValidationMessage))
|
@if (!string.IsNullOrWhiteSpace(_receiverPopupValidationMessage))
|
||||||
@@ -363,10 +366,10 @@
|
|||||||
</BodyContentTemplate>
|
</BodyContentTemplate>
|
||||||
<FooterContentTemplate>
|
<FooterContentTemplate>
|
||||||
<div class="sender-receiver-popup__footer">
|
<div class="sender-receiver-popup__footer">
|
||||||
<DxButton Text="Abbrechen"
|
<DxButton Text='@L("Cancel", "Abbrechen")'
|
||||||
RenderStyle="ButtonRenderStyle.Secondary"
|
RenderStyle="ButtonRenderStyle.Secondary"
|
||||||
Click="CloseAddReceiverPopup" />
|
Click="CloseAddReceiverPopup" />
|
||||||
<DxButton Text="Empfänger hinzufügen"
|
<DxButton Text='@L("AddReceiver", "Empfänger hinzufügen")'
|
||||||
RenderStyle="ButtonRenderStyle.Primary"
|
RenderStyle="ButtonRenderStyle.Primary"
|
||||||
Click="SaveReceiverAsync" />
|
Click="SaveReceiverAsync" />
|
||||||
</div>
|
</div>
|
||||||
@@ -375,7 +378,7 @@
|
|||||||
|
|
||||||
@* ── Save result popup (success + error) ── *@
|
@* ── Save result popup (success + error) ── *@
|
||||||
<DxPopup @bind-Visible="_savePopupVisible"
|
<DxPopup @bind-Visible="_savePopupVisible"
|
||||||
HeaderText="@(_saveErrorMessage is null ? _saveSuccessHeader : "Fehler")"
|
HeaderText='@(_saveErrorMessage is null ? _saveSuccessHeader : L("Error", "Fehler"))'
|
||||||
Width="460px"
|
Width="460px"
|
||||||
MaxWidth="95vw"
|
MaxWidth="95vw"
|
||||||
ShowFooter="true"
|
ShowFooter="true"
|
||||||
@@ -414,7 +417,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p style="margin:0 0 0.4rem;font-weight:600;color:#1f2937;font-size:0.95rem;">
|
<p style="margin:0 0 0.4rem;font-weight:600;color:#1f2937;font-size:0.95rem;">
|
||||||
Fehler beim Erstellen des Umschlags
|
@L("SenderEditor_ErrorCreatingEnvelope", "Fehler beim Erstellen des Umschlags")
|
||||||
</p>
|
</p>
|
||||||
<p style="margin:0;color:#6b7280;font-size:0.85rem;line-height:1.5;word-break:break-word;">
|
<p style="margin:0;color:#6b7280;font-size:0.85rem;line-height:1.5;word-break:break-word;">
|
||||||
@_saveErrorMessage
|
@_saveErrorMessage
|
||||||
@@ -430,7 +433,7 @@
|
|||||||
<button class="btn btn-outline-secondary"
|
<button class="btn btn-outline-secondary"
|
||||||
@onclick="() => _savePopupVisible = false"
|
@onclick="() => _savePopupVisible = false"
|
||||||
style="border-radius:6px;padding:0.5rem 1.25rem;font-weight:500;">
|
style="border-radius:6px;padding:0.5rem 1.25rem;font-weight:500;">
|
||||||
Schließen
|
@L("Close", "Schließen")
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@if (_saveNavigateToDashboard)
|
@if (_saveNavigateToDashboard)
|
||||||
@@ -439,7 +442,7 @@
|
|||||||
@onclick="GoToDashboard"
|
@onclick="GoToDashboard"
|
||||||
style="background:linear-gradient(135deg,#059669,#047857);border:none;border-radius:6px;
|
style="background:linear-gradient(135deg,#059669,#047857);border:none;border-radius:6px;
|
||||||
padding:0.5rem 1.5rem;font-weight:600;color:#fff;">
|
padding:0.5rem 1.5rem;font-weight:600;color:#fff;">
|
||||||
Zur Übersicht
|
@L("SenderEditor_ToOverview", "Zur Übersicht")
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -448,7 +451,7 @@
|
|||||||
<button class="btn btn-outline-secondary"
|
<button class="btn btn-outline-secondary"
|
||||||
@onclick="() => _savePopupVisible = false"
|
@onclick="() => _savePopupVisible = false"
|
||||||
style="border-radius:6px;padding:0.5rem 1.25rem;font-weight:500;">
|
style="border-radius:6px;padding:0.5rem 1.25rem;font-weight:500;">
|
||||||
Schließen
|
@L("Close", "Schließen")
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -653,7 +656,10 @@
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogError(ex, "Failed to load envelope {EnvelopeId} for edit mode", envelopeId);
|
Logger.LogError(ex, "Failed to load envelope {EnvelopeId} for edit mode", envelopeId);
|
||||||
_errorMessage = $"Fehler beim Laden des Umschlags #{envelopeId}: {ex.Message}";
|
_errorMessage = string.Format(
|
||||||
|
L("SenderEditor_LoadEnvelopeError", "Fehler beim Laden des Umschlags #{0}: {1}"),
|
||||||
|
envelopeId,
|
||||||
|
ex.Message);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -670,7 +676,7 @@
|
|||||||
|
|
||||||
if (!file.Name.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase))
|
if (!file.Name.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
_errorMessage = "Bitte wählen Sie eine PDF-Datei aus.";
|
_errorMessage = L("SenderEditor_SelectPdf", "Bitte wählen Sie eine PDF-Datei aus.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -695,7 +701,9 @@
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_errorMessage = $"Fehler beim Laden der Datei: {ex.Message}";
|
_errorMessage = string.Format(
|
||||||
|
L("SenderEditor_LoadFileError", "Fehler beim Laden der Datei: {0}"),
|
||||||
|
ex.Message);
|
||||||
Logger.LogError(ex, "Failed to load PDF file");
|
Logger.LogError(ex, "Failed to load PDF file");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -853,25 +861,25 @@
|
|||||||
_titleTouched = true;
|
_titleTouched = true;
|
||||||
if (!_pdfLoaded || _originalPdfBytes is null)
|
if (!_pdfLoaded || _originalPdfBytes is null)
|
||||||
{
|
{
|
||||||
_saveErrorMessage = "Bitte laden Sie zuerst ein PDF-Dokument hoch.";
|
_saveErrorMessage = L("SenderEditor_Validation_UploadPdf", "Bitte laden Sie zuerst ein PDF-Dokument hoch.");
|
||||||
_savePopupVisible = true;
|
_savePopupVisible = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (string.IsNullOrWhiteSpace(_envelopeTitle))
|
if (string.IsNullOrWhiteSpace(_envelopeTitle))
|
||||||
{
|
{
|
||||||
_saveErrorMessage = "Bitte geben Sie einen Titel ein.";
|
_saveErrorMessage = L("SenderEditor_Validation_TitleRequired", "Bitte geben Sie einen Titel ein.");
|
||||||
_savePopupVisible = true;
|
_savePopupVisible = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_receivers.Count == 0)
|
if (_receivers.Count == 0)
|
||||||
{
|
{
|
||||||
_saveErrorMessage = "Bitte fügen Sie mindestens einen Empfänger hinzu.";
|
_saveErrorMessage = L("SenderEditor_Validation_ReceiverRequired", "Bitte fügen Sie mindestens einen Empfänger hinzu.");
|
||||||
_savePopupVisible = true;
|
_savePopupVisible = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_signatureFields.Count == 0)
|
if (_signatureFields.Count == 0)
|
||||||
{
|
{
|
||||||
_saveErrorMessage = "Bitte platzieren Sie mindestens ein Signaturfeld.";
|
_saveErrorMessage = L("SenderEditor_Validation_SignatureFieldRequired", "Bitte platzieren Sie mindestens ein Signaturfeld.");
|
||||||
_savePopupVisible = true;
|
_savePopupVisible = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -916,9 +924,9 @@
|
|||||||
|
|
||||||
// Success — show popup; GoToDashboard clears cache and navigates
|
// Success — show popup; GoToDashboard clears cache and navigates
|
||||||
_saveErrorMessage = null;
|
_saveErrorMessage = null;
|
||||||
_saveSuccessHeader = "Umschlag gesendet";
|
_saveSuccessHeader = L("SenderEditor_Sent_Header", "Umschlag gesendet");
|
||||||
_saveSuccessTitle = "Umschlag wurde erfolgreich gesendet.";
|
_saveSuccessTitle = L("SenderEditor_Sent_Title", "Umschlag wurde erfolgreich gesendet.");
|
||||||
_saveSuccessBody = "Der bestehende Entwurf wurde versendet.";
|
_saveSuccessBody = L("SenderEditor_Sent_Body", "Der bestehende Entwurf wurde versendet.");
|
||||||
_saveNavigateToDashboard = true;
|
_saveNavigateToDashboard = true;
|
||||||
_savePopupVisible = true;
|
_savePopupVisible = true;
|
||||||
}
|
}
|
||||||
@@ -1222,22 +1230,22 @@
|
|||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(fullName))
|
if (string.IsNullOrWhiteSpace(fullName))
|
||||||
{
|
{
|
||||||
_receiverPopupValidationMessage = "Bitte geben Sie einen Vor- und Nachnamen ein.";
|
_receiverPopupValidationMessage = L("SenderEditor_Validation_FullNameRequired", "Bitte geben Sie einen Vor- und Nachnamen ein.");
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
if (string.IsNullOrWhiteSpace(email))
|
if (string.IsNullOrWhiteSpace(email))
|
||||||
{
|
{
|
||||||
_receiverPopupValidationMessage = "Bitte geben Sie eine E-Mail-Adresse ein.";
|
_receiverPopupValidationMessage = L("SenderEditor_Validation_EmailRequired", "Bitte geben Sie eine E-Mail-Adresse ein.");
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
if (!ReceiverEmailValidator.IsValid(email))
|
if (!ReceiverEmailValidator.IsValid(email))
|
||||||
{
|
{
|
||||||
_receiverPopupValidationMessage = "Bitte geben Sie eine gültige E-Mail-Adresse ein.";
|
_receiverPopupValidationMessage = L("SenderEditor_Validation_EmailInvalid", "Bitte geben Sie eine gültige E-Mail-Adresse ein.");
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
if (_receivers.Any(r => string.Equals(r.Email, email, StringComparison.OrdinalIgnoreCase)))
|
if (_receivers.Any(r => string.Equals(r.Email, email, StringComparison.OrdinalIgnoreCase)))
|
||||||
{
|
{
|
||||||
_receiverPopupValidationMessage = "Diese E-Mail-Adresse wurde bereits hinzugefügt.";
|
_receiverPopupValidationMessage = L("SenderEditor_Validation_EmailDuplicate", "Diese E-Mail-Adresse wurde bereits hinzugefügt.");
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1275,4 +1283,10 @@
|
|||||||
"#BE185D", // pink
|
"#BE185D", // pink
|
||||||
"#65A30D", // lime
|
"#65A30D", // lime
|
||||||
];
|
];
|
||||||
|
|
||||||
|
string L(string key, string fallback)
|
||||||
|
{
|
||||||
|
var value = UiLocalizer[key];
|
||||||
|
return value.ResourceNotFound ? fallback : value.Value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,11 @@
|
|||||||
@using DevExpress.Blazor.PdfViewer
|
@using DevExpress.Blazor.PdfViewer
|
||||||
@using EnvelopeGenerator.Server.Client.Services
|
@using EnvelopeGenerator.Server.Client.Services
|
||||||
@using EnvelopeGenerator.Application.Common.Dto
|
@using EnvelopeGenerator.Application.Common.Dto
|
||||||
|
@using EnvelopeGenerator.Application.Common.Dto.History
|
||||||
|
@using EnvelopeGenerator.Application.Resources
|
||||||
|
@using EnvelopeGenerator.Domain.Resources
|
||||||
@using EnvelopeGenerator.Server.Services
|
@using EnvelopeGenerator.Server.Services
|
||||||
|
@using Microsoft.Extensions.Localization
|
||||||
@inject EnvelopeGenerator.Server.Client.Services.EnvelopeService EnvelopeService
|
@inject EnvelopeGenerator.Server.Client.Services.EnvelopeService EnvelopeService
|
||||||
@inject EnvelopeGenerator.Server.Client.Services.AuthService AuthService
|
@inject EnvelopeGenerator.Server.Client.Services.AuthService AuthService
|
||||||
@inject EnvelopeGenerator.Server.Client.Services.ThemeService ThemeService
|
@inject EnvelopeGenerator.Server.Client.Services.ThemeService ThemeService
|
||||||
@@ -19,6 +23,8 @@
|
|||||||
@inject NavigationManager Navigation
|
@inject NavigationManager Navigation
|
||||||
@inject IJSRuntime JSRuntime
|
@inject IJSRuntime JSRuntime
|
||||||
@inject AppVersionService AppVersion
|
@inject AppVersionService AppVersion
|
||||||
|
@inject IStringLocalizer<Model> ModelLocalizer
|
||||||
|
@inject IStringLocalizer<Resource> UiLocalizer
|
||||||
|
|
||||||
<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" />
|
||||||
@@ -66,29 +72,29 @@
|
|||||||
|
|
||||||
@* ── Toolbar (rechts) ─────────────────────────────────────────── *@
|
@* ── Toolbar (rechts) ─────────────────────────────────────────── *@
|
||||||
<div class="sender-toolbar">
|
<div class="sender-toolbar">
|
||||||
<button class="sender-btn sender-btn--primary" @onclick="CreateEnvelope" title="Neuen Umschlag erstellen">
|
<button class="sender-btn sender-btn--primary" @onclick="CreateEnvelope" title="@L("Sender_CreateEnvelope_Title", "Neuen Umschlag erstellen")">
|
||||||
<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="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z" />
|
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z" />
|
||||||
</svg>
|
</svg>
|
||||||
Neuer Umschlag
|
@L("Sender_CreateEnvelope", "Neuer Umschlag")
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="sender-btn" @onclick="EditEnvelope" disabled="@(_selectedEnvelope == null || !IsEnvelopeEditable(_selectedEnvelope))" title="Ausgewählten Umschlag bearbeiten">
|
<button class="sender-btn" @onclick="EditEnvelope" disabled="@(_selectedEnvelope == null || !IsEnvelopeEditable(_selectedEnvelope))" title="@L("Sender_EditEnvelope_Title", "Ausgewählten Umschlag bearbeiten")">
|
||||||
<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="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z" />
|
<path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z" />
|
||||||
</svg>
|
</svg>
|
||||||
Bearbeiten
|
@L("Sender_Edit", "Bearbeiten")
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="sender-btn sender-btn--danger" @onclick="DeleteEnvelope" disabled="@(_selectedEnvelope == null)" title="Ausgewählten Umschlag löschen">
|
<button class="sender-btn sender-btn--danger" @onclick="DeleteEnvelope" disabled="@(_selectedEnvelope == null)" title="@L("Sender_DeleteEnvelope_Title", "Ausgewählten Umschlag löschen")">
|
||||||
<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="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-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" />
|
<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>
|
</svg>
|
||||||
Löschen
|
@L("Delete", "Löschen")
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="sender-btn" @onclick="RefreshEnvelopes" disabled="@_isLoading" title="Aktualisieren">
|
<button class="sender-btn" @onclick="RefreshEnvelopes" disabled="@_isLoading" title="@L("Refresh", "Aktualisieren")">
|
||||||
<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 fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z" />
|
<path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z" />
|
||||||
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z" />
|
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z" />
|
||||||
@@ -99,16 +105,16 @@
|
|||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="sender-btn" @onclick="ShowPreview" disabled="@(_selectedEnvelope == null)" title="Ausgewählten Umschlag als PDF anzeigen">
|
<button class="sender-btn" @onclick="ShowPreview" disabled="@(_selectedEnvelope == null)" title="@L("Sender_Preview_Title", "Ausgewählten Umschlag als PDF 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>
|
||||||
Vorschau
|
@L("Preview", "Vorschau")
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="sender-export-group" title="Export der aktuellen Tabellenansicht">
|
<div class="sender-export-group" title="@L("Sender_ExportView_Title", "Export der aktuellen Tabellenansicht")">
|
||||||
<select id="sender-export-format" class="sender-export-format" @bind="_selectedExportFormat" aria-label="Exportformat auswählen">
|
<select id="sender-export-format" class="sender-export-format" @bind="_selectedExportFormat" aria-label="@L("Sender_ExportFormat_Aria", "Exportformat auswählen")">
|
||||||
<option value="xlsx">XLSX</option>
|
<option value="xlsx">XLSX</option>
|
||||||
<option value="csv">CSV</option>
|
<option value="csv">CSV</option>
|
||||||
<option value="pdf">PDF</option>
|
<option value="pdf">PDF</option>
|
||||||
@@ -117,15 +123,15 @@
|
|||||||
<label class="sender-export-selected">
|
<label class="sender-export-selected">
|
||||||
<input type="checkbox" @bind="_exportSelectedRowsOnly" />
|
<input type="checkbox" @bind="_exportSelectedRowsOnly" />
|
||||||
<span class="sender-export-selected-text">
|
<span class="sender-export-selected-text">
|
||||||
<span>Nur</span>
|
<span>@L("Only", "Nur")</span>
|
||||||
<span>ausgewählte</span>
|
<span>@L("Selected", "ausgewählte")</span>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<button class="sender-btn sender-btn--export"
|
<button class="sender-btn sender-btn--export"
|
||||||
@onclick="ExportSelectedFormatAsync"
|
@onclick="ExportSelectedFormatAsync"
|
||||||
disabled="@IsExportDisabled()"
|
disabled="@IsExportDisabled()"
|
||||||
title="Aktuelle Ansicht exportieren">
|
title="@L("Sender_ExportCurrentView_Title", "Aktuelle Ansicht exportieren")">
|
||||||
<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="M.5 9.9a.5.5 0 0 1 .5.5V13a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.6a.5.5 0 0 1 1 0V13a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.6a.5.5 0 0 1 .5-.5z" />
|
<path d="M.5 9.9a.5.5 0 0 1 .5.5V13a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.6a.5.5 0 0 1 1 0V13a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.6a.5.5 0 0 1 .5-.5z" />
|
||||||
<path d="M5.354 6.146a.5.5 0 0 1 .707 0L7.5 7.586V.5a.5.5 0 0 1 1 0v7.086l1.439-1.44a.5.5 0 0 1 .707.708l-2.293 2.293a.5.5 0 0 1-.707 0L5.354 6.854a.5.5 0 0 1 0-.708z" />
|
<path d="M5.354 6.146a.5.5 0 0 1 .707 0L7.5 7.586V.5a.5.5 0 0 1 1 0v7.086l1.439-1.44a.5.5 0 0 1 .707.708l-2.293 2.293a.5.5 0 0 1-.707 0L5.354 6.854a.5.5 0 0 1 0-.708z" />
|
||||||
@@ -136,12 +142,12 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<span>Exportieren</span>
|
<span>@L("Export", "Exportieren")</span>
|
||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="sender-btn sender-btn--logout" @onclick="LogoutAsync" disabled="@_isLoggingOut" title="Abmelden">
|
<button class="sender-btn sender-btn--logout" @onclick="LogoutAsync" disabled="@_isLoggingOut" title="@L("Logout", "Abmelden")">
|
||||||
@if (_isLoggingOut)
|
@if (_isLoggingOut)
|
||||||
{
|
{
|
||||||
<span class="spinner-border spinner-border-sm" style="width: 14px; height: 14px;"></span>
|
<span class="spinner-border spinner-border-sm" style="width: 14px; height: 14px;"></span>
|
||||||
@@ -164,9 +170,9 @@
|
|||||||
<div class="d-flex justify-content-center align-items-center h-100">
|
<div class="d-flex justify-content-center align-items-center h-100">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div class="spinner-border text-white mb-3" style="width: 3.5rem; height: 3.5rem;" role="status">
|
<div class="spinner-border text-white mb-3" style="width: 3.5rem; height: 3.5rem;" role="status">
|
||||||
<span class="visually-hidden">Lädt...</span>
|
<span class="visually-hidden">@L("Loading", "Lädt...")</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-white fw-semibold">Umschläge werden geladen...</p>
|
<p class="text-white fw-semibold">@L("Sender_LoadingEnvelopes", "Umschläge werden geladen...")</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -180,7 +186,7 @@
|
|||||||
<path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z" />
|
<path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z" />
|
||||||
</svg>
|
</svg>
|
||||||
<div>
|
<div>
|
||||||
<h5 class="mb-2">Fehler beim Laden der Umschläge</h5>
|
<h5 class="mb-2">@L("Sender_LoadError_Title", "Fehler beim Laden der Umschläge")</h5>
|
||||||
<p class="mb-0">@_errorMessage</p>
|
<p class="mb-0">@_errorMessage</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -192,14 +198,14 @@
|
|||||||
<div class="sender-grid-container">
|
<div class="sender-grid-container">
|
||||||
<div class="sender-tabs">
|
<div class="sender-tabs">
|
||||||
<button class="sender-tab @(_activeTab == "active" ? "sender-tab--active" : "")" @onclick='() => _activeTab = "active"'>
|
<button class="sender-tab @(_activeTab == "active" ? "sender-tab--active" : "")" @onclick='() => _activeTab = "active"'>
|
||||||
<span>Aktive Umschläge</span>
|
<span>@L("Sender_ActiveEnvelopes", "Aktive Umschläge")</span>
|
||||||
@if (_activeEnvelopes != null)
|
@if (_activeEnvelopes != null)
|
||||||
{
|
{
|
||||||
<span style="opacity: 0.6; margin-left: 0.5rem;">(@_activeEnvelopes.Count())</span>
|
<span style="opacity: 0.6; margin-left: 0.5rem;">(@_activeEnvelopes.Count())</span>
|
||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
<button class="sender-tab @(_activeTab == "completed" ? "sender-tab--active" : "")" @onclick='() => _activeTab = "completed"'>
|
<button class="sender-tab @(_activeTab == "completed" ? "sender-tab--active" : "")" @onclick='() => _activeTab = "completed"'>
|
||||||
<span>Abgeschlossene Umschläge</span>
|
<span>@L("Sender_CompletedEnvelopes", "Abgeschlossene Umschläge")</span>
|
||||||
@if (_completedEnvelopes != null)
|
@if (_completedEnvelopes != null)
|
||||||
{
|
{
|
||||||
<span style="opacity: 0.6; margin-left: 0.5rem;">(@_completedEnvelopes.Count())</span>
|
<span style="opacity: 0.6; margin-left: 0.5rem;">(@_completedEnvelopes.Count())</span>
|
||||||
@@ -208,15 +214,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sender-grid-wrapper">
|
<div class="sender-grid-wrapper">
|
||||||
@if (_activeTab == "active")
|
<DxGrid @key="_activeTab"
|
||||||
{
|
Data="@CurrentGridData"
|
||||||
<DxGrid Data="@_activeEnvelopes"
|
@ref="_grid"
|
||||||
@ref="_gridActive"
|
|
||||||
KeyFieldName="Id"
|
KeyFieldName="Id"
|
||||||
ShowFilterRow="true"
|
ShowFilterRow="false"
|
||||||
|
FilterMenuButtonDisplayMode="GridFilterMenuButtonDisplayMode.Always"
|
||||||
ShowSearchBox="true"
|
ShowSearchBox="true"
|
||||||
AllowColumnReorder="true"
|
AllowColumnReorder="true"
|
||||||
AllowSort=true
|
AllowSort="true"
|
||||||
ColumnResizeMode="GridColumnResizeMode.ColumnsContainer"
|
ColumnResizeMode="GridColumnResizeMode.ColumnsContainer"
|
||||||
PageSize="20"
|
PageSize="20"
|
||||||
PagerVisible="true"
|
PagerVisible="true"
|
||||||
@@ -227,22 +233,30 @@
|
|||||||
RowClick="OnRowClick"
|
RowClick="OnRowClick"
|
||||||
RowDoubleClick="OnRowDoubleClick"
|
RowDoubleClick="OnRowDoubleClick"
|
||||||
CustomizeElement="OnCustomizeElement"
|
CustomizeElement="OnCustomizeElement"
|
||||||
LayoutAutoLoading="OnActiveGridLayoutAutoLoading"
|
LayoutAutoLoading="OnGridLayoutAutoLoading"
|
||||||
LayoutAutoSaving="OnActiveGridLayoutAutoSaving">
|
LayoutAutoSaving="OnGridLayoutAutoSaving">
|
||||||
<Columns>
|
<Columns>
|
||||||
<DxGridDataColumn FieldName="Id" Caption="ID"
|
<DxGridDataColumn FieldName="Id" Caption="ID"
|
||||||
|
DisplayFormat="0"
|
||||||
SortIndex="0"
|
SortIndex="0"
|
||||||
SortOrder="GridColumnSortOrder.Descending">
|
SortOrder="GridColumnSortOrder.Descending">
|
||||||
<CellDisplayTemplate Context="cellContext">
|
<CellDisplayTemplate Context="cellContext">
|
||||||
@((cellContext.DataItem as EnvelopeDto)?.Id)
|
@((cellContext.DataItem as EnvelopeDto)?.Id)
|
||||||
</CellDisplayTemplate>
|
</CellDisplayTemplate>
|
||||||
</DxGridDataColumn>
|
</DxGridDataColumn>
|
||||||
<DxGridDataColumn FieldName="Title" Caption="Titel">
|
<DxGridDataColumn FieldName="Title" Caption="@L("Title", "Titel")">
|
||||||
<CellDisplayTemplate Context="cellContext">
|
<CellDisplayTemplate Context="cellContext">
|
||||||
<strong>@((cellContext.DataItem as EnvelopeDto)?.Title)</strong>
|
<strong>@((cellContext.DataItem as EnvelopeDto)?.Title)</strong>
|
||||||
</CellDisplayTemplate>
|
</CellDisplayTemplate>
|
||||||
</DxGridDataColumn>
|
</DxGridDataColumn>
|
||||||
<DxGridDataColumn FieldName="Status" Caption="Status" ExportEnabled="false">
|
<DxGridDataColumn FieldName="Status" Caption="@L("Status", "Status")" ExportEnabled="false">
|
||||||
|
<EditSettings>
|
||||||
|
<DxComboBoxSettings Data="@GetStatusFilterItems(CurrentGridData)"
|
||||||
|
ValueFieldName="Value"
|
||||||
|
TextFieldName="Text"
|
||||||
|
SearchMode="ListSearchMode.AutoSearch"
|
||||||
|
SearchFilterCondition="ListSearchFilterCondition.Contains" />
|
||||||
|
</EditSettings>
|
||||||
<CellDisplayTemplate Context="cellContext">
|
<CellDisplayTemplate Context="cellContext">
|
||||||
@{
|
@{
|
||||||
var envelope = cellContext.DataItem as EnvelopeDto;
|
var envelope = cellContext.DataItem as EnvelopeDto;
|
||||||
@@ -257,18 +271,18 @@
|
|||||||
}
|
}
|
||||||
</CellDisplayTemplate>
|
</CellDisplayTemplate>
|
||||||
</DxGridDataColumn>
|
</DxGridDataColumn>
|
||||||
<DxGridDataColumn FieldName="EnvelopeReceivers" Caption="Empfänger" ExportEnabled="false">
|
<DxGridDataColumn FieldName="EnvelopeReceivers" Caption="@L("Receiver", "Empfänger")" ExportEnabled="false">
|
||||||
<CellDisplayTemplate Context="cellContext">
|
<CellDisplayTemplate Context="cellContext">
|
||||||
@{
|
@{
|
||||||
var envelope = cellContext.DataItem as EnvelopeDto;
|
var envelope = cellContext.DataItem as EnvelopeDto;
|
||||||
if (envelope != null)
|
if (envelope != null)
|
||||||
{
|
{
|
||||||
var receivers = envelope.EnvelopeReceivers?.ToList() ?? [];
|
var receivers = envelope.EnvelopeReceivers?.ToList() ?? [];
|
||||||
var signed = receivers.Count(r => r.Signed);
|
var signed = GetSignedReceiverCount(envelope);
|
||||||
var total = receivers.Count;
|
var total = receivers.Count;
|
||||||
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
||||||
<span style="font-size: 0.875rem; color: #6b7280;">
|
<span style="font-size: 0.875rem; color: #6b7280;">
|
||||||
@signed / @total unterschrieben
|
@signed / @total @L("Signed", "unterschrieben")
|
||||||
</span>
|
</span>
|
||||||
@if (total > 0)
|
@if (total > 0)
|
||||||
{
|
{
|
||||||
@@ -281,16 +295,16 @@
|
|||||||
}
|
}
|
||||||
</CellDisplayTemplate>
|
</CellDisplayTemplate>
|
||||||
</DxGridDataColumn>
|
</DxGridDataColumn>
|
||||||
<DxGridDataColumn FieldName="EnvelopeTypeTitle" Caption="Typ"
|
<DxGridDataColumn FieldName="EnvelopeTypeTitle" Caption="@L("Type", "Typ")"
|
||||||
Width="120px" AllowSort="true" />
|
Width="120px" AllowSort="true" />
|
||||||
<DxGridDataColumn FieldName="AddedWhen" Caption="Erstellt"
|
<DxGridDataColumn FieldName="AddedWhen" Caption="@L("Created", "Erstellt")"
|
||||||
DisplayFormat="{0:dd.MM.yyyy}" Width="110px" AllowSort="true" />
|
DisplayFormat="{0:dd.MM.yyyy}" Width="110px" AllowSort="true" />
|
||||||
<DxGridDataColumn FieldName="ChangedWhen" Caption="Geändert am"
|
<DxGridDataColumn FieldName="ChangedWhen" Caption="@L("ChangedAt", "Geändert am")"
|
||||||
DisplayFormat="{0:dd.MM.yyyy}" Width="110px" AllowSort="true" />
|
DisplayFormat="{0:dd.MM.yyyy}" Width="110px" AllowSort="true" />
|
||||||
</Columns>
|
</Columns>
|
||||||
<DetailRowTemplate Context="detailContext">
|
<DetailRowTemplate Context="detailContext">
|
||||||
<div style="padding: 1rem; background: #f9fafb;">
|
<div style="padding: 1rem; background: #f9fafb;">
|
||||||
<h6 style="font-weight: 600; color: #374151; margin-bottom: 0.75rem;">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?.EnvelopeReceivers?.Any() == true)
|
if (envelope?.EnvelopeReceivers?.Any() == true)
|
||||||
@@ -299,13 +313,18 @@
|
|||||||
@foreach (var receiver in envelope.EnvelopeReceivers)
|
@foreach (var receiver in envelope.EnvelopeReceivers)
|
||||||
{
|
{
|
||||||
<div style="display: flex; align-items: center; gap: 1rem; padding: 0.5rem; background: white; border-radius: 6px; border: 1px solid #e5e7eb;">
|
<div style="display: flex; align-items: center; gap: 1rem; padding: 0.5rem; background: white; border-radius: 6px; border: 1px solid #e5e7eb;">
|
||||||
<span class="receiver-badge receiver-badge--@(receiver.Signed ? "signed" : "unsigned")" style="min-width: 100px;">
|
@{
|
||||||
@if (receiver.Signed)
|
var isSigned = IsReceiverSigned(envelope, receiver);
|
||||||
|
var accessCode = receiver.AccessCode;
|
||||||
|
var hasAccessCode = !string.IsNullOrWhiteSpace(accessCode);
|
||||||
|
}
|
||||||
|
<span class="receiver-badge receiver-badge--@(isSigned ? "signed" : "unsigned")" style="min-width: 100px;">
|
||||||
|
@if (isSigned)
|
||||||
{
|
{
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
|
||||||
<path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z" />
|
<path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>Unterschrieben</span>
|
<span>@L("Signed", "Unterschrieben")</span>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -313,134 +332,62 @@
|
|||||||
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
|
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
|
||||||
<path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z" />
|
<path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>Ausstehend</span>
|
<span>@L("Pending", "Ausstehend")</span>
|
||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
<div style="flex: 1; font-size: 0.875rem;">
|
<div style="flex: 1; font-size: 0.875rem;">
|
||||||
<strong style="color: #1f2937;">@receiver.Name</strong>
|
<strong style="color: #1f2937;">@receiver.Name</strong>
|
||||||
<span style="color: #6b7280; margin-left: 0.5rem;">@receiver.Receiver?.EmailAddress</span>
|
<span style="color: #6b7280; margin-left: 0.5rem;">@receiver.Receiver?.EmailAddress</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="sender-access-code">
|
||||||
|
<span class="sender-access-code__label">@L("AccessCode", "Access code")</span>
|
||||||
|
<div class="sender-access-code__row">
|
||||||
|
<input class="sender-access-code__value"
|
||||||
|
value="@(hasAccessCode ? accessCode : "-")"
|
||||||
|
readonly
|
||||||
|
title="@L("AccessCode", "Access code")" />
|
||||||
|
<button type="button"
|
||||||
|
class="sender-access-code__copy"
|
||||||
|
disabled="@(!hasAccessCode)"
|
||||||
|
@onclick='() => CopyAccessCodeAsync(accessCode)'
|
||||||
|
title="@L("Copy", "Copy")">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true">
|
||||||
|
<path d="M10 1.5A1.5 1.5 0 0 1 11.5 3v1H13A1.5 1.5 0 0 1 14.5 5.5v8A1.5 1.5 0 0 1 13 15H5A1.5 1.5 0 0 1 3.5 13.5V12H3A1.5 1.5 0 0 1 1.5 10.5v-8A1.5 1.5 0 0 1 3 1h7zm.5 2A.5.5 0 0 0 10 3H3a.5.5 0 0 0-.5.5v7A.5.5 0 0 0 3 11h.5V5.5A1.5 1.5 0 0 1 5 4h5.5v-.5zM5 5a.5.5 0 0 0-.5.5v8a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5v-8A.5.5 0 0 0 13 5H5z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<p style="color: #9ca3af; font-size: 0.875rem; margin: 0;">Keine Empfänger</p>
|
<p style="color: #9ca3af; font-size: 0.875rem; margin: 0;">@L("Sender_NoReceivers", "Keine Empfänger")</p>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</div>
|
|
||||||
</DetailRowTemplate>
|
<h6 style="font-weight: 600; color: #374151; margin: 1rem 0 0.75rem;">@L("History", "Historie")</h6>
|
||||||
</DxGrid>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<DxGrid Data="@_completedEnvelopes"
|
|
||||||
@ref="_gridCompleted"
|
|
||||||
KeyFieldName="Id"
|
|
||||||
ShowFilterRow="true"
|
|
||||||
ShowSearchBox="true"
|
|
||||||
PageSize="20"
|
|
||||||
PagerVisible="true"
|
|
||||||
SelectionMode="GridSelectionMode.Single"
|
|
||||||
AllowSelectRowByClick="true"
|
|
||||||
SelectedDataItem="@_selectedEnvelope"
|
|
||||||
SelectedDataItemChanged="@((object o) => _selectedEnvelope = o as EnvelopeDto)"
|
|
||||||
RowClick="OnRowClick"
|
|
||||||
RowDoubleClick="OnRowDoubleClick"
|
|
||||||
CustomizeElement="OnCustomizeElement"
|
|
||||||
LayoutAutoLoading="OnCompletedGridLayoutAutoLoading"
|
|
||||||
LayoutAutoSaving="OnCompletedGridLayoutAutoSaving">
|
|
||||||
<Columns>
|
|
||||||
<DxGridDataColumn FieldName="Id" Caption="ID"
|
|
||||||
SortIndex="0"
|
|
||||||
SortOrder="GridColumnSortOrder.Descending">
|
|
||||||
<CellDisplayTemplate Context="cellContext">
|
|
||||||
@((cellContext.DataItem as EnvelopeDto)?.Id)
|
|
||||||
</CellDisplayTemplate>
|
|
||||||
</DxGridDataColumn>
|
|
||||||
<DxGridDataColumn FieldName="Title" Caption="Titel">
|
|
||||||
<CellDisplayTemplate Context="cellContext">
|
|
||||||
<strong>@((cellContext.DataItem as EnvelopeDto)?.Title)</strong>
|
|
||||||
</CellDisplayTemplate>
|
|
||||||
</DxGridDataColumn>
|
|
||||||
<DxGridDataColumn FieldName="Status" Caption="Status" ExportEnabled="false">
|
|
||||||
<CellDisplayTemplate Context="cellContext">
|
|
||||||
@{
|
|
||||||
var envelope = cellContext.DataItem as EnvelopeDto;
|
|
||||||
if (envelope != null)
|
|
||||||
{
|
|
||||||
var statusInfo = GetStatusInfo(envelope.Status);
|
|
||||||
<div class="status-badge status-badge--@statusInfo.CssClass">
|
|
||||||
<span class="status-dot status-dot--@statusInfo.DotColor"></span>
|
|
||||||
@statusInfo.Label
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</CellDisplayTemplate>
|
|
||||||
</DxGridDataColumn>
|
|
||||||
<DxGridDataColumn FieldName="EnvelopeReceivers" Caption="Empfänger" ExportEnabled="false">
|
|
||||||
<CellDisplayTemplate Context="cellContext">
|
|
||||||
@{
|
|
||||||
var envelope = cellContext.DataItem as EnvelopeDto;
|
|
||||||
if (envelope != null)
|
|
||||||
{
|
|
||||||
var receivers = envelope.EnvelopeReceivers?.ToList() ?? [];
|
|
||||||
var signed = receivers.Count(r => r.Signed);
|
|
||||||
var total = receivers.Count;
|
|
||||||
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
|
||||||
<span style="font-size: 0.875rem; color: #6b7280;">
|
|
||||||
@signed / @total unterschrieben
|
|
||||||
</span>
|
|
||||||
@if (total > 0)
|
|
||||||
{
|
|
||||||
<div style="flex: 1; min-width: 60px; max-width: 120px; height: 6px; background: #e5e7eb; border-radius: 3px; overflow: hidden;">
|
|
||||||
<div style="height: 100%; background: linear-gradient(90deg, #81c784 0%, #66bb6a 100%); width: @((signed * 100.0 / total).ToString("F0"))%;"></div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</CellDisplayTemplate>
|
|
||||||
</DxGridDataColumn>
|
|
||||||
<DxGridDataColumn FieldName="EnvelopeTypeTitle" Caption="Typ"
|
|
||||||
Width="120px" AllowSort="true" />
|
|
||||||
<DxGridDataColumn FieldName="AddedWhen" Caption="Erstellt"
|
|
||||||
DisplayFormat="{0:dd.MM.yyyy}" Width="110px" AllowSort="true" />
|
|
||||||
<DxGridDataColumn FieldName="ChangedWhen" Caption="Geändert am"
|
|
||||||
DisplayFormat="{0:dd.MM.yyyy}" Width="110px" AllowSort="true" />
|
|
||||||
</Columns>
|
|
||||||
<DetailRowTemplate Context="detailContext">
|
|
||||||
<div style="padding: 1rem; background: #f9fafb;">
|
|
||||||
<h6 style="font-weight: 600; color: #374151; margin-bottom: 0.75rem;">Empfänger</h6>
|
|
||||||
@{
|
@{
|
||||||
var envelope = detailContext.DataItem as EnvelopeDto;
|
var historyEntries = envelope is null
|
||||||
if (envelope?.EnvelopeReceivers?.Any() == true)
|
? Enumerable.Empty<HistoryDto>()
|
||||||
|
: GetOrderedHistories(envelope);
|
||||||
|
if (historyEntries.Any())
|
||||||
{
|
{
|
||||||
<div style="display: flex; flex-direction: column; gap: 0.5rem;">
|
<div class="sender-history-list">
|
||||||
@foreach (var receiver in envelope.EnvelopeReceivers)
|
@foreach (var history in historyEntries)
|
||||||
{
|
{
|
||||||
<div style="display: flex; align-items: center; gap: 1rem; padding: 0.5rem; background: white; border-radius: 6px; border: 1px solid #e5e7eb;">
|
var statusInfo = GetStatusInfo(history.Status);
|
||||||
<span class="receiver-badge receiver-badge--@(receiver.Signed ? "signed" : "unsigned")" style="min-width: 100px;">
|
<div class="sender-history-item">
|
||||||
@if (receiver.Signed)
|
<div class="sender-history-item__left">
|
||||||
{
|
<div class="status-badge status-badge--@statusInfo.CssClass">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
|
<span class="status-dot status-dot--@statusInfo.DotColor"></span>
|
||||||
<path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z" />
|
@statusInfo.Label
|
||||||
</svg>
|
</div>
|
||||||
<span>Unterschrieben</span>
|
<span class="sender-history-item__actor">@GetHistoryActor(history)</span>
|
||||||
}
|
</div>
|
||||||
else
|
<div class="sender-history-item__right">
|
||||||
{
|
@GetHistoryDate(history).ToString("dd.MM.yyyy HH:mm")
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
|
|
||||||
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
|
|
||||||
<path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z" />
|
|
||||||
</svg>
|
|
||||||
<span>Ausstehend</span>
|
|
||||||
}
|
|
||||||
</span>
|
|
||||||
<div style="flex: 1; font-size: 0.875rem;">
|
|
||||||
<strong style="color: #1f2937;">@receiver.Name</strong>
|
|
||||||
<span style="color: #6b7280; margin-left: 0.5rem;">@receiver.Receiver?.EmailAddress</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -448,22 +395,22 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<p style="color: #9ca3af; font-size: 0.875rem; margin: 0;">Keine Empfänger</p>
|
<p style="color: #9ca3af; font-size: 0.875rem; margin: 0;">@L("Sender_NoHistory", "Keine Historie vorhanden")</p>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</DetailRowTemplate>
|
</DetailRowTemplate>
|
||||||
</DxGrid>
|
</DxGrid>
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@* ── PDF Vorschau Popup ────────────────────────────────────────────────── *@
|
@* ── PDF Vorschau Popup ────────────────────────────────────────────────── *@
|
||||||
<DxPopup @bind-Visible="@_previewVisible"
|
<DxPopup @bind-Visible="@_previewVisible"
|
||||||
HeaderText="@($"Vorschau — {_selectedEnvelope?.Title} (#{_selectedEnvelope?.Id})")"
|
HeaderText="@($"{L("Preview", "Vorschau")} — {_selectedEnvelope?.Title} (#{_selectedEnvelope?.Id})")"
|
||||||
Width="80vw"
|
Width="80vw"
|
||||||
MaxWidth="80vw"
|
MaxWidth="80vw"
|
||||||
Height="90vh"
|
Height="90vh"
|
||||||
@@ -486,9 +433,9 @@
|
|||||||
<div class="sender-preview-state-wrap">
|
<div class="sender-preview-state-wrap">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div class="spinner-border mb-3" style="width:3rem;height:3rem;" role="status">
|
<div class="spinner-border mb-3" style="width:3rem;height:3rem;" role="status">
|
||||||
<span class="visually-hidden">Lädt...</span>
|
<span class="visually-hidden">@L("Loading", "Lädt...")</span>
|
||||||
</div>
|
</div>
|
||||||
<p style="color:#6b7280;font-size:0.875rem;">Dokument wird geladen…</p>
|
<p style="color:#6b7280;font-size:0.875rem;">@L("DocumentLoading", "Dokument wird geladen…")</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -503,7 +450,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p class="sender-preview-error-title">Dokument konnte nicht geladen werden</p>
|
<p class="sender-preview-error-title">@L("DocumentCouldNotBeLoaded", "Dokument konnte nicht geladen werden")</p>
|
||||||
<p class="sender-preview-error-msg">@_previewErrorMessage</p>
|
<p class="sender-preview-error-msg">@_previewErrorMessage</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -522,7 +469,7 @@
|
|||||||
</DxPopup>
|
</DxPopup>
|
||||||
|
|
||||||
<DxPopup @bind-Visible="@_deletePopupVisible"
|
<DxPopup @bind-Visible="@_deletePopupVisible"
|
||||||
HeaderText="Umschlag löschen"
|
HeaderText="@L("Sender_DeleteEnvelope_Header", "Umschlag löschen")"
|
||||||
Width="560px"
|
Width="560px"
|
||||||
MaxWidth="95vw"
|
MaxWidth="95vw"
|
||||||
ShowFooter="true"
|
ShowFooter="true"
|
||||||
@@ -534,16 +481,16 @@
|
|||||||
<BodyContentTemplate>
|
<BodyContentTemplate>
|
||||||
<div style="display:flex;flex-direction:column;gap:0.75rem;">
|
<div style="display:flex;flex-direction:column;gap:0.75rem;">
|
||||||
<p style="margin:0;color:#374151;font-size:0.9rem;line-height:1.45;">
|
<p style="margin:0;color:#374151;font-size:0.9rem;line-height:1.45;">
|
||||||
Bitte geben Sie einen Grund an, warum der Umschlag gelöscht/zurückgezogen werden soll.
|
@L("Sender_DeleteEnvelope_ReasonPrompt", "Bitte geben Sie einen Grund an, warum der Umschlag gelöscht/zurückgezogen werden soll.")
|
||||||
</p>
|
</p>
|
||||||
@if (_selectedEnvelope is not null)
|
@if (_selectedEnvelope is not null)
|
||||||
{
|
{
|
||||||
<p style="margin:0;color:#6b7280;font-size:0.825rem;">
|
<p style="margin:0;color:#6b7280;font-size:0.825rem;">
|
||||||
Umschlag: <strong>#@_selectedEnvelope.Id - @_selectedEnvelope.Title</strong>
|
@L("Envelope", "Umschlag"): <strong>#@_selectedEnvelope.Id - @_selectedEnvelope.Title</strong>
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
<DxMemo @bind-Text="_deleteReason"
|
<DxMemo @bind-Text="_deleteReason"
|
||||||
NullText="Begründung eingeben..."
|
NullText="@L("Sender_DeleteEnvelope_ReasonPlaceholder", "Begründung eingeben...")"
|
||||||
Rows="5"
|
Rows="5"
|
||||||
MaxLength="2000"
|
MaxLength="2000"
|
||||||
CssClass="w-100" />
|
CssClass="w-100" />
|
||||||
@@ -557,11 +504,11 @@
|
|||||||
</BodyContentTemplate>
|
</BodyContentTemplate>
|
||||||
<FooterContentTemplate>
|
<FooterContentTemplate>
|
||||||
<div style="display:flex;justify-content:flex-end;gap:0.5rem;width:100%;">
|
<div style="display:flex;justify-content:flex-end;gap:0.5rem;width:100%;">
|
||||||
<DxButton Text="Abbrechen"
|
<DxButton Text="@L("Cancel", "Abbrechen")"
|
||||||
RenderStyle="ButtonRenderStyle.Secondary"
|
RenderStyle="ButtonRenderStyle.Secondary"
|
||||||
Click="CloseDeletePopup"
|
Click="CloseDeletePopup"
|
||||||
Enabled="@(!_isDeletingEnvelope)" />
|
Enabled="@(!_isDeletingEnvelope)" />
|
||||||
<DxButton Text="Löschen"
|
<DxButton Text="@L("Delete", "Löschen")"
|
||||||
RenderStyle="ButtonRenderStyle.Danger"
|
RenderStyle="ButtonRenderStyle.Danger"
|
||||||
Click="ConfirmDeleteEnvelopeAsync"
|
Click="ConfirmDeleteEnvelopeAsync"
|
||||||
Enabled="@(!_isDeletingEnvelope)" />
|
Enabled="@(!_isDeletingEnvelope)" />
|
||||||
@@ -578,8 +525,7 @@
|
|||||||
private bool _isLoading = true;
|
private bool _isLoading = true;
|
||||||
private bool _isLoggingOut = false;
|
private bool _isLoggingOut = false;
|
||||||
private string? _errorMessage;
|
private string? _errorMessage;
|
||||||
private DxGrid? _gridActive;
|
private DxGrid? _grid;
|
||||||
private DxGrid? _gridCompleted;
|
|
||||||
|
|
||||||
// Logo config loaded from /api/CustomImages after auth
|
// Logo config loaded from /api/CustomImages after auth
|
||||||
private EnvelopeGenerator.Server.Client.Services.CustomImageDto? _appLogo;
|
private EnvelopeGenerator.Server.Client.Services.CustomImageDto? _appLogo;
|
||||||
@@ -602,10 +548,15 @@
|
|||||||
private string _deleteReason = string.Empty;
|
private string _deleteReason = string.Empty;
|
||||||
private string? _deleteErrorMessage;
|
private string? _deleteErrorMessage;
|
||||||
|
|
||||||
// Stable keys used to store/restore each grid's layout in UserPreferences.GridLayouts
|
// Stable keys used to store/restore each tab's layout in UserPreferences.GridLayouts
|
||||||
private const string GridKeyActive = "sender.active-envelopes";
|
private const string GridKeyActive = "sender.active-envelopes";
|
||||||
private const string GridKeyCompleted = "sender.completed-envelopes";
|
private const string GridKeyCompleted = "sender.completed-envelopes";
|
||||||
|
|
||||||
|
private IEnumerable<EnvelopeDto> CurrentGridData =>
|
||||||
|
(_activeTab == "completed" ? _completedEnvelopes : _activeEnvelopes) ?? Enumerable.Empty<EnvelopeDto>();
|
||||||
|
|
||||||
|
private string CurrentGridKey => _activeTab == "completed" ? GridKeyCompleted : GridKeyActive;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
var hasAccess = await AuthService.CheckSenderAccessAsync();
|
var hasAccess = await AuthService.CheckSenderAccessAsync();
|
||||||
@@ -670,7 +621,7 @@
|
|||||||
|
|
||||||
async Task ExportSelectedFormatAsync()
|
async Task ExportSelectedFormatAsync()
|
||||||
{
|
{
|
||||||
var grid = _activeTab == "active" ? _gridActive : _gridCompleted;
|
var grid = _grid;
|
||||||
if (grid is null)
|
if (grid is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -757,7 +708,7 @@
|
|||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(_deleteReason))
|
if (string.IsNullOrWhiteSpace(_deleteReason))
|
||||||
{
|
{
|
||||||
_deleteErrorMessage = "Bitte geben Sie einen Löschgrund an.";
|
_deleteErrorMessage = L("Sender_DeleteEnvelope_ReasonRequired", "Bitte geben Sie einen Löschgrund an.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -806,21 +757,124 @@
|
|||||||
|
|
||||||
(string Label, string CssClass, string DotColor) GetStatusInfo(EnvelopeStatus status)
|
(string Label, string CssClass, string DotColor) GetStatusInfo(EnvelopeStatus status)
|
||||||
{
|
{
|
||||||
|
var label = GetStatusLabel(status);
|
||||||
|
|
||||||
return status switch
|
return status switch
|
||||||
{
|
{
|
||||||
EnvelopeStatus.EnvelopePartlySigned => ("Teilweise unterschrieben", "partly-signed", "green"),
|
EnvelopeStatus.EnvelopePartlySigned => (label, "partly-signed", "green"),
|
||||||
EnvelopeStatus.EnvelopeQueued => ("In Warteschlange", "queued", "orange"),
|
EnvelopeStatus.EnvelopeCompletelySigned => (label, "completed", "green"),
|
||||||
EnvelopeStatus.EnvelopeSent => ("Gesendet", "sent", "orange"),
|
EnvelopeStatus.DocumentSigned => (label, "partly-signed", "green"),
|
||||||
EnvelopeStatus.EnvelopeCompletelySigned => ("Vollständig unterschrieben", "completed", "green"),
|
EnvelopeStatus.MessageCompletionSent => (label, "completed", "green"),
|
||||||
EnvelopeStatus.EnvelopeDeleted => ("Gelöscht", "deleted", "red"),
|
|
||||||
EnvelopeStatus.EnvelopeRejected => ("Abgelehnt", "rejected", "red"),
|
EnvelopeStatus.EnvelopeQueued => (label, "queued", "orange"),
|
||||||
EnvelopeStatus.EnvelopeWithdrawn => ("Zurückgezogen", "withdrawn", "red"),
|
EnvelopeStatus.EnvelopeSent => (label, "sent", "orange"),
|
||||||
EnvelopeStatus.EnvelopeCreated => ("Erstellt", "created", "blue"),
|
EnvelopeStatus.MessageInvitationSent => (label, "queued", "orange"),
|
||||||
EnvelopeStatus.EnvelopeSaved => ("Gespeichert", "saved", "blue"),
|
EnvelopeStatus.MessageAccessCodeSent => (label, "queued", "orange"),
|
||||||
_ => ("Unbekannt", "unknown", "blue")
|
EnvelopeStatus.MessageConfirmationSent => (label, "queued", "orange"),
|
||||||
|
|
||||||
|
EnvelopeStatus.EnvelopeDeleted => (label, "deleted", "red"),
|
||||||
|
EnvelopeStatus.EnvelopeRejected => (label, "rejected", "red"),
|
||||||
|
EnvelopeStatus.EnvelopeWithdrawn => (label, "withdrawn", "red"),
|
||||||
|
EnvelopeStatus.DocumentRejected => (label, "rejected", "red"),
|
||||||
|
EnvelopeStatus.AccessCodeIncorrect => (label, "rejected", "red"),
|
||||||
|
EnvelopeStatus.MessageDeletionSent => (label, "deleted", "red"),
|
||||||
|
|
||||||
|
_ => (label, "created", "blue")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string GetStatusLabel(EnvelopeStatus status)
|
||||||
|
{
|
||||||
|
var key = status.ToString();
|
||||||
|
var localized = ModelLocalizer[key];
|
||||||
|
return localized.ResourceNotFound ? key : localized.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerable<HistoryDto> GetOrderedHistories(EnvelopeDto envelope)
|
||||||
|
{
|
||||||
|
return (envelope.Histories ?? Enumerable.Empty<HistoryDto>())
|
||||||
|
.OrderByDescending(GetHistoryDate)
|
||||||
|
.Take(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
IReadOnlyList<StatusFilterItem> GetStatusFilterItems(IEnumerable<EnvelopeDto>? source)
|
||||||
|
{
|
||||||
|
return (source ?? Enumerable.Empty<EnvelopeDto>())
|
||||||
|
.Select(x => x.Status)
|
||||||
|
.Distinct()
|
||||||
|
.OrderBy(x => x)
|
||||||
|
.Select(x => new StatusFilterItem(x, GetStatusLabel(x)))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetSignedReceiverCount(EnvelopeDto envelope)
|
||||||
|
{
|
||||||
|
var receivers = envelope.EnvelopeReceivers?.ToList() ?? [];
|
||||||
|
if (receivers.Count == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return receivers.Count(r => IsReceiverSigned(envelope, r));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsReceiverSigned(EnvelopeDto envelope, EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiver.EnvelopeReceiverDto receiver)
|
||||||
|
{
|
||||||
|
var status = (EnvelopeStatus)envelope.Status;
|
||||||
|
if (status is EnvelopeStatus.EnvelopeCompletelySigned or EnvelopeStatus.MessageCompletionSent)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
var histories = envelope.Histories?.Where(h => h.Status == EnvelopeStatus.DocumentSigned).ToList() ?? [];
|
||||||
|
if (histories.Count == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (histories.Any(h => h.Receiver?.Id == receiver.ReceiverId))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
var receiverEmail = receiver.Receiver?.EmailAddress;
|
||||||
|
if (!string.IsNullOrWhiteSpace(receiverEmail) &&
|
||||||
|
histories.Any(h => string.Equals(h.UserReference, receiverEmail, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(receiver.Name) &&
|
||||||
|
histories.Any(h => string.Equals(h.UserReference, receiver.Name, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTime GetHistoryDate(HistoryDto history)
|
||||||
|
{
|
||||||
|
return history.ActionDate ?? history.AddedWhen;
|
||||||
|
}
|
||||||
|
|
||||||
|
string GetHistoryActor(HistoryDto history)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(history.UserReference))
|
||||||
|
return history.UserReference;
|
||||||
|
|
||||||
|
return L("System", "System");
|
||||||
|
}
|
||||||
|
|
||||||
|
string L(string key, string fallback)
|
||||||
|
{
|
||||||
|
var value = UiLocalizer[key];
|
||||||
|
return value.ResourceNotFound ? fallback : value.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task CopyAccessCodeAsync(string? accessCode)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(accessCode))
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await JSRuntime.InvokeVoidAsync("navigator.clipboard.writeText", accessCode);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
await JSRuntime.InvokeVoidAsync("console.warn", "[Sender] Failed to copy access code to clipboard.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void OnCustomizeElement(GridCustomizeElementEventArgs e)
|
void OnCustomizeElement(GridCustomizeElementEventArgs e)
|
||||||
{
|
{
|
||||||
// Future: Add custom row coloring based on status if needed
|
// Future: Add custom row coloring based on status if needed
|
||||||
@@ -872,7 +926,7 @@
|
|||||||
var user = EnvelopeAuthService.GetCurrentUser();
|
var user = EnvelopeAuthService.GetCurrentUser();
|
||||||
if (user?.Identity is null || !user.Identity.IsAuthenticated)
|
if (user?.Identity is null || !user.Identity.IsAuthenticated)
|
||||||
{
|
{
|
||||||
_previewErrorMessage = "Nicht authentifiziert. Bitte melden Sie sich erneut an.";
|
_previewErrorMessage = L("NotAuthenticatedPleaseLoginAgain", "Nicht authentifiziert. Bitte melden Sie sich erneut an.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -883,12 +937,16 @@
|
|||||||
}
|
}
|
||||||
catch (DigitalData.Core.Exceptions.NotFoundException)
|
catch (DigitalData.Core.Exceptions.NotFoundException)
|
||||||
{
|
{
|
||||||
_previewErrorMessage = $"Das Dokument für Umschlag #{envelope.Id} wurde nicht gefunden oder Sie haben keinen Zugriff darauf.";
|
_previewErrorMessage = string.Format(
|
||||||
|
L("Sender_PreviewDocumentNotFound", "Das Dokument für Umschlag #{0} wurde nicht gefunden oder Sie haben keinen Zugriff darauf."),
|
||||||
|
envelope.Id);
|
||||||
Logger.LogWarning("Preview: document not found for envelope {EnvelopeId}", envelope.Id);
|
Logger.LogWarning("Preview: document not found for envelope {EnvelopeId}", envelope.Id);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_previewErrorMessage = $"Fehler beim Laden des Dokuments: {ex.Message}";
|
_previewErrorMessage = string.Format(
|
||||||
|
L("ErrorLoadingDocument_WithReason", "Fehler beim Laden des Dokuments: {0}"),
|
||||||
|
ex.Message);
|
||||||
Logger.LogError(ex, "Preview: failed to load document for envelope {EnvelopeId}", envelope.Id);
|
Logger.LogError(ex, "Preview: failed to load document for envelope {EnvelopeId}", envelope.Id);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -919,21 +977,21 @@
|
|||||||
// UserPreferencesService.GetAsync() uses the 1h in-memory cache → 0 HTTP on restore.
|
// UserPreferencesService.GetAsync() uses the 1h in-memory cache → 0 HTTP on restore.
|
||||||
// UserPreferencesService.SaveGridLayoutAsync() merges the single key → 1 PUT.
|
// UserPreferencesService.SaveGridLayoutAsync() merges the single key → 1 PUT.
|
||||||
|
|
||||||
async Task OnActiveGridLayoutAutoLoading(GridPersistentLayoutEventArgs e)
|
async Task OnGridLayoutAutoLoading(GridPersistentLayoutEventArgs e)
|
||||||
{
|
{
|
||||||
// Use cached preferences only — avoid HTTP during grid initialization.
|
// Use cached preferences only — avoid HTTP during grid initialization.
|
||||||
// If cache is empty (LoadFromServerAsync failed), skip layout restore silently.
|
// If cache is empty (LoadFromServerAsync failed), skip layout restore silently.
|
||||||
var prefs = UserPreferencesService.GetCached();
|
var prefs = UserPreferencesService.GetCached();
|
||||||
if (prefs is not null && prefs.GridLayouts.TryGetValue(GridKeyActive, out var json))
|
if (prefs is not null && prefs.GridLayouts.TryGetValue(CurrentGridKey, out var json))
|
||||||
e.Layout = JsonSerializer.Deserialize<GridPersistentLayout>(json);
|
e.Layout = JsonSerializer.Deserialize<GridPersistentLayout>(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task OnActiveGridLayoutAutoSaving(GridPersistentLayoutEventArgs e)
|
async Task OnGridLayoutAutoSaving(GridPersistentLayoutEventArgs e)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var json = JsonSerializer.Serialize(e.Layout);
|
var json = JsonSerializer.Serialize(e.Layout);
|
||||||
await UserPreferencesService.SaveGridLayoutAsync(GridKeyActive, json);
|
await UserPreferencesService.SaveGridLayoutAsync(CurrentGridKey, json);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -941,16 +999,5 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task OnCompletedGridLayoutAutoLoading(GridPersistentLayoutEventArgs e)
|
private sealed record StatusFilterItem(EnvelopeStatus Value, string Text);
|
||||||
{
|
|
||||||
var prefs = UserPreferencesService.GetCached();
|
|
||||||
if (prefs is not null && prefs.GridLayouts.TryGetValue(GridKeyCompleted, out var json))
|
|
||||||
e.Layout = JsonSerializer.Deserialize<GridPersistentLayout>(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task OnCompletedGridLayoutAutoSaving(GridPersistentLayoutEventArgs e)
|
|
||||||
{
|
|
||||||
var json = JsonSerializer.Serialize(e.Layout);
|
|
||||||
await UserPreferencesService.SaveGridLayoutAsync(GridKeyCompleted, json);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,28 @@
|
|||||||
@page "/Error"
|
@page "/Error"
|
||||||
@using System.Diagnostics
|
@using System.Diagnostics
|
||||||
|
@using EnvelopeGenerator.Application.Resources
|
||||||
|
@using Microsoft.Extensions.Localization
|
||||||
|
@inject IStringLocalizer<Resource> UiLocalizer
|
||||||
|
|
||||||
<PageTitle>Error</PageTitle>
|
<PageTitle>@L("Error", "Error")</PageTitle>
|
||||||
|
|
||||||
<h1 class="text-danger">Error.</h1>
|
<h1 class="text-danger">@L("Error", "Error").</h1>
|
||||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
<h2 class="text-danger">@L("ErrorPage_ProcessingRequest", "An error occurred while processing your request.")</h2>
|
||||||
|
|
||||||
@if (ShowRequestId)
|
@if (ShowRequestId)
|
||||||
{
|
{
|
||||||
<p>
|
<p>
|
||||||
<strong>Request ID:</strong> <code>@RequestId</code>
|
<strong>@L("ErrorPage_RequestId", "Request ID"):</strong> <code>@RequestId</code>
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
|
|
||||||
<h3>Development Mode</h3>
|
<h3>@L("ErrorPage_DevelopmentMode", "Development Mode")</h3>
|
||||||
<p>
|
<p>
|
||||||
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
|
@L("ErrorPage_DevelopmentMode_Info", "Swapping to Development environment will display more detailed information about the error that occurred.")
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
<strong>@L("ErrorPage_DevelopmentMode_Warning", "The Development environment shouldn't be enabled for deployed applications.")</strong>
|
||||||
It can result in displaying sensitive information from exceptions to end users.
|
@L("ErrorPage_DevelopmentMode_Detail", "It can result in displaying sensitive information from exceptions to end users. For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development and restarting the app.")
|
||||||
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
|
|
||||||
and restarting the app.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@code{
|
@code{
|
||||||
@@ -33,4 +34,10 @@
|
|||||||
|
|
||||||
protected override void OnInitialized() =>
|
protected override void OnInitialized() =>
|
||||||
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
|
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
|
||||||
|
|
||||||
|
string L(string key, string fallback)
|
||||||
|
{
|
||||||
|
var value = UiLocalizer[key];
|
||||||
|
return value.ResourceNotFound ? fallback : value.Value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,18 @@
|
|||||||
@using EnvelopeGenerator.Server.Client.Models
|
@using EnvelopeGenerator.Server.Client.Models
|
||||||
@using EnvelopeGenerator.Server.Client.Models.Constants
|
@using EnvelopeGenerator.Server.Client.Models.Constants
|
||||||
@using EnvelopeGenerator.Server.Client.Services
|
@using EnvelopeGenerator.Server.Client.Services
|
||||||
|
@using EnvelopeGenerator.Application.Common.Extensions
|
||||||
@using EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiver
|
@using EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiver
|
||||||
|
@using EnvelopeGenerator.Application.EnvelopeReceivers.Queries
|
||||||
|
@using EnvelopeGenerator.Domain.Constants
|
||||||
@using Microsoft.JSInterop
|
@using Microsoft.JSInterop
|
||||||
@using DevExpress.Blazor
|
@using DevExpress.Blazor
|
||||||
@using System.Drawing
|
@using System.Drawing
|
||||||
@using System.Security.Claims
|
@using System.Security.Claims
|
||||||
|
@using System.ComponentModel.DataAnnotations
|
||||||
@using Microsoft.Extensions.Caching.Memory
|
@using Microsoft.Extensions.Caching.Memory
|
||||||
|
@using MediatR
|
||||||
|
@using EnvelopeGenerator.Server.Extensions
|
||||||
@inject NavigationManager Navigation
|
@inject NavigationManager Navigation
|
||||||
@inject IJSRuntime JSRuntime
|
@inject IJSRuntime JSRuntime
|
||||||
@inject EnvelopeGenerator.Server.Client.Services.AuthService AuthService
|
@inject EnvelopeGenerator.Server.Client.Services.AuthService AuthService
|
||||||
@@ -19,6 +25,7 @@
|
|||||||
@inject AppVersionService AppVersion
|
@inject AppVersionService AppVersion
|
||||||
@inject IMemoryCache MemoryCache
|
@inject IMemoryCache MemoryCache
|
||||||
@inject ILogger<ReceiverPage> Logger
|
@inject ILogger<ReceiverPage> Logger
|
||||||
|
@inject IMediator Mediator
|
||||||
@implements IDisposable
|
@implements IDisposable
|
||||||
|
|
||||||
<link href="_content/DevExpress.Blazor.Themes/blazing-berry.bs5.min.css" rel="stylesheet" />
|
<link href="_content/DevExpress.Blazor.Themes/blazing-berry.bs5.min.css" rel="stylesheet" />
|
||||||
@@ -125,6 +132,32 @@
|
|||||||
<span class="pdf-toolbar__btn-text">Unterschreiben</span>
|
<span class="pdf-toolbar__btn-text">Unterschreiben</span>
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@if (!(_envelopeReceiver?.Envelope?.ReadOnly ?? false))
|
||||||
|
{
|
||||||
|
<button class="pdf-toolbar__btn"
|
||||||
|
@onclick="OpenSharePopup"
|
||||||
|
title="Dokument teilen"
|
||||||
|
style="flex-shrink: 0;">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" viewBox="0 0 16 16">
|
||||||
|
<path d="M13.5 1a1.5 1.5 0 1 0 1.415 2H15a1.5 1.5 0 0 0-1.5-2zm-11 5a1.5 1.5 0 1 0 1.415 2H4a1.5 1.5 0 0 0-1.5-2zm11 5a1.5 1.5 0 1 0 1.415 2H15a1.5 1.5 0 0 0-1.5-2z"/>
|
||||||
|
<path d="M2.854 7.646a.5.5 0 0 1 .0-.707l9-5a.5.5 0 1 1 .486.874l-9 5a.5.5 0 0 1-.486-.167zm0 .708a.5.5 0 0 1 .486-.167l9 5a.5.5 0 0 1-.486.874l-9-5a.5.5 0 0 1 0-.707z"/>
|
||||||
|
</svg>
|
||||||
|
<span class="pdf-toolbar__btn-text">Teilen</span>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
|
||||||
|
<button class="pdf-toolbar__btn"
|
||||||
|
@onclick="LogoutAsync"
|
||||||
|
disabled="@_isLoggingOut"
|
||||||
|
title="Abmelden"
|
||||||
|
style="flex-shrink: 0;">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" viewBox="0 0 16 16">
|
||||||
|
<path fill-rule="evenodd" d="M10 15a1 1 0 0 0 1-1v-2a.5.5 0 0 1 1 0v2a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h7a2 2 0 0 1 2 2v2a.5.5 0 0 1-1 0V2a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h7z" />
|
||||||
|
<path fill-rule="evenodd" d="M15.854 8.354a.5.5 0 0 0 0-.708l-3-3a.5.5 0 1 0-.708.708L14.293 7.5H6.5a.5.5 0 0 0 0 1h7.793l-2.147 2.146a.5.5 0 0 0 .708.708l3-3z" />
|
||||||
|
</svg>
|
||||||
|
<span class="pdf-toolbar__btn-text">@(_isLoggingOut ? "Abmelden..." : "Abmelden")</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -342,6 +375,57 @@
|
|||||||
</FooterContentTemplate>
|
</FooterContentTemplate>
|
||||||
</DxPopup>
|
</DxPopup>
|
||||||
|
|
||||||
|
<DxPopup @bind-Visible="_sharePopupVisible"
|
||||||
|
HeaderText="Dokument teilen"
|
||||||
|
Width="520px"
|
||||||
|
MaxWidth="95vw"
|
||||||
|
ShowFooter="true"
|
||||||
|
CloseOnOutsideClick="true">
|
||||||
|
<BodyContentTemplate>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label" for="rp-share-mail" style="font-size: 0.9rem; font-weight: 500;">E-Mail</label>
|
||||||
|
<input id="rp-share-mail"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="user@mail.com"
|
||||||
|
value="@_shareReceiverMail"
|
||||||
|
@oninput="args => _shareReceiverMail = args.Value?.ToString() ?? string.Empty" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-1">
|
||||||
|
<label class="form-label" for="rp-share-date" style="font-size: 0.9rem; font-weight: 500;">Gultig bis</label>
|
||||||
|
<input id="rp-share-date"
|
||||||
|
class="form-control"
|
||||||
|
type="date"
|
||||||
|
min="@DateTime.Today.AddDays(1).ToString("yyyy-MM-dd")"
|
||||||
|
max="@DateTime.Today.AddDays(90).ToString("yyyy-MM-dd")"
|
||||||
|
value="@_shareDateValid.ToString("yyyy-MM-dd")"
|
||||||
|
@oninput="OnShareDateChanged" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (!string.IsNullOrWhiteSpace(_shareMessage))
|
||||||
|
{
|
||||||
|
<div class="mt-3 alert @(_shareMessageIsError ? "alert-danger" : "alert-success") mb-0" role="alert">
|
||||||
|
@_shareMessage
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</BodyContentTemplate>
|
||||||
|
<FooterContentTemplate>
|
||||||
|
<div class="d-flex justify-content-end gap-2 w-100">
|
||||||
|
<button class="btn btn-outline-secondary"
|
||||||
|
@onclick="CloseSharePopup"
|
||||||
|
disabled="@_isSharing">
|
||||||
|
Schliessen
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-primary"
|
||||||
|
@onclick="ShareEnvelopeAsync"
|
||||||
|
disabled="@_isSharing"
|
||||||
|
style="background: linear-gradient(135deg, #4F46E5 0%, #4338CA 100%); border: none;">
|
||||||
|
@(_isSharing ? "Senden..." : "Senden")
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</FooterContentTemplate>
|
||||||
|
</DxPopup>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
// ----- Constants -----
|
// ----- Constants -----
|
||||||
const string SignatureTabDraw = "draw";
|
const string SignatureTabDraw = "draw";
|
||||||
@@ -386,6 +470,114 @@
|
|||||||
string _signerFullName = string.Empty;
|
string _signerFullName = string.Empty;
|
||||||
string _signerPosition = string.Empty;
|
string _signerPosition = string.Empty;
|
||||||
string _signaturePlace = string.Empty;
|
string _signaturePlace = string.Empty;
|
||||||
|
bool _isLoggingOut = false;
|
||||||
|
|
||||||
|
// ----- Share popup state -----
|
||||||
|
bool _sharePopupVisible = false;
|
||||||
|
bool _isSharing = false;
|
||||||
|
string _shareReceiverMail = string.Empty;
|
||||||
|
DateTime _shareDateValid = DateTime.Today.AddDays(7);
|
||||||
|
string? _shareMessage;
|
||||||
|
bool _shareMessageIsError = false;
|
||||||
|
|
||||||
|
async Task LogoutAsync()
|
||||||
|
{
|
||||||
|
if (_isLoggingOut || string.IsNullOrWhiteSpace(EnvelopeKey))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_isLoggingOut = true;
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await AuthService.LogoutEnvelopeReceiverAsync(EnvelopeKey);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogWarning(ex, "Receiver logout failed for {EnvelopeKey}", EnvelopeKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
Navigation.NavigateTo($"/envelope/login/{Uri.EscapeDataString(EnvelopeKey)}", forceLoad: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenSharePopup()
|
||||||
|
{
|
||||||
|
_shareReceiverMail = string.Empty;
|
||||||
|
_shareDateValid = DateTime.Today.AddDays(7);
|
||||||
|
_shareMessage = null;
|
||||||
|
_shareMessageIsError = false;
|
||||||
|
_sharePopupVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CloseSharePopup()
|
||||||
|
{
|
||||||
|
if (_isSharing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_sharePopupVisible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnShareDateChanged(Microsoft.AspNetCore.Components.ChangeEventArgs args)
|
||||||
|
{
|
||||||
|
if (DateTime.TryParse(args.Value?.ToString(), out var parsedDate))
|
||||||
|
_shareDateValid = parsedDate.Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task ShareEnvelopeAsync()
|
||||||
|
{
|
||||||
|
if (_isSharing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var email = _shareReceiverMail.Trim();
|
||||||
|
if (string.IsNullOrWhiteSpace(email) || !new EmailAddressAttribute().IsValid(email))
|
||||||
|
{
|
||||||
|
_shareMessage = "Bitte geben Sie eine gultige E-Mail-Adresse ein.";
|
||||||
|
_shareMessageIsError = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var minDate = DateTime.Today.AddDays(1);
|
||||||
|
var maxDate = DateTime.Today.AddDays(90);
|
||||||
|
if (_shareDateValid.Date < minDate || _shareDateValid.Date > maxDate)
|
||||||
|
{
|
||||||
|
_shareMessage = "Bitte wahlen Sie ein Datum zwischen morgen und 90 Tagen.";
|
||||||
|
_shareMessageIsError = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isSharing = true;
|
||||||
|
_shareMessage = null;
|
||||||
|
_shareMessageIsError = false;
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var shared = await AuthService.ShareEnvelopeReadOnlyAsync(EnvelopeKey!, email, _shareDateValid.Date);
|
||||||
|
if (shared)
|
||||||
|
{
|
||||||
|
_shareMessage = "Dokument wurde erfolgreich als Nur-Lesen freigegeben.";
|
||||||
|
_shareMessageIsError = false;
|
||||||
|
_shareReceiverMail = string.Empty;
|
||||||
|
_shareDateValid = DateTime.Today.AddDays(7);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_shareMessage = "Freigabe fehlgeschlagen. Bitte versuchen Sie es erneut.";
|
||||||
|
_shareMessageIsError = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogWarning(ex, "Failed to share envelope in read-only mode for {EnvelopeKey}", EnvelopeKey);
|
||||||
|
_shareMessage = "Freigabe fehlgeschlagen. Bitte versuchen Sie es erneut.";
|
||||||
|
_shareMessageIsError = true;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_isSharing = false;
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ----- Lifecycle -----
|
// ----- Lifecycle -----
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
@@ -397,6 +589,12 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (EnvelopeKey.TryDecode(out var decodedKeys) && decodedKeys.GetEncodeType() == EncodeType.EnvelopeReceiverReadOnly)
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo($"/envelope/readonly/{Uri.EscapeDataString(EnvelopeKey)}", forceLoad: true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Authorization — same pattern as EnvelopeReceiverPage
|
// Authorization — same pattern as EnvelopeReceiverPage
|
||||||
_receiverUser = await ReceiverAuthorizationService.AuthorizeAsync(EnvelopeKey);
|
_receiverUser = await ReceiverAuthorizationService.AuthorizeAsync(EnvelopeKey);
|
||||||
if (_receiverUser is null)
|
if (_receiverUser is null)
|
||||||
@@ -405,6 +603,12 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (await Mediator.IsSignedAsync(_receiverUser.EnvelopeUuid(), _receiverUser.ReceiverSignature()))
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo("/envelope/signed-result", forceLoad: true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Load PDF bytes via MediatR (uses authenticated user's claims)
|
// Load PDF bytes via MediatR (uses authenticated user's claims)
|
||||||
|
|||||||
@@ -0,0 +1,188 @@
|
|||||||
|
@page "/envelope/readonly/{ReadOnlyKey}"
|
||||||
|
@rendermode InteractiveServer
|
||||||
|
@using DevExpress.Blazor.Reporting
|
||||||
|
@using DevExpress.XtraReports.UI
|
||||||
|
@using EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiverReadOnly
|
||||||
|
@using EnvelopeGenerator.Application.Common.Extensions
|
||||||
|
@using EnvelopeGenerator.Application.Common.Interfaces.Services
|
||||||
|
@using EnvelopeGenerator.Domain.Constants
|
||||||
|
@inject EnvelopeGenerator.Server.Client.Services.AppVersionService AppVersion
|
||||||
|
@inject ILogger<ReceiverReadOnlyPage> Logger
|
||||||
|
@inject IEnvelopeReceiverReadOnlyService ReadOnlyService
|
||||||
|
@inject IEnvelopeReceiverService EnvelopeReceiverService
|
||||||
|
@inject IEnvelopeHistoryService HistoryService
|
||||||
|
@implements IDisposable
|
||||||
|
|
||||||
|
<link href="_content/DevExpress.Blazor.Themes/blazing-berry.bs5.min.css" rel="stylesheet" />
|
||||||
|
<link href="_content/DevExpress.Blazor.Reporting.Viewer/css/dx-blazor-reporting-components.bs5.css" rel="stylesheet" />
|
||||||
|
<link href="@AppVersion.GetVersionedUrl("css/envelope-viewer.css")" rel="stylesheet" />
|
||||||
|
|
||||||
|
<div class="envelope-viewer-layout">
|
||||||
|
<div class="envelope-action-bar">
|
||||||
|
<div class="envelope-action-bar__inner" style="padding: 0.5rem 1.25rem;">
|
||||||
|
<div style="display:flex; align-items:center; justify-content:space-between; gap:1rem;">
|
||||||
|
<div style="font-weight:600; color:#1f2937;">Dokumentansicht (Nur Lesen)</div>
|
||||||
|
@if (_readOnly is not null)
|
||||||
|
{
|
||||||
|
<div style="font-size:0.8rem; color:#6b7280;">Gultig bis: @_readOnly.DateValid.ToString("dd.MM.yyyy")</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="envelope-content" style="padding:0; overflow:hidden;">
|
||||||
|
@if (_isLoading)
|
||||||
|
{
|
||||||
|
<div class="d-flex justify-content-center align-items-center h-100">
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="spinner-border text-white mb-3" style="width:3.5rem; height:3.5rem;" role="status"></div>
|
||||||
|
<p class="text-white fw-semibold">Dokument wird geladen...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrWhiteSpace(_errorMessage))
|
||||||
|
{
|
||||||
|
<div class="error-container">
|
||||||
|
<div class="alert alert-danger shadow-lg mb-0">
|
||||||
|
<h5 class="mb-2">Dokument nicht verfugbar</h5>
|
||||||
|
<p class="mb-0">@_errorMessage</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else if (_isExpired)
|
||||||
|
{
|
||||||
|
<div class="error-container">
|
||||||
|
<div class="alert alert-warning shadow-lg mb-0">
|
||||||
|
<h5 class="mb-2">Freigabe abgelaufen</h5>
|
||||||
|
<p class="mb-0">Der Zeitraum fur diese Nur-Lesen-Freigabe ist abgelaufen.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else if (_report is not null)
|
||||||
|
{
|
||||||
|
<DxReportViewer @ref="_reportViewer"
|
||||||
|
Report="_report"
|
||||||
|
RootCssClasses="w-100 h-100" />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter] public string? ReadOnlyKey { get; set; }
|
||||||
|
|
||||||
|
bool _isLoading = true;
|
||||||
|
bool _isExpired = false;
|
||||||
|
string? _errorMessage;
|
||||||
|
EnvelopeReceiverReadOnlyDto? _readOnly;
|
||||||
|
|
||||||
|
DxReportViewer? _reportViewer;
|
||||||
|
XtraReport? _report;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(ReadOnlyKey))
|
||||||
|
{
|
||||||
|
_errorMessage = "Freigabe-Schlussel fehlt.";
|
||||||
|
_isLoading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ReadOnlyKey.TryDecode(out var decodedKeys) || decodedKeys.GetEncodeType() != EncodeType.EnvelopeReceiverReadOnly)
|
||||||
|
{
|
||||||
|
_errorMessage = "Ungultiger Freigabe-Link.";
|
||||||
|
_isLoading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var readOnlyId = decodedKeys.ParseReadOnlyId();
|
||||||
|
var readOnlyRes = await ReadOnlyService.ReadByIdAsync(readOnlyId);
|
||||||
|
if (readOnlyRes.IsFailed)
|
||||||
|
{
|
||||||
|
_errorMessage = "Freigabe konnte nicht geladen werden.";
|
||||||
|
_isLoading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_readOnly = readOnlyRes.Data;
|
||||||
|
|
||||||
|
if (DateTime.Now > _readOnly.DateValid)
|
||||||
|
{
|
||||||
|
_isExpired = true;
|
||||||
|
_isLoading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_readOnly.Envelope is null || _readOnly.Receiver is null)
|
||||||
|
{
|
||||||
|
_errorMessage = "Freigabe ist unvollstandig.";
|
||||||
|
_isLoading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var erRes = await EnvelopeReceiverService.ReadByUuidSignatureAsync(_readOnly.Envelope.Uuid, _readOnly.Receiver.Signature);
|
||||||
|
if (erRes.IsFailed)
|
||||||
|
{
|
||||||
|
_errorMessage = "Dokument wurde nicht gefunden.";
|
||||||
|
_isLoading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var envelopeReceiver = erRes.Data;
|
||||||
|
var documentBytes = envelopeReceiver.Envelope?.Documents?.FirstOrDefault()?.ByteData;
|
||||||
|
if (documentBytes is not { Length: > 0 })
|
||||||
|
{
|
||||||
|
_errorMessage = "Dokumentdaten konnten nicht geladen werden.";
|
||||||
|
_isLoading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_report = BuildReport(documentBytes);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var historyResult = await HistoryService.RecordAsync((int)_readOnly.EnvelopeId, _readOnly.AddedWho, EnvelopeStatus.EnvelopeViewed);
|
||||||
|
if (historyResult.IsFailed)
|
||||||
|
Logger.LogWarning("Failed to record EnvelopeViewed history for read-only key {ReadOnlyKey}", ReadOnlyKey);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogWarning(ex, "Failed to record read-only history for {ReadOnlyKey}", ReadOnlyKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError(ex, "Unexpected error in read-only page for {ReadOnlyKey}", ReadOnlyKey);
|
||||||
|
_errorMessage = "Beim Laden ist ein unerwarteter Fehler aufgetreten.";
|
||||||
|
}
|
||||||
|
|
||||||
|
_isLoading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static XtraReport BuildReport(byte[] pdfBytes)
|
||||||
|
{
|
||||||
|
var report = new XtraReport
|
||||||
|
{
|
||||||
|
PaperKind = DevExpress.Drawing.Printing.DXPaperKind.A4,
|
||||||
|
Landscape = false,
|
||||||
|
Margins = new System.Drawing.Printing.Margins(0, 0, 0, 0)
|
||||||
|
};
|
||||||
|
|
||||||
|
var detail = new DetailBand { HeightF = 0f };
|
||||||
|
report.Bands.Add(detail);
|
||||||
|
|
||||||
|
detail.Controls.Add(new XRPdfContent
|
||||||
|
{
|
||||||
|
Source = pdfBytes,
|
||||||
|
GenerateOwnPages = true
|
||||||
|
});
|
||||||
|
|
||||||
|
return report;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_report?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,16 +5,20 @@
|
|||||||
@using EnvelopeGenerator.Server.Client.Models
|
@using EnvelopeGenerator.Server.Client.Models
|
||||||
@using EnvelopeGenerator.Server.Client.Services
|
@using EnvelopeGenerator.Server.Client.Services
|
||||||
@using EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiver
|
@using EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiver
|
||||||
|
@using EnvelopeGenerator.Application.Resources
|
||||||
@using Microsoft.Extensions.Caching.Memory
|
@using Microsoft.Extensions.Caching.Memory
|
||||||
|
@using Microsoft.Extensions.Localization
|
||||||
@using System.Security.Claims
|
@using System.Security.Claims
|
||||||
@inject NavigationManager Navigation
|
@inject NavigationManager Navigation
|
||||||
@inject IJSRuntime JSRuntime
|
@inject IJSRuntime JSRuntime
|
||||||
@inject EnvelopeGenerator.Server.Client.Services.AuthService AuthService
|
@inject EnvelopeGenerator.Server.Client.Services.AuthService AuthService
|
||||||
@inject EnvelopeGenerator.Server.Services.EnvelopeReceiverAuthorizationService ReceiverAuthorizationService
|
@inject EnvelopeGenerator.Server.Services.EnvelopeReceiverAuthorizationService ReceiverAuthorizationService
|
||||||
@inject EnvelopeGenerator.Server.Services.EnvelopeReceiverPageDataService PageDataService
|
@inject EnvelopeGenerator.Server.Services.EnvelopeReceiverPageDataService PageDataService
|
||||||
|
@inject SignatureService SignatureService
|
||||||
@inject AppVersionService AppVersion
|
@inject AppVersionService AppVersion
|
||||||
@inject IMemoryCache MemoryCache
|
@inject IMemoryCache MemoryCache
|
||||||
@inject ILogger<ReceiverSignedPage> Logger
|
@inject ILogger<ReceiverSignedPage> Logger
|
||||||
|
@inject IStringLocalizer<Resource> UiLocalizer
|
||||||
@implements IDisposable
|
@implements IDisposable
|
||||||
|
|
||||||
<link href="_content/DevExpress.Blazor.Themes/blazing-berry.bs5.min.css" rel="stylesheet" />
|
<link href="_content/DevExpress.Blazor.Themes/blazing-berry.bs5.min.css" rel="stylesheet" />
|
||||||
@@ -29,18 +33,18 @@
|
|||||||
@if (_envelopeReceiver is not null)
|
@if (_envelopeReceiver is not null)
|
||||||
{
|
{
|
||||||
<div style="font-size: 0.9rem; font-weight: 600; color: #1f2937; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
|
<div style="font-size: 0.9rem; font-weight: 600; color: #1f2937; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
|
||||||
@(_envelopeReceiver.Envelope?.Title ?? "Dokument")
|
@(_envelopeReceiver.Envelope?.Title ?? L("Document", "Dokument"))
|
||||||
</div>
|
</div>
|
||||||
@if (!string.IsNullOrWhiteSpace(_envelopeReceiver.Envelope?.User?.FullName))
|
@if (!string.IsNullOrWhiteSpace(_envelopeReceiver.Envelope?.User?.FullName))
|
||||||
{
|
{
|
||||||
<span style="font-size: 0.7rem; color: #6b7280; white-space: nowrap;">
|
<span style="font-size: 0.7rem; color: #6b7280; white-space: nowrap;">
|
||||||
Von <span style="font-weight: 500; color: #374151;">@_envelopeReceiver.Envelope.User.FullName</span>
|
@L("From", "Von") <span style="font-weight: 500; color: #374151;">@_envelopeReceiver.Envelope.User.FullName</span>
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div style="font-size: 0.9rem; font-weight: 600; color: #1f2937;">Signiertes Dokument</div>
|
<div style="font-size: 0.9rem; font-weight: 600; color: #1f2937;">@L("SignedDocument", "Signiertes Dokument")</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -49,12 +53,12 @@
|
|||||||
<button class="pdf-toolbar__btn pdf-toolbar__btn--signature-change pdf-toolbar__btn--signature-change-active"
|
<button class="pdf-toolbar__btn pdf-toolbar__btn--signature-change pdf-toolbar__btn--signature-change-active"
|
||||||
@onclick="OpenSubmitConfirmPopup"
|
@onclick="OpenSubmitConfirmPopup"
|
||||||
disabled="@_isLoggingOut"
|
disabled="@_isLoggingOut"
|
||||||
title="Abschließen"
|
title="@L("Complete", "Abschließen")"
|
||||||
style="flex-shrink: 0;">
|
style="flex-shrink: 0;">
|
||||||
<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="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"/>
|
<path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="pdf-toolbar__btn-text">Abschließen</span>
|
<span class="pdf-toolbar__btn-text">@L("Complete", "Abschließen")</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -67,9 +71,9 @@
|
|||||||
<div class="d-flex justify-content-center align-items-center h-100">
|
<div class="d-flex justify-content-center align-items-center h-100">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div class="spinner-border text-white mb-3" style="width: 3.5rem; height: 3.5rem;" role="status">
|
<div class="spinner-border text-white mb-3" style="width: 3.5rem; height: 3.5rem;" role="status">
|
||||||
<span class="visually-hidden">Lädt...</span>
|
<span class="visually-hidden">@L("Loading", "Lädt...")</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-white fw-semibold">Dokument wird geladen...</p>
|
<p class="text-white fw-semibold">@L("DocumentLoading", "Dokument wird geladen...")</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -83,7 +87,7 @@
|
|||||||
<path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z" />
|
<path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z" />
|
||||||
</svg>
|
</svg>
|
||||||
<div>
|
<div>
|
||||||
<h5 class="mb-2">Fehler</h5>
|
<h5 class="mb-2">@L("Error", "Fehler")</h5>
|
||||||
<p class="mb-0">@_errorMessage</p>
|
<p class="mb-0">@_errorMessage</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -99,7 +103,7 @@
|
|||||||
|
|
||||||
@* Submit confirmation popup *@
|
@* Submit confirmation popup *@
|
||||||
<DxPopup @bind-Visible="_submitConfirmVisible"
|
<DxPopup @bind-Visible="_submitConfirmVisible"
|
||||||
HeaderText="Unterschrift bestätigen"
|
HeaderText="@L("ConfirmSignature", "Unterschrift bestätigen")"
|
||||||
Width="440px"
|
Width="440px"
|
||||||
MaxWidth="95vw"
|
MaxWidth="95vw"
|
||||||
ShowFooter="true"
|
ShowFooter="true"
|
||||||
@@ -115,20 +119,26 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p style="margin: 0 0 0.4rem; font-weight: 600; color: #1f2937; font-size: 0.95rem;">
|
<p style="margin: 0 0 0.4rem; font-weight: 600; color: #1f2937; font-size: 0.95rem;">
|
||||||
Möchten Sie das Dokument verbindlich unterschreiben?
|
@L("ReceiverSigned_ConfirmQuestion", "Möchten Sie das Dokument verbindlich unterschreiben?")
|
||||||
</p>
|
</p>
|
||||||
<p style="margin: 0; color: #6b7280; font-size: 0.85rem; line-height: 1.5;">
|
<p style="margin: 0; color: #6b7280; font-size: 0.85rem; line-height: 1.5;">
|
||||||
Diese Aktion kann nicht rückgängig gemacht werden. Mit der Bestätigung erklären Sie, das oben angezeigte Dokument elektronisch unterzeichnet zu haben. Das unterzeichnete Dokument wird anschließend an alle beteiligten Parteien übermittelt.
|
@L("ReceiverSigned_ConfirmBody", "Diese Aktion kann nicht rückgängig gemacht werden. Mit der Bestätigung erklären Sie, das oben angezeigte Dokument elektronisch unterzeichnet zu haben. Das unterzeichnete Dokument wird anschließend an alle beteiligten Parteien übermittelt.")
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@if (!string.IsNullOrWhiteSpace(_submitErrorMessage))
|
||||||
|
{
|
||||||
|
<div class="alert alert-danger mb-0" role="alert" style="font-size: 0.85rem;">
|
||||||
|
@_submitErrorMessage
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</BodyContentTemplate>
|
</BodyContentTemplate>
|
||||||
<FooterContentTemplate>
|
<FooterContentTemplate>
|
||||||
<div class="d-flex gap-2 justify-content-end w-100" style="padding: 0.5rem 0;">
|
<div class="d-flex gap-2 justify-content-end w-100" style="padding: 0.5rem 0;">
|
||||||
<button class="btn btn-outline-secondary"
|
<button class="btn btn-outline-secondary"
|
||||||
@onclick="() => _submitConfirmVisible = false"
|
@onclick="() => _submitConfirmVisible = false"
|
||||||
style="border-radius: 6px; padding: 0.5rem 1.25rem; font-weight: 500;">
|
style="border-radius: 6px; padding: 0.5rem 1.25rem; font-weight: 500;">
|
||||||
Abbrechen
|
@L("Cancel", "Abbrechen")
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-primary"
|
<button class="btn btn-primary"
|
||||||
@onclick="SubmitAndLogoutAsync"
|
@onclick="SubmitAndLogoutAsync"
|
||||||
@@ -138,7 +148,7 @@
|
|||||||
{
|
{
|
||||||
<span class="spinner-border spinner-border-sm me-1" role="status"></span>
|
<span class="spinner-border spinner-border-sm me-1" role="status"></span>
|
||||||
}
|
}
|
||||||
Abschließen
|
@L("Complete", "Abschließen")
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</FooterContentTemplate>
|
</FooterContentTemplate>
|
||||||
@@ -161,24 +171,55 @@
|
|||||||
// ----- Submit / logout state -----
|
// ----- Submit / logout state -----
|
||||||
bool _isLoggingOut = false;
|
bool _isLoggingOut = false;
|
||||||
bool _submitConfirmVisible = false;
|
bool _submitConfirmVisible = false;
|
||||||
|
string? _submitErrorMessage;
|
||||||
|
|
||||||
void OpenSubmitConfirmPopup() => _submitConfirmVisible = true;
|
void OpenSubmitConfirmPopup() => _submitConfirmVisible = true;
|
||||||
|
|
||||||
async Task SubmitAndLogoutAsync()
|
async Task SubmitAndLogoutAsync()
|
||||||
{
|
{
|
||||||
if (_isLoggingOut) return;
|
if (_isLoggingOut) return;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(EnvelopeKey) || _sig is null)
|
||||||
|
return;
|
||||||
|
|
||||||
_isLoggingOut = true;
|
_isLoggingOut = true;
|
||||||
_submitConfirmVisible = false;
|
_submitConfirmVisible = false;
|
||||||
|
_submitErrorMessage = null;
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
await AuthService.LogoutEnvelopeReceiverAsync(EnvelopeKey!);
|
|
||||||
Navigation.NavigateTo("/", forceLoad: true);
|
try
|
||||||
|
{
|
||||||
|
if (_signatures.Count == 0)
|
||||||
|
throw new InvalidOperationException("No signature fields found for receiver.");
|
||||||
|
|
||||||
|
var payload = _signatures.Select(field => new SignatureSubmitDto
|
||||||
|
{
|
||||||
|
Id = field.Id,
|
||||||
|
DataUrl = _sig.DataUrl,
|
||||||
|
FullName = _sig.FullName,
|
||||||
|
Position = string.IsNullOrWhiteSpace(_sig.Position) ? null : _sig.Position,
|
||||||
|
Place = _sig.Place
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
await SignatureService.SubmitAsync(EnvelopeKey, payload);
|
||||||
|
|
||||||
|
await AuthService.LogoutEnvelopeReceiverAsync(EnvelopeKey);
|
||||||
|
Navigation.NavigateTo("/envelope/signed-result", forceLoad: true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError(ex, "Failed to finalize signature for {EnvelopeKey}", EnvelopeKey);
|
||||||
|
_submitErrorMessage = "Die Unterschrift konnte nicht abgeschlossen werden. Bitte versuchen Sie es erneut.";
|
||||||
|
_isLoggingOut = false;
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(EnvelopeKey))
|
if (string.IsNullOrWhiteSpace(EnvelopeKey))
|
||||||
{
|
{
|
||||||
_errorMessage = "Envelope-Schlüssel fehlt.";
|
_errorMessage = L("ReceiverSigned_EnvelopeKeyMissing", "Envelope-Schlüssel fehlt.");
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -215,7 +256,7 @@
|
|||||||
var pdfBytes = await PageDataService.GetDocumentOfReceiverAsync(_receiverUser);
|
var pdfBytes = await PageDataService.GetDocumentOfReceiverAsync(_receiverUser);
|
||||||
if (pdfBytes is not { Length: > 0 })
|
if (pdfBytes is not { Length: > 0 })
|
||||||
{
|
{
|
||||||
_errorMessage = "Dokument konnte nicht geladen werden.";
|
_errorMessage = L("DocumentCouldNotBeLoaded", "Dokument konnte nicht geladen werden.");
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -244,7 +285,7 @@
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_errorMessage = $"Fehler: {ex.Message}";
|
_errorMessage = string.Format(L("ErrorWithReason", "Fehler: {0}"), ex.Message);
|
||||||
Logger.LogError(ex, "Error loading signed page for {EnvelopeKey}", EnvelopeKey);
|
Logger.LogError(ex, "Error loading signed page for {EnvelopeKey}", EnvelopeKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -393,4 +434,10 @@
|
|||||||
if (comma < 0) return null;
|
if (comma < 0) return null;
|
||||||
return Convert.FromBase64String(dataUrl[(comma + 1)..]);
|
return Convert.FromBase64String(dataUrl[(comma + 1)..]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string L(string key, string fallback)
|
||||||
|
{
|
||||||
|
var value = UiLocalizer[key];
|
||||||
|
return value.ResourceNotFound ? fallback : value.Value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
using DigitalData.Auth.Claims;
|
using DigitalData.Auth.Claims;
|
||||||
|
using EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||||
|
using EnvelopeGenerator.Application.EnvelopeReceivers.Queries;
|
||||||
using EnvelopeGenerator.Server.Controllers.Interfaces;
|
using EnvelopeGenerator.Server.Controllers.Interfaces;
|
||||||
using EnvelopeGenerator.Server.Models;
|
using EnvelopeGenerator.Server.Models;
|
||||||
using EnvelopeGenerator.Domain.Constants;
|
using EnvelopeGenerator.Domain.Constants;
|
||||||
|
using MediatR;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
@@ -15,7 +18,9 @@ namespace EnvelopeGenerator.Server.Controllers;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
public partial class AuthController(IOptions<AuthTokenKeys> authTokenKeyOptions, IAuthorizationService authService) : ControllerBase, IAuthController
|
public partial class AuthController(
|
||||||
|
IOptions<AuthTokenKeys> authTokenKeyOptions,
|
||||||
|
IAuthorizationService authService) : ControllerBase, IAuthController
|
||||||
{
|
{
|
||||||
private readonly AuthTokenKeys authTokenKeys = authTokenKeyOptions.Value;
|
private readonly AuthTokenKeys authTokenKeys = authTokenKeyOptions.Value;
|
||||||
|
|
||||||
@@ -82,6 +87,64 @@ public partial class AuthController(IOptions<AuthTokenKeys> authTokenKeyOptions,
|
|||||||
[HttpGet("check/envelope/{envelopeKey}")]
|
[HttpGet("check/envelope/{envelopeKey}")]
|
||||||
public IActionResult CheckEnvelopeReceiver([FromRoute] string envelopeKey) => Ok();
|
public IActionResult CheckEnvelopeReceiver([FromRoute] string envelopeKey) => Ok();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends the receiver access code e-mail on the first envelope login-page visit.
|
||||||
|
/// The operation is idempotent: if the code was already requested, it returns 200 without re-sending.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">Envelope receiver key.</param>
|
||||||
|
/// <response code="200">Access code is already available or requested successfully.</response>
|
||||||
|
/// <response code="404">Envelope receiver key was not found.</response>
|
||||||
|
/// <response code="500">Access code could not be persisted or e-mail could not be sent.</response>
|
||||||
|
[AllowAnonymous]
|
||||||
|
[HttpPost("envelope-receiver/{key}/request-access-code")]
|
||||||
|
public async Task<IActionResult> RequestEnvelopeReceiverAccessCode(
|
||||||
|
[FromRoute] string key,
|
||||||
|
[FromServices] IMediator mediator,
|
||||||
|
[FromServices] IEnvelopeHistoryService historyService,
|
||||||
|
[FromServices] IEnvelopeMailService mailService,
|
||||||
|
[FromServices] ILogger<AuthController> logger,
|
||||||
|
CancellationToken cancel)
|
||||||
|
{
|
||||||
|
var envelopeReceiver = (await mediator.Send(new ReadEnvelopeReceiverQuery { Key = key }, cancel)).SingleOrDefault();
|
||||||
|
|
||||||
|
if (envelopeReceiver is null || envelopeReceiver.Envelope is null || envelopeReceiver.Receiver is null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
if (envelopeReceiver.Envelope.UseAccessCode != true)
|
||||||
|
return Ok();
|
||||||
|
|
||||||
|
var userReference = envelopeReceiver.Receiver.EmailAddress;
|
||||||
|
|
||||||
|
if (await historyService.IsSigned(envelopeReceiver.EnvelopeId, userReference))
|
||||||
|
return Ok();
|
||||||
|
|
||||||
|
var accessCodeAlreadyRequested = await historyService.AccessCodeAlreadyRequested(envelopeReceiver.EnvelopeId, userReference);
|
||||||
|
if (accessCodeAlreadyRequested)
|
||||||
|
return Ok();
|
||||||
|
|
||||||
|
var historyResult = await historyService.RecordAsync(envelopeReceiver.EnvelopeId, userReference, EnvelopeStatus.AccessCodeRequested);
|
||||||
|
if (historyResult.IsFailed)
|
||||||
|
{
|
||||||
|
logger.LogWarning(
|
||||||
|
"Failed to record AccessCodeRequested history for envelope {EnvelopeId} and receiver {ReceiverEmail}.",
|
||||||
|
envelopeReceiver.EnvelopeId,
|
||||||
|
userReference);
|
||||||
|
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||||
|
}
|
||||||
|
|
||||||
|
var mailResult = await mailService.SendAccessCodeAsync(envelopeReceiver);
|
||||||
|
if (mailResult.IsFailed)
|
||||||
|
{
|
||||||
|
logger.LogWarning(
|
||||||
|
"Failed to send access code mail for envelope {EnvelopeId} and receiver {ReceiverEmail}.",
|
||||||
|
envelopeReceiver.EnvelopeId,
|
||||||
|
userReference);
|
||||||
|
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removes the per-envelope receiver cookie for the given envelope key.
|
/// Removes the per-envelope receiver cookie for the given envelope key.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace EnvelopeGenerator.Server.Controllers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Redirects legacy envelope key links to Blazor routes.
|
||||||
|
/// </summary>
|
||||||
|
[ApiExplorerSettings(IgnoreApi = true)]
|
||||||
|
[Route("EnvelopeKey")]
|
||||||
|
public class EnvelopeKeyRedirectController : Controller
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Redirects /EnvelopeKey/{*path} links to /envelope/{path}.
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet("{*path}")]
|
||||||
|
public IActionResult RedirectToEnvelopePath([FromRoute] string path)
|
||||||
|
=> Redirect($"/envelope/{path}");
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@ using Microsoft.AspNetCore.Localization;
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Microsoft.Extensions.Localization;
|
using Microsoft.Extensions.Localization;
|
||||||
using EnvelopeGenerator.Application.Resources;
|
|
||||||
|
|
||||||
namespace EnvelopeGenerator.Server.Controllers;
|
namespace EnvelopeGenerator.Server.Controllers;
|
||||||
|
|
||||||
@@ -20,7 +19,6 @@ public class LocalizationController : ControllerBase
|
|||||||
|
|
||||||
private readonly ILogger<LocalizationController> _logger;
|
private readonly ILogger<LocalizationController> _logger;
|
||||||
private readonly IStringLocalizer<Resource> _mLocalizer;
|
private readonly IStringLocalizer<Resource> _mLocalizer;
|
||||||
private readonly IStringLocalizer<Resource> _localizer;
|
|
||||||
private readonly IMemoryCache _cache;
|
private readonly IMemoryCache _cache;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -32,14 +30,12 @@ public class LocalizationController : ControllerBase
|
|||||||
/// <param name="_modelLocalizer">Lokalisierungsdienst für Modelle.</param>
|
/// <param name="_modelLocalizer">Lokalisierungsdienst für Modelle.</param>
|
||||||
public LocalizationController(
|
public LocalizationController(
|
||||||
ILogger<LocalizationController> logger,
|
ILogger<LocalizationController> logger,
|
||||||
IStringLocalizer<Resource> localizer,
|
|
||||||
IMemoryCache memoryCache,
|
IMemoryCache memoryCache,
|
||||||
IStringLocalizer<Resource> _modelLocalizer)
|
IStringLocalizer<Resource> modelLocalizer)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_localizer = localizer;
|
|
||||||
_cache = memoryCache;
|
_cache = memoryCache;
|
||||||
_mLocalizer = _modelLocalizer;
|
_mLocalizer = modelLocalizer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -92,10 +88,10 @@ public class LocalizationController : ControllerBase
|
|||||||
var cookieValue = Request.Cookies[CookieRequestCultureProvider.DefaultCookieName];
|
var cookieValue = Request.Cookies[CookieRequestCultureProvider.DefaultCookieName];
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(cookieValue))
|
if (string.IsNullOrEmpty(cookieValue))
|
||||||
return null;
|
return "de-DE";
|
||||||
|
|
||||||
var culture = CookieRequestCultureProvider.ParseCookieValue(cookieValue)?.Cultures[0];
|
var culture = CookieRequestCultureProvider.ParseCookieValue(cookieValue)?.Cultures[0];
|
||||||
return culture?.Value ?? null;
|
return culture?.Value ?? "de-DE";
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
using DigitalData.Core.Abstraction.Application.DTO;
|
using DigitalData.Core.Abstraction.Application.DTO;
|
||||||
using EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiverReadOnly;
|
using EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiverReadOnly;
|
||||||
using EnvelopeGenerator.Application.Common.Interfaces.Services;
|
using EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||||
|
using EnvelopeGenerator.Application.EnvelopeReceivers.Queries;
|
||||||
using EnvelopeGenerator.Domain.Constants;
|
using EnvelopeGenerator.Domain.Constants;
|
||||||
using EnvelopeGenerator.Server.Extensions;
|
using MediatR;
|
||||||
|
using System.Security.Claims;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
@@ -33,24 +35,36 @@ public class ReadOnlyController : ControllerBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new read-only receiver for the current envelope.
|
/// Creates a new read-only receiver for an envelope.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="envelopeKey">Envelope key (uuid+signature encoded).</param>
|
||||||
/// <param name="createDto">Creation payload.</param>
|
/// <param name="createDto">Creation payload.</param>
|
||||||
[HttpPost]
|
[HttpPost("{envelopeKey}")]
|
||||||
[Authorize(Policy = AuthPolicy.Receiver)]
|
[Authorize(Policy = AuthPolicy.SenderOrReceiver)]
|
||||||
[Obsolete("Use MediatR")]
|
[Obsolete("Use MediatR")]
|
||||||
public async Task<IActionResult> CreateAsync([FromBody] EnvelopeReceiverReadOnlyCreateDto createDto)
|
public async Task<IActionResult> CreateAsync(
|
||||||
|
[FromRoute] string envelopeKey,
|
||||||
|
[FromBody] EnvelopeReceiverReadOnlyCreateDto createDto,
|
||||||
|
[FromServices] IMediator mediator,
|
||||||
|
CancellationToken cancel)
|
||||||
{
|
{
|
||||||
var authReceiverMail = User.ReceiverMail();
|
var envelopeReceiver = await mediator.Send(new ReadEnvelopeReceiverQuery { Key = envelopeKey }, cancel);
|
||||||
if (authReceiverMail is null)
|
var authEnvelopeReceiver = envelopeReceiver.FirstOrDefault();
|
||||||
|
|
||||||
|
if (authEnvelopeReceiver?.Envelope is null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
var addedWho = User.FindFirstValue(ClaimTypes.Email)
|
||||||
|
?? authEnvelopeReceiver.Receiver?.EmailAddress;
|
||||||
|
if (string.IsNullOrWhiteSpace(addedWho))
|
||||||
{
|
{
|
||||||
_logger.LogError("EmailAddress claim is not found in envelope-receiver-read-only creation process. Create DTO is:\n {dto}", JsonConvert.SerializeObject(createDto));
|
_logger.LogError("Receiver e-mail is missing in envelope-receiver-read-only creation process. Create DTO is:\n {dto}", JsonConvert.SerializeObject(createDto));
|
||||||
return Unauthorized();
|
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||||
}
|
}
|
||||||
|
|
||||||
var envelopeId = User.EnvelopeId();
|
var envelopeId = authEnvelopeReceiver.EnvelopeId;
|
||||||
|
|
||||||
createDto.AddedWho = authReceiverMail;
|
createDto.AddedWho = addedWho;
|
||||||
createDto.EnvelopeId = envelopeId;
|
createDto.EnvelopeId = envelopeId;
|
||||||
|
|
||||||
var creationRes = await _readOnlyService.CreateAsync(createDto: createDto);
|
var creationRes = await _readOnlyService.CreateAsync(createDto: createDto);
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
using EnvelopeGenerator.Server.Extensions;
|
using EnvelopeGenerator.Server.Extensions;
|
||||||
using EnvelopeGenerator.Application.Common.Dto;
|
using EnvelopeGenerator.Application.Common.Dto;
|
||||||
using EnvelopeGenerator.Application.Common.Extensions;
|
using EnvelopeGenerator.Application.Common.Extensions;
|
||||||
|
using EnvelopeGenerator.Application.DocReceiverElements.Commands;
|
||||||
using EnvelopeGenerator.Application.Documents.Queries;
|
using EnvelopeGenerator.Application.Documents.Queries;
|
||||||
|
using EnvelopeGenerator.Application.EnvelopeReceivers.Queries;
|
||||||
|
using EnvelopeGenerator.Application.Histories.Queries;
|
||||||
using EnvelopeGenerator.Domain.Constants;
|
using EnvelopeGenerator.Domain.Constants;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
@@ -18,13 +21,15 @@ namespace EnvelopeGenerator.Server.Controllers;
|
|||||||
public class SignatureController : ControllerBase
|
public class SignatureController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly IMediator _mediator;
|
private readonly IMediator _mediator;
|
||||||
|
private readonly ILogger<SignatureController> _logger;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of <see cref="SignatureController"/>.
|
/// Initializes a new instance of <see cref="SignatureController"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SignatureController(IMediator mediator)
|
public SignatureController(IMediator mediator, ILogger<SignatureController> logger)
|
||||||
{
|
{
|
||||||
_mediator = mediator;
|
_mediator = mediator;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: update to use signature query
|
//TODO: update to use signature query
|
||||||
@@ -54,4 +59,46 @@ public class SignatureController : ControllerBase
|
|||||||
else
|
else
|
||||||
return Ok(rcvSignatures);
|
return Ok(rcvSignatures);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finalizes the receiver signing flow and persists signature data to TBSIG_DOCUMENT_RECEIVER_ELEMENT.
|
||||||
|
/// Also triggers status/history/mail side effects through SigningCommand pipeline.
|
||||||
|
/// </summary>
|
||||||
|
[Authorize(Policy = AuthPolicy.Receiver)]
|
||||||
|
[HttpPost("submit/{envelopeKey}")]
|
||||||
|
public async Task<IActionResult> Submit(
|
||||||
|
[FromRoute] string envelopeKey,
|
||||||
|
[FromBody] IEnumerable<Signature>? signatures,
|
||||||
|
CancellationToken cancel)
|
||||||
|
{
|
||||||
|
if (signatures is null)
|
||||||
|
return BadRequest("Signature payload is required.");
|
||||||
|
|
||||||
|
var uuid = User.EnvelopeUuid();
|
||||||
|
var receiverSignature = User.ReceiverSignature();
|
||||||
|
|
||||||
|
if (await _mediator.IsSignedAsync(uuid, receiverSignature, cancel))
|
||||||
|
return Problem(statusCode: StatusCodes.Status409Conflict);
|
||||||
|
|
||||||
|
if (await _mediator.AnyHistoryAsync(uuid, new[] { EnvelopeStatus.EnvelopeRejected, EnvelopeStatus.DocumentRejected }, cancel))
|
||||||
|
return Problem(statusCode: StatusCodes.Status423Locked);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _mediator.Send(new SigningCommand
|
||||||
|
{
|
||||||
|
Envelope = new() { Uuid = uuid },
|
||||||
|
Receiver = new() { Signature = receiverSignature },
|
||||||
|
ReceiverAppType = ReceiverAppType.ReceiverUI,
|
||||||
|
Signatures = signatures.ToList()
|
||||||
|
}, cancel);
|
||||||
|
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to submit signatures for envelope {EnvelopeKey}", envelopeKey);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,9 @@
|
|||||||
<Authors>Digital Data GmbH</Authors>
|
<Authors>Digital Data GmbH</Authors>
|
||||||
<Company>Digital Data GmbH</Company>
|
<Company>Digital Data GmbH</Company>
|
||||||
<Product>EnvelopeGenerator.Server</Product>
|
<Product>EnvelopeGenerator.Server</Product>
|
||||||
<Version>1.2.0-beta</Version>
|
<Version>1.4.0-beta</Version>
|
||||||
<FileVersion>1.2.0.0</FileVersion>
|
<FileVersion>1.4.0.0</FileVersion>
|
||||||
<AssemblyVersion>1.2.0.0</AssemblyVersion>
|
<AssemblyVersion>1.4.0.0</AssemblyVersion>
|
||||||
<Copyright>Copyright © 2026 Digital Data GmbH. All rights reserved.</Copyright>
|
<Copyright>Copyright © 2026 Digital Data GmbH. All rights reserved.</Copyright>
|
||||||
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
|
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ using NLog.Web;
|
|||||||
using NLog;
|
using NLog;
|
||||||
using DigitalData.Auth.Claims;
|
using DigitalData.Auth.Claims;
|
||||||
using EnvelopeGenerator.Server;
|
using EnvelopeGenerator.Server;
|
||||||
|
using DigitalData.EmailProfilerDispatcher;
|
||||||
|
|
||||||
var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
|
var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
|
||||||
logger.Info("EnvelopeGenerator.Server logging initialized!");
|
logger.Info("EnvelopeGenerator.Server logging initialized!");
|
||||||
@@ -323,6 +324,8 @@ try
|
|||||||
.AddEnvelopeGeneratorServices(config);
|
.AddEnvelopeGeneratorServices(config);
|
||||||
#pragma warning restore CS0618
|
#pragma warning restore CS0618
|
||||||
|
|
||||||
|
builder.Services.AddDispatcher<EGDbContext>();
|
||||||
|
|
||||||
// User Preferences (theme, dark mode, grid layouts — stored in TBDD_CACHE)
|
// User Preferences (theme, dark mode, grid layouts — stored in TBDD_CACHE)
|
||||||
// Registration handled by Infrastructure DependencyInjection (AddEnvelopeGeneratorInfrastructureServices)
|
// Registration handled by Infrastructure DependencyInjection (AddEnvelopeGeneratorInfrastructureServices)
|
||||||
|
|
||||||
@@ -337,6 +340,7 @@ try
|
|||||||
builder.Services.AddScoped<EnvelopeGenerator.Server.Client.Services.UserPreferencesService>();
|
builder.Services.AddScoped<EnvelopeGenerator.Server.Client.Services.UserPreferencesService>();
|
||||||
builder.Services.AddScoped<EnvelopeGenerator.Server.Client.Services.ThemeService>();
|
builder.Services.AddScoped<EnvelopeGenerator.Server.Client.Services.ThemeService>();
|
||||||
builder.Services.AddScoped<EnvelopeGenerator.Server.Client.Services.CustomImagesService>();
|
builder.Services.AddScoped<EnvelopeGenerator.Server.Client.Services.CustomImagesService>();
|
||||||
|
builder.Services.AddScoped<EnvelopeGenerator.Server.Client.Services.LocalizationService>();
|
||||||
|
|
||||||
// Business Services (Server specific)
|
// Business Services (Server specific)
|
||||||
builder.Services.AddScoped<DocumentService>();
|
builder.Services.AddScoped<DocumentService>();
|
||||||
@@ -345,6 +349,7 @@ try
|
|||||||
builder.Services.AddScoped<EnvelopeReceiverService>();
|
builder.Services.AddScoped<EnvelopeReceiverService>();
|
||||||
builder.Services.AddScoped<SignatureService>();
|
builder.Services.AddScoped<SignatureService>();
|
||||||
builder.Services.AddScoped<SignatureCacheService>();
|
builder.Services.AddScoped<SignatureCacheService>();
|
||||||
|
builder.Services.AddScoped<EnvelopeGenerator.Application.Common.Interfaces.Services.IEnvelopeMailService, EnvelopeGenerator.Application.Services.EnvelopeMailService>();
|
||||||
builder.Services.AddSingleton<AppVersionService>();
|
builder.Services.AddSingleton<AppVersionService>();
|
||||||
|
|
||||||
// EnvelopeService with HttpClient factory (for SSR scenarios)
|
// EnvelopeService with HttpClient factory (for SSR scenarios)
|
||||||
@@ -410,16 +415,22 @@ try
|
|||||||
// Set CORS policy
|
// Set CORS policy
|
||||||
app.UseCors("AllowSpecificOriginsPolicy");
|
app.UseCors("AllowSpecificOriginsPolicy");
|
||||||
|
|
||||||
// Localization
|
// Localization (default German; can be switched via cookie)
|
||||||
string[] supportedCultureNames = ["de-DE", "en-US"];
|
var defaultCulture = new CultureInfo("de-DE");
|
||||||
IList<CultureInfo> list = [.. supportedCultureNames.Select(cn => new CultureInfo(cn))];
|
IList<CultureInfo> list =
|
||||||
var cultureInfo = list.FirstOrDefault() ?? throw new InvalidOperationException("There is no supported culture.");
|
[
|
||||||
|
defaultCulture,
|
||||||
|
new CultureInfo("en-US"),
|
||||||
|
new CultureInfo("fr-FR")
|
||||||
|
];
|
||||||
var requestLocalizationOptions = new RequestLocalizationOptions
|
var requestLocalizationOptions = new RequestLocalizationOptions
|
||||||
{
|
{
|
||||||
|
DefaultRequestCulture = new RequestCulture(defaultCulture),
|
||||||
SupportedCultures = list,
|
SupportedCultures = list,
|
||||||
SupportedUICultures = list
|
SupportedUICultures = list,
|
||||||
|
RequestCultureProviders = [new CookieRequestCultureProvider()]
|
||||||
};
|
};
|
||||||
requestLocalizationOptions.RequestCultureProviders.Add(new QueryStringRequestCultureProvider());
|
requestLocalizationOptions.ApplyCurrentCultureToResponseHeaders = true;
|
||||||
app.UseRequestLocalization(requestLocalizationOptions);
|
app.UseRequestLocalization(requestLocalizationOptions);
|
||||||
|
|
||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
|
|||||||
@@ -185,9 +185,10 @@
|
|||||||
"TimeLimit": "00:30:00"
|
"TimeLimit": "00:30:00"
|
||||||
},
|
},
|
||||||
"DbTriggerParams": {
|
"DbTriggerParams": {
|
||||||
"Envelope": [ "TBSIG_ENVELOPE_HISTORY_AFT_INS" ],
|
|
||||||
"History": [ "TBSIG_ENVELOPE_HISTORY_AFT_INS" ],
|
|
||||||
"DocReceiverElement": [ "TBSIG_DOCUMENT_RECEIVER_ELEMENT_AFT_INS", "TBSIG_DOCUMENT_RECEIVER_ELEMENT_AFT_UPD" ],
|
"DocReceiverElement": [ "TBSIG_DOCUMENT_RECEIVER_ELEMENT_AFT_INS", "TBSIG_DOCUMENT_RECEIVER_ELEMENT_AFT_UPD" ],
|
||||||
|
"Envelope": [ "TBSIG_ENVELOPE_HISTORY_AFT_INS" ],
|
||||||
|
"EnvelopeHistory": [ "TBSIG_ENVELOPE_HISTORY_AFT_INS" ],
|
||||||
|
"History": [ "TBSIG_ENVELOPE_HISTORY_AFT_INS" ],
|
||||||
"EmailOut": [ "TBEMLP_EMAIL_OUT_AFT_INS", "TBEMLP_EMAIL_OUT_AFT_UPD" ],
|
"EmailOut": [ "TBEMLP_EMAIL_OUT_AFT_INS", "TBEMLP_EMAIL_OUT_AFT_UPD" ],
|
||||||
"EnvelopeReceiverReadOnly": [ "TBSIG_ENVELOPE_RECEIVER_READ_ONLY_UPD" ],
|
"EnvelopeReceiverReadOnly": [ "TBSIG_ENVELOPE_RECEIVER_READ_ONLY_UPD" ],
|
||||||
"Receiver": [],
|
"Receiver": [],
|
||||||
|
|||||||
@@ -345,6 +345,83 @@ article {
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Signed result page */
|
||||||
|
.receiver-signed-result-page {
|
||||||
|
min-height: calc(100vh - 36px);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 2rem 1rem;
|
||||||
|
background: linear-gradient(160deg, #1e2e3e 0%, #2c3e50 40%, #3498db 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.receiver-signed-result-card {
|
||||||
|
width: min(680px, 100%);
|
||||||
|
background: rgba(255, 255, 255, 0.96);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.7);
|
||||||
|
border-radius: 18px;
|
||||||
|
box-shadow: 0 18px 44px rgba(15, 23, 42, 0.24);
|
||||||
|
padding: 2rem 1.5rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receiver-signed-result-icon {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 92px;
|
||||||
|
height: 92px;
|
||||||
|
border-radius: 50%;
|
||||||
|
color: #0f8f58;
|
||||||
|
background: linear-gradient(135deg, #dcfce7 0%, #bbf7d0 100%);
|
||||||
|
border: 1px solid #86efac;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receiver-signed-result-title {
|
||||||
|
margin: 0;
|
||||||
|
color: #1f2937;
|
||||||
|
font-size: 1.7rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receiver-signed-result-message {
|
||||||
|
margin: 0.85rem auto 0;
|
||||||
|
max-width: 58ch;
|
||||||
|
color: #4b5563;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.62;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-sf-theme="purple"] .receiver-signed-result-page {
|
||||||
|
background: linear-gradient(160deg, #1a0533 0%, #3b0764 45%, #6b21a8 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-sf-theme="blazing-dark"] .receiver-signed-result-page {
|
||||||
|
background: linear-gradient(160deg, #0a0f1e 0%, #111827 50%, #1e1b4b 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-sf-theme="blazing-dark"] .receiver-signed-result-card {
|
||||||
|
background: rgba(15, 20, 40, 0.94);
|
||||||
|
border-color: rgba(129, 140, 248, 0.28);
|
||||||
|
box-shadow: 0 20px 46px rgba(0, 0, 0, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-sf-theme="blazing-dark"] .receiver-signed-result-title {
|
||||||
|
color: rgba(255, 255, 255, 0.92);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-sf-theme="blazing-dark"] .receiver-signed-result-message {
|
||||||
|
color: rgba(255, 255, 255, 0.68);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-sf-theme="blazing-dark"] .receiver-signed-result-icon {
|
||||||
|
color: #86efac;
|
||||||
|
background: rgba(16, 185, 129, 0.16);
|
||||||
|
border-color: rgba(110, 231, 183, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Logos ───────────────────────────────────────────────────────────────── */
|
/* ── Logos ───────────────────────────────────────────────────────────────── */
|
||||||
/* Heights are controlled by inline style="height: Xpx" from appsettings.json */
|
/* Heights are controlled by inline style="height: Xpx" from appsettings.json */
|
||||||
|
|
||||||
@@ -404,6 +481,89 @@ article {
|
|||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.receiver-footer__right {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-lang-combo {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-lang-trigger {
|
||||||
|
width: 28px;
|
||||||
|
height: 20px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.28);
|
||||||
|
border-radius: 5px;
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
padding: 0;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-lang-trigger:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.16);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-lang-menu {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 28px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
min-width: 64px;
|
||||||
|
padding: 6px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.18);
|
||||||
|
background: rgba(29, 38, 55, 0.98);
|
||||||
|
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);
|
||||||
|
z-index: 110;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-lang-backdrop {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
cursor: default;
|
||||||
|
z-index: 105;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-lang-option {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: transparent;
|
||||||
|
color: rgba(255, 255, 255, 0.88);
|
||||||
|
font-size: 0.68rem;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 4px 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-lang-option:hover,
|
||||||
|
.sf-lang-option--active {
|
||||||
|
background: rgba(255, 255, 255, 0.14);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-lang-trigger .fi,
|
||||||
|
.sf-lang-option .fi {
|
||||||
|
width: 16px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 2px;
|
||||||
|
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.25) inset;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Footer: Theme Slider ────────────────────────────────────────────────── */
|
/* ── Footer: Theme Slider ────────────────────────────────────────────────── */
|
||||||
/* 🌙 blazing-dark ──◇──·──◇── purple ☀️ */
|
/* 🌙 blazing-dark ──◇──·──◇── purple ☀️ */
|
||||||
/* Left group: copyright | datenschutz → Right group: slider */
|
/* Left group: copyright | datenschutz → Right group: slider */
|
||||||
|
|||||||
@@ -238,6 +238,7 @@
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.sender-grid-container {
|
.sender-grid-container {
|
||||||
background: rgba(255, 255, 255, 0.98);
|
background: rgba(255, 255, 255, 0.98);
|
||||||
backdrop-filter: blur(20px);
|
backdrop-filter: blur(20px);
|
||||||
@@ -381,6 +382,109 @@
|
|||||||
color: #c62828;
|
color: #c62828;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sender-access-code {
|
||||||
|
min-width: 120px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-access-code__label {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-access-code__row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-access-code__value {
|
||||||
|
width: 72px;
|
||||||
|
height: 28px;
|
||||||
|
border: 1px solid rgba(126, 34, 206, 0.22);
|
||||||
|
border-right: none;
|
||||||
|
border-radius: 6px 0 0 6px;
|
||||||
|
padding: 0 0.55rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1e293b;
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-access-code__value:focus {
|
||||||
|
outline: 2px solid rgba(126, 34, 206, 0.18);
|
||||||
|
border-color: rgba(126, 34, 206, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-access-code__copy {
|
||||||
|
height: 28px;
|
||||||
|
border: 1px solid rgba(126, 34, 206, 0.3);
|
||||||
|
border-radius: 0 6px 6px 0;
|
||||||
|
width: 30px;
|
||||||
|
min-width: 30px;
|
||||||
|
padding: 0;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #4c1d95;
|
||||||
|
background: rgba(126, 34, 206, 0.08);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-access-code__copy:hover:not(:disabled) {
|
||||||
|
border-color: rgba(126, 34, 206, 0.45);
|
||||||
|
background: rgba(126, 34, 206, 0.16);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-access-code__copy:disabled {
|
||||||
|
opacity: 0.45;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-history-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-history-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
background: white;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-history-item__left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-history-item__actor {
|
||||||
|
color: #4b5563;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-history-item__right {
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 1320px) {
|
@media (max-width: 1320px) {
|
||||||
.sender-action-bar__inner {
|
.sender-action-bar__inner {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -482,6 +586,10 @@
|
|||||||
padding: 0.875rem 1rem;
|
padding: 0.875rem 1rem;
|
||||||
font-size: 0.813rem;
|
font-size: 0.813rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sender-access-code {
|
||||||
|
min-width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 560px) {
|
@media (max-width: 560px) {
|
||||||
@@ -652,6 +760,7 @@
|
|||||||
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.06);
|
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.06);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[data-sf-theme="blazing-dark"] .sender-grid-container::before {
|
[data-sf-theme="blazing-dark"] .sender-grid-container::before {
|
||||||
background: linear-gradient(90deg, #6366f1 0%, #4338ca 100%);
|
background: linear-gradient(90deg, #6366f1 0%, #4338ca 100%);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user