Add reusable UI components and toast notification system
- Introduce ActionPanel, EnvelopeInfoCard, TfaForm, ConfirmDialog, StatusPage, and Toast components for modular, presentational UI - Add ToastService for pub/sub toast notifications; register in DI - Refactor AccessCodeForm for improved UX and parameterization - Enhance MainLayout with Toast integration and better error handling - Standardize and extend app.css for new components and responsive design - All new components are "dumb" (no service/API knowledge), using EventCallbacks for parent interaction - ConfirmDialog supports awaitable user confirmation via TaskCompletionSource
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
namespace EnvelopeGenerator.ReceiverUI.Client.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service für Toast-Benachrichtigungen. Ersetzt AlertifyJS aus dem Web-Projekt.
|
||||
///
|
||||
/// WARUM ein Service und keine Komponente mit Parametern?
|
||||
/// - Toasts können von ÜBERALL ausgelöst werden (Pages, Services, andere Komponenten)
|
||||
/// - Ein Service ist über Dependency Injection überall verfügbar
|
||||
/// - Die Toast-Komponente im Layout hört auf diesen Service und rendert die Nachrichten
|
||||
///
|
||||
/// PATTERN: Pub/Sub (Publisher/Subscriber)
|
||||
/// - Publisher: Jede Komponente die _toast.ShowSuccess("...") aufruft
|
||||
/// - Subscriber: Die Toast-Komponente im MainLayout, die auf OnChange hört
|
||||
///
|
||||
/// Das ist das gleiche Pattern wie beim EnvelopeState.
|
||||
/// </summary>
|
||||
public class ToastService
|
||||
{
|
||||
/// <summary>
|
||||
/// Liste aller aktuell sichtbaren Toasts.
|
||||
/// Mehrere Toasts können gleichzeitig angezeigt werden (gestapelt).
|
||||
/// </summary>
|
||||
public List<ToastMessage> Messages { get; } = [];
|
||||
|
||||
/// <summary>Event: Informiert die Toast-Komponente über Änderungen</summary>
|
||||
public event Action? OnChange;
|
||||
|
||||
public void ShowSuccess(string text) => Show(text, "success");
|
||||
public void ShowError(string text) => Show(text, "danger");
|
||||
public void ShowInfo(string text) => Show(text, "info");
|
||||
public void ShowWarning(string text) => Show(text, "warning");
|
||||
|
||||
/// <summary>
|
||||
/// Fügt einen Toast hinzu und entfernt ihn nach der angegebenen Dauer automatisch.
|
||||
///
|
||||
/// WARUM async void?
|
||||
/// Normalerweise vermeidet man async void. Hier ist es ok, weil:
|
||||
/// - Es ist ein Fire-and-Forget-Timer (wir warten nicht auf das Ergebnis)
|
||||
/// - Fehler im Delay können die App nicht zum Absturz bringen
|
||||
/// - Das ist ein gängiges Pattern für Auto-Dismiss-Logik
|
||||
/// </summary>
|
||||
private async void Show(string text, string type, int durationMs = 4000)
|
||||
{
|
||||
var message = new ToastMessage(text, type);
|
||||
Messages.Add(message);
|
||||
OnChange?.Invoke();
|
||||
|
||||
// Nach Ablauf der Dauer automatisch entfernen
|
||||
await Task.Delay(durationMs);
|
||||
Messages.Remove(message);
|
||||
OnChange?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>Entfernt einen Toast sofort (z.B. wenn der Benutzer auf X klickt)</summary>
|
||||
public void Dismiss(ToastMessage message)
|
||||
{
|
||||
Messages.Remove(message);
|
||||
OnChange?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ein einzelner Toast-Eintrag.
|
||||
///
|
||||
/// WARUM ein record statt class?
|
||||
/// - Records haben automatisch Equals/GetHashCode basierend auf allen Properties
|
||||
/// - Wir brauchen das für Messages.Remove() — es vergleicht über Referenz-Gleichheit
|
||||
/// - Die Id (Guid) macht jeden Toast einzigartig, auch bei gleichem Text
|
||||
/// </summary>
|
||||
public record ToastMessage(string Text, string Type)
|
||||
{
|
||||
/// <summary>Eindeutige Id — damit zwei Toasts mit gleichem Text unterscheidbar sind</summary>
|
||||
public Guid Id { get; } = Guid.NewGuid();
|
||||
|
||||
/// <summary>
|
||||
/// Gibt die Bootstrap-Icon-Klasse basierend auf dem Typ zurück.
|
||||
/// success → check-circle, danger → x-circle, etc.
|
||||
/// </summary>
|
||||
public string IconClass => Type switch
|
||||
{
|
||||
"success" => "bi-check-circle-fill",
|
||||
"danger" => "bi-x-circle-fill",
|
||||
"warning" => "bi-exclamation-triangle-fill",
|
||||
_ => "bi-info-circle-fill"
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user