added change-image feature in profile, sign-up validating password and shows correct error, if user already exists

This commit is contained in:
Alibek 2024-02-29 20:45:09 +06:00
parent 6650a98503
commit 0a7513dd89
5 changed files with 398 additions and 27 deletions

View File

@ -11,11 +11,7 @@
object-fit: cover;
}
input[type="file"] {
display: none;
}
label {
&__change-btn {
position: absolute;
bottom: 0;
right: 0;
@ -34,4 +30,126 @@
height: 22px;
}
}
&__modal {
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.5);
}
&__wrapper {
position: relative;
max-width: 400px;
padding: 24px;
display: flex;
flex-direction: column;
align-items: center;
background-color: white;
border-radius: 15px;
box-shadow: 0px 16px 24px 0px rgba(0, 0, 0, 0.15);
}
&__text {
div {
display: flex;
align-items: center;
justify-content: space-between;
h4 {
font-size: 18px;
font-weight: 500;
line-height: 28px;
color: rgb(51, 65, 85);
}
button {
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid rgb(226, 232, 240);
border-radius: 50%;
}
}
p {
width: 90%;
font-size: 14px;
font-weight: 400;
line-height: 20px;
color: rgb(100, 116, 139);
}
}
&__user-img {
margin: 24px 0;
width: 130px;
height: 130px;
border-radius: 50%;
object-fit: cover;
}
input[type="file"] {
display: none;
}
&__btns {
display: flex;
align-items: center;
gap: 8px;
button,
label {
min-width: 110px;
padding: 8px 16px;
margin-top: 24px;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: 500;
line-height: 20px;
border-radius: 8px;
cursor: pointer;
}
}
&__blue-btn {
color: white;
background-color: rgb(72, 159, 225);
}
&__gray-btn {
color: rgb(51, 65, 85);
border: 1px solid rgb(226, 232, 240);
background: rgb(255, 255, 255);
}
&__message {
position: absolute;
bottom: 32px;
padding: 8px;
width: 302px;
display: flex;
align-items: center;
justify-content: space-between;
border-radius: 3px;
background-color: rgb(0, 0, 0);
font-size: 14px;
font-weight: 500;
line-height: 20px;
color: rgb(255, 255, 255);
button {
font-size: 0;
}
}
}

View File

