Refactor envelope receiver auth flow and state handling

- Introduce IReceiverAuthService and ReceiverAuthService for all envelope receiver authentication and status API calls
- Add ReceiverAuthModel as a client-side DTO for API responses
- Refactor EnvelopeState to store all relevant fields and update via ApplyApiResponse
- Overhaul EnvelopePage.razor to use new service and state, with improved status handling and UI
- Enhance ApiResponse and ApiServiceBase to support structured error deserialization
- Register IReceiverAuthService in DI container
This commit is contained in:
OlgunR
2026-03-23 15:57:20 +01:00
parent 4aa889f178
commit 93488ba83e
8 changed files with 320 additions and 59 deletions

View File

@@ -1,6 +1,6 @@
@page "/envelope/{EnvelopeKey}"
@rendermode InteractiveAuto
@inject IEnvelopeService EnvelopeService
@inject IReceiverAuthService ReceiverAuthService
@inject EnvelopeState State
@implements IDisposable
@@ -13,24 +13,59 @@
break;
case EnvelopePageStatus.NotFound:
<ErrorDisplay Title="Nicht gefunden"
Message="Dieses Dokument existiert nicht oder ist nicht mehr verfügbar." />
<StatusPage Type="not_found" />
break;
case EnvelopePageStatus.AlreadySigned:
<ErrorDisplay Title="Bereits unterschrieben"
Message="Dieses Dokument wurde bereits unterschrieben."
Icon="check-circle" />
<StatusPage Type="signed"
Title="@State.Title"
SenderEmail="@State.SenderEmail" />
break;
case EnvelopePageStatus.Rejected:
<StatusPage Type="rejected"
Title="@State.Title"
SenderEmail="@State.SenderEmail" />
break;
case EnvelopePageStatus.Expired:
<StatusPage Type="expired" />
break;
case EnvelopePageStatus.RequiresAccessCode:
<AccessCodeForm EnvelopeKey="@EnvelopeKey"
ErrorMessage="@State.ErrorMessage"
SenderEmail="@State.SenderEmail"
Title="@State.Title"
TfaEnabled="@State.TfaEnabled"
HasPhoneNumber="@State.HasPhoneNumber"
OnSubmit="HandleAccessCodeSubmit" />
break;
case EnvelopePageStatus.RequiresTwoFactor:
<TfaForm EnvelopeKey="@EnvelopeKey"
TfaType="@(State.TfaType ?? "authenticator")"
TfaExpiration="@State.TfaExpiration"
HasPhoneNumber="@State.HasPhoneNumber"
ErrorMessage="@State.ErrorMessage"
OnSubmit="HandleTfaSubmit" />
break;
case EnvelopePageStatus.ShowDocument:
<PdfViewer DocumentBytes="@_documentBytes" />
@* Phase 4 (PSPDFKit) kommt später — vorerst Platzhalter *@
<div class="text-center mt-5">
<div class="status-icon signed">
<i class="bi bi-file-earmark-check"></i>
</div>
<h2>Dokument bereit</h2>
<p class="text-muted">
«@State.Title» — PDF-Viewer wird in Phase 4 integriert.
</p>
@if (State.ReadOnly)
{
<span class="badge bg-secondary">Nur Lesen</span>
}
</div>
break;
case EnvelopePageStatus.Error:
@@ -41,44 +76,83 @@
@code {
[Parameter] public string EnvelopeKey { get; set; } = default!;
private byte[]? _documentBytes;
protected override async Task OnInitializedAsync()
{
State.OnChange += StateHasChanged;
await LoadEnvelopeAsync();
}
private async Task LoadEnvelopeAsync()
{
State.SetLoading();
var result = await EnvelopeService.GetEnvelopeReceiversAsync();
if (!result.IsSuccess)
{
if (result.StatusCode == 401)
State.SetAccessCodeRequired();
else if (result.StatusCode == 404)
State.SetNotFound();
else
State.SetError(result.ErrorMessage ?? "Unbekannter Fehler");
return;
}
State.SetDocument();
await LoadStatusAsync();
}
/// <summary>
/// Empfängt das Tuple (Code, PreferSms) von AccessCodeForm.OnSubmit.
/// AccessCodeForm gibt immer ein Tuple zurück, weil es auch den
/// SMS-Toggle-Zustand enthält (für TFA).
/// Erster API-Call: Status prüfen.
/// Entspricht dem GET /Envelope/{key} im Web-Projekt.
/// Die API entscheidet, was passiert (AccessCode nötig? Bereits signiert? etc.)
/// </summary>
private async Task LoadStatusAsync()
{
State.SetLoading();
var result = await ReceiverAuthService.GetStatusAsync(EnvelopeKey);
if (result.IsSuccess && result.Data is not null)
{
State.ApplyApiResponse(result.Data);
}
else if (result.StatusCode == 404)
{
State.SetNotFound();
}
else
{
State.SetError(result.ErrorMessage ?? "Verbindung zum Server fehlgeschlagen.");
}
}
/// <summary>
/// Zweiter API-Call: AccessCode senden.
/// Wird von AccessCodeForm aufgerufen (OnSubmit-Callback).
/// Die API prüft den Code und antwortet mit dem nächsten Status.
/// </summary>
private async Task HandleAccessCodeSubmit((string Code, bool PreferSms) submission)
{
// AccessCode an API senden
// Bei Erfolg: State.SetDocument() oder State.SetTwoFactorRequired()
// Bei Fehler: State.SetError(...)
var result = await ReceiverAuthService.SubmitAccessCodeAsync(
EnvelopeKey, submission.Code, submission.PreferSms);
if (result.IsSuccess && result.Data is not null)
{
State.ApplyApiResponse(result.Data);
}
else if (result.Data is not null)
{
// 401 mit Body → falscher Code, API gibt trotzdem ReceiverAuthModel zurück
State.ApplyApiResponse(result.Data);
}
else
{
State.SetError(result.ErrorMessage ?? "Fehler bei der Code-Prüfung.");
}
}
/// <summary>
/// Dritter API-Call: TFA-Code senden.
/// Wird von TfaForm aufgerufen (OnSubmit-Callback).
/// </summary>
private async Task HandleTfaSubmit((string Code, string Type) submission)
{
var result = await ReceiverAuthService.SubmitTfaCodeAsync(
EnvelopeKey, submission.Code, submission.Type);
if (result.IsSuccess && result.Data is not null)
{
State.ApplyApiResponse(result.Data);
}
else if (result.Data is not null)
{
State.ApplyApiResponse(result.Data);
}
else
{
State.SetError(result.ErrorMessage ?? "Fehler bei der TFA-Prüfung.");
}
}
public void Dispose() => State.OnChange -= StateHasChanged;