Add change password layout

This commit is contained in:
ariari04 2024-09-24 23:42:42 +06:00
parent e3b73d38c0
commit 813a0bbc77
6 changed files with 302 additions and 21 deletions

View File

@ -0,0 +1,166 @@
import Link from "next/link";
import { useState } from "react";
import { apiInstance } from "@/shared/config/apiConfig";
import { useSession } from "next-auth/react";
import { AxiosError } from "axios";
import Loader from "@/shared/ui/Loader/Loader";
import ChangePasswordInput from "./ChangePasswordInput/ChangePasswordInput";
interface IChangePasswordProps {
closeWindow: (bool: boolean) => void;
}
const ChangePassword: React.FC<IChangePasswordProps> = ({
closeWindow,
}: IChangePasswordProps) => {
const session = useSession();
const [oldPassword, setOldPassword] = useState<string>("");
const [newPassword, setNewPassword] = useState<string>("");
const [confirmNewPassword, setConfirmNewPassword] = useState<string>("");
const [warningOldPassword, setWarningOldPassword] = useState<string>("");
const [warningNewPassword, setWarningNewPassword] = useState<string>("");
const [warningConfirmNewPassword, setWarningConfirmNewPassword] =
useState<string>("");
const [error, setError] = useState<string>("");
const [loader, setLoader] = useState<boolean>(false);
const [success, setSuccess] = useState<boolean>(false);
const changePass = async () => {
if (!oldPassword.trim()) {
setError("");
setWarningNewPassword("");
setWarningConfirmNewPassword("");
setWarningOldPassword("Пожалуйста введите старый пароль");
return;
}
if (!newPassword.trim()) {
setError("");
setWarningConfirmNewPassword("");
setWarningOldPassword("");
setWarningNewPassword("Пожалуйста введите новый пароль");
return;
}
if (!confirmNewPassword.trim()) {
setError("");
setWarningNewPassword("");
setWarningOldPassword("");
setWarningConfirmNewPassword("Пожалуйста потвердите новый пароль");
return;
}
if (confirmNewPassword !== newPassword) {
setError("");
setWarningNewPassword("");
setWarningOldPassword("");
setWarningConfirmNewPassword("Пароли отличаются");
return;
}
const data = {
old_password: oldPassword,
new_password1: newPassword,
new_password2: confirmNewPassword,
};
const Authorization = `Bearer ${session.data?.access_token}`;
const config = {
headers: {
Authorization,
},
};
try {
setError("");
setWarningNewPassword("");
setWarningOldPassword("");
setWarningConfirmNewPassword("");
setLoader(true);
const res = await apiInstance.patch(
"/users/change_password/",
data,
config
);
if ([200, 201].includes(res.status)) return setSuccess(true);
} catch (error: unknown) {
if (error instanceof AxiosError) {
if (error.response?.status === 400) {
setError("Некорректный старый пароль или недопустимый новый пароль");
}
} else {
setError("Произошла непредвиденная ошибка");
}
} finally {
setLoader(false);
}
};
return (
<div
onClick={() => closeWindow(false)}
className="w-full h-full fixed top-0 left-0 z-10 bg-gray-950 flex items-center justify-center"
>
<div
onClick={(e) => e.stopPropagation()}
className="flex w-full max-w-[400px] p-6 flex-col gap-4 rounded-md bg-white"
>
<h4 className="mb-2 text-[18px] font-bold leading-7 text-gray-900">
Изменить пароль
</h4>
<ChangePasswordInput
onChange={(e) => setOldPassword(e.target.value)}
value={oldPassword}
placeholder="Введите старый пароль"
label="Старый пароль"
error={warningOldPassword}
/>
<Link
href="/sign-in/forgot-password"
className="self-end leading-8 text-blue"
>
Забыли пароль?
</Link>
<ChangePasswordInput
onChange={(e) => setNewPassword(e.target.value)}
value={newPassword}
placeholder="Введите новый пароль"
label="Новый пароль"
error={warningNewPassword}
/>
<ChangePasswordInput
onChange={(e) => setConfirmNewPassword(e.target.value)}
value={confirmNewPassword}
placeholder="Повторите новый пароль"
label="Потвердить новый пароль"
error={warningConfirmNewPassword}
/>
{error ? <p className="text-red-500">{error}</p> : null}
{success ? (
<p className="text-light-blue">Вы успешно поменяли пароль!</p>
) : null}
<div className="mt-9 flex flex-col gap-3">
<button
type="button"
onClick={changePass}
className="py-4 px-4 rounded-sm font-bold leading-6 shadow-sm border border-blue bg-blue text-white"
>
{loader ? <Loader /> : "Сохранить"}
</button>
<button
className="py-4 px-4 rounded-sm font-bold leading-6 shadow-sm border border-gray-400 bg-slate-200 text-gray-500"
type="button"
onClick={() => closeWindow(false)}
>
Отмена
</button>
</div>
</div>
</div>
);
};
export default ChangePassword;

