Dev #1
19
package-lock.json
generated
19
package-lock.json
generated
@@ -12,7 +12,7 @@
|
|||||||
"@maskito/phone": "^3.9.1",
|
"@maskito/phone": "^3.9.1",
|
||||||
"@maskito/react": "^3.9.1",
|
"@maskito/react": "^3.9.1",
|
||||||
"next": "15.3.4",
|
"next": "15.3.4",
|
||||||
"nodemailer": "^7.0.3",
|
"nodemailer": "^7.0.5",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"swiper": "^11.2.10"
|
"swiper": "^11.2.10"
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
|
"@types/nodemailer": "^6.4.17",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
@@ -995,6 +996,16 @@
|
|||||||
"undici-types": "~6.21.0"
|
"undici-types": "~6.21.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/nodemailer": {
|
||||||
|
"version": "6.4.17",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.17.tgz",
|
||||||
|
"integrity": "sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "19.1.8",
|
"version": "19.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz",
|
||||||
@@ -4492,9 +4503,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/nodemailer": {
|
"node_modules/nodemailer": {
|
||||||
"version": "7.0.3",
|
"version": "7.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.5.tgz",
|
||||||
"integrity": "sha512-Ajq6Sz1x7cIK3pN6KesGTah+1gnwMnx5gKl3piQlQQE/PwyJ4Mbc8is2psWYxK3RJTVeqsDaCv8ZzXLCDHMTZw==",
|
"integrity": "sha512-nsrh2lO3j4GkLLXoeEksAMgAOqxOv6QumNRVQTJwKH4nuiww6iC2y7GyANs9kRAxCexg3+lTWM3PZ91iLlVjfg==",
|
||||||
"license": "MIT-0",
|
"license": "MIT-0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
"@maskito/phone": "^3.9.1",
|
"@maskito/phone": "^3.9.1",
|
||||||
"@maskito/react": "^3.9.1",
|
"@maskito/react": "^3.9.1",
|
||||||
"next": "15.3.4",
|
"next": "15.3.4",
|
||||||
"nodemailer": "^7.0.3",
|
"nodemailer": "^7.0.5",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"swiper": "^11.2.10"
|
"swiper": "^11.2.10"
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
|
"@types/nodemailer": "^6.4.17",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
|||||||
9
src/app/api/heartbeat/route.ts
Normal file
9
src/app/api/heartbeat/route.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export async function GET(request: Request) {
|
||||||
|
return new Response('Heartbeat is OK!', {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
66
src/app/api/sendform/route.ts
Normal file
66
src/app/api/sendform/route.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import nodemailer from 'nodemailer';
|
||||||
|
import { TBaseForm } from '@shared/api/api.types';
|
||||||
|
import { CORE } from '@shared/config/core';
|
||||||
|
|
||||||
|
async function sendMail(data: TBaseForm) {
|
||||||
|
const { name, phone, message, form } = data;
|
||||||
|
|
||||||
|
const formattedBody = `
|
||||||
|
<html lang="ru-RU">
|
||||||
|
<body>
|
||||||
|
<p>Сообщение с сайта "Экспертиза и Оценка"</p>
|
||||||
|
<p>Форма отправки: ${form}</p>
|
||||||
|
<p>Имя отправителя: ${name ?? 'не указано'}</p>
|
||||||
|
<p>Номер телефона: ${phone}</p>
|
||||||
|
<p>Сообщение: ${message ?? 'отсутствует'}</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const transporter = nodemailer.createTransport({
|
||||||
|
service: 'Yandex',
|
||||||
|
auth: {
|
||||||
|
user: CORE.MAIL_USER,
|
||||||
|
pass: CORE.MAIL_PASS,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return await transporter.sendMail({
|
||||||
|
from: CORE.MAIL_FROM,
|
||||||
|
to: CORE.MAIL_TO,
|
||||||
|
subject: 'Заявка с сайта Ocenka-Sochi',
|
||||||
|
html: formattedBody,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function POST(request: Request) {
|
||||||
|
try {
|
||||||
|
const payload = await request.json();
|
||||||
|
|
||||||
|
if (payload.secure !== CORE.MAIL_SECURE_KEY) {
|
||||||
|
await Promise.reject('Request failure!');
|
||||||
|
}
|
||||||
|
|
||||||
|
const sendResult = await sendMail({ ...payload });
|
||||||
|
|
||||||
|
const data = { message: 'Form accepted' };
|
||||||
|
const headers = new Headers({
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
});
|
||||||
|
const options = {
|
||||||
|
status: 200,
|
||||||
|
statusText: 'OK',
|
||||||
|
headers: headers,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (sendResult?.messageId) {
|
||||||
|
return new Response(JSON.stringify(data), options);
|
||||||
|
} else {
|
||||||
|
await Promise.reject('Sending request failure!');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return new Response(`Api error: ${error}`, {
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
35
src/shared/api/api.service.ts
Normal file
35
src/shared/api/api.service.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { API_ROUTES } from '@shared/config/routes';
|
||||||
|
import { TBaseForm } from '@shared/api/api.types';
|
||||||
|
import { CORE } from '@shared/config/core';
|
||||||
|
|
||||||
|
type TRequest = TBaseForm;
|
||||||
|
|
||||||
|
const sendFormFn = async ({ ...props }: TRequest) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api' + API_ROUTES.SEND_FORM, {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
...props,
|
||||||
|
secure: CORE.MAIL_SECURE_KEY,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return response;
|
||||||
|
} else {
|
||||||
|
if (response.status === 400) throw new Error(`400 Bad request`);
|
||||||
|
if (response.status === 401) throw new Error(`401 Unauthorized`);
|
||||||
|
if (response.status === 500) throw new Error('500 Internal server error');
|
||||||
|
|
||||||
|
throw new Error(`${response.status} - Network response failure`);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { sendFormFn };
|
||||||
6
src/shared/api/api.types.ts
Normal file
6
src/shared/api/api.types.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export type TBaseForm = {
|
||||||
|
form: string;
|
||||||
|
name?: string;
|
||||||
|
phone: string;
|
||||||
|
message?: string;
|
||||||
|
};
|
||||||
7
src/shared/config/core.ts
Normal file
7
src/shared/config/core.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export const CORE = {
|
||||||
|
MAIL_USER: process.env.NEXT_PUBLIC_MAIL_USER,
|
||||||
|
MAIL_PASS: process.env.NEXT_PUBLIC_MAIL_PASS,
|
||||||
|
MAIL_FROM: process.env.NEXT_PUBLIC_MAIL_FROM,
|
||||||
|
MAIL_TO: process.env.NEXT_PUBLIC_MAIL_TO,
|
||||||
|
MAIL_SECURE_KEY: process.env.NEXT_PUBLIC_MAIL_SECURE_KEY,
|
||||||
|
} as const;
|
||||||
4
src/shared/config/routes.ts
Normal file
4
src/shared/config/routes.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export const API_ROUTES = {
|
||||||
|
SEND_FORM: '/sendform',
|
||||||
|
HEARTBEAT: '/heartbeat',
|
||||||
|
} as const;
|
||||||
Reference in New Issue
Block a user