5631d1d57a
Email delivery failures and PDF generation errors now show a permanent warning/error toast that requires manual dismissal, so users cannot accidentally miss critical action-blocking feedback. - ToastHelper: WarningPermanent TempData key + Warning/WarningPermanent extension methods on both ITempDataDictionary and Controller - SetNotificationResultToast: NotificationStatus.Failed now uses ToastWarningPermanent (previously auto-dismissed in 5 s) - InvoicesController.Send: TempData["Warning"] → TempData["WarningPermanent"] when PDF generation or email dispatch fails - InvoicesController.DownloadPdf: TempData["Error"] → TempData["ErrorPermanent"] with the actual exception message so root cause is visible - _Layout.cshtml: WarningPermanent hidden div - toast-notifications.js: WarningPermanent handler (timeOut: 0) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
197 lines
8.9 KiB
C#
197 lines
8.9 KiB
C#
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
|
using PowderCoating.Core.Entities;
|
|
using PowderCoating.Core.Enums;
|
|
|
|
namespace PowderCoating.Web.Helpers
|
|
{
|
|
/// <summary>
|
|
/// Extension methods on <see cref="ITempDataDictionary"/> for storing toast
|
|
/// notification messages that survive a POST→redirect→GET cycle.
|
|
/// <para>
|
|
/// TempData is used (rather than ViewBag or ViewData) because toast messages
|
|
/// must survive an HTTP redirect: a controller sets the message before calling
|
|
/// <c>RedirectToAction</c>, and the layout reads and renders it on the next GET.
|
|
/// TempData is session-backed and automatically cleared after it is read.
|
|
/// </para>
|
|
/// <para>
|
|
/// The four well-known string keys (<c>Success</c>, <c>Error</c>, <c>Warning</c>,
|
|
/// <c>Info</c>) match the JavaScript toast-renderer in <c>_Layout.cshtml</c>;
|
|
/// changing them here requires a matching update in the layout script.
|
|
/// </para>
|
|
/// </summary>
|
|
public static class ToastHelper
|
|
{
|
|
/// <summary>TempData key read by the layout to render a green success toast.</summary>
|
|
private const string SuccessKey = "Success";
|
|
|
|
/// <summary>TempData key read by the layout to render a red error toast.</summary>
|
|
private const string ErrorKey = "Error";
|
|
|
|
/// <summary>TempData key read by the layout to render a yellow warning toast.</summary>
|
|
private const string WarningKey = "Warning";
|
|
|
|
/// <summary>TempData key read by the layout to render a yellow warning toast that does not auto-dismiss.</summary>
|
|
private const string WarningPermanentKey = "WarningPermanent";
|
|
|
|
/// <summary>TempData key read by the layout to render a blue info toast.</summary>
|
|
private const string InfoKey = "Info";
|
|
|
|
/// <summary>
|
|
/// Stores a success (green) toast message in TempData for display on the
|
|
/// next page render after a redirect.
|
|
/// </summary>
|
|
/// <param name="tempData">The TempData dictionary for the current request.</param>
|
|
/// <param name="message">The human-readable success message.</param>
|
|
public static void Success(this ITempDataDictionary tempData, string message)
|
|
{
|
|
tempData[SuccessKey] = message;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stores an error (red) toast message in TempData for display on the
|
|
/// next page render after a redirect.
|
|
/// </summary>
|
|
/// <param name="tempData">The TempData dictionary for the current request.</param>
|
|
/// <param name="message">The human-readable error message.</param>
|
|
public static void Error(this ITempDataDictionary tempData, string message)
|
|
{
|
|
tempData[ErrorKey] = message;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stores a warning (yellow) toast message in TempData for display on the
|
|
/// next page render after a redirect.
|
|
/// </summary>
|
|
/// <param name="tempData">The TempData dictionary for the current request.</param>
|
|
/// <param name="message">The human-readable warning message.</param>
|
|
public static void Warning(this ITempDataDictionary tempData, string message)
|
|
{
|
|
tempData[WarningKey] = message;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stores a warning (yellow) toast that requires manual dismissal (no auto-timeout).
|
|
/// Use for critical warnings the user must not miss, such as email delivery failures.
|
|
/// </summary>
|
|
public static void WarningPermanent(this ITempDataDictionary tempData, string message)
|
|
{
|
|
tempData[WarningPermanentKey] = message;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stores an informational (blue) toast message in TempData for display on
|
|
/// the next page render after a redirect.
|
|
/// </summary>
|
|
/// <param name="tempData">The TempData dictionary for the current request.</param>
|
|
/// <param name="message">The human-readable informational message.</param>
|
|
public static void Info(this ITempDataDictionary tempData, string message)
|
|
{
|
|
tempData[InfoKey] = message;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convenience extension methods on <see cref="Controller"/> that delegate to
|
|
/// <see cref="ToastHelper"/> via the controller's own <c>TempData</c>.
|
|
/// <para>
|
|
/// These shorten controller code from <c>TempData.Success("…")</c> to the
|
|
/// more readable <c>this.ToastSuccess("…")</c>, which reads closer to
|
|
/// natural language and is consistent with other MVC helper conventions.
|
|
/// </para>
|
|
/// </summary>
|
|
public static class ControllerToastExtensions
|
|
{
|
|
/// <summary>
|
|
/// Stores a success (green) toast in the controller's TempData.
|
|
/// Convenience wrapper around <see cref="ToastHelper.Success"/>.
|
|
/// </summary>
|
|
/// <param name="controller">The calling MVC controller.</param>
|
|
/// <param name="message">The success message to display after redirect.</param>
|
|
public static void ToastSuccess(this Controller controller, string message)
|
|
{
|
|
controller.TempData.Success(message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stores an error (red) toast in the controller's TempData.
|
|
/// Convenience wrapper around <see cref="ToastHelper.Error"/>.
|
|
/// </summary>
|
|
/// <param name="controller">The calling MVC controller.</param>
|
|
/// <param name="message">The error message to display after redirect.</param>
|
|
public static void ToastError(this Controller controller, string message)
|
|
{
|
|
controller.TempData.Error(message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stores a warning (yellow) toast in the controller's TempData.
|
|
/// Convenience wrapper around <see cref="ToastHelper.Warning"/>.
|
|
/// </summary>
|
|
/// <param name="controller">The calling MVC controller.</param>
|
|
/// <param name="message">The warning message to display after redirect.</param>
|
|
public static void ToastWarning(this Controller controller, string message)
|
|
{
|
|
controller.TempData.Warning(message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stores a permanent warning (yellow, no auto-dismiss) in the controller's TempData.
|
|
/// Use for failures the user must not miss — email delivery errors, PDF generation failures.
|
|
/// </summary>
|
|
public static void ToastWarningPermanent(this Controller controller, string message)
|
|
{
|
|
controller.TempData.WarningPermanent(message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stores an informational (blue) toast in the controller's TempData.
|
|
/// Convenience wrapper around <see cref="ToastHelper.Info"/>.
|
|
/// </summary>
|
|
/// <param name="controller">The calling MVC controller.</param>
|
|
/// <param name="message">The informational message to display after redirect.</param>
|
|
public static void ToastInfo(this Controller controller, string message)
|
|
{
|
|
controller.TempData.Info(message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Translates a <see cref="NotificationLog"/> outcome into the appropriate
|
|
/// toast severity and message.
|
|
/// <para>
|
|
/// Sent notifications use Info (not Success) because the notification is a
|
|
/// side effect of the main action — the main action already gets a Success
|
|
/// toast. Skipped and Failed outcomes are Warning (not Error) because the
|
|
/// primary operation succeeded even if the notification did not.
|
|
/// </para>
|
|
/// </summary>
|
|
/// <param name="controller">The calling MVC controller.</param>
|
|
/// <param name="log">
|
|
/// The most recent <see cref="NotificationLog"/> record, or <c>null</c>
|
|
/// if no notification was attempted (in which case this method is a no-op).
|
|
/// </param>
|
|
public static void SetNotificationResultToast(this Controller controller, NotificationLog? log)
|
|
{
|
|
if (log == null) return;
|
|
|
|
var channel = log.Channel == NotificationChannel.Sms ? "SMS" : "Email";
|
|
switch (log.Status)
|
|
{
|
|
case NotificationStatus.Sent:
|
|
controller.ToastInfo($"{channel} sent to {log.RecipientName} ({log.Recipient}).");
|
|
break;
|
|
case NotificationStatus.Skipped:
|
|
controller.ToastWarning(!string.IsNullOrEmpty(log.Message)
|
|
? $"{channel} skipped: {log.Message}"
|
|
: $"{channel} notification was skipped.");
|
|
break;
|
|
case NotificationStatus.Failed:
|
|
controller.ToastWarningPermanent(!string.IsNullOrEmpty(log.ErrorMessage)
|
|
? $"{channel} delivery failed: {log.ErrorMessage}"
|
|
: $"{channel} notification failed.");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|