feat: add bookmarks & bookmarks category pages

This commit is contained in:
Kentai Radiquum 2024-07-16 09:22:09 +05:00
parent a3f5f2e116
commit bccc8407fc
Signed by: Radiquum
GPG key ID: 858E8EE696525EED
13 changed files with 624 additions and 155 deletions

81
app/pages/Bookmarks.jsx Normal file
View file

@ -0,0 +1,81 @@
"use client";
import useSWR from "swr";
import { ReleaseCourusel } from "@/app/components/ReleaseCourusel/ReleaseCourusel";
import { Spinner } from "@/app/components/Spinner/Spinner";
const fetcher = (...args) => fetch(...args).then((res) => res.json());
import { useUserStore } from "@/app/store/auth";
export function BookmarksPage() {
const token = useUserStore((state) => state.token);
function useFetchReleases(list) {
let url;
url = `/api/bookmarks?list=${list}&token=${token}`;
const { data } = useSWR(url, fetcher);
return [data];
}
const [watchingData] = useFetchReleases("watching");
const [plannedData] = useFetchReleases("planned");
const [watchedData] = useFetchReleases("watched");
const [delayedData] = useFetchReleases("delayed");
const [abandonedData] = useFetchReleases("abandoned");
return (
<main className="container flex flex-col pt-2 pb-16 mx-auto sm:pt-4 sm:pb-0">
{!watchingData ||
!plannedData ||
!watchedData ||
!delayedData ||
!abandonedData ? (
<div className="flex items-center justify-center min-w-full min-h-screen">
<Spinner />
</div>
) : (
""
)}
{watchingData &&
watchingData.content &&
watchingData.content.length > 0 && (
<ReleaseCourusel
sectionTitle="Смотрю"
showAllLink="/bookmarks/watching"
content={watchingData.content}
/>
)}
{plannedData && plannedData.content && plannedData.content.length > 0 && (
<ReleaseCourusel
sectionTitle="В планах"
showAllLink="/bookmarks/planned"
content={plannedData.content}
/>
)}
{watchedData && watchedData.content && watchedData.content.length > 0 && (
<ReleaseCourusel
sectionTitle="Просмотрено"
showAllLink="/bookmarks/watched"
content={watchedData.content}
/>
)}
{delayedData &&
delayedData.content &&
delayedData.content.length > 0 && (
<ReleaseCourusel
sectionTitle="Отложено"
showAllLink="/bookmarks/delayed"
content={delayedData.content}
/>
)}
{abandonedData &&
abandonedData.content &&
abandonedData.content.length > 0 && (
<ReleaseCourusel
sectionTitle="Заброшено"
showAllLink="/bookmarks/abandoned"
content={abandonedData.content}
/>
)}
</main>
);
}

View file

