Add sign in function

This commit is contained in:
ariari04 2024-08-25 13:46:32 +06:00
parent 38b869084d
commit eb81d45008
10 changed files with 320 additions and 52 deletions

25
lib/next-auth.d.ts vendored Normal file
View File

@ -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;
}
}

View File

@ -70,5 +70,18 @@
"resendIn": "Resend code via", "resendIn": "Resend code via",
"resendBtn": "Resend code", "resendBtn": "Resend code",
"error": "An error occurred" "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"
} }
} }

View File

@ -70,5 +70,18 @@
"resendIn": "Кодду кайра жөнөтүү", "resendIn": "Кодду кайра жөнөтүү",
"resendBtn": "Кодду кайра жөнөтүү", "resendBtn": "Кодду кайра жөнөтүү",
"error": "Ката кетти" "error": "Ката кетти"
},
"signIn": {
"login": "Аккаунтка кириңиз",
"info": "Маалыматты киргизиңиз",
"emailInp": "Emailди териңиз",
"password": "Сырсөз",
"passwordInp": "Сырсөздү териңиз",
"error": "Email же сырсөз дүрткүчтү киргизүү",
"passwordForget": "Сырсөздү унуттуңузбу?",
"loginBtn": "Кирүү",
"loginGoogle": "Google аркылуу кируңуз",
"noAccount": "Аккаунт жок?",
"register": "Каттоо"
} }
} }

View File

@ -11,13 +11,6 @@
"search": "Поиск", "search": "Поиск",
"search_for": "Искать", "search_for": "Искать",
"city": "Город", "city": "Город",
"added_roads": "Добавлено дорог",
"broken_roads": "Разбитых дорог",
"accident_hotspots": "Очагов аварийности",
"local_defects": "Локальных дефектов",
"repair_plans": "В планах ремонта",
"repaired": "Отремонтировано",
"fixed_local_defects": "Локальных дефектов исправлено",
"news": "Новости", "news": "Новости",
"details": "Подробнее", "details": "Подробнее",
"navigation": "Навигация", "navigation": "Навигация",
@ -32,9 +25,6 @@
"send": "Отправить", "send": "Отправить",
"receive": "Получить", "receive": "Получить",
"delete": "Удалить", "delete": "Удалить",
"show_on_map": "Показать на карте",
"author_of_appeal": "Автор обращения",
"enter_city": "Введите населенный пункт",
"page_not_found": "Страница не найдена (404)", "page_not_found": "Страница не найдена (404)",
"incorrect_address_or_nonexistent_page": "Неправильно набран адрес или такой страницы не существует.", "incorrect_address_or_nonexistent_page": "Неправильно набран адрес или такой страницы не существует.",
"home": "На главную", "home": "На главную",
@ -71,5 +61,18 @@
"resendIn": "Отправить код повторно через", "resendIn": "Отправить код повторно через",
"resendBtn": "Отправить код повторно", "resendBtn": "Отправить код повторно",
"error": "Произошла ошибка" "error": "Произошла ошибка"
},
"signIn": {
"login": "Войдите в аккаунт",
"info": "Пожалуйста, введите свои данные",
"emailInp": "Введите email",
"password": "Пароль",
"passwordInp": "Введите пароль",
"error": "Неверный email или пароль",
"passwordForget": "Забыли пароль?",
"loginBtn": "Войти",
"loginGoogle": "Войти через Google",
"noAccount": "Еще нет аккаунта?",
"register": "Зарегистрируйтесь"
} }
} }

View File

