Wave/Wave/Components/Pages/EmailSignup.razor

154 lines
5.7 KiB
Plaintext

@page "/Email/Subscribe"
@page "/Email/Confirm"
@using Microsoft.Extensions.Options
@using Wave.Data
@using System.ComponentModel.DataAnnotations
@using System.Net
@using Microsoft.EntityFrameworkCore
@using Wave.Services
@using Wave.Utilities
@inject ILogger<EmailSignup> Logger
@inject IStringLocalizer<EmailSignup> Localizer
@inject IDbContextFactory<ApplicationDbContext> ContextFactory
@inject IOptions<Features> Features
@inject IOptions<Customization> Customizations
@inject NavigationManager Navigation
@inject IAdvancedEmailSender EmailSender
@inject EmailTemplateService TemplateService
<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">
<DataAnnotationsValidator />
<InputLabelComponent LabelText="@Localizer["Name_Label"]" For="() => Model.Name">
<InputText @bind-Value="Model.Name" class="input input-bordered w-full" autocomplete="name"
placeholder="@Localizer["Name_Placeholder"]" />
</InputLabelComponent>
<InputLabelComponent LabelText="@Localizer["Email_Label"]" For="() => Model.Name">
<InputText @bind-Value="Model.Email" class="input input-bordered w-full" autocomplete="email" type="email"
required aria-required="true" placeholder="@Localizer["Email_Placeholder"]" />
</InputLabelComponent>
<button type="submit" class="btn btn-primary w-full">@Localizer["Submit"]</button>
</EditForm>
</BoardCardComponent>
</BoardComponent>
@code {
[CascadingParameter(Name = "TitlePrefix")]
private string TitlePrefix { get; set; } = default!;
[SupplyParameterFromForm(FormName = "email-signup")]
private InputModel Model { get; set; } = new();
[Parameter, SupplyParameterFromQuery(Name = "user")]
public string? Id { get; set; }
[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.");
if (Id is null || Token is null) return;
try {
var id = await TemplateService.ValidateTokensAsync(Id, Token, deleteToken: false);
if (id is null) {
Message = 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"];
return;
}
subscriber.Unsubscribed = false;
await context.SaveChangesAsync();
Message = Localizer["Success_Message"];
await TemplateService.ValidateTokensAsync(Id, Token, deleteToken: true);
var articles = await context.Set<EmailNewsletter>()
.IgnoreAutoIncludes()
.Include(a => a.Article).ThenInclude(a => a.Author)
.OrderByDescending(a => a.DistributionDateTime)
.Take(3)
.ToListAsync();
string body = $"<p>{Localizer["WelcomeEmailBody"]}</p>\n";
foreach (var n in articles) {
string articleLink = ArticleUtilities.GenerateArticleLink(n.Article, new Uri(Customizations.Value.AppUrl, UriKind.Absolute));
body = body +
"<div style=\"padding: 10px; background: #333; color: #fff; margin-bottom: 10px\">" +
$"<h3>{n.Article.Title}</h3>" +
$"<small>{n.Article.Author.Name}</small>" +
$"<p>{n.Article.Body[..Math.Min(100, n.Article.Body.Length)]}...</p>" +
$"<a href=\"{articleLink}\">Link</a>" +
"</div>";
}
await EmailSender.SendSubscribedMailAsync(subscriber,
Localizer["WelcomeEmailSubject"], Localizer["WelcomeEmailTitle"], body, subscribedRole: "welcome");
} catch (Exception ex) {
Logger.LogError(ex, "Error trying to confirm subscriber.");
Message = Localizer["Failure_Message"];
}
}
private async Task OnValidSubmit() {
if (Features.Value.EmailSubscriptions is not true)
throw new ApplicationException("Email subscriptions not enabled.");
try {
Message = Localizer["Submit_Message"];
await using var context = await ContextFactory.CreateDbContextAsync();
var subscriber = context.Set<EmailSubscriber>().IgnoreQueryFilters().FirstOrDefault(s => s.Email == Model.Email);
if (subscriber?.Unsubscribed is false) return;
subscriber ??= new EmailSubscriber {
Email = Model.Email.Trim(),
Unsubscribed = true
};
subscriber.Name = Model.Name;
context.Update(subscriber);
await context.SaveChangesAsync();
if (subscriber.Unsubscribed) {
(string id, string token) = await TemplateService.CreateConfirmTokensAsync(subscriber.Id);
string confirmLink = Navigation.ToAbsoluteUri(
$"/Email/Confirm?user={WebUtility.UrlEncode(id)}&token={WebUtility.UrlEncode(token)}").AbsoluteUri;
string body = string.Format(Localizer["ConfirmEmailBody"], Customizations.Value.AppName) +
$"""<p style="text-align: center"><a href="{confirmLink}">{Localizer["Submit"]}</a></p>""";
await EmailSender.SendDefaultMailAsync(subscriber.Email, subscriber.Name, Localizer["ConfirmEmailSubject"],
Localizer["ConfirmEmailTitle"], body);
}
} catch (Exception ex) {
Logger.LogError(ex, "Failed to create subscriber/send confirmation mail.");
Message = Localizer["Failure_Message"];
}
}
private sealed class InputModel {
[MaxLength(128)]
public string? Name { get; set; }
[EmailAddress, Required(AllowEmptyStrings = false), MaxLength(256)]
public string Email { get; set; } = string.Empty;
}
}