From b7809615c7fda18db1073566c345fe80d5d0463f Mon Sep 17 00:00:00 2001 From: Clive Date: Thu, 20 Aug 2026 15:55:24 +1000 Subject: [PATCH] Connect form email. --- plushealthtech/Pages/Shared/_Layout.cshtml | 63 +++++++++++++++++++-- plushealthtech/Pages/Subscribe.cshtml | 10 ++++ plushealthtech/Pages/Subscribe.cshtml.cs | 64 ++++++++++++++++++++++ 3 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 plushealthtech/Pages/Subscribe.cshtml create mode 100644 plushealthtech/Pages/Subscribe.cshtml.cs diff --git a/plushealthtech/Pages/Shared/_Layout.cshtml b/plushealthtech/Pages/Shared/_Layout.cshtml index a30fe4a..c827b92 100644 --- a/plushealthtech/Pages/Shared/_Layout.cshtml +++ b/plushealthtech/Pages/Shared/_Layout.cshtml @@ -100,10 +100,14 @@
Connect

Subscribe to our technical updates and newsletter.

-
- - -
+
+ @Html.AntiForgeryToken() +
+ + +
+ +
@@ -140,6 +144,57 @@ })(); + + @await RenderSectionAsync("Scripts", required: false) \ No newline at end of file diff --git a/plushealthtech/Pages/Subscribe.cshtml b/plushealthtech/Pages/Subscribe.cshtml new file mode 100644 index 0000000..3a2468f --- /dev/null +++ b/plushealthtech/Pages/Subscribe.cshtml @@ -0,0 +1,10 @@ +@page +@model SubscribeModel +@{ + ViewData["Title"] = "Subscribe"; +} + +@* No markup: POST /Subscribe is handled by SubscribeModel.OnPostAsync and + always returns JSON to the footer "Connect" form. This view exists so + Razor Pages discovers and routes the page model (a page model without a + .cshtml is not registered). *@ diff --git a/plushealthtech/Pages/Subscribe.cshtml.cs b/plushealthtech/Pages/Subscribe.cshtml.cs new file mode 100644 index 0000000..b3145b6 --- /dev/null +++ b/plushealthtech/Pages/Subscribe.cshtml.cs @@ -0,0 +1,64 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using plushealthtech.Mail; + +namespace plushealthtech.Pages +{ + /// + /// Handles the footer "Connect" newsletter form (POST /Subscribe). + /// An API-style page handler: success and failure come back as JSON to + /// the layout's inline script; Subscribe.cshtml is an empty view that + /// exists only so the page model is routed. + /// + public class SubscribeModel : PageModel + { + private readonly MailSender _mailSender; + private readonly ILogger _logger; + + public SubscribeModel(MailSender mailSender, ILogger logger) + { + _mailSender = mailSender; + _logger = logger; + } + + [BindProperty] + [Required] + [EmailAddress] + [StringLength(320)] + public string Email { get; set; } = string.Empty; + + public async Task OnPostAsync() + { + if (!ModelState.IsValid) + { + return new JsonResult(new { success = false, message = "Please enter a valid email address." }) + { + StatusCode = StatusCodes.Status400BadRequest + }; + } + + var body = string.Join("\n", new List + { + $"Email: {Email}", + string.Empty, + "Subscribed from the footer newsletter form (Connect) on the Plus Health Tech website." + }); + + try + { + await _mailSender.SendAsync($"New newsletter subscription — {Email}", body); + return new JsonResult(new { success = true, message = "You're on the list — thanks for subscribing!" }); + } + catch (Exception ex) + { + // Log the full exception; never surface SMTP details to the user. + _logger.LogError(ex, "Failed to send newsletter subscription for {Email}", Email); + return new JsonResult(new { success = false, message = "We couldn't sign you up right now. Please try again later." }) + { + StatusCode = StatusCodes.Status500InternalServerError + }; + } + } + } +}