Add sign in function
This commit is contained in:
parent
38b869084d
commit
eb81d45008
25
lib/next-auth.d.ts
vendored
Normal file
25
lib/next-auth.d.ts
vendored
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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": "Каттоо"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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": "Зарегистрируйтесь"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
6
src/app/api/auth/[...nextauth]/route.ts
Normal file
6
src/app/api/auth/[...nextauth]/route.ts
Normal 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 };
|
111
src/shared/config/authConfig.ts
Normal file
111
src/shared/config/authConfig.ts
Normal 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;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
10
src/shared/types/token-type.ts
Normal file
10
src/shared/types/token-type.ts
Normal 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;
|
||||||
|
}
|
@ -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) {
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user