Merge branch 'refactor__ProfilePage' into V3

This commit is contained in:
Kentai Radiquum 2024-08-27 16:29:24 +05:00
commit c762c4a940
Signed by: Radiquum
GPG key ID: 858E8EE696525EED
23 changed files with 1125 additions and 275 deletions

View file

@ -10,7 +10,7 @@ import { ENDPOINTS } from "#/api/config";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
export function BookmarksPage() {
export function BookmarksPage(props: { profile_id?: number }) {
const token = useUserStore((state) => state.token);
const authState = useUserStore((state) => state.state);
const router = useRouter();
@ -18,8 +18,15 @@ export function BookmarksPage() {
function useFetchReleases(listName: string) {
let url: string;
if (token) {
url = `${ENDPOINTS.user.bookmark}/all/${BookmarksList[listName]}/0?token=${token}`;
if (props.profile_id) {
url = `${ENDPOINTS.user.bookmark}/all/${props.profile_id}/${BookmarksList[listName]}/0?sort=1`;
if (token) {
url += `&token=${token}`;
}
} else {
if (token) {
url = `${ENDPOINTS.user.bookmark}/all/${BookmarksList[listName]}/0?sort=1&token=${token}`;
}
}
const { data } = useSWR(url, fetcher);
@ -33,7 +40,7 @@ export function BookmarksPage() {
const [abandonedData] = useFetchReleases("abandoned");
useEffect(() => {
if (authState === "finished" && !token) {
if (authState === "finished" && !token && !props.profile_id) {
router.push("/login?redirect=/bookmarks");
}
// eslint-disable-next-line react-hooks/exhaustive-deps
@ -56,28 +63,44 @@ export function BookmarksPage() {
watchingData.content.length > 0 && (
<ReleaseCourusel
sectionTitle="Смотрю"
showAllLink="/bookmarks/watching"
showAllLink={
!props.profile_id
? "/bookmarks/watching"
: `/profile/${props.profile_id}/bookmarks/watching`
}
content={watchingData.content}
/>
)}
{plannedData && plannedData.content && plannedData.content.length > 0 && (
<ReleaseCourusel
sectionTitle="В планах"
showAllLink="/bookmarks/planned"
showAllLink={
!props.profile_id
? "/bookmarks/planned"
: `/profile/${props.profile_id}/bookmarks/planned`
}
content={plannedData.content}
/>
)}
{watchedData && watchedData.content && watchedData.content.length > 0 && (
<ReleaseCourusel
sectionTitle="Просмотрено"
showAllLink="/bookmarks/watched"
showAllLink={
!props.profile_id
? "/bookmarks/watched"
: `/profile/${props.profile_id}/bookmarks/watched`
}
content={watchedData.content}
/>
)}
{delayedData && delayedData.content && delayedData.content.length > 0 && (
<ReleaseCourusel
sectionTitle="Отложено"
showAllLink="/bookmarks/delayed"
showAllLink={
!props.profile_id
? "/bookmarks/delayed"
: `/profile/${props.profile_id}/bookmarks/delayed`
}
content={delayedData.content}
/>
)}
@ -86,7 +109,11 @@ export function BookmarksPage() {
abandonedData.content.length > 0 && (
<ReleaseCourusel
sectionTitle="Заброшено"
showAllLink="/bookmarks/abandoned"
showAllLink={
!props.profile_id
? "/bookmarks/abandoned"
: `/profile/${props.profile_id}/bookmarks/abandoned`
}
content={abandonedData.content}
/>
)}

View file

@ -40,11 +40,22 @@ export function BookmarksCategoryPage(props: any) {
const getKey = (pageIndex: number, previousPageData: any) => {
if (previousPageData && !previousPageData.content.length) return null;
if (token) {
return `${ENDPOINTS.user.bookmark}/all/${
let url: string;
if (props.profile_id) {
url = `${ENDPOINTS.user.bookmark}/all/${props.profile_id}/${
BookmarksList[props.slug]
}/${pageIndex}?token=${token}&sort=${sort.values[selectedSort].id}`;
}/${pageIndex}?sort=${sort.values[selectedSort].id}`;
if (token) {
url += `&token=${token}`;
}
} else {
if (token) {
url = `${ENDPOINTS.user.bookmark}/all/${
BookmarksList[props.slug]
}/${pageIndex}?sort=${sort.values[selectedSort].id}&token=${token}`;
}
}
return url;
};
const { data, error, isLoading, size, setSize } = useSWRInfinite(
@ -74,7 +85,7 @@ export function BookmarksCategoryPage(props: any) {
}, [scrollPosition]);
useEffect(() => {
if (authState === "finished" && !token) {
if (authState === "finished" && !token && !props.profile_id) {
router.push(`/login?redirect=/bookmarks/${props.slug}`);
}
// eslint-disable-next-line react-hooks/exhaustive-deps

View file

@ -2,30 +2,50 @@
import { useUserStore } from "#/store/auth";
import { useEffect, useState } from "react";
import { Spinner } from "../components/Spinner/Spinner";
import { Avatar, Card, Button, Table } from "flowbite-react";
import { Chip } from "../components/Chip/Chip";
import { fetchDataViaGet, unixToDate, minutesToTime } from "../api/utils";
import { ReleaseCourusel } from "#/components/ReleaseCourusel/ReleaseCourusel";
import { ENDPOINTS } from "#/api/config";
import useSWR from "swr";
import { ProfileUser } from "#/components/Profile/Profile.User";
import { ProfileBannedBanner } from "#/components/Profile/ProfileBannedBanner";
import { ProfilePrivacyBanner } from "#/components/Profile/Profile.PrivacyBanner";
import { ProfileActivity } from "#/components/Profile/Profile.Activity";
import { ProfileStats } from "#/components/Profile/Profile.Stats";
import { ProfileWatchDynamic } from "#/components/Profile/Profile.WatchDynamic";
import { ProfileActions } from "#/components/Profile/Profile.Actions";
import { ProfileReleaseRatings } from "#/components/Profile/Profile.ReleaseRatings";
import { ProfileReleaseHistory } from "#/components/Profile/Profile.ReleaseHistory";
const fetcher = async (url: string) => {
const res = await fetch(url);
if (!res.ok) {
const error = new Error(
`An error occurred while fetching the data. status: ${res.status}`
);
error.message = await res.json();
throw error;
}
return res.json();
};
export const ProfilePage = (props: any) => {
const authUser = useUserStore((state) => state);
const authUser = useUserStore();
const [user, setUser] = useState(null);
const [isMyProfile, setIsMyProfile] = useState(false);
let url = `${ENDPOINTS.user.profile}/${props.id}`;
if (authUser.token) {
url += `?token=${authUser.token}`;
}
const { data } = useSWR(url, fetcher);
useEffect(() => {
async function _getData() {
let url = `${ENDPOINTS.user.profile}/${props.id}`;
if (authUser.token) {
url += `?token=${authUser.token}`;
}
const data = await fetchDataViaGet(url);
if (data) {
setUser(data.profile);
setIsMyProfile(data.is_my_profile);
}
_getData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [authUser]);
}, [data]);
if (!user) {
return (
@ -47,13 +67,13 @@ export const ProfilePage = (props: any) => {
name: "vk",
nickname: user.vk_page,
icon: "fa6-brands--vk",
urlPrefix: "https://vk.com",
urlPrefix: "https://vk.com/",
},
{
name: "telegram",
nickname: user.tg_page,
icon: "fa6-brands--telegram",
urlPrefix: "https://t.me",
urlPrefix: "https://t.me/",
},
{
name: "discord",
@ -64,249 +84,124 @@ export const ProfilePage = (props: any) => {
name: "tiktok",
nickname: user.tt_page,
icon: "fa6-brands--tiktok",
urlPrefix: "https://tiktok.com",
urlPrefix: "https://tiktok.com/@",
},
{
name: "instagram",
nickname: user.inst_page,
icon: "fa6-brands--instagram",
urlPrefix: "https://instagram.com",
urlPrefix: "https://instagram.com/",
},
];
const hasChips = user.is_verified || user.is_blocked || isMyProfile;
const hasChips =
user.is_verified ||
user.is_blocked ||
(user.roles && user.roles.length > 0) ||
isMyProfile;
const isPrivacy =
user.is_stats_hidden || user.is_counts_hidden || user.is_social_hidden;
return (
<main className="container flex flex-col gap-4 px-4 pt-4 pb-32 mx-auto overflow-hidden sm:pb-4">
{(user.is_banned || user.is_perm_banned) && (
<div className="flex flex-col justify-between w-full p-4 border border-red-200 rounded-md md:flex-row bg-red-50 dark:bg-red-700 dark:border-red-600">
<div className="mb-4 md:mb-0 md:me-4">
<h2 className="mb-1 text-base font-semibold text-gray-900 dark:text-white">
{user.is_perm_banned
? "Пользователь был заблокирован администрацией навсегда"
: `Пользователь был заблокирован администрацией до
${unixToDate(user.ban_expires)}`}
</h2>
<p className="flex items-center text-sm font-normal text-gray-500 dark:text-gray-200">
{user.ban_reason}
</p>
</div>
<>
<div className="flex flex-col gap-2">
<ProfileBannedBanner
is_banned={user.is_banned}
is_perm_banned={user.is_perm_banned}
ban_reason={user.ban_reason}
ban_expires={user.ban_expires}
/>
<ProfilePrivacyBanner
is_privacy={isPrivacy}
is_me_blocked={user.is_me_blocked}
/>
</div>
<div
className={`flex flex-wrap gap-2 ${
isPrivacy || user.is_banned || user.is_perm_banned ? "mt-4" : ""
}`}
>
<div className="flex flex-col gap-2 w-full xl:w-[50%]">
<ProfileUser
isOnline={user.is_online}
avatar={user.avatar}
login={user.login}
status={user.status}
socials={{
isPrivate: user.is_social_hidden,
hasSocials: hasSocials,
socials: socials,
}}
chips={{
hasChips: hasChips,
isMyProfile: isMyProfile,
isVerified: user.is_verified,
isSponsor: user.is_sponsor,
isBlocked: user.is_blocked,
roles: user.roles,
}}
rating={user.rating_score}
/>
{!user.is_counts_hidden && (
<ProfileActivity
profile_id={user.id}
commentCount={user.comment_count}
videoCount={user.video_count}
collectionCount={user.collection_count}
friendsCount={user.friend_count}
/>
)}
{!user.is_stats_hidden && (
<div className="flex-col hidden gap-2 xl:flex">
{user.votes && user.votes.length > 0 && (
<ProfileReleaseRatings ratings={user.votes} />
)}
{user.history && user.history.length > 0 && (
<ProfileReleaseHistory history={user.history} />
)}
</div>
)}
</div>
)}
<div className="flex flex-col gap-4">
<Card className="max-w-full">
{hasChips && (
<div className="flex gap-2 overflow-x-auto scrollbar-thin">
{isMyProfile && (
<Chip bg_color="bg-blue-500" name="Мой профиль" />
)}
{user.is_blocked && (
<Chip bg_color="bg-red-500" name="Заблокирован вами" />
)}
{user.is_verified && (
<Chip bg_color="bg-green-500" name="Подтверждён" />
)}
</div>
<div className="flex flex-col w-full gap-2 xl:flex-1 xl:w-auto ">
{authUser.token && (
<ProfileActions
isMyProfile={isMyProfile}
profile_id={user.id}
isFriendRequestsDisallowed={user.is_friend_requests_disallowed}
friendStatus={user.friend_status}
my_profile_id={authUser.user.id}
token={authUser.token}
is_me_blocked={user.is_me_blocked}
is_blocked={user.is_blocked}
/>
)}
<Avatar
img={user.avatar}
rounded={true}
bordered={true}
size="lg"
className="flex-col justify-start space-x-0 sm:flex-row sm:space-x-4"
>
<div className="mt-2 space-y-1 font-medium sm:mt-0 dark:text-white">
<div className="text-xl">{user.login}</div>
<p className="max-w-full text-sm text-gray-500 whitespace-pre-wrap dark:text-gray-400 sm:max-w-96">
{user.status}
</p>
</div>
</Avatar>
{hasSocials && (
<div className="flex gap-1 overflow-x-auto scrollbar-thin">
{socials
.filter((social: any) => {
if (social.nickname == "") {
return false;
}
return true;
})
.map((social: any) => {
if (social.name == "discord" && social.nickname != "")
return (
<Button color="light" key={social.name} as="a">
<div className="flex items-center justify-center gap-2">
<span
className={`iconify h-4 w-4 sm:h-6 sm:w-6 ${social.icon} dark:fill-white`}
></span>
{social.nickname}
</div>
</Button>
);
return (
<Button
color="light"
key={social.name}
href={`${social.urlPrefix}/${social.nickname}`}
className="[&:is(a)]:hover:bg-gray-100"
>
<div className="flex items-center justify-center gap-2">
<span
className={`iconify h-4 w-4 sm:h-6 sm:w-6 ${social.icon} dark:fill-white`}
></span>
{social.nickname}
</div>
</Button>
);
})}
</div>
{!user.is_stats_hidden && (
<>
<ProfileStats
lists={[
user.watching_count,
user.plan_count,
user.completed_count,
user.hold_on_count,
user.dropped_count,
]}
watched_count={user.watched_episode_count}
watched_time={user.watched_time}
profile_id={user.id}
/>
<ProfileWatchDynamic watchDynamic={user.watch_dynamics || []} />
<div className="flex flex-col gap-2 xl:hidden">
{user.votes && user.votes.length > 0 && (
<ProfileReleaseRatings ratings={user.votes} />
)}
{user.history && user.history.length > 0 && (
<ProfileReleaseHistory history={user.history} />
)}
</div>
</>
)}
</Card>
<div className="flex flex-wrap gap-4">
<Card className="flex-1 max-w-full">
<h1>Активность</h1>
<Table>
<Table.Body className="divide-y">
<Table.Row>
<Table.Cell className="px-0 font-medium text-gray-900 whitespace-nowrap dark:text-white">
Регистрация
</Table.Cell>
<Table.Cell className="font-medium text-gray-900 whitespace-nowrap dark:text-white">
{unixToDate(user.register_date)}
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell className="px-0 font-medium text-gray-900 whitespace-nowrap dark:text-white">
Был(а) в сети
</Table.Cell>
<Table.Cell className="font-medium text-gray-900 whitespace-nowrap dark:text-white">
{unixToDate(user.last_activity_time)}
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell className="px-0 font-medium text-gray-900 whitespace-nowrap dark:text-white">
Комментарий
</Table.Cell>
<Table.Cell className="font-medium text-gray-900 whitespace-nowrap dark:text-white">
{user.comment_count}
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell className="px-0 font-medium text-gray-900 whitespace-nowrap dark:text-white">
друзей
</Table.Cell>
<Table.Cell className="font-medium text-gray-900 whitespace-nowrap dark:text-white">
{user.friend_count}
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell className="px-0 font-medium text-gray-900 whitespace-nowrap dark:text-white">
видео
</Table.Cell>
<Table.Cell className="font-medium text-gray-900 whitespace-nowrap dark:text-white">
{user.video_count}
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell className="px-0 font-medium text-gray-900 whitespace-nowrap dark:text-white">
коллекций
</Table.Cell>
<Table.Cell className="font-medium text-gray-900 whitespace-nowrap dark:text-white">
{user.collection_count}
</Table.Cell>
</Table.Row>
</Table.Body>
</Table>
</Card>
<Card className="flex-1 max-w-full">
<h1>Статистика</h1>
<Table>
<Table.Body className="divide-y">
<Table.Row>
<Table.Cell className="flex items-center px-0 font-medium text-gray-900 whitespace-nowrap dark:text-white">
<span className="w-4 h-4 mr-2 iconify mdi--123 "></span>
Просмотрено серий
</Table.Cell>
<Table.Cell className="font-medium text-gray-900 whitespace-nowrap dark:text-white">
{user.watched_episode_count}
</Table.Cell>
</Table.Row>
<Table.Row className="hidden sm:table-row">
<Table.Cell className="flex items-center px-0 font-medium text-gray-900 whitespace-nowrap dark:text-white">
<span className="w-4 h-4 mr-2 iconify mdi--clock "></span>
Время просмотра
</Table.Cell>
<Table.Cell className="font-medium text-gray-900 whitespace-pre sm:whitespace-nowrap dark:text-white">
{minutesToTime(user.watched_time) ||
"Нет просмотренных серий."}
</Table.Cell>
</Table.Row>
<Table.Row className="table-row sm:hidden">
<Table.Cell className="flex items-center px-0 font-medium text-gray-900 whitespace-pre sm:whitespace-nowrap dark:text-white">
<span className="w-4 h-4 mr-2 iconify mdi--clock "></span>
{minutesToTime(user.watched_time) ||
"Нет просмотренных серий."}
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell className="flex items-center px-0 font-medium text-gray-900 whitespace-nowrap dark:text-white">
<span className="w-4 h-4 mr-2 iconify mdi--play "></span>
Смотрю
</Table.Cell>
<Table.Cell className="font-medium text-gray-900 whitespace-nowrap dark:text-white">
{user.watching_count}
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell className="flex items-center px-0 font-medium text-gray-900 whitespace-nowrap dark:text-white">
<span className="w-4 h-4 mr-2 iconify mdi--note-multiple "></span>
В Планах
</Table.Cell>
<Table.Cell className="font-medium text-gray-900 whitespace-nowrap dark:text-white">
{user.plan_count}
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell className="flex items-center px-0 font-medium text-gray-900 whitespace-nowrap dark:text-white">
<span className="w-4 h-4 mr-2 iconify mdi--tick "></span>
Просмотрено
</Table.Cell>
<Table.Cell className="font-medium text-gray-900 whitespace-nowrap dark:text-white">
{user.completed_count}
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell className="flex items-center px-0 font-medium text-gray-900 whitespace-nowrap dark:text-white">
<span className="w-4 h-4 mr-2 iconify mdi--question-mark "></span>
Отложено
</Table.Cell>
<Table.Cell className="font-medium text-gray-900 whitespace-nowrap dark:text-white">
{user.hold_on_count}
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell className="flex items-center px-0 font-medium text-gray-900 whitespace-nowrap dark:text-white">
<span className="w-4 h-4 mr-2 iconify mdi--erase "></span>
Брошено
</Table.Cell>
<Table.Cell className="font-medium text-gray-900 whitespace-nowrap dark:text-white">
{user.dropped_count}
</Table.Cell>
</Table.Row>
</Table.Body>
</Table>
</Card>
</div>
</div>
{user.history.length > 0 && (
<div className="px-4 py-2 bg-white border border-gray-200 rounded-lg shadow-md dark:border-gray-700 dark:bg-gray-800">
<ReleaseCourusel
sectionTitle="Недавно просмотренные"
content={user.history}
/>
</div>
)}
</main>
</>
);
};