Add profile avatar
This commit is contained in:
parent
95674a489c
commit
81aafdda7c
41
src/app/[locale]/profile/personal/page.tsx
Normal file
41
src/app/[locale]/profile/personal/page.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
"use server";
|
||||
|
||||
import ProfileAvatar from "@/features/ProfileAvatar/ProfileAvatar";
|
||||
import { apiInstance } from "@/shared/config/apiConfig";
|
||||
import { authConfig } from "@/shared/config/authConfig";
|
||||
import { IProfile } from "@/shared/types/profile-type";
|
||||
import { AxiosError } from "axios";
|
||||
import { getServerSession } from "next-auth";
|
||||
import React from "react";
|
||||
|
||||
const Personal = async () => {
|
||||
const session = await getServerSession(authConfig);
|
||||
const getProfile = async () => {
|
||||
const Authorization = `Bearer ${session?.access_token}`;
|
||||
const config = {
|
||||
headers: {
|
||||
Authorization,
|
||||
},
|
||||
};
|
||||
try {
|
||||
const response = await apiInstance.get<IProfile>(
|
||||
"/users/profile/",
|
||||
config
|
||||
);
|
||||
|
||||
return response.data;
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof AxiosError) console.log(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
const data = await getProfile();
|
||||
|
||||
return (
|
||||
<div className="personal">
|
||||
<ProfileAvatar img={data?.image as string} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Personal;
|
244
src/features/ProfileAvatar/ProfileAvatar.tsx
Normal file
244
src/features/ProfileAvatar/ProfileAvatar.tsx
Normal file
@ -0,0 +1,244 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
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/Loader/Loader";
|
||||
|
||||
interface IProfileAvatarProps {
|
||||
img: string;
|
||||
}
|
||||
|
||||
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 [success, setSuccess] = useState<string>("");
|
||||
const [loader, setLoader] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string>("");
|
||||
|
||||
const session = useSession();
|
||||
const router = useRouter();
|
||||
const def = "lflvl";
|
||||
const handleChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
if (e.target.files) {
|
||||
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);
|
||||
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="mb-[25px] relative w-[135px] h-[135px]">
|
||||
<img
|
||||
className="w-full h-full rounded-lg object-cover"
|
||||
src={img}
|
||||
alt="User Image"
|
||||
/>
|
||||
<button
|
||||
onClick={() => setModal(true)}
|
||||
className="absolute bottom-0 right-0 w-[50px] h-[50px] flex items-center justify-center shadow-sm bg-white rounded-lg cursor-pointer"
|
||||
>
|
||||
<Image src={pen} alt="Pen Icon" width={22} height={22} />
|
||||
</button>
|
||||
|
||||
{modal && (
|
||||
<div
|
||||
onClick={() => setModal(false)}
|
||||
className="w-full h-full fixed top-0 left-0 flex items-center justify-center bg-black"
|
||||
>
|
||||
<div
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="relative max-w-[400px] p-6 flex flex-col items-center bg-white rounded-sm shadow-sm"
|
||||
>
|
||||
<div>
|
||||
<div className="flex items-center justify-between">
|
||||
<h4 className="text-[14px] font-semibold leading-6 ">
|
||||
Фото профиля
|
||||
</h4>
|
||||
<button
|
||||
className="w-6 h-6 flex items-center justify-center border border-white rounded-lg"
|
||||
onClick={() => setModal(false)}
|
||||
>
|
||||
<Image src={close} alt="Close Icon" />
|
||||
</button>
|
||||
</div>
|
||||
<p className="w-[90%] text-[14px] leading-5 text-gray-400">
|
||||
По фото профиля другие люди смогут вас узнавать, а вам будет
|
||||
проще определять, в какой аккаунт вы вошли.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<img
|
||||
className="my-6 w-[130px] h-[130px] rounded-lg object-cover"
|
||||
src={imageIsString}
|
||||
alt="User image"
|
||||
/>
|
||||
<div className="flex items-center gap-2">
|
||||
{img === def && display_image === def ? (
|
||||
<>
|
||||
<label
|
||||
className="min-w-[110px] py-2 px-4 mt-6 flex items-center justify-center text-[14px] font-semibold leading-5 rounded-sm cursor-pointer"
|
||||
htmlFor="change-image"
|
||||
>
|
||||
Добавить фото профиля
|
||||
</label>
|
||||
<input
|
||||
onChange={handleChange}
|
||||
id="change-image"
|
||||
type="file"
|
||||
/>
|
||||
</>
|
||||
) : isDeleting ? (
|
||||
<>
|
||||
<button
|
||||
onClick={() => setIsDeleting(false)}
|
||||
className="bg-white text-black"
|
||||
>
|
||||
Отмена
|
||||
</button>
|
||||
<button
|
||||
onClick={returnDefaultImage}
|
||||
disabled={loader}
|
||||
className="bg-blue text-white"
|
||||
>
|
||||
{loader ? <Loader /> : "Удалить"}
|
||||
</button>
|
||||
</>
|
||||
) : img === display_image || success !== "" ? (
|
||||
<>
|
||||
<button
|
||||
onClick={() => setIsDeleting(true)}
|
||||
className="bg-white text-black"
|
||||
>
|
||||
Удалить
|
||||
</button>
|
||||
<label className="bg-blue text-white" htmlFor="change-image">
|
||||
Сменить
|
||||
</label>
|
||||
<input
|
||||
onChange={handleChange}
|
||||
id="change-image"
|
||||
type="file"
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<button
|
||||
onClick={() => setDisplayImage(img)}
|
||||
className="bg-white text-black"
|
||||
>
|
||||
Назад
|
||||
</button>
|
||||
<button
|
||||
disabled={loader}
|
||||
onClick={changeImage}
|
||||
className="bg-blue text-white"
|
||||
>
|
||||
{loader ? <Loader /> : "Сохранить"}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{message && (
|
||||
<div className="absolute bottom-[32px] p-2 w-[302px] flex items-center justify-between rounded-sm bg-black text-[14px] font-semibold leading-5 text-white">
|
||||
<p>
|
||||
{success} {error}
|
||||
</p>
|
||||
<button className="text-0" onClick={() => setMessage(false)}>
|
||||
<Image src={close_white} alt="Close Icon White" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProfileAvatar;
|
14
src/features/ProfileAvatar/icons/close-white.svg
Normal file
14
src/features/ProfileAvatar/icons/close-white.svg
Normal 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 |
14
src/features/ProfileAvatar/icons/close.svg
Normal file
14
src/features/ProfileAvatar/icons/close.svg
Normal 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 |
8
src/features/ProfileAvatar/icons/pen.svg
Normal file
8
src/features/ProfileAvatar/icons/pen.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<svg width="21.908203" height="22.000000" viewBox="0 0 21.9082 22" 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="Vector (Stroke)" d="M14.7539 1.22754C16.3906 -0.40918 19.041 -0.40918 20.6777 1.22754C22.3145 2.86426 22.3145 5.51562 20.6777 7.15234L8.5918 19.2383C8.45508 19.375 8.28516 19.4707 8.09766 19.5166L4.51562 20.3867C4.47852 20.3984 4.44141 20.4072 4.4043 20.4141L2.23047 20.9424C1.875 21.0293 1.5 20.9238 1.24023 20.665C0.982422 20.4062 0.876953 20.0312 0.962891 19.6758L2.38867 13.8076C2.43555 13.6211 2.53125 13.4502 2.66797 13.3145L14.7539 1.22754ZM4.53711 18.2236L7.31641 17.5479L19.1953 5.66895C20.0117 4.85156 20.0117 3.52832 19.1953 2.71094C18.377 1.89355 17.0547 1.89355 16.2363 2.71094L4.35742 14.5889L3.68359 17.3652L4.53711 18.2236Z" fill="#B2B5BE" fill-opacity="1.000000" fill-rule="evenodd"/>
|
||||
<path id="Union" d="M14.5332 4.09473C14.9434 3.68652 15.6074 3.6875 16.0176 4.09766L19.123 7.21191C19.5312 7.62207 19.5312 8.28613 19.1211 8.69531C18.7109 9.10449 18.0469 9.10352 17.6367 8.69336L14.5312 5.57812C14.123 5.16797 14.123 4.50391 14.5332 4.09473ZM8.77734 19.9238C8.77734 19.3447 9.24609 18.875 9.82617 18.875L20.8594 18.875C21.4375 18.875 21.9082 19.3447 21.9082 19.9238C21.9082 20.5029 21.4375 20.9727 20.8594 20.9727L9.82617 20.9727C9.24609 20.9727 8.77734 20.5029 8.77734 19.9238Z" fill="#B2B5BE" fill-opacity="1.000000" fill-rule="evenodd"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
10
src/shared/types/profile-type.ts
Normal file
10
src/shared/types/profile-type.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export interface IProfile {
|
||||
id: number;
|
||||
email: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
image: string;
|
||||
role: number;
|
||||
govern_status: null;
|
||||
report_count: number;
|
||||
}
|
Loading…
Reference in New Issue
Block a user