Add send appeal form

This commit is contained in:
ariari04 2024-09-17 16:00:23 +06:00
parent 9dda476c16
commit 9c3657190a
7 changed files with 141 additions and 23 deletions

View File

@ -118,5 +118,19 @@
"serverError": "Error on the server side", "serverError": "Error on the server side",
"noPassword": "Weak password, please avoid obvious passwords", "noPassword": "Weak password, please avoid obvious passwords",
"errorOccured": "An unexpected error occurred" "errorOccured": "An unexpected error occurred"
},
"contacts": {
"serverError": "Error on the server side",
"addressTitle": "Address",
"address": "Bishkek, Turusbekov str. 109/1, office 108",
"phoneTitle": "Phone",
"title": "We're in touch",
"name": "Name",
"surname": "Last name",
"phone": "Phone number",
"message": "Message",
"checkbox": "I agree to the processing of my personal data",
"send": "Send a message",
"successMessage": "Your message has been sent"
} }
} }

View File

@ -118,5 +118,19 @@
"serverError": "Сервер тараптагы ката", "serverError": "Сервер тараптагы ката",
"noPassword": "Начар сырсөз, ачык сырсөздөрдү колдонбоңуз", "noPassword": "Начар сырсөз, ачык сырсөздөрдү колдонбоңуз",
"errorOccured": "Күтүлбөгөн ката кетти" "errorOccured": "Күтүлбөгөн ката кетти"
},
"contacts": {
"serverError": "Сервер тараптагы ката",
"addressTitle": "Дарек",
"address": "Бишкек ш., Турусбеков көч. 109/1, офис 108",
"phoneTitle": "Телефон",
"title": "Биз байланыштабыз",
"name": "Аты-жөнү",
"familia": "фамилиясы",
"phone": "Телефон номери",
"message": "Кабар",
"checkbox": "Мен жеке маалыматтарымды иштетүүгө макулмун",
"send": "Билдирүү жөнөтүү",
"successMessage": "Сиздин билдирүү жөнөтүлдү"
} }
} }

View File

@ -109,5 +109,19 @@
"serverError": "Ошибка на стороне сервера", "serverError": "Ошибка на стороне сервера",
"noPassword": "Слабый пароль, прошу избегайте очевидных паролей", "noPassword": "Слабый пароль, прошу избегайте очевидных паролей",
"errorOccured": "Произошла непредвиденная ошибка" "errorOccured": "Произошла непредвиденная ошибка"
},
"contacts": {
"serverError": "Ошибка на стороне сервера",
"addressTitle": "Адрес",
"address": "г. Бишкек, ул. Турусбекова 109/1, офис 108",
"phoneTitle": "Телефон",
"title": "Мы на связи",
"name": "Имя",
"surname": "Фамилия",
"phone": "Номер телефона",
"message": "Сообщение",
"checkbox": "Я согласен (-на) на обработку моих личных данных",
"send": "Отправить сообщение",
"successMessage": "Ваше сообщение отправлено"
} }
} }

14
package-lock.json generated
View File