@ -3,8 +3,11 @@ import sign_in_icon from "./icons/sign-in_icon.svg";
import { Link } from "@/shared/config/navigation"; import { Link } from "@/shared/config/navigation";
import { Container } from "@/shared/ui"; import { Container } from "@/shared/ui";
import SignInForm from "@/widgets/forms/SignInForm"; import SignInForm from "@/widgets/forms/SignInForm";
import { useTranslations } from "next-intl";
const SignIn = () => { const SignIn = () => {
const t = useTranslations("signIn");
return ( return (
<Container> <Container>
<div className="h-full min-h-[800px] w-full flex justify-center"> <div className="h-full min-h-[800px] w-full flex justify-center">
@ -14,19 +17,19 @@ const SignIn = () => {
</div> </div>
<div className="mb-2 flex flex-col items-center gap-2 text-center"> <div className="mb-2 flex flex-col items-center gap-2 text-center">
<h2 className="text-[24px] font-bold leading-8 text-gray-900"> <h2 className="text-[24px] font-bold leading-8 text-gray-900">
Войдите в аккаунт {t("login")}
</h2> </h2>
<p className="text-gray-500">Пожалуйста, введите свои данные</p> <p className="text-gray-500">{t("info")}</p>
</div> </div>
<SignInForm /> <SignInForm />
<p className="text-[14px] font-semibold leading-5 text-gray-500"> <p className="text-[14px] font-semibold leading-5 text-gray-500">
Еще нет аккаунта?{" "} {t("noAccount")}{" "}
<Link <Link
className="text-[14px] font-semibold leading-5 text-light-blue" className="text-[14px] font-semibold leading-5 text-light-blue"
href="/sign-up" href="/sign-up"
> >
Зарегистрируйтесь {t("register")}
</Link> </Link>
</p> </p>
</div> </div>

View File

@ -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 };

View File

@ -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<JWT> => {
const UTC = new Date();
const data = {
refresh: token.refresh_token,
};
const response = await apiInstance.post<IRefresh>(
"/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<any> {
if (!credentials?.email || !credentials?.password) return null;
const { email, password } = credentials;
const data = {
email,
password,
};
const res = await apiInstance.post<ITokens>("/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;
},
},
};

View File

@ -0,0 +1,10 @@
export interface ITokens {
refresh_token: string;
access_token: string;
expires_in: string;
}
export interface IRefresh {
access: string;
expires_at: string;
}

View File

@ -38,13 +38,12 @@ const ConfirmEmailForm: React.FC<IConfirmEmailFormProps> = ({
e.preventDefault(); e.preventDefault();
const codeString = otp.join(""); const codeString = otp.join("");
const codeNumber = parseInt(codeString);
console.log(parseInt("01"));
try { try {
setLoader(true); setLoader(true);
console.log(codeNumber);
const response = await apiInstance.post("/auth/confirm_user/", { const response = await apiInstance.post("/auth/confirm_user/", {
code: codeNumber, code: codeString,
}); });
console.log(response); console.log(response);
if (response.status === 200 || response.status === 201) { if (response.status === 200 || response.status === 201) {

View File

@ -1,18 +1,28 @@
"use client"; "use client";
import React, { FC, useState } from "react"; import React from "react";
import { z } from "zod"; import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/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 Link from "next/link";
import { useForm } from "react-hook-form"; 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 SignInForm = ({}) => {
const session = useSession();
const t = useTranslations("signIn");
const router = useRouter();
const [showPassword, setShowPassword] = React.useState(false);
const [loader, setLoader] = React.useState<boolean>(false);
const [error, setError] = React.useState<string>("");
const signInFormScheme = z.object({ const signInFormScheme = z.object({
email: z.string().email("Неверный формат email"), email: z.string().email("Неверный формат email"),
password: z.string().min(8, "Пароль должен содержать минимум 8 символов"), password: z.string().min(8, "Пароль должен содержать минимум 8 символов"),
@ -23,46 +33,121 @@ const SignInForm = ({}) => {
const { const {
register, register,
handleSubmit, handleSubmit,
formState: { errors }, formState: { errors, isSubmitting },
} = useForm<FormFields>({ } = useForm<FormFields>({
resolver: zodResolver(signInFormScheme), 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 ( return (
<form className="mb-2 w-[360px] flex flex-col"> <form
className="mb-2 w-[360px] flex flex-col"
onSubmit={handleSubmit(onSubmit)}
>
<div className="flex flex-col gap-5"> <div className="flex flex-col gap-5">
<AuthInput <div>
type="email" <label className="text-[14px] leading-5 text-gray-700">Email</label>
label="Email" <div
placeholder="Введите email" className={`flex items-center${
error={errors.email?.message} errors.email?.message && "border border-red-400"
{...register("email", { required: true })} }`}
/> >
<AuthInput <input
isPassword placeholder="Email"
label="Пароль" className="w-full text-[16px] leading-6 text-gray-900 px-[10px] py-[14px] border border-gray-300 rounded-lg shadow-sm bg-white"
placeholder="Введите пароль" type="text"
error={errors.password?.message} {...register("email", { required: true })}
{...register("password", { required: true })} />
/> </div>
{errors?.email?.message && (
<p className="flex items-center justify-between text-[14px] font-normal leading-5 text-red-500">
{errors.email.message} <Image src={alert} alt="Alert Icon" />
</p>
)}
</div>
<div>
<label className="text-[14px] leading-5 text-gray-700">
{t("password")}
</label>
<div
className={`flex items-center border border-gray-300 rounded-lg shadow-sm bg-white${
errors?.password?.message && "border border-red-400"
}`}
>
<input
placeholder={t("password")}
className="w-full text-[16px] leading-6 text-gray-900 px-[10px] py-[14px]"
type={showPassword ? "text" : "password"}
{...register("password", { required: true })}
/>
<button
onClick={() => setShowPassword((prev) => !prev)}
type="button"
className="pr-2"
>
<Image src={showPassword ? eye_on : eye_off} alt="Eye Icon" />
</button>
</div>
{errors?.password?.message && (
<p className="flex items-center justify-between text-[14px] font-normal leading-5 text-red-500">
{errors.password.message} <Image src={alert} alt="Alert Icon" />
</p>
)}
</div>
{errors.root && ( {errors.root && (
<div className="text-red-500">{errors.root.message}</div> <div className="text-red-500">{errors.root.message}</div>
)} )}
<button <div className="flex flex-col mt-[36px] gap-4">
type="submit" <button
className="p-4 h-[50px] w-full font-bold leading-6 text-white bg-light-blue rounded-md" className="p-4 h-[50px] w-full font-bold leading-6 text-white bg-light-blue rounded-md"
// disabled={isLoading} type="submit"
> >
{/* {isLoading ? <Loader /> : "Войти"} */} {loader ? <Loader /> : t("loginBtn")}
Войти </button>
</button> </div>
<div className="text-blue font-semibold pt-2 cursor-pointer"> <div className="text-blue font-semibold pt-2 cursor-pointer">
<Link <Link
href="/forgot-password-email" href="/forgot-password-email"
className="text-blue font-semibold pt-2 cursor-pointer " className="text-blue font-semibold pt-2 cursor-pointer "
> >
Забыли пароль? {t("passwordForget")}
</Link> </Link>
</div> </div>
</div> </div>