Перейти к содержимому

Nodemailer

Установка

Окно терминала
yarn add nodemailer react-hot-toast

Настройки

Использование

.env.local
GMAIL_USER=АДРЕС_ПОЧТЫ@gmail.com
GMAIL_PASS=ПАРОЛЬ
./data/form.tsx
// Contact Form
export const form = {
name: "Имя*",
email: "Email*",
tel: "Телефон*",
commmets: "Запрос (необязательно)",
button: "Отправить запрос",
};
// Contact Form Alerts
export const alerts = {
errorName: "Имя должно содержать от 3 до 20 символов",
errorEmail: "Неверный формат электронной почты",
errorTel: "Неверный формат телефона",
errorCommmets: "Сообщение должно содержать от 10 до 500 символов",
messageSuccess: "Сообщение успешно отправлено",
};
./components/ContactForm.tsx
"use client";
import { alerts, form } from "@/data/form";
import { useState } from "react";
import toast from "react-hot-toast"; // Документация по Toats тут: https://react-hot-toast.com
export default function ContactForm() {
const [loading, setLoading] = useState(false);
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [tel, setTel] = useState("");
const [message, setMessage] = useState("");
const validateForm = () => {
if (name.length < 3 || name.length > 20) {
toast.error(alerts.errorName, {
style: {
borderRadius: "12px",
background: "#0070BA",
color: "#fff",
padding: "12px 20px",
},
iconTheme: {
primary: "#fff",
secondary: "#0070BA",
},
});
return false;
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
toast.error(alerts.errorEmail, {
style: {
borderRadius: "10px",
background: "#0070BA",
color: "#fff",
},
iconTheme: {
primary: "#fff",
secondary: "#0070BA",
},
});
return false;
}
const telRegex =
/^(?:(?:\(?(?:00|\+)([1-4]\d\d|[1-9]\d*)\)?)[\-\.\ \\\/]?)?((?:\(?\d{1,}\)?[\-\.\ \\\/]?)+)(?:[\-\.\ \\\/]?(?:#|ext\.?|extension|x)[\-\.\ \\\/]?(\d+))?$/i;
if (!telRegex.test(tel)) {
toast.error(alerts.errorTel, {
style: {
borderRadius: "12px",
background: "#0070BA",
color: "#fff",
padding: "12px 20px",
},
iconTheme: {
primary: "#fff",
secondary: "#0070BA",
},
});
return false;
}
if (message.length < 0 || message.length > 500) {
toast.error(alerts.errorCommmets, {
style: {
borderRadius: "10px",
background: "#0070BA",
color: "#fff",
},
iconTheme: {
primary: "#fff",
secondary: "#0070BA",
},
});
return false;
}
return true;
};
const handleSubmit = async (e: any) => {
e.preventDefault();
if (!validateForm()) {
return;
}
setLoading(true);
const formData = {
name,
email,
tel,
message,
};
const response = await fetch("/api/contact", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(formData),
});
setLoading(false);
if (response.ok) {
toast.success(alerts.messageSuccess, {
icon: "Emoji-Icons",
style: {
borderRadius: "10px",
background: "#0070BA",
color: "#fff",
},
});
e.target.reset(); // Сброс формы после успешной отправки
setName("");
setEmail("");
setTel("");
setMessage("");
} else {
console.error("Ошибка отправки сообщения");
toast.error("Ошибка отправки сообщения");
}
};
return (
<div className="w-full">
<form onSubmit={handleSubmit} className="flex flex-col gap-6">
<input
className="h-16 rounded-xl bg-[#DAEEFC] focus:bg-white duration-150 px-4 text-xl font-medium placeholder-slate-400 !outline-none !ring-0 border-0 focus:outline-none"
name="name"
type="text"
placeholder={form.name}
value={name}
onChange={(e) => setName(e.target.value)}
/>
<input
className="h-16 rounded-xl bg-[#DAEEFC] focus:bg-white duration-150 px-4 text-xl font-medium placeholder-slate-400 !outline-none !ring-0 border-0 focus:outline-none"
name="email"
type="email"
placeholder={form.email}
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
className="h-16 rounded-xl bg-[#DAEEFC] focus:bg-white duration-150 px-4 text-xl font-medium placeholder-slate-400 !outline-none !ring-0 border-0 focus:outline-none"
name="tel"
type="tel"
placeholder={form.tel}
value={tel}
onChange={(e) => setTel(e.target.value)}
/>
<textarea
className="rounded-xl bg-[#DAEEFC] focus:bg-white duration-150 px-4 text-xl font-medium placeholder-slate-400 !outline-none !ring-0 border-0 focus:outline-none"
name="message"
placeholder={form.commmets}
rows={4}
value={message}
onChange={(e) => setMessage(e.target.value)}
></textarea>
<button type="submit" className="h-16 rounded-xl bg-gradient-to-tr from-[#EF7D00] to-[#E6007E] px-12 text-xl font-medium text-white duration-150 hover:opacity-90 focus:outline-none inline-flex justify-center items-center" disabled={loading}>
{loading ? "Загрузка..." : form.button}
</button>
</form>
</div>
);
}
./app/page.tsx
import ContactForm from "./components/ContactForm";
import { Toaster } from "react-hot-toast";
import { contact } from "@/data/content";
export default function ContactForm() {
return (
<Toaster position="top-center" reverseOrder={true} />
<ContactForm />
);
}
./app/api/contact/route.js
import { NextResponse } from "next/server";
import nodemailer from "nodemailer";
export const POST = async (req) => {
if (req.method !== "POST") {
// res.status(405).json({ message: "Метод не разрешен" });
return new NextResponse(
JSON.stringify({ message: "Метод не разрешен" }, { status: 405 }),
);
}
const { name, email, tel, message } = req.body;
const user = process.env.GMAIL_USER;
const transporter = nodemailer.createTransport({
host: "smtp.gmail.com",
port: 465,
secure: true,
service: "gmail",
auth: {
user: user,
pass: process.env.GMAIL_PASS,
},
});
try {
const { name, email, tel, message } = await req.json();
const mail = await transporter.sendMail({
from: user,
replyTo: email,
to: "mail@mail.ru", // Изменените на вашу электронную почту
subject: `Новое сообщение от ${name} на сайте Название Сайта`,
html: `
<p>Имя: ${name}</p>
<p>Email: ${email}</p>
<p>Телефон: ${tel}</p>
<p>Запрос: ${message}</p>
`,
});
console.log(name, email, tel, message);
console.log("Сообщение отправлено:", mail.messageId);
// res.status(200).json({ message: "Успех" });
return new NextResponse(
JSON.stringify({ message: "Успех" }, { status: 200 }),
);
} catch (error) {
console.error("Ошибка", error);
// res.status(500).json({ message: "Что-то пошло не так" });
return new NextResponse(
JSON.stringify({ message: "Что-то пошло не так" }, { status: 500 }),
);
}
};
// Переименуйте свою функцию в POST и экспортируйте ее в таком виде