From eb81d45008461d19714115970262677a19484b30 Mon Sep 17 00:00:00 2001 From: ariari04 Date: Sun, 25 Aug 2024 13:46:32 +0600 Subject: [PATCH] Add sign in function --- lib/next-auth.d.ts | 25 +++ messages/en.json | 13 ++ messages/kg.json | 13 ++ messages/ru.json | 23 +-- src/app/[locale]/sign-in/page.tsx | 11 +- src/app/api/auth/[...nextauth]/route.ts | 6 + src/shared/config/authConfig.ts | 111 +++++++++++++ src/shared/types/token-type.ts | 10 ++ .../ConfirmEmailForm/ConfirmEmailForm.tsx | 7 +- src/widgets/forms/SignInForm.tsx | 153 ++++++++++++++---- 10 files changed, 320 insertions(+), 52 deletions(-) create mode 100644 lib/next-auth.d.ts create mode 100644 src/app/api/auth/[...nextauth]/route.ts create mode 100644 src/shared/config/authConfig.ts create mode 100644 src/shared/types/token-type.ts diff --git a/lib/next-auth.d.ts b/lib/next-auth.d.ts new file mode 100644 index 0000000..d0e228b --- /dev/null +++ b/lib/next-auth.d.ts @@ -0,0 +1,25 @@ +import NextAuth from "next-auth"; + +declare module "next-auth" { + interface Session { + refresh_token: string; + access_token: string; + expires_in: Date; + } + + interface User { + refresh_token: string; + access_token: string; + expires_in: Date; + } +} + +import { JWT } from "next-auth/jwt"; + +declare module "next-auth/jwt" { + interface JWT { + refresh_token: string; + access_token: string; + expires_in: Date; + } +} diff --git a/messages/en.json b/messages/en.json index 0c36e48..aa44104 100644 --- a/messages/en.json +++ b/messages/en.json @@ -70,5 +70,18 @@ "resendIn": "Resend code via", "resendBtn": "Resend code", "error": "An error occurred" + }, + "signIn": { + "login": "Login to your account", + "info": "Please enter your details", + "emailInp": "Enter email", + "password": "Password", + "passwordInp": "Enter password", + "error": "Invalid email or password", + "passwordForget": "Forgot your password?", + "loginBtn": "Login", + "loginGoogle": "Login with Google", + "noAccount": "Don't have an account yet?", + "register": "Register" } } diff --git a/messages/kg.json b/messages/kg.json index 8ae2963..1fe48ef 100644 --- a/messages/kg.json +++ b/messages/kg.json @@ -70,5 +70,18 @@ "resendIn": "Кодду кайра жөнөтүү", "resendBtn": "Кодду кайра жөнөтүү", "error": "Ката кетти" + }, + "signIn": { + "login": "Аккаунтка кириңиз", + "info": "Маалыматты киргизиңиз", + "emailInp": "Emailди териңиз", + "password": "Сырсөз", + "passwordInp": "Сырсөздү териңиз", + "error": "Email же сырсөз дүрткүчтү киргизүү", + "passwordForget": "Сырсөздү унуттуңузбу?", + "loginBtn": "Кирүү", + "loginGoogle": "Google аркылуу кируңуз", + "noAccount": "Аккаунт жок?", + "register": "Каттоо" } } diff --git a/messages/ru.json b/messages/ru.json index 127c3b4..3570266 100644 --- a/messages/ru.json +++ b/messages/ru.json @@ -11,13 +11,6 @@ "search": "Поиск", "search_for": "Искать", "city": "Город", - "added_roads": "Добавлено дорог", - "broken_roads": "Разбитых дорог", - "accident_hotspots": "Очагов аварийности", - "local_defects": "Локальных дефектов", - "repair_plans": "В планах ремонта", - "repaired": "Отремонтировано", - "fixed_local_defects": "Локальных дефектов исправлено", "news": "Новости", "details": "Подробнее", "navigation": "Навигация", @@ -32,9 +25,6 @@ "send": "Отправить", "receive": "Получить", "delete": "Удалить", - "show_on_map": "Показать на карте", - "author_of_appeal": "Автор обращения", - "enter_city": "Введите населенный пункт", "page_not_found": "Страница не найдена (404)", "incorrect_address_or_nonexistent_page": "Неправильно набран адрес или такой страницы не существует.", "home": "На главную", @@ -71,5 +61,18 @@ "resendIn": "Отправить код повторно через", "resendBtn": "Отправить код повторно", "error": "Произошла ошибка" + }, + "signIn": { + "login": "Войдите в аккаунт", + "info": "Пожалуйста, введите свои данные", + "emailInp": "Введите email", + "password": "Пароль", + "passwordInp": "Введите пароль", + "error": "Неверный email или пароль", + "passwordForget": "Забыли пароль?", + "loginBtn": "Войти", + "loginGoogle": "Войти через Google", + "noAccount": "Еще нет аккаунта?", + "register": "Зарегистрируйтесь" } } diff --git a/src/app/[locale]/sign-in/page.tsx b/src/app/[locale]/sign-in/page.tsx index 653e3c9..fa9b29c 100644 --- a/src/app/[locale]/sign-in/page.tsx +++ b/src/app/[locale]/sign-in/page.tsx @@ -3,8 +3,11 @@ import sign_in_icon from "./icons/sign-in_icon.svg"; import { Link } from "@/shared/config/navigation"; import { Container } from "@/shared/ui"; import SignInForm from "@/widgets/forms/SignInForm"; +import { useTranslations } from "next-intl"; const SignIn = () => { + const t = useTranslations("signIn"); + return (
@@ -14,19 +17,19 @@ const SignIn = () => {

- Войдите в аккаунт + {t("login")}

-

Пожалуйста, введите свои данные

+

{t("info")}

- Еще нет аккаунта?{" "} + {t("noAccount")}{" "} - Зарегистрируйтесь + {t("register")}

diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000..bf58899 --- /dev/null +++ b/src/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,6 @@ +import NextAuth from "next-auth"; +import { authConfig } from "@/shared/config/authConfig"; + +const handler = NextAuth(authConfig); + +export { handler as GET, handler as POST }; diff --git a/src/shared/config/authConfig.ts b/src/shared/config/authConfig.ts new file mode 100644 index 0000000..7fa6791 --- /dev/null +++ b/src/shared/config/authConfig.ts @@ -0,0 +1,111 @@ +import { AuthOptions } from "next-auth"; +import { JWT } from "next-auth/jwt"; +import CredentialsProvider from "next-auth/providers/credentials"; +import { apiInstance } from "./apiConfig"; +import { IRefresh, ITokens } from "../types/token-type"; + +const refreshToken = async (token: JWT): Promise => { + const UTC = new Date(); + + const data = { + refresh: token.refresh_token, + }; + + const response = await apiInstance.post( + "/auth/token/refresh/", + data + ); + + const expirationTime = new Date(UTC.getTime() + 14 * 60000); + expirationTime.setTime( + expirationTime.getTime() + + expirationTime.getTimezoneOffset() * 60 * 1000 * -1 + ); + + return { + ...token, + access_token: response.data.access, + expires_in: expirationTime, + }; +}; + +export const authConfig: AuthOptions = { + providers: [ + CredentialsProvider({ + name: "Credentials", + credentials: { + email: { + label: "Email", + type: "text", + placeholder: "jsmith@example.com", + }, + password: { label: "Password", type: "password" }, + }, + async authorize(credentials, req): Promise { + if (!credentials?.email || !credentials?.password) return null; + + const { email, password } = credentials; + const data = { + email, + password, + }; + + const res = await apiInstance.post("/auth/login/", data); + + if ([200, 201].includes(res.status)) { + const currentTime = new Date(); + const expirationTime = new Date(currentTime.getTime() + 14 * 60000); + expirationTime.setTime( + expirationTime.getTime() + + expirationTime.getTimezoneOffset() * 60 * 1000 * -1 + ); + + const user = { + refresh_token: res.data.refresh_token, + access_token: res.data.access_token, + expires_in: expirationTime, + }; + + return user; + } + + return null; + }, + }), + ], + pages: { + signIn: "/sign-in", + }, + session: { + strategy: "jwt", + }, + callbacks: { + async signIn({ account, user }) { + return true; + }, + async jwt({ token, user }) { + if (user) return { ...token, ...user }; + + const UTC = new Date(); + const currentTime = new Date(UTC.getTime()); + currentTime.setTime( + currentTime.getTime() + currentTime.getTimezoneOffset() * 60 * 1000 * -1 + ); + + const isValid = + new Date(currentTime).getTime() <= new Date(token.expires_in).getTime(); + + if (isValid) return token; + + return await refreshToken(token); + }, + + async session({ token, session }) { + session.access_token = token.access_token; + session.refresh_token = token.refresh_token; + session.expires_in = token.expires_in; + + return session; + }, + }, +}; diff --git a/src/shared/types/token-type.ts b/src/shared/types/token-type.ts new file mode 100644 index 0000000..0c78c69 --- /dev/null +++ b/src/shared/types/token-type.ts @@ -0,0 +1,10 @@ +export interface ITokens { + refresh_token: string; + access_token: string; + expires_in: string; +} + +export interface IRefresh { + access: string; + expires_at: string; +} diff --git a/src/widgets/forms/ConfirmEmailForm/ConfirmEmailForm.tsx b/src/widgets/forms/ConfirmEmailForm/ConfirmEmailForm.tsx index 6a721a1..550606b 100644 --- a/src/widgets/forms/ConfirmEmailForm/ConfirmEmailForm.tsx +++ b/src/widgets/forms/ConfirmEmailForm/ConfirmEmailForm.tsx @@ -38,13 +38,12 @@ const ConfirmEmailForm: React.FC = ({ e.preventDefault(); const codeString = otp.join(""); - const codeNumber = parseInt(codeString); - console.log(parseInt("01")); + try { setLoader(true); - console.log(codeNumber); + const response = await apiInstance.post("/auth/confirm_user/", { - code: codeNumber, + code: codeString, }); console.log(response); if (response.status === 200 || response.status === 201) { diff --git a/src/widgets/forms/SignInForm.tsx b/src/widgets/forms/SignInForm.tsx index a2ecf5d..7206182 100644 --- a/src/widgets/forms/SignInForm.tsx +++ b/src/widgets/forms/SignInForm.tsx @@ -1,18 +1,28 @@ "use client"; -import React, { FC, useState } from "react"; +import React from "react"; import { z } from "zod"; import { zodResolver } from "@hookform/resolvers/zod"; -import AuthInput from "@/features/AuthInput"; -import Loader from "@/shared/ui/Loader/Loader"; import Link from "next/link"; import { useForm } from "react-hook-form"; +import { useTranslations } from "next-intl"; +import { useRouter } from "@/shared/config/navigation"; +import alert from "./icons/alert-circle.svg"; +import Image from "next/image"; +import eye_off from "./icons/eye-off.svg"; +import eye_on from "./icons/eye-on.svg"; +import Loader from "@/shared/ui/Loader/Loader"; +import { apiInstance } from "@/shared/config/apiConfig"; +import { signIn, useSession } from "next-auth/react"; +import { AxiosError } from "axios"; -// interface SignInProps { -// isLoading: boolean; -// onSubmitFunc: () => void; -// noAccountError: Error | null; -// } const SignInForm = ({}) => { + const session = useSession(); + const t = useTranslations("signIn"); + const router = useRouter(); + const [showPassword, setShowPassword] = React.useState(false); + const [loader, setLoader] = React.useState(false); + const [error, setError] = React.useState(""); + const signInFormScheme = z.object({ email: z.string().email("Неверный формат email"), password: z.string().min(8, "Пароль должен содержать минимум 8 символов"), @@ -23,46 +33,121 @@ const SignInForm = ({}) => { const { register, handleSubmit, - formState: { errors }, + formState: { errors, isSubmitting }, } = useForm({ resolver: zodResolver(signInFormScheme), }); - const onSubmit = (data: FormFields) => {}; + // const onSubmit = async (data: FormFields) => { + // try { + // console.log(data); + // const res = await apiInstance.post("/auth/login/", data); + // if ([200, 201].includes(res.status)) { + // router.push("/"); + // } + // } catch (error: unknown) { + // if (error instanceof AxiosError) { + // if ([400, 401, 404].includes(error.response?.status as number)) { + // setError("Неверный Email или Пароль"); + // } else if (error.response?.status.toString().slice(0, 1) === "5") { + // setError("Ошибка на стороне сервера"); + // } + // } else { + // setError("Непредвиденная ошибка"); + // } + // } finally { + // setLoader(false); + // } + // }; + const onSubmit = async (data: FormFields) => { + setLoader(true); + const result = await signIn("credentials", { + redirect: false, + email: data.email, + password: data.password, + }); + + if (result?.error) { + setError(result.error); + } else if (result?.ok) { + router.push("/"); + } + + setLoader(false); + }; + return ( -
+
- - +
+ +
+ +
+ {errors?.email?.message && ( +

+ {errors.email.message} Alert Icon +

+ )} +
+
+ +
+ + +
+ {errors?.password?.message && ( +

+ {errors.password.message} Alert Icon +

+ )} +
{errors.root && (
{errors.root.message}
)} - +
+ +
- Забыли пароль? + {t("passwordForget")}