diff --git a/package-lock.json b/package-lock.json index 55e20cc..1aae244 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "autoprefixer": "^10.4.20", + "axios": "^1.7.4", "clsx": "^2.1.1", "next": "14.2.5", "next-auth": "^4.24.7", @@ -947,6 +948,11 @@ "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", "dev": true }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/autoprefixer": { "version": "10.4.20", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", @@ -1007,6 +1013,16 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", + "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/axobject-query": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", @@ -1237,6 +1253,17 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -1438,6 +1465,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -2229,6 +2264,25 @@ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -2254,6 +2308,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -3282,6 +3349,25 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -4055,6 +4141,11 @@ "react-is": "^16.13.1" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/package.json b/package.json index 7b54664..650adfb 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "autoprefixer": "^10.4.20", + "axios": "^1.7.4", "clsx": "^2.1.1", "next": "14.2.5", "next-auth": "^4.24.7", diff --git a/src/app/[locale]/contacts/assets/Container (1).png b/src/app/[locale]/contacts/assets/Container (1).png new file mode 100644 index 0000000..0db906a Binary files /dev/null and b/src/app/[locale]/contacts/assets/Container (1).png differ diff --git a/src/app/[locale]/contacts/page.tsx b/src/app/[locale]/contacts/page.tsx new file mode 100644 index 0000000..27f0772 --- /dev/null +++ b/src/app/[locale]/contacts/page.tsx @@ -0,0 +1,31 @@ +import { Container, Title } from "@/shared/ui"; +import Img from "./assets/Container (1).png"; +import Image from "next/image"; +// import ContactForm from "@/widgets/forms/ContactForm"; + +export default function Contacts() { + return ( + + + + + + + + г. Бишкек, ул. Турусбекова 109/1, офис 108 + + + + + (0312) 39 40 38 + + + + kyrgyzstan@transparency.org + + + {/* */} + + + ); +} diff --git a/src/app/[locale]/sign-up/icons/flag.svg b/src/app/[locale]/sign-up/icons/flag.svg new file mode 100644 index 0000000..4d918fa --- /dev/null +++ b/src/app/[locale]/sign-up/icons/flag.svg @@ -0,0 +1,7 @@ + + + Created with Pixso. + + + + diff --git a/src/app/[locale]/sign-up/page.tsx b/src/app/[locale]/sign-up/page.tsx new file mode 100644 index 0000000..b10a41b --- /dev/null +++ b/src/app/[locale]/sign-up/page.tsx @@ -0,0 +1,39 @@ +import Image from "next/image"; +import flag from "./icons/flag.svg"; +import { Link } from "@/shared/config/navigation"; +import SignUpForm from "@/widgets/forms/SignUpForm"; +import { Container } from "@/shared/ui"; + +const SignUp = () => { + return ( + + + + + + + + + Регистрация + + Пожалуйста, введите свои данные + + + + + + Уже есть аккаунт?{" "} + + Войти в аккаунт + + + + + + ); +}; + +export default SignUp; diff --git a/src/features/AuthInput.tsx b/src/features/AuthInput.tsx new file mode 100644 index 0000000..4a3617a --- /dev/null +++ b/src/features/AuthInput.tsx @@ -0,0 +1,53 @@ +"use client"; + +import Image from "next/image"; +import eye_off from "./icons/eye-off.svg"; +import eye_on from "./icons/eye-on.svg"; +import alert from "./icons/alert-circle.svg"; +import { useState } from "react"; + +interface IAuthInputProps extends React.InputHTMLAttributes { + isPassword?: boolean; + label: string; + error: string; +} + +const AuthInput: React.FC = ({ + isPassword, + label, + error, + placeholder, + name, + type, +}: IAuthInputProps) => { + const [isOpen, setIsOpen] = useState(false); + return ( + + {label} + + + {isPassword && ( + setIsOpen((prev) => !prev)} type="button"> + + + )} + + {error ? ( + + {error} + + ) : null} + + ); +}; + +export default AuthInput; diff --git a/src/features/icons/alert-circle.svg b/src/features/icons/alert-circle.svg new file mode 100644 index 0000000..51cc2b1 --- /dev/null +++ b/src/features/icons/alert-circle.svg @@ -0,0 +1,7 @@ + + + Created with Pixso. + + + + diff --git a/src/features/icons/eye-off.svg b/src/features/icons/eye-off.svg new file mode 100644 index 0000000..99bfa55 --- /dev/null +++ b/src/features/icons/eye-off.svg @@ -0,0 +1,15 @@ + + + Created with Pixso. + + + + + + + + + + + + diff --git a/src/features/icons/eye-on.svg b/src/features/icons/eye-on.svg new file mode 100644 index 0000000..feb18f7 --- /dev/null +++ b/src/features/icons/eye-on.svg @@ -0,0 +1,15 @@ + + + Created with Pixso. + + + + + + + + + + + + diff --git a/src/shared/config/apiConfig.ts b/src/shared/config/apiConfig.ts new file mode 100644 index 0000000..7b1d24d --- /dev/null +++ b/src/shared/config/apiConfig.ts @@ -0,0 +1,17 @@ +import axios from "axios"; + +const API_URL = process.env["NEXT_PUBLIC_BASE_API"]; + +export const apiInstance = axios.create({ + baseURL: API_URL, +}); + +export const authInstanse = (access_token: string) => { + return axios.create({ + baseURL: API_URL, + headers: { + Authorization: `Bearer ${access_token}`, + }, + method: "", + }); +}; diff --git a/src/shared/ui/Loader/Loader.css b/src/shared/ui/Loader/Loader.css new file mode 100644 index 0000000..826be78 --- /dev/null +++ b/src/shared/ui/Loader/Loader.css @@ -0,0 +1,13 @@ +@keyframes rotation { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } + } + + .animate-spin { + animation: rotation 1s linear infinite; + } + \ No newline at end of file diff --git a/src/shared/ui/Loader/Loader.tsx b/src/shared/ui/Loader/Loader.tsx new file mode 100644 index 0000000..eedaca6 --- /dev/null +++ b/src/shared/ui/Loader/Loader.tsx @@ -0,0 +1,16 @@ +import "./Loader.css"; + +interface ILoader { + color?: string; +} + +const Loader: React.FC = ({ color }: ILoader) => { + return ( + + ); +}; + +export default Loader; diff --git a/src/widgets/forms/SignUpForm.tsx b/src/widgets/forms/SignUpForm.tsx new file mode 100644 index 0000000..bd2fbe1 --- /dev/null +++ b/src/widgets/forms/SignUpForm.tsx @@ -0,0 +1,153 @@ +"use client"; + +import { useState } from "react"; +import { AxiosError } from "axios"; +import AuthInput from "@/features/AuthInput"; +import Loader from "@/shared/ui/Loader/Loader"; +import { apiInstance } from "@/shared/config/apiConfig"; +import { Link, useRouter } from "@/shared/config/navigation"; + +const SignUpForm = () => { + const [checkbox, setCheckbox] = useState(false); + const [emailWarning, setEmailWarning] = useState(""); + const [passwordWarning, setPasswordWarning] = useState(""); + const [passwordConfirmWarning, setPasswordConfirmWarning] = + useState(""); + const [error, setError] = useState(""); + const [loader, setLoader] = useState(false); + + const router = useRouter(); + + const handleSubmit: React.MouseEventHandler = async (e) => { + e.preventDefault(); + const formData = new FormData(e.currentTarget); + const regex = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/; + + if (!formData.get("email")?.toString()) { + setError(""); + setPasswordWarning(""); + setPasswordConfirmWarning(""); + setEmailWarning("Заполните поле Email"); + return; + } + + if (!formData.get("password")?.toString()) { + setError(""); + setEmailWarning(""); + setPasswordConfirmWarning(""); + setPasswordWarning("Заполните поле Пароль"); + return; + } + + if ((formData.get("password")?.toString().length as number) < 8) { + setError(""); + setEmailWarning(""); + setPasswordConfirmWarning(""); + setPasswordWarning("Пароль должен содержать минимум 8 символов"); + return; + } + + if (!regex.test(formData.get("password")?.toString() as string)) { + setError(""); + setEmailWarning(""); + setPasswordConfirmWarning(""); + setPasswordWarning( + "Пароль должен содержать по меньшей мере 1 прописную букву, одну заглавную букву и одну цифру" + ); + return; + } + + if (!formData.get("password2")?.toString()) { + setError(""); + setEmailWarning(""); + setPasswordWarning(""); + setPasswordConfirmWarning("Заполните поле потверждения"); + return; + } + + if ( + formData.get("password")?.toString() !== + formData.get("password2")?.toString() + ) { + setError(""); + setEmailWarning(""); + setPasswordWarning(""); + setPasswordConfirmWarning("Пароли не совпадают"); + return; + } + + if (!checkbox) { + setEmailWarning(""); + setPasswordWarning(""); + setPasswordConfirmWarning(""); + setError("Необходимо принять политику конфиденциальности"); + return; + } + + try { + setError(""); + setEmailWarning(""); + setPasswordWarning(""); + setPasswordConfirmWarning(""); + setLoader(true); + + const res = await apiInstance.post("/users/register/", formData); + + if ([200, 201].includes(res.status)) { + router.push(`/sign-up/confirm-email/?email=${formData.get("email")}`); + } + } catch (error: unknown) { + if (error instanceof AxiosError) { + if ([401, 400].includes(error.response?.status as number)) { + setError("Такой пользователь уже существует"); + } else if (error.response?.status.toString().slice(0, 1) === "5") { + setError("Ошибка на стороне сервера"); + } + } else { + setError("Непредвиденная ошибка"); + } + } finally { + setLoader(false); + } + }; + + return ( + + + + + + + {error ? {error} : null} + + + + {loader ? : "Зарегистрироваться"} + + + + ); +}; + +export default SignUpForm; diff --git a/src/widgets/forms/icons/check.svg b/src/widgets/forms/icons/check.svg new file mode 100644 index 0000000..9b5d538 --- /dev/null +++ b/src/widgets/forms/icons/check.svg @@ -0,0 +1,7 @@ + + + Created with Pixso. + + + +
+ г. Бишкек, ул. Турусбекова 109/1, офис 108 +
(0312) 39 40 38
kyrgyzstan@transparency.org
Пожалуйста, введите свои данные
+ Уже есть аккаунт?{" "} + + Войти в аккаунт + +
+ {error} +
{error}