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 _userManager; private readonly ITenantContext _tenantContext; private readonly IUnitOfWork _unitOfWork; private readonly ILogger _logger; public ContactController( IAdminNotificationService adminNotification, UserManager userManager, ITenantContext tenantContext, IUnitOfWork unitOfWork, ILogger logger) { _adminNotification = adminNotification; _userManager = userManager; _tenantContext = tenantContext; _unitOfWork = unitOfWork; _logger = logger; } /// /// Renders the Contact Us page pre-filled with the current user's name, email, and company. /// [HttpGet] public async Task Index() { var model = await BuildViewModelAsync(); return View(model); } /// /// 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. /// [HttpPost, ValidateAntiForgeryToken] public async Task 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)); } /// /// Admin-only list of all contact form submissions, newest first. /// Unread submissions are highlighted. SuperAdmin sees all companies; company admins see their own. /// [HttpGet] [Authorize(Policy = AppConstants.Policies.SuperAdminOnly)] public async Task Submissions() { var all = await _unitOfWork.ContactSubmissions.GetAllAsync(); var list = all.OrderByDescending(s => s.CreatedAt).ToList(); return View(list); } /// /// Marks a submission as read and optionally saves an admin note. SuperAdmin only. /// [HttpPost, ValidateAntiForgeryToken] [Authorize(Policy = AppConstants.Policies.SuperAdminOnly)] public async Task 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 ───────────────────────────────────────────────────────────── /// /// Builds the ContactViewModel pre-filled with the current user's identity and company name. /// private async Task 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; /// Standard contact reason categories shown in the dropdown. public static readonly string[] Categories = [ "General Question", "Technical Issue / Bug", "Billing & Subscription", "Feature Request", "Account Access Issue", "Data Import / Migration Help", "Training & Onboarding", "Other", ]; }