mirror of
https://github.com/OwethuManagedServices/oms-website-nextjs.git
synced 2025-12-17 17:28:09 +00:00
feature: create post
This commit is contained in:
148
actions/contact.ts
Normal file
148
actions/contact.ts
Normal file
@ -0,0 +1,148 @@
|
||||
"use server";
|
||||
|
||||
import { z } from "zod";
|
||||
import nodemailer from "nodemailer";
|
||||
import { GoogleGenerativeAI } from "@google/generative-ai";
|
||||
|
||||
// Define the schema for contact form validation
|
||||
const ContactSchema = z.object({
|
||||
name: z.string().min(2, "Name must be at least 2 characters"),
|
||||
email: z.string().email("Invalid email address"),
|
||||
subject: z.string().min(5, "Subject must be at least 5 characters"),
|
||||
message: z.string().min(10, "Message must be at least 10 characters"),
|
||||
});
|
||||
|
||||
// Define the state structure for the form action
|
||||
export type ContactFormState = {
|
||||
errors?: {
|
||||
name?: string[];
|
||||
email?: string[];
|
||||
subject?: string[];
|
||||
message?: string[];
|
||||
_form?: string[]; // General form error
|
||||
};
|
||||
message?: string | null; // Success or general error message
|
||||
success?: boolean; // Flag for successful submission
|
||||
};
|
||||
|
||||
// --- Initialize Gemini ---
|
||||
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY || "");
|
||||
const geminiModel = genAI.getGenerativeModel({ model: "gemini-2.0-flash" }); // Or other suitable model
|
||||
|
||||
// --- Initialize Nodemailer Transporter ---
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: process.env.EMAIL_SERVER_HOST,
|
||||
port: parseInt(process.env.EMAIL_SERVER_PORT || "587"), // Default to 587
|
||||
secure: parseInt(process.env.EMAIL_SERVER_PORT || "587") === 465, // true for 465, false for other ports
|
||||
auth: {
|
||||
user: process.env.EMAIL_SERVER_USER,
|
||||
pass: process.env.EMAIL_SERVER_PASSWORD,
|
||||
},
|
||||
});
|
||||
|
||||
// --- Helper function for Spam Check ---
|
||||
async function isSpamOrAdvertisement(content: {
|
||||
subject: string;
|
||||
message: string;
|
||||
email: string;
|
||||
}): Promise<boolean> {
|
||||
if (!process.env.GEMINI_API_KEY) {
|
||||
console.warn("GEMINI_API_KEY not set. Skipping spam check.");
|
||||
return false; // Skip check if API key is missing
|
||||
}
|
||||
|
||||
const prompt = `Analyze the following email content and classify it as "spam", "advertisement", or "legitimate inquiry". Consider the subject, message body, and sender email. Provide only the classification word as the response.
|
||||
|
||||
Subject: ${content.subject}
|
||||
Sender Email: ${content.email}
|
||||
Message:
|
||||
${content.message}`;
|
||||
|
||||
try {
|
||||
const result = await geminiModel.generateContent(prompt);
|
||||
const response = await result.response;
|
||||
const text = response.text().trim().toLowerCase();
|
||||
console.log("Gemini Classification:", text); // Log classification for debugging
|
||||
// Consider "spam" or "advertisement" as unwanted
|
||||
return text === "spam" || text === "advertisement";
|
||||
} catch (error) {
|
||||
console.error("Error checking content with Gemini:", error);
|
||||
// Fail open (treat as not spam) if Gemini check fails
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Server action to process the contact form
|
||||
export async function submitContactForm(
|
||||
prevState: ContactFormState,
|
||||
formData: FormData
|
||||
): Promise<ContactFormState> {
|
||||
// Validate form data
|
||||
const validatedFields = ContactSchema.safeParse({
|
||||
name: formData.get("name"),
|
||||
email: formData.get("email"),
|
||||
subject: formData.get("subject"),
|
||||
message: formData.get("message"),
|
||||
});
|
||||
|
||||
// If validation fails, return errors
|
||||
if (!validatedFields.success) {
|
||||
console.error(
|
||||
"Contact Form Validation Errors:",
|
||||
validatedFields.error.flatten().fieldErrors
|
||||
);
|
||||
return {
|
||||
errors: validatedFields.error.flatten().fieldErrors,
|
||||
message: "Please correct the errors above.",
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
||||
const { name, email, subject, message } = validatedFields.data;
|
||||
|
||||
// --- Spam/Advertisement Check ---
|
||||
try {
|
||||
const isUnwanted = await isSpamOrAdvertisement({ subject, message, email });
|
||||
if (isUnwanted) {
|
||||
console.log(`Message from ${email} flagged as spam/advertisement.`);
|
||||
// Return generic error as requested
|
||||
return { message: "Message could not be sent.", success: false };
|
||||
}
|
||||
} catch (error) {
|
||||
// Log error but proceed if check fails unexpectedly
|
||||
console.error("Error during spam check:", error);
|
||||
}
|
||||
// --- End Spam Check ---
|
||||
|
||||
// --- Send Email using Nodemailer ---
|
||||
const mailOptions = {
|
||||
from: process.env.EMAIL_FROM, // Sender address (configured in .env)
|
||||
to: process.env.EMAIL_TO, // List of receivers (configured in .env)
|
||||
replyTo: email, // Set reply-to to the user's email
|
||||
subject: `Website Contact: ${subject}`, // Subject line
|
||||
text: `Name: ${name}\nEmail: ${email}\n\nMessage:\n${message}`, // Plain text body
|
||||
html: `<p><strong>Name:</strong> ${name}</p>
|
||||
<p><strong>Email:</strong> <a href="mailto:${email}">${email}</a></p>
|
||||
<hr>
|
||||
<p><strong>Message:</strong></p>
|
||||
<p>${message.replace(/\n/g, "<br>")}</p>`, // HTML body
|
||||
};
|
||||
|
||||
try {
|
||||
await transporter.sendMail(mailOptions);
|
||||
console.log("Contact email sent successfully from:", email);
|
||||
return {
|
||||
message: "Thank you for your message! We'll get back to you soon.",
|
||||
success: true,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Failed to send contact email:", error);
|
||||
return {
|
||||
message:
|
||||
"Failed to send message due to a server error. Please try again later.",
|
||||
success: false,
|
||||
errors: { _form: ["Email sending failed."] },
|
||||
};
|
||||
}
|
||||
// --- End Send Email ---
|
||||
}
|
||||
@ -48,16 +48,16 @@ const PostSchema = z.object({
|
||||
});
|
||||
|
||||
export type CreatePostState = {
|
||||
errors?: {
|
||||
message: string | null;
|
||||
errors: {
|
||||
title?: string[];
|
||||
slug?: string[];
|
||||
content?: string[];
|
||||
image?: string[]; // Changed from imageUrl
|
||||
image?: string[];
|
||||
tags?: string[];
|
||||
excerpt?: string[];
|
||||
_form?: string[];
|
||||
};
|
||||
message?: string | null;
|
||||
// Add a field to hold previous input values
|
||||
previousInput?: {
|
||||
title?: string;
|
||||
slug?: string;
|
||||
@ -65,7 +65,6 @@ export type CreatePostState = {
|
||||
excerpt?: string;
|
||||
tags?: string;
|
||||
published?: boolean;
|
||||
// We don't repopulate the file input for security reasons
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user