const { addBaseUrlToImages } = require("../utils/imageHelper"); const { ICON_OPTIONS, normalizeIconClass } = require("../utils/iconOptions"); const Contact = require("../models/contact"); const ContactSubmission = require("../models/contactSubmission"); const writeAuditLog = require("../audit/writeAuditLog"); const diffObject = require("../audit/diffObject"); const AUDIT_ACTIONS = require("../constants/auditAction"); const SLUG = "contact"; function parseJsonField(raw) { if (raw == null || raw === "") return null; if (typeof raw === "object") return raw; try { return JSON.parse(raw); } catch { return null; } } async function getDocument() { return Contact.findOne({ slug: SLUG }).lean(); } /** * GET /api/contact — public payload for frontend LAMS. */ exports.api = async (req, res) => { try { const doc = await getDocument(); if (!doc) { return res.status(404).json({ error: "Contact data not found" }); } const baseUrl = process.env.BACKEND_URL ?? `${req.protocol}://${req.get("host")}`; const body = { metadata: doc.metadata || {}, hero: doc.hero || {}, infoCards: doc.infoCards || {}, form: doc.form || {}, faq: doc.faq || {}, cta: doc.cta || {}, }; res.json(addBaseUrlToImages(body, baseUrl)); } catch (err) { console.error("contact.api:", err); res.status(500).json({ error: "Error loading contact data" }); } }; // Legacy alias exports.getContactData = exports.api; /** * GET /admin/contact — Admin view. */ exports.index = async (req, res) => { try { const doc = await getDocument(); const data = doc ? { metadata: doc.metadata || {}, hero: doc.hero || {}, infoCards: doc.infoCards || {}, form: doc.form || {}, faq: doc.faq || {}, cta: doc.cta || {}, } : { metadata: {}, hero: { badge: "", titleMain: "", titleHighlight: "", description: "" }, infoCards: { contactInfo: { title: "", channels: [] }, supportHours: { title: "", hours: [], footer: {} } }, form: { heading: "", description: "", fields: {}, submitLabel: "Send Message", successMessage: "", errorMessage: "" }, faq: { title: "", subtitle: "", items: [] }, cta: { title: "", description: "", primaryButton: {}, secondaryButton: {} }, }; const { startDate, endDate } = req.query; const query = {}; if (startDate || endDate) { query.createdAt = {}; if (startDate) query.createdAt.$gte = new Date(startDate); if (endDate) { const end = new Date(endDate); end.setHours(23, 59, 59, 999); query.createdAt.$lte = end; } } const submissions = await ContactSubmission.find(query).sort({ createdAt: -1 }).limit(50); const frontendUrl = process.env.FRONTEND_URL || ""; res.render("admin/contact/index", { title: "Contact Management", layout: "layouts/main", data, submissions, startDate, endDate, frontendUrl, iconOptions: ICON_OPTIONS, currentPath: req.path, user: req.session.user, }); } catch (error) { console.error("contact.index:", error); req.flash("error_msg", "An error occurred while loading the page"); res.redirect("/admin/dashboard"); } }; /** * POST /admin/contact/update — Save & audit. */ exports.update = async (req, res) => { try { const metadata = parseJsonField(req.body.metadata); const hero = parseJsonField(req.body.hero); const infoCards = parseJsonField(req.body.infoCards); const form = parseJsonField(req.body.form); const faq = parseJsonField(req.body.faq); const cta = parseJsonField(req.body.cta); if (!metadata || !hero || !infoCards || !form || !faq || !cta) { req.flash("error_msg", "Invalid form payload. Please try again."); return res.redirect("/admin/contact"); } // Normalize channel icons against whitelist if (infoCards?.contactInfo?.channels?.length) { infoCards.contactInfo.channels = infoCards.contactInfo.channels.map((ch) => ({ ...ch, icon: normalizeIconClass(ch.icon), })); } const payload = { metadata, hero, infoCards, form, faq, cta }; let doc = await Contact.findOne({ slug: SLUG }); const beforeData = doc ? JSON.parse(JSON.stringify(doc.toObject())) : {}; if (!doc) { doc = new Contact({ slug: SLUG, ...payload }); } else { doc.set(payload); } await doc.save(); const afterData = JSON.parse(JSON.stringify(doc.toObject())); const changes = diffObject(beforeData, afterData); if (changes.length > 0) { await writeAuditLog({ model: "Contact", documentId: doc._id, action: AUDIT_ACTIONS.UPDATE_CONTACT, before: beforeData, after: afterData, changes, req, }); } req.flash("success_msg", "Contact updated successfully"); res.redirect("/admin/contact"); } catch (err) { console.error("contact.update:", err); req.flash("error_msg", err.message || "Error updating contact"); res.redirect("/admin/contact"); } }; // ───────────────────────────────────────────── // Form Submissions (unchanged) // ───────────────────────────────────────────── exports.submitForm = async (req, res) => { try { const { name, email, phone, address, date, message } = req.body; if (!name || !email) { return res.status(400).json({ success: false, error: "Name and email are required", }); } const submission = new ContactSubmission({ name: name.trim(), email: email.trim().toLowerCase(), phone: phone?.trim() || "", address: address?.trim() || "", date: date?.trim() || "", message: message?.trim() || "", ipAddress: req.ip || req.connection?.remoteAddress || "", userAgent: req.get("User-Agent") || "", }); await submission.save(); res.status(201).json({ success: true, message: "Thank you for contacting us! We will get back to you soon.", data: { id: submission._id, name: submission.name, email: submission.email, }, }); } catch (err) { console.error("Error submitting contact form:", err); if (err.name === "ValidationError") { const errors = Object.values(err.errors).map((e) => e.message); return res.status(400).json({ success: false, error: errors.join(", ") }); } res.status(500).json({ success: false, error: "Error submitting form. Please try again later.", }); } }; exports.getSubmissions = async (req, res) => { try { const { status, page = 1, limit = 20 } = req.query; const query = {}; if (status && ["pending", "read", "replied", "archived"].includes(status)) { query.status = status; } const skip = (parseInt(page) - 1) * parseInt(limit); const [submissions, total] = await Promise.all([ ContactSubmission.find(query) .sort({ createdAt: -1 }) .skip(skip) .limit(parseInt(limit)), ContactSubmission.countDocuments(query), ]); res.json({ success: true, data: submissions, pagination: { page: parseInt(page), limit: parseInt(limit), total, totalPages: Math.ceil(total / parseInt(limit)), }, }); } catch (err) { console.error("Error getting submissions:", err); res.status(500).json({ success: false, error: "Error loading submissions" }); } }; exports.updateSubmissionStatus = async (req, res) => { try { const { id } = req.params; const { status, notes } = req.body; const validStatuses = ["pending", "read", "replied", "archived"]; if (!validStatuses.includes(status)) { return res.status(400).json({ success: false, error: "Invalid status" }); } const updateData = { status }; if (notes !== undefined) updateData.notes = notes; if (status === "replied") updateData.repliedAt = new Date(); const submission = await ContactSubmission.findByIdAndUpdate( id, updateData, { new: true } ); if (!submission) { return res.status(404).json({ success: false, error: "Submission not found" }); } res.json({ success: true, data: submission }); } catch (err) { console.error("Error updating submission:", err); res.status(500).json({ success: false, error: "Error updating submission" }); } };