View File

@ -0,0 +1,50 @@
"use client";
import Image from "next/image";
import { useState } from "react";
import eye_off from "./icons/eye-off.svg";
import eye_on from "./icons/eye-on.svg";
interface IChangePasswordInputProps
extends React.InputHTMLAttributes<HTMLInputElement> {
label: string;
error: string;
}
const ChangePasswordInput: React.FC<IChangePasswordInputProps> = ({
label,
error,
placeholder,
name,
onChange,
value,
}: IChangePasswordInputProps) => {
const [isOpen, setIsOpen] = useState<boolean>(false);
return (
<div className="flex flex-col gap-2">
<label className="font-bold leading-5 text-gray-700">{label}</label>
<div
className={`py-[10px] px-[14px] flex items-center justify-between border border-gray-300 rounded-md shadow-sm bg-gray-200${
error ? "-with-error" : ""
}`}
>
<input
onChange={onChange}
value={value}
name={name}
placeholder={placeholder}
type={isOpen ? "text" : "password"}
className="w-full text-[14px] placeholder:leading-6 bg-gray-500"
/>
<button onClick={() => setIsOpen((prev) => !prev)} type="button">
<Image src={isOpen ? eye_on : eye_off} alt="Eye Icon" />
</button>
</div>
{error ? (
<p className="text-[14px] leading-5 text-red-500">{error}</p>
) : null}
</div>
);
};
export default ChangePasswordInput;

View File

@ -0,0 +1,15 @@
<svg width="24.000000" height="24.000000" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<desc>
Created with Pixso.
</desc>
<defs>
<clipPath id="clip1668_50526">
<rect id="eye-off" width="24.000000" height="24.000000" fill="white" fill-opacity="0"/>
</clipPath>
</defs>
<rect id="eye-off" width="24.000000" height="24.000000" fill="#FFFFFF" fill-opacity="0"/>
<g clip-path="url(#clip1668_50526)">
<path id="Vector" d="M17.9404 17.9404C16.2305 19.2432 14.1494 19.9648 12 20C5 20 1 12 1 12C2.24414 9.68164 3.96875 7.65625 6.05957 6.05957M9.90039 4.24023C10.5879 4.0791 11.293 3.99805 12 4C19 4 23 12 23 12C22.3926 13.1357 21.6689 14.2051 20.8398 15.1904M14.1201 14.1201C13.8457 14.415 13.5137 14.6514 13.1465 14.8154C12.7783 14.9795 12.3809 15.0674 11.9785 15.0742C11.5752 15.0811 11.1748 15.0078 10.8018 14.8564C10.4277 14.7061 10.0889 14.4814 9.80371 14.1963C9.51855 13.9111 9.29395 13.5723 9.14355 13.1982C8.99219 12.8252 8.91895 12.4248 8.92578 12.0215C8.93262 11.6191 9.02051 11.2217 9.18457 10.8535C9.34863 10.4863 9.58496 10.1543 9.87988 9.87988" stroke="#979797" stroke-opacity="1.000000" stroke-width="2.000000" stroke-linejoin="round"/>
<path id="Vector" d="M1 1L23 23" stroke="#979797" stroke-opacity="1.000000" stroke-width="2.000000" stroke-linejoin="round" stroke-linecap="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,15 @@
<svg width="24.000000" height="24.000000" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<desc>
Created with Pixso.
</desc>
<defs>
<clipPath id="clip1668_50161">
<rect id="eye" width="24.000000" height="24.000000" fill="white" fill-opacity="0"/>
</clipPath>
</defs>
<rect id="eye" width="24.000000" height="24.000000" fill="#FFFFFF" fill-opacity="0"/>
<g clip-path="url(#clip1668_50161)">
<path id="Vector" d="M12 4C19 4 23 12 23 12C23 12 19 20 12 20C5 20 1 12 1 12C1 12 5 4 12 4Z" stroke="#979797" stroke-opacity="1.000000" stroke-width="2.000000" stroke-linejoin="round"/>
<path id="Vector" d="M12 15C10.3428 15 9 13.6572 9 12C9 10.3428 10.3428 9 12 9C13.6572 9 15 10.3428 15 12C15 13.6572 13.6572 15 12 15Z" stroke="#979797" stroke-opacity="1.000000" stroke-width="2.000000" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 901 B

View File

