Add change password layout
This commit is contained in:
parent
e3b73d38c0
commit
813a0bbc77
166
src/widgets/forms/ProfileForm/ChangePassword/ChangePassword.tsx
Normal file
166
src/widgets/forms/ProfileForm/ChangePassword/ChangePassword.tsx
Normal 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;
|
@ -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;
|
@ -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 |
@ -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 |
@ -9,6 +9,11 @@ import { useSession } from "next-auth/react";
|
|||||||
import { useRouter } from "@/shared/config/navigation";
|
import { useRouter } from "@/shared/config/navigation";
|
||||||
import Loader from "@/shared/ui/Loader/Loader";
|
import Loader from "@/shared/ui/Loader/Loader";
|
||||||
import LogoutButton from "@/features/LogoutButton";
|
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 {
|
interface IProfileFormProps {
|
||||||
id: number;
|
id: number;
|
||||||
@ -42,14 +47,22 @@ const ProfileForm: React.FC<IProfileFormProps> = ({
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateProfile: React.MouseEventHandler<HTMLFormElement> = async (e) => {
|
const updateProfileScheme = z.object({
|
||||||
e.preventDefault();
|
firstName: z.string().min(2, "Минимум 2 символа"),
|
||||||
|
lastName: z.string().min(2, "Минимум 2 символа"),
|
||||||
|
});
|
||||||
|
|
||||||
const data = {
|
type FormFields = z.infer<typeof updateProfileScheme>;
|
||||||
first_name: firstName,
|
|
||||||
last_name: lastName,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<FormFields>({
|
||||||
|
resolver: zodResolver(updateProfileScheme),
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = async (data: FormFields) => {
|
||||||
const Authorization = `Bearer ${session.data?.access_token}`;
|
const Authorization = `Bearer ${session.data?.access_token}`;
|
||||||
const config = {
|
const config = {
|
||||||
headers: {
|
headers: {
|
||||||
@ -59,11 +72,7 @@ const ProfileForm: React.FC<IProfileFormProps> = ({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
setLoader(true);
|
setLoader(true);
|
||||||
const res = await apiInstance.patch(
|
const res = await apiInstance.patch("/users/profile/", data, config);
|
||||||
"/users/profile/update/",
|
|
||||||
data,
|
|
||||||
config
|
|
||||||
);
|
|
||||||
if ([200, 201].includes(res.status)) {
|
if ([200, 201].includes(res.status)) {
|
||||||
router.refresh();
|
router.refresh();
|
||||||
}
|
}
|
||||||
@ -83,14 +92,20 @@ const ProfileForm: React.FC<IProfileFormProps> = ({
|
|||||||
};
|
};
|
||||||
return (
|
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]">
|
<div className="flex flex-col gap-[10px]">
|
||||||
<label className="text-[18px] leading-6 text-gray-500">Имя</label>
|
<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
|
<input
|
||||||
value={firstName}
|
value={firstName}
|
||||||
onChange={(e) => setFirstName(e.target.value)}
|
{...register("firstName", { required: true })}
|
||||||
disabled={editFirstName}
|
|
||||||
type="text"
|
type="text"
|
||||||
className="w-full max-w-[957px] p-4 border border-white rounded-sm text-[18px] leading-6 text-black"
|
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" />
|
<Image className="w-[22px] h-[22px]" src={pen} alt="Pen Icon" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<div className="flex flex-col gap-[10px]">
|
<div className="flex flex-col gap-[10px]">
|
||||||
<label className="text-[18px] leading-6 text-gray-500">Фамилия</label>
|
<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
|
<input
|
||||||
value={lastName}
|
value={lastName}
|
||||||
onChange={(e) => setLastName(e.target.value)}
|
{...register("lastName", { required: true })}
|
||||||
disabled={editLastName}
|
|
||||||
type="text"
|
type="text"
|
||||||
className="w-full max-w-[957px] p-4 border border-white rounded-sm text-[18px] leading-6 text-black"
|
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]" />
|
<Image src={pen} alt="Pen Icon" className="w-[22px] h-[22px]" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<div className="flex flex-col gap-[10px]">
|
<div className="flex flex-col gap-[10px]">
|
||||||
@ -149,20 +177,20 @@ const ProfileForm: React.FC<IProfileFormProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* {openPopup && <ChangePassword closeWindow={setOpenPopup} />} */}
|
{openPopup && <ChangePassword closeWindow={setOpenPopup} />}
|
||||||
|
|
||||||
{error ? <p>{error}</p> : null}
|
{error ? <p>{error}</p> : null}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
disabled={thereAreChanges()}
|
disabled={thereAreChanges()}
|
||||||
type="submit"
|
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"
|
thereAreChanges() ? "" : "bg-light-blue cursor-pointer"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{loader ? <Loader /> : "Сохранить изменения"}
|
{loader ? <Loader /> : "Сохранить изменения"}
|
||||||
</button>
|
</button>
|
||||||
<LogoutButton className="sm:block " />
|
<LogoutButton className="sm:block lg:flex-none" />
|
||||||
</form>
|
</form>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
7
src/widgets/forms/ProfileForm/icons/alert-circle.svg
Normal file
7
src/widgets/forms/ProfileForm/icons/alert-circle.svg
Normal 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 |
Loading…
Reference in New Issue
Block a user