Add AccessCode feature to EnvelopeSenderPage UI
Introduced a new `AccessCode` property in `EnvelopeReceiverDto` to support displaying and copying access codes in the envelope sender page. - Updated `EnvelopeReceiverDto` to include the nullable `AccessCode` property. - Enhanced `EnvelopeSenderPage.razor` to display the `AccessCode` for each receiver, with a copy-to-clipboard button. - Implemented `CopyAccessCodeAsync` for clipboard functionality using JavaScript interop. - Styled the `AccessCode` section in `sender-page.css` for a responsive and user-friendly design. - Improved error handling for clipboard operations with console warnings. These changes improve the user experience by allowing senders to view and copy access codes for receivers.
This commit is contained in:
@@ -60,6 +60,11 @@ public record EnvelopeReceiverDto
|
||||
/// </summary>
|
||||
public string? PrivateMessage { get; init; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string? AccessCode { get; init; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
@@ -83,4 +88,4 @@ public record EnvelopeReceiverDto
|
||||
h.Receiver?.Id == ReceiverId &&
|
||||
h.Status == EnvelopeStatus.DocumentSigned
|
||||
) ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,6 +315,8 @@
|
||||
<div style="display: flex; align-items: center; gap: 1rem; padding: 0.5rem; background: white; border-radius: 6px; border: 1px solid #e5e7eb;">
|
||||
@{
|
||||
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)
|
||||
@@ -337,6 +339,24 @@
|
||||
<strong style="color: #1f2937;">@receiver.Name</strong>
|
||||
<span style="color: #6b7280; margin-left: 0.5rem;">@receiver.Receiver?.EmailAddress</span>
|
||||
</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>
|
||||
@@ -840,6 +860,21 @@
|
||||
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)
|
||||
{
|
||||
// Future: Add custom row coloring based on status if needed
|
||||
|
||||
@@ -382,6 +382,71 @@
|
||||
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;
|
||||
@@ -521,6 +586,10 @@
|
||||
padding: 0.875rem 1rem;
|
||||
font-size: 0.813rem;
|
||||
}
|
||||
|
||||
.sender-access-code {
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 560px) {
|
||||
|
||||
Reference in New Issue
Block a user