@ -18,6 +18,7 @@
"react": "^18", "react": "^18",
"react-dom": "^18", "react-dom": "^18",
"react-hook-form": "^7.52.2", "react-hook-form": "^7.52.2",
"react-toastify": "^10.0.5",
"sass": "^1.77.8", "sass": "^1.77.8",
"tailwind-merge": "^2.5.2", "tailwind-merge": "^2.5.2",
"zod": "^3.23.8", "zod": "^3.23.8",
@ -4224,6 +4225,19 @@
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true "dev": true
}, },
"node_modules/react-toastify": {
"version": "10.0.5",
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.5.tgz",
"integrity": "sha512-mNKt2jBXJg4O7pSdbNUfDdTsK9FIdikfsIE/yUCxbAEXl4HMyJaivrVFcn3Elvt5xvCQYhUZm+hqTIu1UXM3Pw==",
"license": "MIT",
"dependencies": {
"clsx": "^2.1.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
}
},
"node_modules/read-cache": { "node_modules/read-cache": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",

View File

@ -19,6 +19,7 @@
"react": "^18", "react": "^18",
"react-dom": "^18", "react-dom": "^18",
"react-hook-form": "^7.52.2", "react-hook-form": "^7.52.2",
"react-toastify": "^10.0.5",
"sass": "^1.77.8", "sass": "^1.77.8",
"tailwind-merge": "^2.5.2", "tailwind-merge": "^2.5.2",
"zod": "^3.23.8", "zod": "^3.23.8",

View File

@ -1,8 +1,58 @@
"use client";
import { apiInstance } from "@/shared/config/apiConfig";
import { Container, Title } from "@/shared/ui"; import { Container, Title } from "@/shared/ui";
import Loader from "@/shared/ui/Loader/Loader";
// import ContactForm from "@/widgets/forms/ContactForm"; import { zodResolver } from "@hookform/resolvers/zod";
import { AxiosError } from "axios";
import { useTranslations } from "next-intl";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "react-toastify";
import { z } from "zod";
export default function Contacts() { export default function Contacts() {
const t = useTranslations("contacts");
const [error, setError] = useState<string>("");
const [loader, setLoader] = useState<boolean>(false);
const appealFormScheme = z.object({
first_name: z.string(),
last_name: z.string(),
email: z.string(),
phone_number: z.string(),
message: z.string(),
});
type FormFields = z.infer<typeof appealFormScheme>;
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<FormFields>({
resolver: zodResolver(appealFormScheme),
});
const onSubmit = async (data: FormFields) => {
try {
setError("");
setLoader(true);
const response = await apiInstance.post("/appeal_response/", data);
console.log(response);
toast.success(t("successMessage"));
} catch (error) {
if (error instanceof AxiosError) {
if (
[500, 501, 502, 503, 504].includes(error.response?.status as number)
) {
toast.error(t("serverError"));
}
}
} finally {
setLoader(false);
}
};
return ( return (
<section className="bg-[#FAFCFF]"> <section className="bg-[#FAFCFF]">
<Container className="py-[100px] "> <Container className="py-[100px] ">
@ -12,13 +62,11 @@ export default function Contacts() {
></iframe> ></iframe>
<section className="flex items-center justify-between mb-[152px] mt-[64px]"> <section className="flex items-center justify-between mb-[152px] mt-[64px]">
<div className="max-w-[384px] max-h-[106px] flex flex-col items-center px-7"> <div className="max-w-[384px] max-h-[106px] flex flex-col items-center px-7">
<Title text="Адрес" className="font-extrabold mb-2" /> <Title text={t("addressTitle")} className="font-extrabold mb-2" />
<p className="text-grey-text"> <p className="text-grey-text">{t("address")}</p>
г. Бишкек, ул. Турусбекова 109/1, офис 108
</p>
</div> </div>
<div className="max-w-[384px] max-h-[106px] flex flex-col items-center px-7"> <div className="max-w-[384px] max-h-[106px] flex flex-col items-center px-7">
<Title text="Телефон" className="font-extrabold mb-2" /> <Title text={t("phoneTitle")} className="font-extrabold mb-2" />
<p className="text-grey-text">(0312) 39 40 38</p> <p className="text-grey-text">(0312) 39 40 38</p>
</div> </div>
<div className="max-w-[384px] max-h-[106px] flex flex-col items-center px-7"> <div className="max-w-[384px] max-h-[106px] flex flex-col items-center px-7">
@ -26,36 +74,38 @@ export default function Contacts() {
<p className="text-grey-text">kyrgyzstan@transparency.org</p> <p className="text-grey-text">kyrgyzstan@transparency.org</p>
</div> </div>
</section> </section>
{/* <ContactForm /> */} <form
<form className="w-[480px] my-0 mx-auto"> onSubmit={handleSubmit(onSubmit)}
className="w-[480px] my-0 mx-auto"
>
<div className="flex items-center flex-col mt-[96px] mb-[96px]"> <div className="flex items-center flex-col mt-[96px] mb-[96px]">
<h1 className="text-gray-900 text-[36px]">Мы на связи</h1> <h1 className="text-gray-900 text-[36px]">{t("title")}</h1>
<div className="flex flex-col items-center gap-6 mt-[64px]"> <div className="flex flex-col items-center gap-6 mt-[64px]">
<div className="flex gap-8 w-full"> <div className="flex gap-8 w-full">
<div className="w-[224px] flex flex-col"> <div className="w-[224px] flex flex-col">
<label htmlFor="firstName" className="mb-[6px]"> <label htmlFor="firstName" className="mb-[6px]">
Имя {t("name")}
</label> </label>
<input <input
type="text" type="text"
name="firstName"
className="w-full h-[48px] bg-white py-3 px-4 rounded-md shadow-sm border border-gray" className="w-full h-[48px] bg-white py-3 px-4 rounded-md shadow-sm border border-gray"
placeholder="Имя" placeholder={t("name")}
id="firstName" id="firstName"
required required
{...register("first_name", { required: true })}
/> />
</div> </div>
<div className="w-[224px] flex flex-col"> <div className="w-[224px] flex flex-col">
<label htmlFor="lastName" className="mb-[6px]"> <label htmlFor="lastName" className="mb-[6px]">
Фамилия {t("surname")}
</label> </label>
<input <input
type="text" type="text"
name="lastName"
className="w-full h-[48px] bg-white py-3 px-4 rounded-md shadow-sm border border-gray" className="w-full h-[48px] bg-white py-3 px-4 rounded-md shadow-sm border border-gray"
placeholder="Фамилия" placeholder={t("surname")}
id="lastName" id="lastName"
required required
{...register("last_name", { required: true })}
/> />
</div> </div>
</div> </div>
@ -65,36 +115,36 @@ export default function Contacts() {
</label> </label>
<input <input
type="text" type="text"
name="email"
placeholder="user@gmail.com" placeholder="user@gmail.com"
className="w-full h-[48px] bg-white py-3 px-4 rounded-md shadow-sm border border-gray" className="w-full h-[48px] bg-white py-3 px-4 rounded-md shadow-sm border border-gray"
id="email" id="email"
{...register("email", { required: true })}
/> />
</div> </div>
<div className="w-full"> <div className="w-full">
<label htmlFor="phoneNumber" className="mb-[6px]"> <label htmlFor="phoneNumber" className="mb-[6px]">
Номер телефона {t("phone")}
</label> </label>
<input <input
type="text" type="text"
name="phoneNumber"
className="w-full h-[48px] bg-white py-3 px-4 rounded-md shadow-sm border border-gray" className="w-full h-[48px] bg-white py-3 px-4 rounded-md shadow-sm border border-gray"
placeholder="+996" placeholder="+996"
id="phoneNumber" id="phoneNumber"
required required
{...register("phone_number", { required: true })}
/> />
</div> </div>
<div className="w-full"> <div className="w-full">
<label htmlFor="message" className="mb-[6px]"> <label htmlFor="message" className="mb-[6px]">
Сообщение {t("message")}
</label> </label>
<textarea <textarea
className="h-[120px] resize-none w-full bg-white py-3 px-4 rounded-md shadow-sm border border-gray" className="h-[120px] resize-none w-full bg-white py-3 px-4 rounded-md shadow-sm border border-gray"
id="message" id="message"
name="message"
required required
{...register("message", { required: true })}
></textarea> ></textarea>
</div> </div>
@ -110,16 +160,24 @@ export default function Contacts() {
htmlFor="consentCheckbox" htmlFor="consentCheckbox"
className="text-[#667085] ml-[10px]" className="text-[#667085] ml-[10px]"
> >
Я согласен (-на) на обработку моих личных данных {t("checkbox")}
</label> </label>
</div> </div>
{errors.root && (
<p className="text-sm leading-5 text-red-500">
{errors.root.message}
</p>
)}
{error ? (
<p className="text-red-500 leading-5 text-sm">{error}</p>
) : null}
<div className="w-full"> <div className="w-full">
<button <button
type="submit" type="submit"
className="bg-blue text-white w-full h-[48px] rounded-md " className="bg-blue text-white w-full h-[48px] rounded-md "
> >
Отправить сообщение {loader ? <Loader /> : t("send")}
</button> </button>
</div> </div>
</div> </div>

View File

@ -5,6 +5,8 @@ import { NextIntlClientProvider, useMessages } from "next-intl";
import Footer from "../../widgets/Footer/Footer"; import Footer from "../../widgets/Footer/Footer";
import Navbar from "@/widgets/Navbar/Navbar"; import Navbar from "@/widgets/Navbar/Navbar";
import { Providers } from "./Providers"; import { Providers } from "./Providers";
import "react-toastify/dist/ReactToastify.css";
import { ToastContainer } from "react-toastify";
const inter = Inter({ subsets: ["latin"] }); const inter = Inter({ subsets: ["latin"] });
@ -29,6 +31,7 @@ export default function RootLayout({
<Providers> <Providers>
<Navbar /> <Navbar />
<div>{children}</div> <div>{children}</div>
<ToastContainer />
<Footer /> <Footer />
</Providers> </Providers>
</NextIntlClientProvider> </NextIntlClientProvider>