208 lines
7.4 KiB
C#
208 lines
7.4 KiB
C#
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Identity;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using PowderCoating.Application.Interfaces;
|
|
using PowderCoating.Core.Entities;
|
|
using PowderCoating.Core.Interfaces;
|
|
using PowderCoating.Shared.Constants;
|
|
using System.ComponentModel.DataAnnotations;
|
|
|
|
namespace PowderCoating.Web.Controllers;
|
|
|
|
[Authorize]
|
|
public class ContactController : Controller
|
|
{
|
|
private readonly IAdminNotificationService _adminNotification;
|
|
private readonly UserManager<ApplicationUser> _userManager;
|
|
private readonly ITenantContext _tenantContext;
|
|
private readonly IUnitOfWork _unitOfWork;
|
|
private readonly ILogger<ContactController> _logger;
|
|
|
|
public ContactController(
|
|
IAdminNotificationService adminNotification,
|
|
UserManager<ApplicationUser> userManager,
|
|
ITenantContext tenantContext,
|
|
IUnitOfWork unitOfWork,
|
|
ILogger<ContactController> logger)
|
|
{
|
|
_adminNotification = adminNotification;
|
|
_userManager = userManager;
|
|
_tenantContext = tenantContext;
|
|
_unitOfWork = unitOfWork;
|
|
_logger = logger;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Renders the Contact Us page pre-filled with the current user's name, email, and company.
|
|
/// </summary>
|
|
[HttpGet]
|
|
public async Task<IActionResult> Index()
|
|
{
|
|
var model = await BuildViewModelAsync();
|
|
return View(model);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles the contact form submission. Saves to the database and sends an email notification
|
|
/// to configured admin addresses with reply-to set to the submitter.
|
|
/// </summary>
|
|
[HttpPost, ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> Submit(ContactFormModel form)
|
|
{
|
|
if (!ModelState.IsValid)
|
|
{
|
|
var vm = await BuildViewModelAsync();
|
|
vm.Form = form;
|
|
return View("Index", vm);
|
|
}
|
|
|
|
var user = await _userManager.GetUserAsync(User);
|
|
var companyId = _tenantContext.GetCurrentCompanyId() ?? 0;
|
|
|
|
// Persist first — email is best-effort, record must survive even if email fails
|
|
var submission = new ContactSubmission
|
|
{
|
|
CompanyId = companyId,
|
|
SenderName = form.Name,
|
|
SenderEmail = form.Email,
|
|
CompanyName = form.CompanyName,
|
|
Category = form.Category,
|
|
Subject = form.Subject,
|
|
Message = form.Message,
|
|
CreatedBy = user?.Id,
|
|
};
|
|
|
|
await _unitOfWork.ContactSubmissions.AddAsync(submission);
|
|
await _unitOfWork.CompleteAsync();
|
|
|
|
_logger.LogInformation("Contact form saved (id {Id}) from {Email} ({Company}): [{Category}] {Subject}",
|
|
submission.Id, form.Email, form.CompanyName, form.Category, form.Subject);
|
|
|
|
// Email notification is best-effort — a failure doesn't prevent the success message
|
|
try
|
|
{
|
|
await _adminNotification.NotifyContactFormSubmittedAsync(
|
|
senderName: form.Name,
|
|
senderEmail: form.Email,
|
|
companyName: form.CompanyName,
|
|
category: form.Category,
|
|
subject: form.Subject,
|
|
message: form.Message);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Contact form email notification failed for submission {Id} — record was saved", submission.Id);
|
|
}
|
|
|
|
TempData["Success"] = "Your message has been sent. We'll get back to you as soon as possible.";
|
|
return RedirectToAction(nameof(Index));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Admin-only list of all contact form submissions, newest first.
|
|
/// Unread submissions are highlighted. SuperAdmin sees all companies; company admins see their own.
|
|
/// </summary>
|
|
[HttpGet]
|
|
[Authorize(Policy = AppConstants.Policies.SuperAdminOnly)]
|
|
public async Task<IActionResult> Submissions()
|
|
{
|
|
var all = await _unitOfWork.ContactSubmissions.GetAllAsync();
|
|
var list = all.OrderByDescending(s => s.CreatedAt).ToList();
|
|
return View(list);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Marks a submission as read and optionally saves an admin note. SuperAdmin only.
|
|
/// </summary>
|
|
[HttpPost, ValidateAntiForgeryToken]
|
|
[Authorize(Policy = AppConstants.Policies.SuperAdminOnly)]
|
|
public async Task<IActionResult> MarkRead(int id, string? adminNotes)
|
|
{
|
|
var submission = await _unitOfWork.ContactSubmissions.GetByIdAsync(id, ignoreQueryFilters: true);
|
|
if (submission == null) return NotFound();
|
|
|
|
var user = await _userManager.GetUserAsync(User);
|
|
|
|
submission.IsRead = true;
|
|
submission.ReadAt = DateTime.UtcNow;
|
|
submission.ReadByUserId = user?.Id;
|
|
submission.ReadByUserName = user != null ? $"{user.FirstName} {user.LastName}".Trim() : null;
|
|
if (adminNotes != null)
|
|
submission.AdminNotes = adminNotes.Trim();
|
|
|
|
await _unitOfWork.CompleteAsync();
|
|
return RedirectToAction(nameof(Submissions));
|
|
}
|
|
|
|
// ─── Helpers ─────────────────────────────────────────────────────────────
|
|
|
|
/// <summary>
|
|
/// Builds the ContactViewModel pre-filled with the current user's identity and company name.
|
|
/// </summary>
|
|
private async Task<ContactViewModel> BuildViewModelAsync()
|
|
{
|
|
var user = await _userManager.GetUserAsync(User);
|
|
var companyId = _tenantContext.GetCurrentCompanyId();
|
|
var company = companyId.HasValue
|
|
? await _unitOfWork.Companies.GetByIdAsync(companyId.Value)
|
|
: null;
|
|
|
|
return new ContactViewModel
|
|
{
|
|
Form = new ContactFormModel
|
|
{
|
|
Name = user != null ? $"{user.FirstName} {user.LastName}".Trim() : string.Empty,
|
|
Email = user?.Email ?? string.Empty,
|
|
CompanyName = company?.CompanyName ?? string.Empty,
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
// ─── View models ─────────────────────────────────────────────────────────────
|
|
|
|
public class ContactViewModel
|
|
{
|
|
public ContactFormModel Form { get; set; } = new();
|
|
}
|
|
|
|
public class ContactFormModel
|
|
{
|
|
[Required, MaxLength(150)]
|
|
[Display(Name = "Your Name")]
|
|
public string Name { get; set; } = string.Empty;
|
|
|
|
[Required, EmailAddress, MaxLength(200)]
|
|
[Display(Name = "Your Email")]
|
|
public string Email { get; set; } = string.Empty;
|
|
|
|
[Required, MaxLength(200)]
|
|
[Display(Name = "Company")]
|
|
public string CompanyName { get; set; } = string.Empty;
|
|
|
|
[Required]
|
|
[Display(Name = "Category")]
|
|
public string Category { get; set; } = string.Empty;
|
|
|
|
[Required, MaxLength(200)]
|
|
[Display(Name = "Subject")]
|
|
public string Subject { get; set; } = string.Empty;
|
|
|
|
[Required, MaxLength(4000)]
|
|
[Display(Name = "Message")]
|
|
public string Message { get; set; } = string.Empty;
|
|
|
|
/// <summary>Standard contact reason categories shown in the dropdown.</summary>
|
|
public static readonly string[] Categories =
|
|
[
|
|
"General Question",
|
|
"Technical Issue / Bug",
|
|
"Billing & Subscription",
|
|
"Feature Request",
|
|
"Account Access Issue",
|
|
"Data Import / Migration Help",
|
|
"Training & Onboarding",
|
|
"Other",
|
|
];
|
|
}
|