mirror of
https://github.com/Radiquum/AniX.git
synced 2025-04-07 00:34:41 +00:00
feat: add favorites page
This commit is contained in:
parent
484148ada6
commit
28efc5a98c
10 changed files with 177 additions and 36 deletions
|
@ -1,6 +1,7 @@
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { fetchDataViaGet } from "../utils";
|
import { fetchDataViaGet } from "../utils";
|
||||||
import { ENDPOINTS } from "../config";
|
import { ENDPOINTS } from "../config";
|
||||||
|
import { sort } from "../common";
|
||||||
|
|
||||||
const list = {
|
const list = {
|
||||||
watching: 1,
|
watching: 1,
|
||||||
|
@ -10,20 +11,12 @@ const list = {
|
||||||
abandoned: 5,
|
abandoned: 5,
|
||||||
};
|
};
|
||||||
|
|
||||||
const sort = {
|
|
||||||
adding_descending: 1,
|
|
||||||
adding_ascending: 2,
|
|
||||||
// year_descending: 3,
|
|
||||||
// year_ascending: 4,
|
|
||||||
alphabet_descending: 5,
|
|
||||||
alphabet_ascending: 6,
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function GET(request) {
|
export async function GET(request) {
|
||||||
const page = parseInt(request.nextUrl.searchParams.get(["page"])) || 0;
|
const page = parseInt(request.nextUrl.searchParams.get(["page"])) || 0;
|
||||||
const listName = request.nextUrl.searchParams.get(["list"]) || null;
|
const listName = request.nextUrl.searchParams.get(["list"]) || null;
|
||||||
const token = request.nextUrl.searchParams.get(["token"]) || null;
|
const token = request.nextUrl.searchParams.get(["token"]) || null;
|
||||||
const sortName = request.nextUrl.searchParams.get(["sort"]) || "adding_descending";
|
const sortName =
|
||||||
|
request.nextUrl.searchParams.get(["sort"]) || "adding_descending";
|
||||||
|
|
||||||
if (!token || token == "null") {
|
if (!token || token == "null") {
|
||||||
return NextResponse.json({ message: "No token provided" }, { status: 403 });
|
return NextResponse.json({ message: "No token provided" }, { status: 403 });
|
||||||
|
|
8
app/api/common.js
Normal file
8
app/api/common.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
export const sort = {
|
||||||
|
adding_descending: 1,
|
||||||
|
adding_ascending: 2,
|
||||||
|
// year_descending: 3,
|
||||||
|
// year_ascending: 4,
|
||||||
|
alphabet_descending: 5,
|
||||||
|
alphabet_ascending: 6,
|
||||||
|
};
|
|
@ -11,7 +11,7 @@ export const ENDPOINTS = {
|
||||||
profile: `${API_URL}/profile`,
|
profile: `${API_URL}/profile`,
|
||||||
bookmark: `${API_URL}/profile/list/all`,
|
bookmark: `${API_URL}/profile/list/all`,
|
||||||
history: `${API_URL}/history`,
|
history: `${API_URL}/history`,
|
||||||
favorite: `${API_URL}/favorite`,
|
favorite: `${API_URL}/favorite/all`,
|
||||||
},
|
},
|
||||||
filter: `${API_URL}/filter`,
|
filter: `${API_URL}/filter`,
|
||||||
auth: `${API_URL}/auth/signIn`,
|
auth: `${API_URL}/auth/signIn`,
|
||||||
|
|
26
app/api/favorites/route.js
Normal file
26
app/api/favorites/route.js
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { fetchDataViaGet } from "../utils";
|
||||||
|
import { ENDPOINTS } from "../config";
|
||||||
|
import { sort } from "../common";
|
||||||
|
|
||||||
|
|
||||||
|
export async function GET(request) {
|
||||||
|
const page = parseInt(request.nextUrl.searchParams.get(["page"])) || 0;
|
||||||
|
const token = request.nextUrl.searchParams.get(["token"]) || null;
|
||||||
|
const sortName = request.nextUrl.searchParams.get(["sort"]) || "adding_descending";
|
||||||
|
|
||||||
|
if (!token || token == "null") {
|
||||||
|
return NextResponse.json({ message: "No token provided" }, { status: 403 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sort[sortName]) {
|
||||||
|
return NextResponse.json({ message: "Unknown sort" }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = new URL(`${ENDPOINTS.user.favorite}/${page}`);
|
||||||
|
url.searchParams.set("token", token);
|
||||||
|
url.searchParams.set("sort", sort[sortName]);
|
||||||
|
|
||||||
|
const response = await fetchDataViaGet(url.toString());
|
||||||
|
return NextResponse.json(response);
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
title: "AniX | Закладки",
|
title: "Закладки",
|
||||||
};
|
};
|
||||||
|
|
||||||
import { BookmarksPage } from "@/app/pages/Bookmarks";
|
import { BookmarksPage } from "@/app/pages/Bookmarks";
|
||||||
|
|
9
app/favorites/page.js
Normal file
9
app/favorites/page.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
export const metadata = {
|
||||||
|
title: "Избранное",
|
||||||
|
};
|
||||||
|
|
||||||
|
import { FavoritesPage } from "@/app/pages/Favorites";
|
||||||
|
|
||||||
|
export default function Index() {
|
||||||
|
return <FavoritesPage />;
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import { useState, useEffect } from "react";
|
||||||
import { useScrollPosition } from "@/app/hooks/useScrollPosition";
|
import { useScrollPosition } from "@/app/hooks/useScrollPosition";
|
||||||
import { useUserStore } from "../store/auth";
|
import { useUserStore } from "../store/auth";
|
||||||
import { Dropdown } from "flowbite-react";
|
import { Dropdown } from "flowbite-react";
|
||||||
|
import { sort } from "./common";
|
||||||
|
|
||||||
const fetcher = async (url) => {
|
const fetcher = async (url) => {
|
||||||
const res = await fetch(url);
|
const res = await fetch(url);
|
||||||
|
@ -20,29 +21,6 @@ const fetcher = async (url) => {
|
||||||
return res.json();
|
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) {
|
export function BookmarksCategoryPage(props) {
|
||||||
const token = useUserStore((state) => state.token);
|
const token = useUserStore((state) => state.token);
|
||||||
const [selectedSort, setSelectedSort] = useState(0);
|
const [selectedSort, setSelectedSort] = useState(0);
|
||||||
|
@ -56,7 +34,7 @@ export function BookmarksCategoryPage(props) {
|
||||||
const { data, error, isLoading, size, setSize } = useSWRInfinite(
|
const { data, error, isLoading, size, setSize } = useSWRInfinite(
|
||||||
getKey,
|
getKey,
|
||||||
fetcher,
|
fetcher,
|
||||||
{ initialSize: 2, revalidateFirstPage: false }
|
{ initialSize: 2 }
|
||||||
);
|
);
|
||||||
|
|
||||||
const [content, setContent] = useState(null);
|
const [content, setContent] = useState(null);
|
||||||
|
|
105
app/pages/Favorites.jsx
Normal file
105
app/pages/Favorites.jsx
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
"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";
|
||||||
|
import { sort } from "./common";
|
||||||
|
|
||||||
|
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();
|
||||||
|
};
|
||||||
|
|
||||||
|
export function FavoritesPage() {
|
||||||
|
const token = useUserStore((state) => state.token);
|
||||||
|
const [selectedSort, setSelectedSort] = useState(0);
|
||||||
|
const [isLoadingEnd, setIsLoadingEnd] = useState(false);
|
||||||
|
|
||||||
|
const getKey = (pageIndex, previousPageData) => {
|
||||||
|
if (previousPageData && !previousPageData.content.length) return null;
|
||||||
|
return `/api/favorites?page=${pageIndex}&token=${token}&sort=${sort.values[selectedSort].value}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data, error, isLoading, size, setSize } = useSWRInfinite(
|
||||||
|
getKey,
|
||||||
|
fetcher,
|
||||||
|
{ initialSize: 2 }
|
||||||
|
);
|
||||||
|
|
||||||
|
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);
|
||||||
|
setIsLoadingEnd(true);
|
||||||
|
}
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
const scrollPosition = useScrollPosition();
|
||||||
|
useEffect(() => {
|
||||||
|
if (scrollPosition >= 98 && scrollPosition <= 99) {
|
||||||
|
setSize(size + 1);
|
||||||
|
}
|
||||||
|
}, [scrollPosition]);
|
||||||
|
|
||||||
|
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">
|
||||||
|
Избранное
|
||||||
|
</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>
|
||||||
|
{content && content.length > 0 ? (
|
||||||
|
<ReleaseSection content={content} />
|
||||||
|
) : !isLoadingEnd || isLoading ? (
|
||||||
|
<div className="flex flex-col items-center justify-center min-w-full min-h-screen">
|
||||||
|
<Spinner />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<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>В избранном пока ничего нет...</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{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>
|
||||||
|
);
|
||||||
|
}
|
22
app/pages/common.js
Normal file
22
app/pages/common.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
export 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",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
Loading…
Add table
Reference in a new issue