@ -9,6 +9,11 @@ import { useSession } from "next-auth/react";
import { useRouter } from "@/shared/config/navigation";
import Loader from "@/shared/ui/Loader/Loader";
import LogoutButton from "@/features/LogoutButton";
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import alert from "./icons/alert-circle.svg";
import ChangePassword from "./ChangePassword/ChangePassword";
interface IProfileFormProps {
id: number;
@ -42,14 +47,22 @@ const ProfileForm: React.FC<IProfileFormProps> = ({
return true;
};
const updateProfile: React.MouseEventHandler<HTMLFormElement> = async (e) => {
e.preventDefault();
const updateProfileScheme = z.object({
firstName: z.string().min(2, "Минимум 2 символа"),
lastName: z.string().min(2, "Минимум 2 символа"),
});
const data = {
first_name: firstName,
last_name: lastName,
};
type FormFields = z.infer<typeof updateProfileScheme>;
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormFields>({
resolver: zodResolver(updateProfileScheme),
});
const onSubmit = async (data: FormFields) => {
const Authorization = `Bearer ${session.data?.access_token}`;
const config = {
headers: {
@ -59,11 +72,7 @@ const ProfileForm: React.FC<IProfileFormProps> = ({
try {
setLoader(true);
const res = await apiInstance.patch(
"/users/profile/update/",
data,
config
);
const res = await apiInstance.patch("/users/profile/", data, config);
if ([200, 201].includes(res.status)) {
router.refresh();
}
@ -83,14 +92,20 @@ const ProfileForm: React.FC<IProfileFormProps> = ({
};
return (
<>
<form onSubmit={updateProfile} className="flex flex-col gap-[25px]">
<form
onSubmit={handleSubmit(onSubmit)}
className="flex flex-col gap-[25px]"
>
<div className="flex flex-col gap-[10px]">
<label className="text-[18px] leading-6 text-gray-500">Имя</label>
<div className="flex items-center gap-4">
<div
className={`flex items-center gap-4 ${
errors.firstName?.message && "border border-red-400"
}`}
>
<input
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
disabled={editFirstName}
{...register("firstName", { required: true })}
type="text"
className="w-full max-w-[957px] p-4 border border-white rounded-sm text-[18px] leading-6 text-black"
/>
@ -101,15 +116,23 @@ const ProfileForm: React.FC<IProfileFormProps> = ({
<Image className="w-[22px] h-[22px]" src={pen} alt="Pen Icon" />
</button>
</div>
{errors?.firstName?.message && (
<p className="flex items-center justify-between text-[14px] font-normal leading-5 text-red-500">
{errors.firstName.message} <Image src={alert} alt="Alert Icon" />
</p>
)}
</div>
<div className="flex flex-col gap-[10px]">
<label className="text-[18px] leading-6 text-gray-500">Фамилия</label>
<div className="flex items-center gap-4">
<div
className={`flex items-center gap-4 ${
errors.firstName?.message && "border border-red-400"
}`}
>
<input
value={lastName}
onChange={(e) => setLastName(e.target.value)}
disabled={editLastName}
{...register("lastName", { required: true })}
type="text"
className="w-full max-w-[957px] p-4 border border-white rounded-sm text-[18px] leading-6 text-black"
/>
@ -120,6 +143,11 @@ const ProfileForm: React.FC<IProfileFormProps> = ({
<Image src={pen} alt="Pen Icon" className="w-[22px] h-[22px]" />
</button>
</div>
{errors?.lastName?.message && (
<p className="flex items-center justify-between text-[14px] font-normal leading-5 text-red-500">
{errors.lastName.message} <Image src={alert} alt="Alert Icon" />
</p>
)}
</div>
<div className="flex flex-col gap-[10px]">
@ -149,20 +177,20 @@ const ProfileForm: React.FC<IProfileFormProps> = ({
</div>
</div>
{/* {openPopup && <ChangePassword closeWindow={setOpenPopup} />} */}
{openPopup && <ChangePassword closeWindow={setOpenPopup} />}
{error ? <p>{error}</p> : null}
<button
disabled={thereAreChanges()}
type="submit"
className={`mt-[50px] w-fit p-[10px] rounded-sm bg-gray-400 cursor-auto font-bold leading-7 text-gray-200${
className={`mt-[50px] w-fit p-[10px] rounded-lg bg-gray-400 cursor-auto font-bold leading-7 text-gray-200${
thereAreChanges() ? "" : "bg-light-blue cursor-pointer"
}`}
>
{loader ? <Loader /> : "Сохранить изменения"}
</button>
<LogoutButton className="sm:block " />
<LogoutButton className="sm:block lg:flex-none" />
</form>
</>
);

View File

@ -0,0 +1,7 @@
<svg width="14.666992" height="14.666992" viewBox="0 0 14.667 14.667" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<desc>
Created with Pixso.
</desc>
<defs/>
<path id="Icon" d="M7.33398 14C3.65137 14 0.666992 11.0156 0.666992 7.33398C0.666992 3.65137 3.65137 0.666992 7.33398 0.666992C11.0156 0.666992 14 3.65137 14 7.33398C14 11.0156 11.0156 14 7.33398 14ZM7.33398 4.66699L7.33398 7.33398M7.33398 10L7.33984 10" stroke="#F04438" stroke-opacity="1.000000" stroke-width="1.333333" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 566 B