Compare commits
6 Commits
95c8e15887
...
3b66de0691
| Author | SHA1 | Date | |
|---|---|---|---|
| 3b66de0691 | |||
| 9f6004ba8c | |||
| ef246bae32 | |||
| e4ebb29969 | |||
| 83cdb9dfe9 | |||
| c5db676e01 |
@@ -23,7 +23,7 @@
|
|||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"dotnetRunMessages": true,
|
"dotnetRunMessages": true,
|
||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
"launchUrl": "swagger",
|
"launchUrl": "sender",
|
||||||
"applicationUrl": "https://localhost:8088;http://localhost:5131",
|
"applicationUrl": "https://localhost:8088;http://localhost:5131",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
|||||||
@@ -20,5 +20,20 @@ public class EnvelopeDto
|
|||||||
public byte[]? DocResult { get; set; }
|
public byte[]? DocResult { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("envelopeReceivers")]
|
[JsonPropertyName("envelopeReceivers")]
|
||||||
public List<EnvelopeReceiverDto> EnvelopeReceivers { get; set; } = new();
|
public List<EnvelopeReceiverSimpleDto> EnvelopeReceivers { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Simplified receiver model for envelope list display
|
||||||
|
/// </summary>
|
||||||
|
public class EnvelopeReceiverSimpleDto
|
||||||
|
{
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("email")]
|
||||||
|
public string? Email { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("signed")]
|
||||||
|
public bool Signed { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
33
EnvelopeGenerator.ReceiverUI/Models/EnvelopeStatus.cs
Normal file
33
EnvelopeGenerator.ReceiverUI/Models/EnvelopeStatus.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
namespace EnvelopeGenerator.ReceiverUI.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Envelope status enumeration (copied from Domain for ReceiverUI)
|
||||||
|
/// </summary>
|
||||||
|
public enum EnvelopeStatus
|
||||||
|
{
|
||||||
|
Invalid = 0,
|
||||||
|
EnvelopeCreated = 1001,
|
||||||
|
EnvelopeSaved = 1002,
|
||||||
|
EnvelopeQueued = 1003,
|
||||||
|
EnvelopeSent = 1004,
|
||||||
|
EnvelopePartlySigned = 1005,
|
||||||
|
EnvelopeCompletelySigned = 1006,
|
||||||
|
EnvelopeReportCreated = 1007,
|
||||||
|
EnvelopeArchived = 1008,
|
||||||
|
EnvelopeDeleted = 1009,
|
||||||
|
EnvelopeRejected = 10007,
|
||||||
|
EnvelopeWithdrawn = 10009
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class EnvelopeStatusExtensions
|
||||||
|
{
|
||||||
|
public static bool IsActive(this EnvelopeStatus status)
|
||||||
|
{
|
||||||
|
return status >= EnvelopeStatus.EnvelopeCreated && status < EnvelopeStatus.EnvelopePartlySigned;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsCompleted(this EnvelopeStatus status)
|
||||||
|
{
|
||||||
|
return status >= EnvelopeStatus.EnvelopeCompletelySigned && status <= EnvelopeStatus.EnvelopeWithdrawn;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,717 @@
|
|||||||
@page "/sender"
|
@page "/sender"
|
||||||
|
@attribute [Microsoft.AspNetCore.Authorization.Authorize(Policy = "Sender")]
|
||||||
|
|
||||||
<h3>EnvelopeSender</h3>
|
@using System.Text.Json
|
||||||
|
@using EnvelopeGenerator.ReceiverUI.Models
|
||||||
|
@using DevExpress.Blazor
|
||||||
|
@inject EnvelopeGenerator.ReceiverUI.Services.EnvelopeService EnvelopeService
|
||||||
|
@inject EnvelopeGenerator.ReceiverUI.Services.AuthService AuthService
|
||||||
|
@inject NavigationManager Navigation
|
||||||
|
@inject IJSRuntime JSRuntime
|
||||||
|
|
||||||
|
<link href="_content/DevExpress.Blazor.Themes/blazing-berry.bs5.min.css" rel="stylesheet" />
|
||||||
|
<link href="css/envelope-viewer.css" rel="stylesheet" />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.sender-dashboard-layout {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 50%, #7e22ce 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-action-bar {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border-bottom: 3px solid rgba(126, 34, 206, 0.3);
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-action-bar__inner {
|
||||||
|
max-width: 1600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-title-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-logo svg {
|
||||||
|
filter: drop-shadow(0 2px 4px rgba(126, 34, 206, 0.3));
|
||||||
|
color: #7e22ce;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-title {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1e293b;
|
||||||
|
letter-spacing: -0.025em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.625rem 1.125rem;
|
||||||
|
background: linear-gradient(135deg, rgba(126, 34, 206, 0.05) 0%, rgba(42, 82, 152, 0.05) 100%);
|
||||||
|
border: 1px solid rgba(126, 34, 206, 0.2);
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1e293b;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-btn:hover:not(:disabled) {
|
||||||
|
background: linear-gradient(135deg, rgba(126, 34, 206, 0.1) 0%, rgba(42, 82, 152, 0.1) 100%);
|
||||||
|
border-color: rgba(126, 34, 206, 0.4);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 12px rgba(126, 34, 206, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-btn:disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: not-allowed;
|
||||||
|
background: rgba(0, 0, 0, 0.02);
|
||||||
|
border-color: rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-btn--primary {
|
||||||
|
background: linear-gradient(135deg, #7e22ce 0%, #2a5298 100%);
|
||||||
|
border-color: transparent;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-btn--primary:hover:not(:disabled) {
|
||||||
|
background: linear-gradient(135deg, #6b1cb0 0%, #1e3a72 100%);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 16px rgba(126, 34, 206, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-btn--danger {
|
||||||
|
background: linear-gradient(135deg, rgba(239, 68, 68, 0.08) 0%, rgba(220, 38, 38, 0.08) 100%);
|
||||||
|
border-color: rgba(239, 68, 68, 0.3);
|
||||||
|
color: #dc2626;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-btn--danger:hover:not(:disabled) {
|
||||||
|
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
||||||
|
border-color: transparent;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-btn--logout {
|
||||||
|
padding: 0.5rem;
|
||||||
|
min-width: 38px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-content {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
padding: 1.5rem;
|
||||||
|
position: relative;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-grid-container {
|
||||||
|
background: rgba(255, 255, 255, 0.98);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow:
|
||||||
|
0 25px 50px -12px rgba(0, 0, 0, 0.25),
|
||||||
|
0 0 0 1px rgba(255, 255, 255, 0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
max-width: 1600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-grid-container::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 4px;
|
||||||
|
background: linear-gradient(90deg, #7e22ce 0%, #2a5298 100%);
|
||||||
|
z-index: 1;
|
||||||
|
border-radius: 16px 16px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-tabs {
|
||||||
|
display: flex;
|
||||||
|
border-bottom: 2px solid rgba(126, 34, 206, 0.1);
|
||||||
|
padding: 0 2rem;
|
||||||
|
background: rgba(126, 34, 206, 0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-tab {
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #6b7280;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 3px solid transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-tab:hover {
|
||||||
|
color: #7e22ce;
|
||||||
|
background: rgba(126, 34, 206, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-tab--active {
|
||||||
|
color: #7e22ce;
|
||||||
|
border-bottom-color: #7e22ce;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-grid-wrapper {
|
||||||
|
padding: 1.5rem 2rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.375rem;
|
||||||
|
padding: 0.25rem 0.625rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge--partly-signed,
|
||||||
|
.status-badge--completed {
|
||||||
|
background: rgba(129, 199, 132, 0.15);
|
||||||
|
color: #2e7d32;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge--queued,
|
||||||
|
.status-badge--sent {
|
||||||
|
background: rgba(255, 183, 77, 0.15);
|
||||||
|
color: #e65100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge--deleted,
|
||||||
|
.status-badge--rejected,
|
||||||
|
.status-badge--withdrawn {
|
||||||
|
background: rgba(229, 115, 115, 0.15);
|
||||||
|
color: #c62828;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge--created,
|
||||||
|
.status-badge--saved {
|
||||||
|
background: rgba(100, 181, 246, 0.15);
|
||||||
|
color: #1565c0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot--green {
|
||||||
|
background: #81c784;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot--orange {
|
||||||
|
background: #ffb74d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot--red {
|
||||||
|
background: #e57373;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot--blue {
|
||||||
|
background: #64b5f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receiver-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
padding: 0.125rem 0.5rem;
|
||||||
|
background: #f3f4f6;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: #374151;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receiver-badge--signed {
|
||||||
|
background: rgba(129, 199, 132, 0.15);
|
||||||
|
color: #2e7d32;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receiver-badge--unsigned {
|
||||||
|
background: rgba(229, 115, 115, 0.15);
|
||||||
|
color: #c62828;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@media (max-width: 768px) {
|
||||||
|
.sender-action-bar {
|
||||||
|
padding: 1rem 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-action-bar__inner {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-toolbar {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-title {
|
||||||
|
font-size: 1.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-content {
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-grid-wrapper {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-tabs {
|
||||||
|
padding: 0 1rem;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-tab {
|
||||||
|
padding: 0.875rem 1rem;
|
||||||
|
font-size: 0.813rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="sender-dashboard-layout">
|
||||||
|
<div class="sender-action-bar">
|
||||||
|
<div class="sender-action-bar__inner">
|
||||||
|
<div class="sender-title-section">
|
||||||
|
<div class="sender-logo">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" 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"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="sender-title">Umschlag-Übersicht</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sender-toolbar">
|
||||||
|
<button class="sender-btn sender-btn--primary" @onclick="CreateEnvelope" title="Neuen Umschlag erstellen">
|
||||||
|
<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"/>
|
||||||
|
</svg>
|
||||||
|
Neuer Umschlag
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="sender-btn" @onclick="EditEnvelope" disabled="@(_selectedEnvelope == null || IsEnvelopeSent(_selectedEnvelope))" title="Ausgewählten Umschlag bearbeiten">
|
||||||
|
<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"/>
|
||||||
|
</svg>
|
||||||
|
Bearbeiten
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="sender-btn sender-btn--danger" @onclick="DeleteEnvelope" disabled="@(_selectedEnvelope == null)" 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">
|
||||||
|
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/>
|
||||||
|
<path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/>
|
||||||
|
</svg>
|
||||||
|
Löschen
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="sender-btn" @onclick="RefreshEnvelopes" disabled="@_isLoading" title="Aktualisieren">
|
||||||
|
<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 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"/>
|
||||||
|
</svg>
|
||||||
|
@if (_isLoading) {
|
||||||
|
<span class="spinner-border spinner-border-sm" style="width: 14px; height: 14px;"></span>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="sender-btn sender-btn--logout" @onclick="LogoutAsync" disabled="@_isLoggingOut" title="Abmelden">
|
||||||
|
@if (_isLoggingOut) {
|
||||||
|
<span class="spinner-border spinner-border-sm" style="width: 14px; height: 14px;"></span>
|
||||||
|
} else {
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||||
|
<path fill-rule="evenodd" d="M10 12.5a.5.5 0 0 1-.5.5h-8a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 .5.5v2a.5.5 0 0 0 1 0v-2A1.5 1.5 0 0 0 9.5 2h-8A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h8a1.5 1.5 0 0 0 1.5-1.5v-2a.5.5 0 0 0-1 0v2z"/>
|
||||||
|
<path fill-rule="evenodd" d="M15.854 8.354a.5.5 0 0 0 0-.708l-3-3a.5.5 0 0 0-.708.708L14.293 7.5H5.5a.5.5 0 0 0 0 1h8.793l-2.147 2.146a.5.5 0 0 0 .708.708l3-3z"/>
|
||||||
|
</svg>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sender-content">
|
||||||
|
@if (_isLoading && _allEnvelopes == null) {
|
||||||
|
<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">
|
||||||
|
<span class="visually-hidden">Lädt...</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-white fw-semibold">Umschläge werden geladen...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
} else if (_errorMessage != null) {
|
||||||
|
<div class="error-container">
|
||||||
|
<div class="alert alert-danger shadow-lg">
|
||||||
|
<div class="d-flex align-items-start">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" fill="currentColor" class="me-3 flex-shrink-0" 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="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>
|
||||||
|
<div>
|
||||||
|
<h5 class="mb-2">Fehler beim Laden der Umschläge</h5>
|
||||||
|
<p class="mb-0">@_errorMessage</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
} else {
|
||||||
|
<div class="sender-grid-container">
|
||||||
|
<div class="sender-tabs">
|
||||||
|
<button class="sender-tab @(_activeTab == "active" ? "sender-tab--active" : "")" @onclick='() => _activeTab = "active"'>
|
||||||
|
<span>Aktive Umschläge</span>
|
||||||
|
@if (_activeEnvelopes != null) {
|
||||||
|
<span style="opacity: 0.6; margin-left: 0.5rem;">(@_activeEnvelopes.Count())</span>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
<button class="sender-tab @(_activeTab == "completed" ? "sender-tab--active" : "")" @onclick='() => _activeTab = "completed"'>
|
||||||
|
<span>Abgeschlossene Umschläge</span>
|
||||||
|
@if (_completedEnvelopes != null) {
|
||||||
|
<span style="opacity: 0.6; margin-left: 0.5rem;">(@_completedEnvelopes.Count())</span>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sender-grid-wrapper">
|
||||||
|
@if (_activeTab == "active") {
|
||||||
|
<DxGrid Data="@_activeEnvelopes"
|
||||||
|
@ref="_gridActive"
|
||||||
|
ShowFilterRow="true"
|
||||||
|
ShowSearchBox="true"
|
||||||
|
PageSize="20"
|
||||||
|
PagerVisible="true"
|
||||||
|
SelectionMode="GridSelectionMode.Single"
|
||||||
|
SelectedDataItem="@_selectedEnvelope"
|
||||||
|
SelectedDataItemChanged="@OnSelectedEnvelopeChanged"
|
||||||
|
CustomizeElement="OnCustomizeElement">
|
||||||
|
<Columns>
|
||||||
|
<DxGridDataColumn FieldName="Id" Caption="ID" Width="80px" />
|
||||||
|
<DxGridDataColumn FieldName="Title" Caption="Titel" Width="300px">
|
||||||
|
<CellDisplayTemplate Context="cellContext">
|
||||||
|
<strong>@((cellContext.DataItem as EnvelopeDto)?.Title)</strong>
|
||||||
|
</CellDisplayTemplate>
|
||||||
|
</DxGridDataColumn>
|
||||||
|
<DxGridDataColumn FieldName="Status" Caption="Status" Width="180px">
|
||||||
|
<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" Width="250px">
|
||||||
|
<CellDisplayTemplate Context="cellContext">
|
||||||
|
@{
|
||||||
|
var envelope = cellContext.DataItem as EnvelopeDto;
|
||||||
|
if (envelope != null) {
|
||||||
|
var receivers = envelope.EnvelopeReceivers ?? new List<EnvelopeReceiverSimpleDto>();
|
||||||
|
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>
|
||||||
|
</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;
|
||||||
|
if (envelope?.EnvelopeReceivers?.Any() == true) {
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 0.5rem;">
|
||||||
|
@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;">
|
||||||
|
<span class="receiver-badge receiver-badge--@(receiver.Signed ? "signed" : "unsigned")" style="min-width: 100px;">
|
||||||
|
@if (receiver.Signed) {
|
||||||
|
<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"/>
|
||||||
|
</svg>
|
||||||
|
<span>Unterschrieben</span>
|
||||||
|
} else {
|
||||||
|
<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.Email</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
} else {
|
||||||
|
<p style="color: #9ca3af; font-size: 0.875rem; margin: 0;">Keine Empfänger</p>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</DetailRowTemplate>
|
||||||
|
</DxGrid>
|
||||||
|
} else {
|
||||||
|
<DxGrid Data="@_completedEnvelopes"
|
||||||
|
@ref="_gridCompleted"
|
||||||
|
ShowFilterRow="true"
|
||||||
|
ShowSearchBox="true"
|
||||||
|
PageSize="20"
|
||||||
|
PagerVisible="true"
|
||||||
|
SelectionMode="GridSelectionMode.Single"
|
||||||
|
SelectedDataItem="@_selectedEnvelope"
|
||||||
|
SelectedDataItemChanged="@OnSelectedEnvelopeChanged"
|
||||||
|
CustomizeElement="OnCustomizeElement">
|
||||||
|
<Columns>
|
||||||
|
<DxGridDataColumn FieldName="Id" Caption="ID" Width="80px" />
|
||||||
|
<DxGridDataColumn FieldName="Title" Caption="Titel" Width="300px">
|
||||||
|
<CellDisplayTemplate Context="cellContext">
|
||||||
|
<strong>@((cellContext.DataItem as EnvelopeDto)?.Title)</strong>
|
||||||
|
</CellDisplayTemplate>
|
||||||
|
</DxGridDataColumn>
|
||||||
|
<DxGridDataColumn FieldName="Status" Caption="Status" Width="180px">
|
||||||
|
<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" Width="250px">
|
||||||
|
<CellDisplayTemplate Context="cellContext">
|
||||||
|
@{
|
||||||
|
var envelope = cellContext.DataItem as EnvelopeDto;
|
||||||
|
if (envelope != null) {
|
||||||
|
var receivers = envelope.EnvelopeReceivers ?? new List<EnvelopeReceiverSimpleDto>();
|
||||||
|
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(90px, #81c784 0%, #66bb6a 100%); width: @((signed * 100.0 / total).ToString("F0"))%;"></div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</CellDisplayTemplate>
|
||||||
|
</DxGridDataColumn>
|
||||||
|
</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;
|
||||||
|
if (envelope?.EnvelopeReceivers?.Any() == true) {
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 0.5rem;">
|
||||||
|
@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;">
|
||||||
|
<span class="receiver-badge receiver-badge--@(receiver.Signed ? "signed" : "unsigned")" style="min-width: 100px;">
|
||||||
|
@if (receiver.Signed) {
|
||||||
|
<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"/>
|
||||||
|
</svg>
|
||||||
|
<span>Unterschrieben</span>
|
||||||
|
} else {
|
||||||
|
<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.Email</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
} else {
|
||||||
|
<p style="color: #9ca3af; font-size: 0.875rem; margin: 0;">Keine Empfänger</p>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</DetailRowTemplate>
|
||||||
|
</DxGrid>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
private IEnumerable<EnvelopeDto>? _allEnvelopes;
|
||||||
|
private IEnumerable<EnvelopeDto>? _activeEnvelopes;
|
||||||
|
private IEnumerable<EnvelopeDto>? _completedEnvelopes;
|
||||||
|
private EnvelopeDto? _selectedEnvelope;
|
||||||
|
private string _activeTab = "active";
|
||||||
|
private bool _isLoading = true;
|
||||||
|
private bool _isLoggingOut = false;
|
||||||
|
private string? _errorMessage;
|
||||||
|
private DxGrid? _gridActive;
|
||||||
|
private DxGrid? _gridCompleted;
|
||||||
|
|
||||||
}
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
await LoadEnvelopesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task LoadEnvelopesAsync()
|
||||||
|
{
|
||||||
|
_isLoading = true;
|
||||||
|
_errorMessage = null;
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_allEnvelopes = await EnvelopeService.GetAsync();
|
||||||
|
|
||||||
|
// Split into active and completed based on status
|
||||||
|
var envelopes = _allEnvelopes.ToList();
|
||||||
|
_activeEnvelopes = envelopes.Where(e => ((EnvelopeStatus)e.Status).IsActive()).ToList();
|
||||||
|
_completedEnvelopes = envelopes.Where(e => ((EnvelopeStatus)e.Status).IsCompleted()).ToList();
|
||||||
|
|
||||||
|
await JSRuntime.InvokeVoidAsync("console.log", $"Loaded {_activeEnvelopes.Count()} active and {_completedEnvelopes.Count()} completed envelopes");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_errorMessage = ex.Message;
|
||||||
|
await JSRuntime.InvokeVoidAsync("console.error", "Fehler beim Laden der Umschläge:", ex.ToString());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_isLoading = false;
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task RefreshEnvelopes()
|
||||||
|
{
|
||||||
|
await LoadEnvelopesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateEnvelope()
|
||||||
|
{
|
||||||
|
// TODO: Navigate to envelope creation page
|
||||||
|
JSRuntime.InvokeVoidAsync("console.log", "Create envelope clicked - not yet implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditEnvelope()
|
||||||
|
{
|
||||||
|
if (_selectedEnvelope == null) return;
|
||||||
|
// TODO: Navigate to envelope editor
|
||||||
|
JSRuntime.InvokeVoidAsync("console.log", $"Edit envelope {_selectedEnvelope.Id} clicked - not yet implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeleteEnvelope()
|
||||||
|
{
|
||||||
|
if (_selectedEnvelope == null) return;
|
||||||
|
// TODO: Show delete confirmation dialog
|
||||||
|
JSRuntime.InvokeVoidAsync("console.log", $"Delete envelope {_selectedEnvelope.Id} clicked - not yet implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task LogoutAsync()
|
||||||
|
{
|
||||||
|
_isLoggingOut = true;
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
await AuthService.LogoutSenderAsync();
|
||||||
|
Navigation.NavigateTo("/sender/login", forceLoad: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsEnvelopeSent(EnvelopeDto envelope)
|
||||||
|
{
|
||||||
|
var status = (EnvelopeStatus)envelope.Status;
|
||||||
|
return status >= EnvelopeStatus.EnvelopeQueued;
|
||||||
|
}
|
||||||
|
|
||||||
|
(string Label, string CssClass, string DotColor) GetStatusInfo(int statusCode)
|
||||||
|
{
|
||||||
|
var status = (EnvelopeStatus)statusCode;
|
||||||
|
return status switch
|
||||||
|
{
|
||||||
|
EnvelopeStatus.EnvelopePartlySigned => ("Teilweise unterschrieben", "partly-signed", "green"),
|
||||||
|
EnvelopeStatus.EnvelopeQueued => ("In Warteschlange", "queued", "orange"),
|
||||||
|
EnvelopeStatus.EnvelopeSent => ("Gesendet", "sent", "orange"),
|
||||||
|
EnvelopeStatus.EnvelopeCompletelySigned => ("Vollständig unterschrieben", "completed", "green"),
|
||||||
|
EnvelopeStatus.EnvelopeDeleted => ("Gelöscht", "deleted", "red"),
|
||||||
|
EnvelopeStatus.EnvelopeRejected => ("Abgelehnt", "rejected", "red"),
|
||||||
|
EnvelopeStatus.EnvelopeWithdrawn => ("Zurückgezogen", "withdrawn", "red"),
|
||||||
|
EnvelopeStatus.EnvelopeCreated => ("Erstellt", "created", "blue"),
|
||||||
|
EnvelopeStatus.EnvelopeSaved => ("Gespeichert", "saved", "blue"),
|
||||||
|
_ => ("Unbekannt", "unknown", "blue")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnCustomizeElement(GridCustomizeElementEventArgs e)
|
||||||
|
{
|
||||||
|
// Future: Add custom row coloring based on status if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnSelectedEnvelopeChanged(object envelope)
|
||||||
|
{
|
||||||
|
_selectedEnvelope = envelope as EnvelopeDto;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@ builder.Services.AddScoped<EnvelopeReceiverService>();
|
|||||||
builder.Services.AddScoped<SignatureService>();
|
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.AddDevExpressWebAssemblyBlazorReportViewer();
|
builder.Services.AddDevExpressWebAssemblyBlazorReportViewer();
|
||||||
builder.Services.AddDevExpressWebAssemblyBlazorPdfViewer();
|
builder.Services.AddDevExpressWebAssemblyBlazorPdfViewer();
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"profiles": {
|
"profiles": {
|
||||||
"EnvelopeGenerator.ReceiverUI": {
|
"EnvelopeGenerator.ReceiverUI": {
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"launchBrowser": true,
|
"launchBrowser": false,
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -78,4 +78,16 @@ public class AuthService(HttpClient http, IOptions<ApiOptions> apiOptions)
|
|||||||
_ => SenderLoginResult.Error
|
_ => SenderLoginResult.Error
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Logs out the sender user by removing the authentication cookie.
|
||||||
|
/// Calls POST /api/auth/logout.
|
||||||
|
/// </summary>
|
||||||
|
public async Task<bool> LogoutSenderAsync(CancellationToken cancel = default)
|
||||||
|
{
|
||||||
|
var response = await http.PostAsync(
|
||||||
|
$"{_api.BaseUrl}/api/auth/logout",
|
||||||
|
null, cancel);
|
||||||
|
return response.IsSuccessStatusCode;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user