Introduced a new "Receivers" panel in `EnvelopeSenderEditorPage` to manage receivers. Added a popup for adding receivers with validation, email suggestions, and caching for performance. Updated the layout to display receiver details and signature fields. Injected `EnvelopeReceiverPageDataService` for receiver-related operations. Added a `ReceiverDraft` model and implemented methods for managing receivers. Enhanced CSS for the new UI elements and ensured responsiveness. Minor refactoring and cleanup included.
127 lines
5.3 KiB
C#
127 lines
5.3 KiB
C#
using System.Security.Claims;
|
|
using System.Text.Json;
|
|
using EnvelopeGenerator.Application.Common.Dto;
|
|
using EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiver;
|
|
using EnvelopeGenerator.Application.Documents.Queries;
|
|
using EnvelopeGenerator.Application.EnvelopeReceivers.Queries;
|
|
using EnvelopeGenerator.Application.Receivers.Queries;
|
|
using EnvelopeGenerator.Server.Client.Models;
|
|
using EnvelopeGenerator.Server.Client.Models.Constants;
|
|
using EnvelopeGenerator.Server.Extensions;
|
|
using EnvelopeGenerator.Server.Options;
|
|
using MediatR;
|
|
using Microsoft.Extensions.Caching.Distributed;
|
|
using Microsoft.Extensions.Caching.Memory;
|
|
using Microsoft.Extensions.Options;
|
|
using ApplicationEnvelopeReceiverDto = EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiver.EnvelopeReceiverDto;
|
|
|
|
namespace EnvelopeGenerator.Server.Services;
|
|
|
|
/// <summary>
|
|
/// Loads receiver page data directly from MediatR and distributed cache.
|
|
/// </summary>
|
|
public class EnvelopeReceiverPageDataService(
|
|
IMediator mediator,
|
|
IDistributedCache cache,
|
|
IOptions<CacheOptions> cacheOptions,
|
|
IMemoryCache memoryCache)
|
|
{
|
|
private const string SignatureCacheKeyPrefix = "envelope-generator.receiver-ui.signature:";
|
|
|
|
/// <summary>
|
|
/// Loads the PDF document bytes for the authenticated receiver.
|
|
/// </summary>
|
|
public async Task<byte[]?> GetDocumentAsync(ClaimsPrincipal user, CancellationToken cancellationToken = default)
|
|
{
|
|
var document = await mediator.Send(new ReadDocumentQuery(EnvelopeId: user.EnvelopeId()), cancellationToken);
|
|
return document.ByteData;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads the current receiver's signature placeholders.
|
|
/// </summary>
|
|
public async Task<IReadOnlyList<SignatureDto>> GetSignaturesAsync(ClaimsPrincipal user, CancellationToken cancellationToken = default)
|
|
{
|
|
var receiverId = user.ReceiverId();
|
|
var document = await mediator.Send(new ReadDocumentQuery(EnvelopeId: user.EnvelopeId()), cancellationToken);
|
|
|
|
if (document.Elements is not IEnumerable<DocReceiverElementDto> elements)
|
|
return [];
|
|
|
|
var signatures = elements
|
|
.Where(element => element.ReceiverId == receiverId)
|
|
.Select(MapSignature)
|
|
.ToList();
|
|
|
|
return signatures.Convert(UnitOfLength.Point);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads the envelope receiver data for the specified envelope key.
|
|
/// </summary>
|
|
public async Task<ApplicationEnvelopeReceiverDto?> GetEnvelopeReceiverAsync(string envelopeKey, CancellationToken cancellationToken = default)
|
|
{
|
|
var result = await mediator.Send(new ReadEnvelopeReceiverQuery { Key = envelopeKey }, cancellationToken);
|
|
return result.SingleOrDefault();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads the cached signature for the authenticated receiver.
|
|
/// </summary>
|
|
public async Task<SignatureCaptureDto?> GetCachedSignatureAsync(ClaimsPrincipal user, CancellationToken cancellationToken = default)
|
|
{
|
|
var json = await cache.GetStringAsync(GetSignatureCacheKey(user), cancellationToken);
|
|
return json is null ? null : JsonSerializer.Deserialize<SignatureCaptureDto>(json);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Saves the cached signature for the authenticated receiver.
|
|
/// </summary>
|
|
public async Task SaveCachedSignatureAsync(ClaimsPrincipal user, SignatureCaptureDto signature, CancellationToken cancellationToken = default)
|
|
{
|
|
var json = JsonSerializer.Serialize(signature);
|
|
var options = cacheOptions.Value.SignatureCacheExpiration.HasValue
|
|
? new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = cacheOptions.Value.SignatureCacheExpiration.Value }
|
|
: new DistributedCacheEntryOptions();
|
|
|
|
await cache.SetStringAsync(GetSignatureCacheKey(user), json, options, cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deletes the cached signature for the authenticated receiver.
|
|
/// </summary>
|
|
public Task DeleteCachedSignatureAsync(ClaimsPrincipal user, CancellationToken cancellationToken = default)
|
|
=> cache.RemoveAsync(GetSignatureCacheKey(user), cancellationToken);
|
|
|
|
private static string GetSignatureCacheKey(ClaimsPrincipal user)
|
|
=> $"{SignatureCacheKeyPrefix}{user.ReceiverSignature()}";
|
|
|
|
private static SignatureDto MapSignature(DocReceiverElementDto element) => new()
|
|
{
|
|
Id = element.Id,
|
|
X = element.X,
|
|
Y = element.Y,
|
|
Page = element.Page,
|
|
SenderAppType = (EnvelopeGenerator.Server.Client.Models.Constants.SenderAppType)element.SenderAppType
|
|
};
|
|
|
|
private static readonly string ReceiverEmailSearchCacheKey = Guid.NewGuid().ToString();
|
|
|
|
public async Task<IEnumerable<string>> SearchReceiverEMailsAsync(string emailSearchTerm, CancellationToken cancellationToken = default)
|
|
{
|
|
|
|
return await memoryCache.GetOrCreateAsync(ReceiverEmailSearchCacheKey + emailSearchTerm, async entry =>
|
|
{
|
|
var query = new ReadReceiverQuery { EmailAddressSearch = emailSearchTerm };
|
|
var receivers = await mediator.Send(query, cancellationToken);
|
|
|
|
if(receivers.Any())
|
|
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(30);
|
|
else
|
|
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(10);
|
|
|
|
return receivers.Select(r => r.EmailAddress);
|
|
}) ?? [];
|
|
}
|
|
}
|