Reworked message system, implemented in ManageUsers and EmailSignup
This commit is contained in:
parent
33e8d490ac
commit
cfad069d41
45
Wave/Components/AlertComponent.razor
Normal file
45
Wave/Components/AlertComponent.razor
Normal file
|
@ -0,0 +1,45 @@
|
|||
@using Wave.Utilities
|
||||
@implements IDisposable
|
||||
@inject IMessageDisplay Messages
|
||||
|
||||
@if (Message is {} message) {
|
||||
<div class="alert @message.Type" role="alert">
|
||||
<div> </div>
|
||||
<div>
|
||||
@if (message.Title is null) {
|
||||
@message.Body
|
||||
} else {
|
||||
<span class="font-bold">@message.Title</span>
|
||||
<span><small>@message.Body</small></span>
|
||||
}
|
||||
</div>
|
||||
@if (CanDelete) {
|
||||
<button class="btn btn-sm btn-square btn-ghost" onclick="this.parentElement.remove();">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public bool CanDelete { get; set; }
|
||||
|
||||
private IMessageDisplay.Message? Message { get; set; }
|
||||
|
||||
protected override void OnInitialized() {
|
||||
Messages.OnMessage += OnMessage;
|
||||
}
|
||||
|
||||
private bool OnMessage(IMessageDisplay.Message message) {
|
||||
Message = message;
|
||||
StateHasChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
Messages.OnMessage -= OnMessage;
|
||||
}
|
||||
}
|
|
@ -28,7 +28,14 @@
|
|||
<SectionOutlet SectionName="GlobalHeader" />
|
||||
<input id="narrow-reading-toggle" type="checkbox" class="narrow-reading-toggle" />
|
||||
<div class="flex-1 container mx-auto px-4 md:px-12 py-8 reading-toggle-target h-full">
|
||||
<AlertComponent CanDelete="true" />
|
||||
@Body
|
||||
@if (HttpContext is null || HttpContext?.GetEndpoint()?
|
||||
.Metadata.GetMetadata<RenderModeAttribute>()?
|
||||
.Mode is not null) {
|
||||
// for some reason that's how you test for interactive render modes
|
||||
<ToastComponent />
|
||||
}
|
||||
</div>
|
||||
</main>
|
||||
<footer class="flex flex-col md:flex-row items-center justify-center p-4 gap-y-3 gap-x-4 bg-base-300 text-base-content">
|
||||
|
@ -87,4 +94,6 @@
|
|||
@code {
|
||||
[CascadingParameter(Name = "UserTheme")]
|
||||
private string? UserTheme { get; set; }
|
||||
[CascadingParameter]
|
||||
private HttpContext? HttpContext { get; set; }
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
@using System.Net
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using Wave.Services
|
||||
@using Wave.Utilities
|
||||
|
||||
@inject ILogger<EmailSignup> Logger
|
||||
@inject IStringLocalizer<EmailSignup> Localizer
|
||||
|
@ -16,15 +17,10 @@
|
|||
@inject NavigationManager Navigation
|
||||
@inject IAdvancedEmailSender EmailSender
|
||||
@inject EmailTemplateService TemplateService
|
||||
@inject IMessageDisplay Messages
|
||||
|
||||
<PageTitle>@(TitlePrefix + Localizer["Title"])</PageTitle>
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(Message)) {
|
||||
<div class="alert alert-success">
|
||||
<span>@Message</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<BoardComponent CenterContent="true">
|
||||
<BoardCardComponent Heading="@Localizer["Title"]">
|
||||
<EditForm method="post" FormName="email-signup" Model="Model" OnValidSubmit="OnValidSubmit">
|
||||
|
@ -55,8 +51,6 @@
|
|||
[Parameter, SupplyParameterFromQuery(Name = "token")]
|
||||
public string? Token { get; set; }
|
||||
|
||||
private string Message { get; set; } = string.Empty;
|
||||
|
||||
protected override async Task OnInitializedAsync() {
|
||||
if (Features.Value.EmailSubscriptions is not true)
|
||||
throw new ApplicationException("Email subscriptions not enabled.");
|
||||
|
@ -66,19 +60,19 @@
|
|||
var id = await TemplateService.ValidateTokensAsync(Id, Token, deleteToken: false);
|
||||
|
||||
if (id is null) {
|
||||
Message = Localizer["Failure_Message"];
|
||||
Messages.ShowError(Localizer["Failure_Message"]);
|
||||
return;
|
||||
}
|
||||
|
||||
await using var context = await ContextFactory.CreateDbContextAsync();
|
||||
var subscriber = context.Set<EmailSubscriber>().IgnoreQueryFilters().FirstOrDefault(s => s.Id == id);
|
||||
if (subscriber is null) {
|
||||
Message = Localizer["Failure_Message"];
|
||||
Messages.ShowError(Localizer["Failure_Message"]);
|
||||
return;
|
||||
}
|
||||
subscriber.Unsubscribed = false;
|
||||
await context.SaveChangesAsync();
|
||||
Message = Localizer["Success_Message"];
|
||||
Messages.ShowSuccess(Localizer["Success_Message"]);
|
||||
|
||||
await TemplateService.ValidateTokensAsync(Id, Token, deleteToken: true);
|
||||
|
||||
|
@ -92,7 +86,7 @@
|
|||
Localizer["WelcomeEmailSubject"], Localizer["WelcomeEmailTitle"], Localizer["WelcomeEmailBody"], articles);
|
||||
} catch (Exception ex) {
|
||||
Logger.LogError(ex, "Error trying to confirm subscriber.");
|
||||
Message = Localizer["Failure_Message"];
|
||||
Messages.ShowError(Localizer["Failure_Message"]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -101,7 +95,7 @@
|
|||
throw new ApplicationException("Email subscriptions not enabled.");
|
||||
|
||||
try {
|
||||
Message = Localizer["Submit_Message"];
|
||||
Messages.ShowSuccess(Localizer["Submit_Message"]);
|
||||
await using var context = await ContextFactory.CreateDbContextAsync();
|
||||
|
||||
var subscriber = context.Set<EmailSubscriber>().IgnoreQueryFilters().FirstOrDefault(s => s.Email == Model.Email);
|
||||
|
@ -127,7 +121,6 @@
|
|||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.LogError(ex, "Failed to create subscriber/send confirmation mail.");
|
||||
Message = Localizer["Failure_Message"];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
@inject RoleManager<IdentityRole> RoleManager
|
||||
@inject UserManager<ApplicationUser> UserManager
|
||||
@inject IStringLocalizer<ManageUsers> Localizer
|
||||
@inject IMessageDisplay Toast
|
||||
|
||||
<PageTitle>@(TitlePrefix + Localizer["Title"])</PageTitle>
|
||||
|
||||
|
@ -55,12 +56,9 @@
|
|||
}
|
||||
</section>
|
||||
|
||||
<ToastComponent @ref="Toast" />
|
||||
|
||||
@code {
|
||||
[CascadingParameter(Name = "TitlePrefix")]
|
||||
private string TitlePrefix { get; set; } = default!;
|
||||
public IMessageDisplay Toast { get; set; } = null!;
|
||||
private string ModalId { get; } = "UserDialog";
|
||||
|
||||
[CascadingParameter]
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
@using Wave.Utilities
|
||||
@implements IMessageDisplay
|
||||
@implements IDisposable
|
||||
@inject IMessageDisplay Messages
|
||||
@rendermode InteractiveServer
|
||||
|
||||
<div class="toast toast-start z-10" role="alert">
|
||||
@foreach (var message in Messages) {
|
||||
<div class="alert @message.Type" @onclick="() => Messages.Remove(message)">
|
||||
@foreach (var message in MessagesDisplayed) {
|
||||
<div class="alert @message.Type" @onclick="() => MessagesDisplayed.Remove(message)">
|
||||
@if (message.Title is null) {
|
||||
@message.Body
|
||||
} else {
|
||||
|
@ -15,10 +17,19 @@
|
|||
</div>
|
||||
|
||||
@code {
|
||||
private List<IMessageDisplay.Message> Messages { get; } = [];
|
||||
private List<IMessageDisplay.Message> MessagesDisplayed { get; } = [];
|
||||
|
||||
public void ShowMessage(IMessageDisplay.Message message) {
|
||||
Messages.Add(message);
|
||||
protected override void OnInitialized() {
|
||||
Messages.OnMessage += OnMessage;
|
||||
}
|
||||
|
||||
private bool OnMessage(IMessageDisplay.Message message) {
|
||||
MessagesDisplayed.Add(message);
|
||||
StateHasChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
Messages.OnMessage -= OnMessage;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -129,6 +129,7 @@
|
|||
logMessages.Add("No Email provider configured.");
|
||||
}
|
||||
|
||||
builder.Services.AddSingleton<IMessageDisplay, MessageService>();
|
||||
builder.Services.AddSingleton<FileSystemService>();
|
||||
builder.Services.AddSingleton<EmailTemplateService>();
|
||||
builder.Services.AddHostedService<EmailBackgroundWorker>();
|
||||
|
|
19
Wave/Services/MessageService.cs
Normal file
19
Wave/Services/MessageService.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using Wave.Utilities;
|
||||
using static Wave.Utilities.IMessageDisplay;
|
||||
|
||||
namespace Wave.Services;
|
||||
|
||||
public class MessageService : IMessageDisplay {
|
||||
private Queue<Message> Messages { get; } = new();
|
||||
|
||||
public event Func<Message, bool>? OnMessage;
|
||||
|
||||
public IReadOnlyList<Message> GetMessages() => [.. Messages];
|
||||
public Message? Pop() => Messages.TryDequeue(out var m) ? m : null;
|
||||
|
||||
public void ShowMessage(Message message) {
|
||||
if (OnMessage?.Invoke(message) is not true) {
|
||||
Messages.Enqueue(message);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +1,21 @@
|
|||
namespace Wave.Utilities;
|
||||
|
||||
public interface IMessageDisplay {
|
||||
public void ShowMessage(Message message);
|
||||
IReadOnlyList<Message> GetMessages();
|
||||
event Func<Message, bool>? OnMessage;
|
||||
|
||||
Message? Pop();
|
||||
|
||||
void ShowMessage(Message message);
|
||||
|
||||
public void ShowInfo(string message, string? title = null)
|
||||
void ShowInfo(string message, string? title = null)
|
||||
=> ShowMessage(new Message(message, "alert-info", title, DateTimeOffset.UtcNow));
|
||||
public void ShowSuccess(string message, string? title = null)
|
||||
void ShowSuccess(string message, string? title = null)
|
||||
=> ShowMessage(new Message(message, "alert-success", title, DateTimeOffset.UtcNow));
|
||||
public void ShowWarning(string message, string? title = null)
|
||||
void ShowWarning(string message, string? title = null)
|
||||
=> ShowMessage(new Message(message, "alert-warning", title, DateTimeOffset.UtcNow));
|
||||
public void ShowError(string message, string? title = null)
|
||||
void ShowError(string message, string? title = null)
|
||||
=> ShowMessage(new Message(message, "alert-error", title, DateTimeOffset.UtcNow));
|
||||
|
||||
public sealed record Message(string Body, string Type, string? Title, DateTimeOffset Created);
|
||||
sealed record Message(string Body, string Type, string? Title, DateTimeOffset Created);
|
||||
}
|
Loading…
Reference in a new issue