From 76af01ee8b9066d3fddc4d3d8582819c93e984f2 Mon Sep 17 00:00:00 2001 From: ariari04 Date: Mon, 26 Aug 2024 17:07:43 +0600 Subject: [PATCH] Add forgot password page --- .../[locale]/sign-in/forgot-password/page.tsx | 11 ++ src/features/AuthInput/AuthInput.scss | 53 ++++++++ src/features/{ => AuthInput}/AuthInput.tsx | 26 ++-- src/shared/ui/Loader/Loader.css | 13 -- src/shared/ui/Loader/Loader.scss | 19 +++ src/shared/ui/Loader/Loader.tsx | 7 +- .../ForgotPasswordForm/ForgotPasswordForm.tsx | 16 +++ .../confirm-code/ConfirmCode.tsx | 125 ++++++++++++++++++ .../confirm-code/icons/key.svg | 14 ++ .../send-email/SendEmail.tsx | 117 ++++++++++++++++ .../send-email/icons/mail.svg | 14 ++ src/widgets/forms/SignInForm.tsx | 28 +--- 12 files changed, 384 insertions(+), 59 deletions(-) create mode 100644 src/app/[locale]/sign-in/forgot-password/page.tsx create mode 100644 src/features/AuthInput/AuthInput.scss rename src/features/{ => AuthInput}/AuthInput.tsx (56%) delete mode 100644 src/shared/ui/Loader/Loader.css create mode 100644 src/shared/ui/Loader/Loader.scss create mode 100644 src/widgets/forms/ForgotPasswordForm/ForgotPasswordForm.tsx create mode 100644 src/widgets/forms/ForgotPasswordForm/confirm-code/ConfirmCode.tsx create mode 100644 src/widgets/forms/ForgotPasswordForm/confirm-code/icons/key.svg create mode 100644 src/widgets/forms/ForgotPasswordForm/send-email/SendEmail.tsx create mode 100644 src/widgets/forms/ForgotPasswordForm/send-email/icons/mail.svg diff --git a/src/app/[locale]/sign-in/forgot-password/page.tsx b/src/app/[locale]/sign-in/forgot-password/page.tsx new file mode 100644 index 0000000..8b4782a --- /dev/null +++ b/src/app/[locale]/sign-in/forgot-password/page.tsx @@ -0,0 +1,11 @@ +import ForgotPasswordForm from "../../../../widgets/forms/ForgotPasswordForm/ForgotPasswordForm"; + +const ForgotPassword = () => { + return ( +
+ +
+ ); +}; + +export default ForgotPassword; diff --git a/src/features/AuthInput/AuthInput.scss b/src/features/AuthInput/AuthInput.scss new file mode 100644 index 0000000..0ef5c86 --- /dev/null +++ b/src/features/AuthInput/AuthInput.scss @@ -0,0 +1,53 @@ +.auth-input { + display: flex; + flex-direction: column; + gap: 6px; + + label { + font-size: 14px; + font-weight: 400; + line-height: 20px; + color: #344054; + } + + &__field, + &__field-with-error { + padding: 10px 14px; + display: flex; + align-items: center; + border: 1px solid rgb(208, 213, 221); + border-radius: 8px; + box-shadow: 0px 1px 2px 0px rgba(16, 24, 40, 0.05); + background: rgb(255, 255, 255); + + input { + width: 100%; + + font-size: 16px; + font-weight: 400; + line-height: 24px; + color: rgb(16, 24, 40); + + ::placeholder { + font-size: 16px; + font-weight: 400; + line-height: 24px; + color: #667085; + } + } + } + + &__field-with-error { + border: 1px solid rgb(240, 68, 56); + } + + p { + display: flex; + align-items: center; + justify-content: space-between; + font-size: 14px; + font-weight: 400; + line-height: 20px; + color: rgb(240, 68, 56); + } +} diff --git a/src/features/AuthInput.tsx b/src/features/AuthInput/AuthInput.tsx similarity index 56% rename from src/features/AuthInput.tsx rename to src/features/AuthInput/AuthInput.tsx index 9dca71d..2875cc0 100644 --- a/src/features/AuthInput.tsx +++ b/src/features/AuthInput/AuthInput.tsx @@ -1,15 +1,16 @@ "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 "./AuthInput.scss"; +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; + error: string; } const AuthInput: React.FC = ({ @@ -22,17 +23,12 @@ const AuthInput: React.FC = ({ }: IAuthInputProps) => { const [isOpen, setIsOpen] = useState(false); return ( -
- -
+
+ +
{isPassword && ( @@ -41,11 +37,11 @@ const AuthInput: React.FC = ({ )}
- {error && ( -

+ {error ? ( +

{error} Alert Icon

- )} + ) : null}
); }; diff --git a/src/shared/ui/Loader/Loader.css b/src/shared/ui/Loader/Loader.css deleted file mode 100644 index 826be78..0000000 --- a/src/shared/ui/Loader/Loader.css +++ /dev/null @@ -1,13 +0,0 @@ -@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.scss b/src/shared/ui/Loader/Loader.scss new file mode 100644 index 0000000..562a4c2 --- /dev/null +++ b/src/shared/ui/Loader/Loader.scss @@ -0,0 +1,19 @@ +.loader { + width: 24px; + height: 24px; + border-radius: 50%; + display: inline-block; + border-top: 3px solid #fff; + border-right: 3px solid transparent; + box-sizing: border-box; + animation: rotation 1s linear infinite; +} + +@keyframes rotation { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/src/shared/ui/Loader/Loader.tsx b/src/shared/ui/Loader/Loader.tsx index eedaca6..a1aaa9e 100644 --- a/src/shared/ui/Loader/Loader.tsx +++ b/src/shared/ui/Loader/Loader.tsx @@ -1,4 +1,4 @@ -import "./Loader.css"; +import "./Loader.scss"; interface ILoader { color?: string; @@ -6,10 +6,7 @@ interface ILoader { const Loader: React.FC = ({ color }: ILoader) => { return ( - + ); }; diff --git a/src/widgets/forms/ForgotPasswordForm/ForgotPasswordForm.tsx b/src/widgets/forms/ForgotPasswordForm/ForgotPasswordForm.tsx new file mode 100644 index 0000000..246de82 --- /dev/null +++ b/src/widgets/forms/ForgotPasswordForm/ForgotPasswordForm.tsx @@ -0,0 +1,16 @@ +"use client"; + +import { useState } from "react"; +import ConfirmCode from "./confirm-code/ConfirmCode"; +import SendEmail from "./send-email/SendEmail"; + +const ForgotPasswordForm = () => { + const [changeForm, setChangeForm] = useState(false); + return changeForm ? ( + + ) : ( + + ); +}; + +export default ForgotPasswordForm; diff --git a/src/widgets/forms/ForgotPasswordForm/confirm-code/ConfirmCode.tsx b/src/widgets/forms/ForgotPasswordForm/confirm-code/ConfirmCode.tsx new file mode 100644 index 0000000..1831d89 --- /dev/null +++ b/src/widgets/forms/ForgotPasswordForm/confirm-code/ConfirmCode.tsx @@ -0,0 +1,125 @@ +import Image from "next/image"; +import key from "./icons/key.svg"; +import { useState } from "react"; +import { apiInstance } from "@/shared/config/apiConfig"; +import { ITokens } from "@/shared/types/token-type"; +import { useRouter } from "@/shared/config/navigation"; +import { AxiosError } from "axios"; +import Loader from "@/shared/ui/Loader/Loader"; +import { z } from "zod"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import alert from "../../icons/alert-circle.svg"; + +interface IConfirmCodeProps { + setChangeForm: (boolean: boolean) => void; +} + +const ConfirmCode: React.FC = ({ + setChangeForm, +}: IConfirmCodeProps) => { + const [error, setError] = useState(""); + const [loader, setLoader] = useState(false); + const router = useRouter(); + + const confirmCodeScheme = z.object({ + code: z.string().min(6, "Код подтверждения состоит из 6 цифр"), + }); + type FormFields = z.infer; + + const { + register, + handleSubmit, + formState: { errors, isSubmitting }, + } = useForm({ + resolver: zodResolver(confirmCodeScheme), + }); + const onSubmit = async (data: FormFields) => { + try { + setError(""); + setLoader(true); + const res = await apiInstance.post( + "/auth/confirm_password/", + data + ); + console.log(res.data); + if (res.status === 200 || res.status === 201) { + localStorage.setItem("transitional", JSON.stringify(res.data)); + router.push("/sign-in/reset-code"); + } + setLoader(false); + } catch (error: unknown) { + if (error instanceof AxiosError) { + if (error.response?.status === 400) { + setError("Неверный код"); + } else { + setError("Ошибка на стороне сервера"); + } + } else { + setError("Произошла непредвиденная ошибка"); + } + setLoader(false); + } + }; + return ( +
+
+ Key Icon +
+
+

+ Введите код +

+

+ Введите код для сброса и восстановления пароля +

+
+ +
+
+ +
+ +
+ {errors?.code?.message && ( +

+ {errors.code.message} Alert Icon +

+ )} +
+ + {error &&
{error}
} + {errors.root && ( +
{errors.root.message}
+ )} +
+ +
+ ); +}; + +export default ConfirmCode; diff --git a/src/widgets/forms/ForgotPasswordForm/confirm-code/icons/key.svg b/src/widgets/forms/ForgotPasswordForm/confirm-code/icons/key.svg new file mode 100644 index 0000000..88beab1 --- /dev/null +++ b/src/widgets/forms/ForgotPasswordForm/confirm-code/icons/key.svg @@ -0,0 +1,14 @@ + + + Created with Pixso. + + + + + + + + + + + diff --git a/src/widgets/forms/ForgotPasswordForm/send-email/SendEmail.tsx b/src/widgets/forms/ForgotPasswordForm/send-email/SendEmail.tsx new file mode 100644 index 0000000..1ddea63 --- /dev/null +++ b/src/widgets/forms/ForgotPasswordForm/send-email/SendEmail.tsx @@ -0,0 +1,117 @@ +import Image from "next/image"; +import mail from "./icons/mail.svg"; +import { useState } from "react"; +import { apiInstance } from "@/shared/config/apiConfig"; +import { AxiosError } from "axios"; +import Loader from "@/shared/ui/Loader/Loader"; +import { z } from "zod"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import alert from "../../icons/alert-circle.svg"; + +interface ISendEmailProps { + setChangeForm: (boolean: boolean) => void; +} + +const SendEmail: React.FC = ({ + setChangeForm, +}: ISendEmailProps) => { + const [error, setError] = useState(""); + const [loader, setLoader] = useState(false); + + const sendEmailFormScheme = z.object({ + email: z.string().email("Неверный формат email"), + }); + type FormFields = z.infer; + + const { + register, + handleSubmit, + formState: { errors, isSubmitting }, + } = useForm({ + resolver: zodResolver(sendEmailFormScheme), + }); + + const onSubmit = async (data: FormFields) => { + try { + setError(""); + setLoader(true); + const res = await apiInstance.post("/auth/send_mailcode/", data); + + if ([200, 201].includes(res.status)) { + console.log(res.data); + setChangeForm(true); + } + } catch (error: unknown) { + if (error instanceof AxiosError) { + console.log(error); + if (error.response?.status === 400) { + setError("Пользователь с таким email не найден"); + } else { + setError("Ошибка на стороне сервера"); + } + } else { + setError("Произошла непредвиденная ошибка"); + } + } finally { + setLoader(false); + } + }; + return ( +
+
+ Key Icon +
+
+

+ Введите email +

+

+ Введите email и мы отправим код для восстановления пароля +

+
+ +
+
+ +
+ +
+ {errors?.email?.message && ( +

+ {errors.email.message} Alert Icon +

+ )} +
+ +
+ {error &&
{error}
} + {errors.root &&
{errors.root.message}
} + +
+ ); +}; + +export default SendEmail; diff --git a/src/widgets/forms/ForgotPasswordForm/send-email/icons/mail.svg b/src/widgets/forms/ForgotPasswordForm/send-email/icons/mail.svg new file mode 100644 index 0000000..dc055d2 --- /dev/null +++ b/src/widgets/forms/ForgotPasswordForm/send-email/icons/mail.svg @@ -0,0 +1,14 @@ + + + Created with Pixso. + + + + + + + + + + + diff --git a/src/widgets/forms/SignInForm.tsx b/src/widgets/forms/SignInForm.tsx index 7206182..bd6b365 100644 --- a/src/widgets/forms/SignInForm.tsx +++ b/src/widgets/forms/SignInForm.tsx @@ -2,18 +2,15 @@ import React from "react"; import { z } from "zod"; import { zodResolver } from "@hookform/resolvers/zod"; -import Link from "next/link"; import { useForm } from "react-hook-form"; import { useTranslations } from "next-intl"; -import { useRouter } from "@/shared/config/navigation"; +import { Link, 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"; const SignInForm = ({}) => { const session = useSession(); @@ -38,27 +35,6 @@ const SignInForm = ({}) => { resolver: zodResolver(signInFormScheme), }); - // 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", { @@ -144,7 +120,7 @@ const SignInForm = ({}) => {
{t("passwordForget")}