@ -0,0 +1,120 @@
"use client";
import useSWRInfinite from "swr/infinite";
import { ReleaseSection } from "@/app/components/ReleaseSection/ReleaseSection";
import { Spinner } from "@/app/components/Spinner/Spinner";
import { useState, useEffect } from "react";
import { useScrollPosition } from "@/app/hooks/useScrollPosition";
import { useUserStore } from "../store/auth";
import { Dropdown } from "flowbite-react";
const fetcher = async (url) => {
const res = await fetch(url);
if (!res.ok) {
const error = new Error("An error occurred while fetching the data.");
error.info = await res.json();
error.status = res.status;
throw error;
}
return res.json();
};
const sort = {
descendingIcon: "material-symbols--sort",
ascendingIcon: "[transform:rotateX(180deg)] material-symbols--sort",
values: [
{
name: "Сначала новые",
value: "adding_descending",
},
{
name: "Сначала старые",
value: "adding_ascending",
},
{
name: "А-Я",
value: "alphabet_descending",
},
{
name: "Я-А",
value: "alphabet_ascending",
},
],
};
export function BookmarksCategoryPage(props) {
const token = useUserStore((state) => state.token);
const [selectedSort, setSelectedSort] = useState(0);
const getKey = (pageIndex, previousPageData) => {
if (previousPageData && !previousPageData.content.length) return null;
return `/api/bookmarks?list=${props.slug}&page=${pageIndex}&token=${token}&sort=${sort.values[selectedSort].value}`;
};
const { data, error, isLoading, size, setSize } = useSWRInfinite(
getKey,
fetcher,
{ initialSize: 2, revalidateFirstPage: false }
);
const [content, setContent] = useState(null);
useEffect(() => {
if (data) {
let allReleases = [];
for (let i = 0; i < data.length; i++) {
allReleases.push(...data[i].content);
}
setContent(allReleases);
}
}, [data]);
const scrollPosition = useScrollPosition();
useEffect(() => {
if (scrollPosition >= 98 && scrollPosition <= 99) {
setSize(size + 1);
}
}, [scrollPosition]);
if (error) return <div>failed to load</div>;
return (
<main className="container pt-2 pb-16 mx-auto sm:pt-4 sm:pb-0">
<div className="flex items-center justify-between px-4 py-2 border-b-2 border-black">
<h1 className="font-bold text-md sm:text-xl md:text-lg xl:text-xl">
{props.SectionTitleMapping[props.slug]}
</h1>
<Dropdown label={sort.values[selectedSort].name} dismissOnClick={true}>
{sort.values.map((item, index) => (
<Dropdown.Item key={index} onClick={() => setSelectedSort(index)}>
<span
className={`w-6 h-6 iconify ${
sort.values[index].value.split("_")[1] == "descending"
? sort.descendingIcon
: sort.ascendingIcon
}`}
></span>
{item.name}
</Dropdown.Item>
))}
</Dropdown>
</div>
{isLoading && (
<div className="flex flex-col items-center justify-center min-w-full min-h-screen">
<Spinner />
</div>
)}
{content && <ReleaseSection content={content} />}
{data &&
data[data.length - 1].current_page <
data[data.length - 1].total_page_count && (
<button
className="mx-auto w-[calc(100%-10rem)] border border-black rounded-lg p-2 mb-6 flex items-center justify-center gap-2 hover:bg-black hover:text-white transition"
onClick={() => setSize(size + 1)}
>
<span className="w-10 h-10 iconify mdi--plus"></span>
<span className="text-lg">Загрузить ещё</span>
</button>
)}
</main>
);
}

View file

@ -6,7 +6,7 @@ import { useState, useEffect } from "react";
import { useScrollPosition } from "@/app/hooks/useScrollPosition";
import { useUserStore } from "../store/auth";
const fetcher = async url => {
const fetcher = async (url) => {
const res = await fetch(url);
if (!res.ok) {
@ -33,7 +33,7 @@ export function IndexCategoryPage(props) {
const { data, error, isLoading, size, setSize } = useSWRInfinite(
getKey,
fetcher,
{"initialSize": 2, "revalidateFirstPage": false}
{ initialSize: 2, revalidateFirstPage: false }
);
const [content, setContent] = useState(null);
@ -50,9 +50,9 @@ export function IndexCategoryPage(props) {
const scrollPosition = useScrollPosition();
useEffect(() => {
if (scrollPosition >= 98 && scrollPosition <= 99) {
setSize(size + 1)
setSize(size + 1);
}
}, [scrollPosition]);
}, [scrollPosition]);
if (error) return <div>failed to load</div>;
if (isLoading)
@ -64,13 +64,28 @@ export function IndexCategoryPage(props) {
return (
<main className="container pt-2 pb-16 mx-auto sm:pt-4 sm:pb-0">
{content && (
{content && content.length > 0 ? (
<ReleaseSection
sectionTitle={props.SectionTitleMapping[props.slug]}
content={content}
/>
) : (
<div className="flex flex-col items-center justify-center min-w-full gap-4 mt-12 text-xl">
<span className="w-24 h-24 iconify-color twemoji--broken-heart"></span>
<p>
В списке {props.SectionTitleMapping[props.slug]} пока ничего нет...
</p>
</div>
)}
{data && data[data.length - 1].content.length == 25 && (
<button
className="mx-auto w-[calc(100%-10rem)] border border-black rounded-lg p-2 mb-6 flex items-center justify-center gap-2 hover:bg-black hover:text-white transition"
onClick={() => setSize(size + 1)}
>
<span className="w-10 h-10 iconify mdi--plus"> </span>
<span className="text-lg">Загрузить ещё</span>
</button>
)}
<button className="mx-auto w-[calc(100%-10rem)] border border-black rounded-lg p-2 mb-6 flex items-center justify-center gap-2 hover:bg-black hover:text-white transition" onClick={() => setSize(size + 1)}> <span className="w-10 h-10 iconify mdi--plus"> </span> <span className="text-lg">Загрузить ещё</span></button>
</main>
);
}