mirror of
https://github.com/Radiquum/AniX.git
synced 2025-04-06 16:24:40 +00:00
feat: add history api and page
This commit is contained in:
parent
e73b214045
commit
eb620c0b1d
5 changed files with 186 additions and 22 deletions
19
app/api/history/route.js
Normal file
19
app/api/history/route.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { fetchDataViaGet } from "../utils";
|
||||||
|
import { ENDPOINTS } from "../config";
|
||||||
|
|
||||||
|
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 });
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = new URL(`${ENDPOINTS.user.history}/${page}`);
|
||||||
|
url.searchParams.set("token", token);
|
||||||
|
|
||||||
|
const response = await fetchDataViaGet(url.toString());
|
||||||
|
return NextResponse.json(response);
|
||||||
|
}
|
|
@ -44,14 +44,17 @@ export const fetchDataViaPost = async (url, body, API_V2) => {
|
||||||
|
|
||||||
export const authorize = async (url, data) => {
|
export const authorize = async (url, data) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${url}?login=${data.login}&password=${data.password}`, {
|
const response = await fetch(
|
||||||
method: "POST",
|
`${url}?login=${data.login}&password=${data.password}`,
|
||||||
headers: {
|
{
|
||||||
"User-Agent": USER_AGENT,
|
method: "POST",
|
||||||
Sign: "9aa5c7af74e8cd70c86f7f9587bde23d",
|
headers: {
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
"User-Agent": USER_AGENT,
|
||||||
},
|
Sign: "9aa5c7af74e8cd70c86f7f9587bde23d",
|
||||||
});
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
throw new Error("Error authorizing user");
|
throw new Error("Error authorizing user");
|
||||||
}
|
}
|
||||||
|
@ -74,9 +77,34 @@ export function removeJWT() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function numberDeclension(number, one, two, five) {
|
export function numberDeclension(number, one, two, five) {
|
||||||
if (number > 10 && [11, 12, 13, 14].includes(number%100)) return five;
|
if (number > 10 && [11, 12, 13, 14].includes(number % 100)) return five;
|
||||||
let last_num = number%10;
|
let last_num = number % 10;
|
||||||
if (last_num == 1) return one;
|
if (last_num == 1) return one;
|
||||||
if ([2,3,4].includes(last_num)) return two;
|
if ([2, 3, 4].includes(last_num)) return two;
|
||||||
if ([5,6,7,8,9, 0].includes(last_num)) return five;
|
if ([5, 6, 7, 8, 9, 0].includes(last_num)) return five;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sinceUnixDate(unixInSeconds) {
|
||||||
|
const unix = Math.floor(unixInSeconds * 1000);
|
||||||
|
const date = new Date(unix);
|
||||||
|
const currentDate = new Date().valueOf();
|
||||||
|
const dateDifferenceSeconds = new Date(currentDate - unix) / 1000;
|
||||||
|
|
||||||
|
const minutes = Math.floor(dateDifferenceSeconds / 60)
|
||||||
|
const hours = Math.floor(dateDifferenceSeconds / 3600);
|
||||||
|
const days = Math.floor(dateDifferenceSeconds / 86400);
|
||||||
|
|
||||||
|
const minutesName = numberDeclension(minutes, "минута", "минуты", "минут");
|
||||||
|
const hoursName = numberDeclension(hours, "час", "часа", "часов");
|
||||||
|
const daysName = numberDeclension(days, "день", "дня", "дней");
|
||||||
|
|
||||||
|
if (dateDifferenceSeconds < 60) return "менее минуты назад";
|
||||||
|
if (dateDifferenceSeconds < 3600)
|
||||||
|
return `${minutes} ${minutesName} назад`;
|
||||||
|
if (dateDifferenceSeconds < 86400)
|
||||||
|
return `${hours} ${hoursName} назад`;
|
||||||
|
if (dateDifferenceSeconds < 2592000)
|
||||||
|
return `${days} ${daysName} назад`;
|
||||||
|
|
||||||
|
return date.toLocaleString("ru-RU").split(",")[0];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { sinceUnixDate } from "@/app/api/utils";
|
||||||
|
|
||||||
export const ReleaseLink = (props) => {
|
export const ReleaseLink = (props) => {
|
||||||
const grade = props.grade.toFixed(1);
|
const grade = props.grade.toFixed(1);
|
||||||
|
@ -25,7 +26,7 @@ export const ReleaseLink = (props) => {
|
||||||
src={props.image}
|
src={props.image}
|
||||||
alt=""
|
alt=""
|
||||||
/>
|
/>
|
||||||
<div className="absolute flex flex-col items-start justify-start gap-1 left-2 top-2">
|
<div className="absolute flex flex-wrap items-start justify-start max-w-[45%] gap-0.5 sm:gap-1 left-2 top-2">
|
||||||
<div className="flex gap-1 ">
|
<div className="flex gap-1 ">
|
||||||
<div
|
<div
|
||||||
className={`rounded-sm ${
|
className={`rounded-sm ${
|
||||||
|
@ -40,15 +41,10 @@ export const ReleaseLink = (props) => {
|
||||||
: "bg-green-500"
|
: "bg-green-500"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<p className="px-2 sm:px-4 py-0.5 sm:py-1 text-xs xl:text-base text-white">
|
<p className="px-2 sm:px-4 py-0.5 sm:py-1 text-xs sm:text-base text-white">
|
||||||
{grade}
|
{grade}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{props.is_favorite && (
|
|
||||||
<div className="flex items-center justify-center bg-pink-500 rounded-sm">
|
|
||||||
<span className="w-6 h-full bg-white sm:w-8 sm:h-8 iconify mdi--heart"></span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
{user_list && (
|
{user_list && (
|
||||||
<div className={`rounded-sm ${user_list.bg_color}`}>
|
<div className={`rounded-sm ${user_list.bg_color}`}>
|
||||||
|
@ -58,16 +54,16 @@ export const ReleaseLink = (props) => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute flex flex-col items-end gap-1 top-2 right-2">
|
<div className="absolute flex flex-wrap max-w-[45%] justify-end items-end gap-0.5 sm:gap-1 top-2 right-2">
|
||||||
{props.status ? (
|
{props.status ? (
|
||||||
<div className="bg-gray-500 rounded-sm">
|
<div className="bg-gray-500 rounded-sm">
|
||||||
<p className="px-2 sm:px-4 py-0.5 sm:py-1 text-xs xl:text-base text-white">
|
<p className="px-2 sm:px-4 py-0.5 sm:py-1 text-xs xl:text-base text-white text-center">
|
||||||
{props.status.name}
|
{props.status.name}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="bg-gray-500 rounded-sm">
|
<div className="bg-gray-500 rounded-sm">
|
||||||
<p className="px-2 sm:px-4 py-0.5 sm:py-1 text-xs xl:text-base text-white">
|
<p className="px-2 sm:px-4 py-0.5 sm:py-1 text-xs xl:text-base text-white text-center">
|
||||||
{props.status_id == 1
|
{props.status_id == 1
|
||||||
? "Завершено"
|
? "Завершено"
|
||||||
: props.status_id == 2
|
: props.status_id == 2
|
||||||
|
@ -88,6 +84,30 @@ export const ReleaseLink = (props) => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{props.is_favorite && (
|
||||||
|
<div className="flex items-center justify-center bg-pink-500 rounded-sm">
|
||||||
|
<span className="w-3 px-4 py-2.5 text-white sm:px-4 sm:py-3 xl:px-6 xl:py-4 iconify mdi--heart"></span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{props.last_view_episode && (
|
||||||
|
<div className="bg-gray-500 rounded-sm">
|
||||||
|
<div className="px-2 sm:px-4 py-0.5 sm:py-1 text-xs xl:text-base text-white flex">
|
||||||
|
{props.last_view_episode.name ? (
|
||||||
|
<p>{`${props.last_view_episode.name}`} </p>
|
||||||
|
) : (
|
||||||
|
<p>{`${props.last_view_episode.position + 1} серия`}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{"last_view_timestamp" in props &&
|
||||||
|
props.last_view_timestamp != 0 && (
|
||||||
|
<div className="bg-gray-500 rounded-sm">
|
||||||
|
<div className="px-2 sm:px-4 py-0.5 sm:py-1 text-xs xl:text-base text-white flex">
|
||||||
|
<p>{sinceUnixDate(props.last_view_timestamp)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<p className="absolute text-xs text-white xl:text-base lg:text-lg left-2 bottom-2 right-2">
|
<p className="absolute text-xs text-white xl:text-base lg:text-lg left-2 bottom-2 right-2">
|
||||||
{props.title_ru}
|
{props.title_ru}
|
||||||
|
|
9
app/history/page.js
Normal file
9
app/history/page.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
export const metadata = {
|
||||||
|
title: "История",
|
||||||
|
};
|
||||||
|
|
||||||
|
import { HistoryPage } from "@/app/pages/History";
|
||||||
|
|
||||||
|
export default function Index() {
|
||||||
|
return <HistoryPage />;
|
||||||
|
}
|
88
app/pages/History.jsx
Normal file
88
app/pages/History.jsx
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
"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";
|
||||||
|
|
||||||
|
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 HistoryPage() {
|
||||||
|
const token = useUserStore((state) => state.token);
|
||||||
|
const [isLoadingEnd, setIsLoadingEnd] = useState(false);
|
||||||
|
|
||||||
|
const getKey = (pageIndex, previousPageData) => {
|
||||||
|
if (previousPageData && !previousPageData.content.length) return null;
|
||||||
|
return `/api/history?page=${pageIndex}&token=${token}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
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>
|
||||||
|
</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>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue