From 53f214ba28a47d2a41d389e041ddb1b31bb0ca4a Mon Sep 17 00:00:00 2001 From: RedrockJS Date: Wed, 11 Jun 2025 13:40:10 +0300 Subject: [PATCH] fix: add sending form data --- src/app/api/sendform/route.ts | 33 ++++++++++++--------- src/shared/api/api.service.ts | 35 +++++++++++++++++++++++ src/shared/api/api.types.ts | 6 ++++ src/shared/config/core.ts | 7 +++++ src/shared/config/routes.ts | 4 +++ src/shared/ui/phone-input/phone-input.tsx | 10 +++++++ src/widgets/contacts-form/ui.tsx | 9 ++++-- src/widgets/footer-form/ui.tsx | 9 ++++-- src/widgets/license-form/ui.tsx | 9 ++++-- src/widgets/offer-form/ui.tsx | 9 ++++-- src/widgets/offer-request/ui.tsx | 17 +++++++---- 11 files changed, 121 insertions(+), 27 deletions(-) create mode 100644 src/shared/api/api.service.ts create mode 100644 src/shared/api/api.types.ts create mode 100644 src/shared/config/core.ts create mode 100644 src/shared/config/routes.ts create mode 100644 src/shared/ui/phone-input/phone-input.tsx diff --git a/src/app/api/sendform/route.ts b/src/app/api/sendform/route.ts index 2a8ae60..a083b63 100644 --- a/src/app/api/sendform/route.ts +++ b/src/app/api/sendform/route.ts @@ -1,13 +1,8 @@ import nodemailer from 'nodemailer'; +import { TBaseForm } from '@shared/api/api.types'; +import { CORE } from '@shared/config/core'; -type TFormData = { - name?: string; - phone: string; - message?: string; - form: string; -}; - -async function sendMail(data: TFormData) { +async function sendMail(data: TBaseForm) { const { name, phone, message, form } = data; const formattedBody = ` @@ -25,14 +20,14 @@ async function sendMail(data: TFormData) { const transporter = nodemailer.createTransport({ service: 'yandex', auth: { - user: process.env.MAIL_USER, - pass: process.env.MAIL_PASS, + user: CORE.MAIL_USER, + pass: CORE.MAIL_PASS, }, }); return await transporter.sendMail({ - from: process.env.MAIL_FROM, - to: process.env.MAIL_TO, + from: CORE.MAIL_FROM, + to: CORE.MAIL_TO, subject: 'Заявка с сайта FireExams', html: formattedBody, }); @@ -42,14 +37,24 @@ export async function POST(request: Request) { try { const payload = await request.json(); - if (payload.secure === process.env.MAIL_SECURE_KEY) { + 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('Success!', { status: 200 }); + return new Response(JSON.stringify(data), options); } else { await Promise.reject('Sending request failure!'); } diff --git a/src/shared/api/api.service.ts b/src/shared/api/api.service.ts new file mode 100644 index 0000000..2ee7171 --- /dev/null +++ b/src/shared/api/api.service.ts @@ -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 }; diff --git a/src/shared/api/api.types.ts b/src/shared/api/api.types.ts new file mode 100644 index 0000000..e46571f --- /dev/null +++ b/src/shared/api/api.types.ts @@ -0,0 +1,6 @@ +export type TBaseForm = { + form: string; + name?: string; + phone: string; + message?: string; +}; diff --git a/src/shared/config/core.ts b/src/shared/config/core.ts new file mode 100644 index 0000000..ddba9d1 --- /dev/null +++ b/src/shared/config/core.ts @@ -0,0 +1,7 @@ +export const CORE = { + MAIL_USER: process.env.MAIL_USER, + MAIL_PASS: process.env.MAIL_PASS, + MAIL_FROM: process.env.MAIL_FROM, + MAIL_TO: process.env.MAIL_TO, + MAIL_SECURE_KEY: process.env.NEXT_PUBLIC_MAIL_SECURE_KEY, +} as const; diff --git a/src/shared/config/routes.ts b/src/shared/config/routes.ts new file mode 100644 index 0000000..cebba69 --- /dev/null +++ b/src/shared/config/routes.ts @@ -0,0 +1,4 @@ +export const API_ROUTES = { + SEND_FORM: '/sendform', + HEARTBEAT: '/heartbeat', +} as const; diff --git a/src/shared/ui/phone-input/phone-input.tsx b/src/shared/ui/phone-input/phone-input.tsx new file mode 100644 index 0000000..f652e58 --- /dev/null +++ b/src/shared/ui/phone-input/phone-input.tsx @@ -0,0 +1,10 @@ +//import s from './phone-input.module.scss'; +import { Input } from '@shared/ui'; + +export default function PhoneInput() { + return ( + <> + + + ); +} diff --git a/src/widgets/contacts-form/ui.tsx b/src/widgets/contacts-form/ui.tsx index 24a168f..2021003 100644 --- a/src/widgets/contacts-form/ui.tsx +++ b/src/widgets/contacts-form/ui.tsx @@ -9,6 +9,7 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import bgForm from '@public/images/bg-form.jpg'; +import { sendFormFn } from '@shared/api/api.service'; const FormSchema = z.object({ name: z.string().min(3), @@ -27,7 +28,6 @@ export default function ContactsForm() { control, reset, formState: { errors }, - clearErrors, } = useForm({ mode: 'onSubmit', reValidateMode: 'onBlur', @@ -36,8 +36,13 @@ export default function ContactsForm() { }); const onSubmit = async (data: TForm) => { + const payload = { + ...data, + form: 'contacts-form', + }; + try { - console.log('Form', data); + await sendFormFn(payload); toast.success('Заявка на консультацию принята'); reset(defaultValues); } catch (e) { diff --git a/src/widgets/footer-form/ui.tsx b/src/widgets/footer-form/ui.tsx index 2bdb649..dd7739a 100644 --- a/src/widgets/footer-form/ui.tsx +++ b/src/widgets/footer-form/ui.tsx @@ -8,6 +8,7 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import man from '@public/images/footer-man.png'; +import { sendFormFn } from '@shared/api/api.service'; const FormSchema = z.object({ name: z.string().min(3), @@ -28,7 +29,6 @@ export default function FooterForm() { control, reset, formState: { errors }, - clearErrors, } = useForm({ mode: 'onSubmit', reValidateMode: 'onBlur', @@ -37,8 +37,13 @@ export default function FooterForm() { }); const onSubmit = async (data: TForm) => { + const payload = { + ...data, + form: 'footer-form', + }; + try { - console.log('Form', data); + await sendFormFn(payload); toast.success('Заявка на консультацию принята'); reset(defaultValues); } catch (e) { diff --git a/src/widgets/license-form/ui.tsx b/src/widgets/license-form/ui.tsx index b3f44f8..fd7f033 100644 --- a/src/widgets/license-form/ui.tsx +++ b/src/widgets/license-form/ui.tsx @@ -9,6 +9,7 @@ import bgForm from '@public/images/bg-form.jpg'; import { z } from 'zod'; import { Controller, useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; +import { sendFormFn } from '@shared/api/api.service'; const FormSchema = z.object({ name: z.string().min(3), @@ -27,7 +28,6 @@ export default function LicenseForm() { control, reset, formState: { errors }, - clearErrors, } = useForm({ mode: 'onSubmit', reValidateMode: 'onBlur', @@ -36,8 +36,13 @@ export default function LicenseForm() { }); const onSubmit = async (data: TForm) => { + const payload = { + ...data, + form: 'license-form', + }; + try { - console.log('Form', data); + await sendFormFn(payload); toast.success('Заявка на консультацию принята'); reset(defaultValues); } catch (e) { diff --git a/src/widgets/offer-form/ui.tsx b/src/widgets/offer-form/ui.tsx index 979c734..d004ee5 100644 --- a/src/widgets/offer-form/ui.tsx +++ b/src/widgets/offer-form/ui.tsx @@ -6,6 +6,7 @@ import { Controller, useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import toast from 'react-hot-toast'; +import { sendFormFn } from '@shared/api/api.service'; const FormSchema = z.object({ name: z.string().min(3), @@ -24,7 +25,6 @@ export default function OfferForm() { control, reset, formState: { errors }, - clearErrors, } = useForm({ mode: 'onSubmit', reValidateMode: 'onBlur', @@ -33,8 +33,13 @@ export default function OfferForm() { }); const onSubmit = async (data: TForm) => { + const payload = { + ...data, + form: 'offer-form', + }; + try { - console.log('Form', data); + await sendFormFn(payload); toast.success('Заявка на консультацию принята'); reset(defaultValues); } catch (e) { diff --git a/src/widgets/offer-request/ui.tsx b/src/widgets/offer-request/ui.tsx index f89fb5f..a86edfa 100644 --- a/src/widgets/offer-request/ui.tsx +++ b/src/widgets/offer-request/ui.tsx @@ -10,6 +10,7 @@ import toast from 'react-hot-toast'; import bgForm from '@public/images/bg-form.jpg'; import { useState } from 'react'; +import { sendFormFn } from '@shared/api/api.service'; const FormSchema = z.object({ name: z.string().min(3), @@ -28,7 +29,6 @@ export default function OfferRequest() { control, reset, formState: { errors }, - clearErrors, } = useForm({ mode: 'onSubmit', reValidateMode: 'onBlur', @@ -39,8 +39,13 @@ export default function OfferRequest() { const [inputPhone, setInputPhone] = useState(''); const onSubmitForm = async (data: TForm) => { + const payload = { + ...data, + form: 'offer-request-form', + }; + try { - console.log('Form', data); + await sendFormFn(payload); toast.success('Заявка на консультацию принята'); reset(defaultValues); } catch (e) { @@ -51,11 +56,13 @@ export default function OfferRequest() { }; const onSubmitPhone = async (phone: string) => { - const data = { - phone, + const payload = { + phone: phone, + form: 'offer-request-form', }; + try { - console.log('Form', data); + await sendFormFn(payload); toast.success('Заявка на консультацию принята'); setInputPhone(''); } catch (e) {