Compare commits
1
Commits
e5d1446791
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7809615c7 |
@@ -100,10 +100,14 @@
|
|||||||
<div>
|
<div>
|
||||||
<h5 class="text-white text-sm font-semibold mb-4">Connect</h5>
|
<h5 class="text-white text-sm font-semibold mb-4">Connect</h5>
|
||||||
<p class="text-xs text-slate-500 mb-4">Subscribe to our technical updates and newsletter.</p>
|
<p class="text-xs text-slate-500 mb-4">Subscribe to our technical updates and newsletter.</p>
|
||||||
<div class="flex gap-2">
|
<form id="newsletter-form" action="/Subscribe" method="post">
|
||||||
<input type="email" placeholder="Enter email" class="bg-slate-900 border border-slate-800 text-xs px-3 py-2 rounded-lg text-white focus:outline-none focus:border-brand w-full">
|
@Html.AntiForgeryToken()
|
||||||
<button class="bg-[#1c8098] hover:bg-[#145f72] text-white text-xs px-4 py-2 rounded-lg font-semibold transition-colors">Join</button>
|
<div class="flex gap-2">
|
||||||
</div>
|
<input type="email" name="Email" required aria-label="Email address" placeholder="Enter email" class="bg-slate-900 border border-slate-800 text-xs px-3 py-2 rounded-lg text-white focus:outline-none focus:border-brand w-full">
|
||||||
|
<button type="submit" class="bg-[#1c8098] hover:bg-[#145f72] text-white text-xs px-4 py-2 rounded-lg font-semibold transition-colors">Join</button>
|
||||||
|
</div>
|
||||||
|
<p id="newsletter-message" class="mt-2 text-xs" hidden></p>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 mt-12 pt-8 border-t border-slate-900 text-center text-xs text-slate-600">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 mt-12 pt-8 border-t border-slate-900 text-center text-xs text-slate-600">
|
||||||
@@ -140,6 +144,57 @@
|
|||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Footer "Connect" newsletter form — posts to /Subscribe and shows
|
||||||
|
// the result inline instead of navigating away.
|
||||||
|
(function () {
|
||||||
|
var form = document.getElementById('newsletter-form');
|
||||||
|
if (!form) return;
|
||||||
|
var input = form.querySelector('input[type="email"]');
|
||||||
|
var button = form.querySelector('button[type="submit"]');
|
||||||
|
var message = document.getElementById('newsletter-message');
|
||||||
|
|
||||||
|
function show(ok, text) {
|
||||||
|
message.textContent = text;
|
||||||
|
message.classList.toggle('text-emerald-400', ok);
|
||||||
|
message.classList.toggle('text-red-400', !ok);
|
||||||
|
message.hidden = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.addEventListener('submit', async function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
var email = input.value.trim();
|
||||||
|
if (!email) {
|
||||||
|
show(false, 'Please enter your email address.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.disabled = true;
|
||||||
|
try {
|
||||||
|
var response = await fetch(form.action, {
|
||||||
|
method: 'POST',
|
||||||
|
// FormData picks up the Email input plus the antiforgery token.
|
||||||
|
body: new URLSearchParams(new FormData(form)),
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }
|
||||||
|
});
|
||||||
|
var payload = null;
|
||||||
|
try { payload = await response.json(); } catch (e) { /* non-JSON response */ }
|
||||||
|
|
||||||
|
if (response.ok && payload && payload.success) {
|
||||||
|
input.value = '';
|
||||||
|
show(true, payload.message || "You're on the list — thanks for subscribing!");
|
||||||
|
} else {
|
||||||
|
show(false, (payload && payload.message) || "We couldn't sign you up right now. Please try again later.");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
show(false, 'Something went wrong. Please try again later.');
|
||||||
|
} finally {
|
||||||
|
button.disabled = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
@await RenderSectionAsync("Scripts", required: false)
|
@await RenderSectionAsync("Scripts", required: false)
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -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). *@
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
using plushealthtech.Mail;
|
||||||
|
|
||||||
|
namespace plushealthtech.Pages
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
public class SubscribeModel : PageModel
|
||||||
|
{
|
||||||
|
private readonly MailSender _mailSender;
|
||||||
|
private readonly ILogger<SubscribeModel> _logger;
|
||||||
|
|
||||||
|
public SubscribeModel(MailSender mailSender, ILogger<SubscribeModel> logger)
|
||||||
|
{
|
||||||
|
_mailSender = mailSender;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BindProperty]
|
||||||
|
[Required]
|
||||||
|
[EmailAddress]
|
||||||
|
[StringLength(320)]
|
||||||
|
public string Email { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public async Task<IActionResult> 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<string>
|
||||||
|
{
|
||||||
|
$"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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user