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",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
@ -70,5 +70,18 @@
|
||||
"resendIn": "Кодду кайра жөнөтүү",
|
||||
"resendBtn": "Кодду кайра жөнөтүү",
|
||||
"error": "Ката кетти"
|
||||
},
|
||||
"signIn": {
|
||||
"login": "Аккаунтка кириңиз",
|
||||
"info": "Маалыматты киргизиңиз",
|
||||
"emailInp": "Emailди териңиз",
|
||||
"password": "Сырсөз",
|
||||
"passwordInp": "Сырсөздү териңиз",
|
||||
"error": "Email же сырсөз дүрткүчтү киргизүү",
|
||||
"passwordForget": "Сырсөздү унуттуңузбу?",
|
||||
"loginBtn": "Кирүү",
|
||||
"loginGoogle": "Google аркылуу кируңуз",
|
||||
"noAccount": "Аккаунт жок?",
|
||||
"register": "Каттоо"
|
||||
}
|
||||
}
|
||||
|
@ -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": "Зарегистрируйтесь"
|
||||
}
|
||||
}
|
||||
|
@ -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 (
|
||||
<Container>
|
||||
<div className="h-full min-h-[800px] w-full flex justify-center">
|
||||
@ -14,19 +17,19 @@ const SignIn = () => {
|
||||
</div>
|
||||
<div className="mb-2 flex flex-col items-center gap-2 text-center">
|
||||
<h2 className="text-[24px] font-bold leading-8 text-gray-900">
|
||||
Войдите в аккаунт
|
||||
{t("login")}
|
||||
</h2>
|
||||
<p className="text-gray-500">Пожалуйста, введите свои данные</p>
|
||||
<p className="text-gray-500">{t("info")}</p>
|
||||
</div>
|
||||
|
||||
<SignInForm />
|
||||
<p className="text-[14px] font-semibold leading-5 text-gray-500">
|
||||
Еще нет аккаунта?{" "}
|
||||
{t("noAccount")}{" "}
|
||||
<Link
|
||||
className="text-[14px] font-semibold leading-5 text-light-blue"
|
||||
href="/sign-up"
|
||||
>
|
||||
Зарегистрируйтесь
|
||||
{t("register")}
|
||||
</Link>
|
||||
</p>
|
||||
</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();
|
||||
|
||||
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) {
|
||||
|
@ -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<boolean>(false);
|
||||
const [error, setError] = React.useState<string>("");
|
||||
|
||||
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<FormFields>({
|
||||
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 (
|
||||
<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">
|
||||
<AuthInput
|
||||
type="email"
|
||||
label="Email"
|
||||
placeholder="Введите email"
|
||||
error={errors.email?.message}
|
||||
<div>
|
||||
<label className="text-[14px] leading-5 text-gray-700">Email</label>
|
||||
<div
|
||||
className={`flex items-center${
|
||||
errors.email?.message && "border border-red-400"
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
placeholder="Email"
|
||||
className="w-full text-[16px] leading-6 text-gray-900 px-[10px] py-[14px] border border-gray-300 rounded-lg shadow-sm bg-white"
|
||||
type="text"
|
||||
{...register("email", { required: true })}
|
||||
/>
|
||||
<AuthInput
|
||||
isPassword
|
||||
label="Пароль"
|
||||
placeholder="Введите пароль"
|
||||
error={errors.password?.message}
|
||||
</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 && (
|
||||
<div className="text-red-500">{errors.root.message}</div>
|
||||
)}
|
||||
<div className="flex flex-col mt-[36px] gap-4">
|
||||
<button
|
||||
type="submit"
|
||||
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>
|
||||
</div>
|
||||
<div className="text-blue font-semibold pt-2 cursor-pointer">
|
||||
<Link
|
||||
href="/forgot-password-email"
|
||||
className="text-blue font-semibold pt-2 cursor-pointer "
|
||||
>
|
||||
Забыли пароль?
|
||||
{t("passwordForget")}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user