@ -3,9 +3,14 @@
import Image from "next/image";
import "./ProfileAvatar.scss";
import pen from "./icons/pen.svg";
import close from "./icons/close.svg";
import close_white from "./icons/close-white.svg";
import { authInstanse } from "@/shared/config/apiConfig";
import { useRouter } from "next/navigation";
import { useSession } from "next-auth/react";
import { useState } from "react";
import { AxiosError } from "axios";
import Loader from "@/shared/ui/components/Loader/Loader";
interface IProfileAvatarProps {
img: string;
@ -14,29 +19,109 @@ interface IProfileAvatarProps {
const ProfileAvatar: React.FC<IProfileAvatarProps> = ({
img,
}: IProfileAvatarProps) => {
const [modal, setModal] = useState<boolean>(false);
const [display_image, setDisplayImage] = useState<File | string>(
img
);
const [isDeleting, setIsDeleting] = useState<boolean>(false);
const [message, setMessage] = useState<boolean>(false);
const def =
"https://api.kgroaduat.fishrungames.com/media/user_photo/default.webp";
const [success, setSuccess] = useState<string>("");
const [loader, setLoader] = useState<boolean>(false);
const [error, setError] = useState<string>("");
const session = useSession();
const router = useRouter();
const changeImage: React.ChangeEventHandler<
HTMLInputElement
> = async (e) => {
const formData = new FormData();
const handleChange: React.ChangeEventHandler<HTMLInputElement> = (
e
) => {
if (e.target.files) {
const image = Array.from(e.target.files);
formData.append("image", image[0]);
}
if (session.status === "unauthenticated") return;
try {
const res = await authInstanse(
session.data?.access_token as string
).patch("/users/update_image/", formData);
router.refresh();
} catch (error) {
console.log(error);
setDisplayImage(e.target.files[0]);
}
};
const changeImage = async () => {
const formData = new FormData();
if (session.status === "unauthenticated") return;
if (
typeof display_image === typeof "string" ||
display_image === img
)
return;
formData.append("image", display_image);
try {
setLoader(true);
const res = await authInstanse(
session.data?.access_token as string
).patch("/users/update_image/", formData);
setError("");
setSuccess("Фото профиля обновлено");
setMessage(true);
router.refresh();
setTimeout(() => {
setMessage(false);
}, 3000);
} catch (error: unknown) {
if (error instanceof AxiosError) {
setSuccess("");
setError(error.message);
setMessage(true);
setTimeout(() => {
setMessage(false);
}, 3000);
}
} finally {
setLoader(false);
}
};
const returnDefaultImage = async () => {
if (session.status === "unauthenticated") return;
try {
setLoader(true);
const res = await authInstanse(
session.data?.access_token as string
).patch("/users/delete_image/", {});
setError("");
setSuccess("Фото профиля удалено");
setMessage(true);
setDisplayImage(def);
router.refresh();
setTimeout(() => {
setMessage(false);
}, 3000);
} catch (error: unknown) {
if (error instanceof AxiosError) {
setSuccess("");
setError(error.message);
setMessage(true);
setTimeout(() => {
setMessage(false);
}, 3000);
}
} finally {
setLoader(false);
}
};
const imageIsString =
typeof display_image === "string"
? display_image
: typeof display_image === "undefined"
? ""
: URL.createObjectURL(display_image as File);
return (
<div className="profile-avatar">
<img
@ -44,10 +129,123 @@ const ProfileAvatar: React.FC<IProfileAvatarProps> = ({
src={img}
alt="User Image"
/>
<label htmlFor="profile-image">
<button
onClick={() => setModal(true)}
className="profile-avatar__change-btn"
>
<Image src={pen} alt="Pen Icon" />
</button>
{modal && (
<div
onClick={() => setModal(false)}
className="profile-avatar__modal"
>
<div
onClick={(e) => e.stopPropagation()}
className="profile-avatar__wrapper"
>
<div className="profile-avatar__text">
<div>
<h4>Фото профиля</h4>
<button onClick={() => setModal(false)}>
<Image src={close} alt="Close Icon" />
</button>
</div>
<p>
По фото профиля другие люди смогут вас узнавать, а вам
будет проще определять, в какой аккаунт вы вошли.
</p>
</div>
<img
className="profile-avatar__user-img"
src={imageIsString}
alt="User image"
/>
<div className="profile-avatar__btns">
{img === def && display_image === def ? (
<>
<label
className="profile-avatar__blue-btn"
htmlFor="change-image"
>
Добавить фото профиля
</label>
<input onChange={changeImage} id="profile-image" type="file" />
<input
onChange={handleChange}
id="change-image"
type="file"
/>
</>
) : isDeleting ? (
<>
<button
onClick={() => setIsDeleting(false)}
className="profile-avatar__gray-btn"
>
Отмена
</button>
<button
onClick={returnDefaultImage}
disabled={loader}
className="profile-avatar__blue-btn"
>
{loader ? <Loader /> : "Удалить"}
</button>
</>
) : img === display_image || success !== "" ? (
<>
<button
onClick={() => setIsDeleting(true)}
className="profile-avatar__gray-btn"
>
Удалить
</button>
<label
className="profile-avatar__blue-btn"
htmlFor="change-image"
>
Сменить
</label>
<input
onChange={handleChange}
id="change-image"
type="file"
/>
</>
) : (
<>
<button
onClick={() => setDisplayImage(img)}
className="profile-avatar__gray-btn"
>
Назад
</button>
<button
disabled={loader}
onClick={changeImage}
className="profile-avatar__blue-btn"
>
{loader ? <Loader /> : "Сохранить"}
</button>
</>
)}
</div>
{message && (
<div className="profile-avatar__message">
<p>
{success} {error}
</p>
<button onClick={() => setMessage(false)}>
<Image src={close_white} alt="Close Icon White" />
</button>
</div>
)}
</div>
</div>
)}
</div>
);
};

View File

@ -0,0 +1,14 @@
<svg width="20.000000" height="20.000000" viewBox="0 0 20 20" 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="clip2704_56355">
<rect id="icon" width="20.000000" height="20.000000" fill="white" fill-opacity="0"/>
</clipPath>
</defs>
<g clip-path="url(#clip2704_56355)">
<path id="Vector" d="M14.375 5.625L5.625 14.375" stroke="#FFFFFF" stroke-opacity="1.000000" stroke-width="1.500000" stroke-linejoin="round" stroke-linecap="round"/>
<path id="Vector" d="M5.625 5.625L14.375 14.375" stroke="#FFFFFF" stroke-opacity="1.000000" stroke-width="1.500000" stroke-linejoin="round" stroke-linecap="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 730 B

View File

@ -0,0 +1,14 @@
<svg width="12.000000" height="12.000000" viewBox="0 0 12 12" 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="clip4_25648">
<rect id="close" width="12.000000" height="12.000000" fill="white" fill-opacity="0"/>
</clipPath>
</defs>
<g clip-path="url(#clip4_25648)">
<path id="Vector" d="M8.625 3.375L3.375 8.625" stroke="#334155" stroke-opacity="1.000000" stroke-width="1.500000" stroke-linejoin="round" stroke-linecap="round"/>
<path id="Vector" d="M3.375 3.375L8.625 8.625" stroke="#334155" stroke-opacity="1.000000" stroke-width="1.500000" stroke-linejoin="round" stroke-linecap="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 721 B

View File

@ -24,6 +24,7 @@ const SignUpForm = () => {
> = async (e) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const regex = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/;
if (!formData.get("email")?.toString()) {
setError("");
@ -41,6 +42,26 @@ const SignUpForm = () => {
return;
}
if ((formData.get("password")?.toString().length as number) < 8) {
setError("");
setEmailWarning("");
setPasswordConfirmWarning("");
setPasswordWarning(
"Пароль должен содержать минимум 8 символов"
);
return;
}
if (!regex.test(formData.get("password")?.toString() as string)) {
setError("");
setEmailWarning("");
setPasswordConfirmWarning("");
setPasswordWarning(
"Пароль должен содержать по меньшей мере 1 прописную букву, одну заглавную букву и одну цифру"
);
return;
}
if (!formData.get("password2")?.toString()) {
setError("");
setEmailWarning("");
@ -79,9 +100,15 @@ const SignUpForm = () => {
}
} catch (error: unknown) {
if (error instanceof AxiosError) {
setError("Произошла непредвиденная ошибка");
if (error.response?.status === 401) {
setError("Такой пользователь уже существует");
} else if (
error.response?.status.toString().slice(0, 1) === "5"
) {
setError("Ошибка на стороне сервера");
}
} else {
setError("An error ocured");
setError("Непредвиденная ошибка");
}
} finally {
setLoader(false);