feat: add view of release name, description, image, author, releases in lists and releases in collection

This commit is contained in:
Kentai Radiquum 2024-08-14 14:54:21 +05:00
parent 3de552f271
commit 9a5d1eb6bd
Signed by: Radiquum
GPG key ID: 858E8EE696525EED
4 changed files with 256 additions and 0 deletions

View file

@ -0,0 +1,33 @@
import { ViewCollectionPage } from "#/pages/ViewCollection";
import { fetchDataViaGet } from "#/api/utils";
import type { Metadata, ResolvingMetadata } from "next";
export async function generateMetadata(
{ params },
parent: ResolvingMetadata
): Promise<Metadata> {
const id = params.id;
const collection = await fetchDataViaGet(
`https://api.anixart.tv/collection/${id}`
);
const previousOG = (await parent).openGraph;
return {
title: "коллекция - " + collection.collection.title,
description: collection.collection.description,
openGraph: {
...previousOG,
images: [
{
url: collection.collection.image, // Must be an absolute URL
width: 600,
height: 800,
},
],
},
};
}
export default async function Collections({ params }) {
return <ViewCollectionPage id={params.id} />;
}

View file

@ -0,0 +1,47 @@
import { Card, Button, Avatar } from "flowbite-react";
import { useState } from "react";
import { unixToDate } from "#/api/utils";
import Link from "next/link";
export const CollectionInfoBasics = (props: {
image: string;
title: string;
description: string;
authorAvatar: string;
authorLogin: string;
authorId: number;
creationDate: number;
updateDate: number;
}) => {
return (
<Card className="w-full max-w-full lg:max-w-[50%]">
<div className="flex flex-col items-end justify-between sm:items-center sm:flex-row">
<div className="flex flex-col gap-1">
<p>создана: {unixToDate(props.creationDate, "full")}</p>
<p>обновлена: {unixToDate(props.updateDate, "full")}</p>
</div>
<Link href={`/profile/${props.authorId}`}>
<Avatar
img={props.authorAvatar}
rounded={true}
bordered={true}
size="md"
className="flex-row-reverse gap-2"
>
<div className="font-medium dark:text-white">
<div className="text-lg">{props.authorLogin}</div>
<div className="text-right text-gray-500">Автор</div>
</div>
</Avatar>
</Link>
</div>
<div className="min-w-full aspect-video">
<img src={props.image} className="w-full rounded-lg" />
</div>
<div className="flex flex-col gap-1">
<p className="text-xl font-bold">{props.title}</p>
<p className="whitespace-pre-wrap">{props.description}</p>
</div>
</Card>
);
};

View file

@ -0,0 +1,58 @@
import { Card } from "flowbite-react";
export const CollectionInfoLists = (props: {
completed: number;
planned: number;
abandoned: number;
delayed: number;
watching: number;
total: number;
}) => {
return (
<Card className="w-full max-w-full lg:max-w-[48%] h-fit ">
<div
className="flex w-full h-8 overflow-hidden rounded-md"
style={
{
"--width-of-one": "5",
"--watching-percent": `calc(var(--width-of-one) * (${props.watching} / ${props.total} * 100%))`,
"--planned-percent": `calc(var(--width-of-one) * (${props.planned} / ${props.total} * 100%))`,
"--watched-percent": `calc(var(--width-of-one) * (${props.completed} / ${props.total} * 100%))`,
"--delayed-percent": `calc(var(--width-of-one) * (${props.delayed} / ${props.total} * 100%))`,
"--abandoned-percent": `calc(var(--width-of-one) * (${props.abandoned} / ${props.total} * 100%))`,
"--no-list-percent": `calc(var(--width-of-one) * (${props.total - (props.watching - props.planned - props.completed - props.delayed - props.abandoned)} / ${props.total} * 100%))`,
} as React.CSSProperties
}
>
<div className={`bg-green-500 w-[var(--watching-percent)]`}></div>
<div className={`bg-purple-500 w-[var(--planned-percent)]`}></div>
<div className={`bg-blue-500 w-[var(--watched-percent)]`}></div>
<div className={`bg-yellow-300 w-[var(--delayed-percent)]`}></div>
<div className={`bg-red-500 w-[var(--abandoned-percent)]`}></div>
<div className={`bg-gray-400 w-[var(--no-list-percent)]`}></div>
</div>
<div className="flex flex-wrap w-full gap-4">
<p>
<span className="inline-block w-3 h-3 mr-2 bg-green-500 rounded-sm"></span>
Смотрю <span className="font-bold">{props.watching}</span>
</p>
<p>
<span className="inline-block w-3 h-3 mr-2 bg-purple-500 rounded-sm"></span>
В планах <span className="font-bold">{props.planned}</span>
</p>
<p>
<span className="inline-block w-3 h-3 mr-2 bg-blue-500 rounded-sm"></span>
Просмотрено <span className="font-bold">{props.completed}</span>
</p>
<p>
<span className="inline-block w-3 h-3 mr-2 bg-yellow-300 rounded-sm"></span>
Отложено <span className="font-bold">{props.delayed}</span>
</p>
<p>
<span className="inline-block w-3 h-3 mr-2 bg-red-500 rounded-sm"></span>
Брошено <span className="font-bold">{props.abandoned}</span>
</p>
</div>
</Card>
);
};

View file

@ -0,0 +1,118 @@
"use client";
import useSWRInfinite from "swr/infinite";
import useSWR from "swr";
import { Spinner } from "#/components/Spinner/Spinner";
import { useState, useEffect } from "react";
import { useScrollPosition } from "#/hooks/useScrollPosition";
import { useUserStore } from "../store/auth";
import { Button, Card } from "flowbite-react";
import { ENDPOINTS } from "#/api/config";
import { useRouter } from "next/navigation";
import { ReleaseSection } from "#/components/ReleaseSection/ReleaseSection";
import { CollectionInfoBasics } from "#/components/CollectionInfo/CollectionInfo.Basics";
import { CollectionInfoLists } from "#/components/CollectionInfo/CollectionInfoLists";
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 ViewCollectionPage = (props: { id: number }) => {
const userStore = useUserStore();
const [isLoadingEnd, setIsLoadingEnd] = useState(false);
const router = useRouter();
function useFetchCollectionInfo() {
let url: string = `${ENDPOINTS.collection.base}/${props.id}`;
if (userStore.token) {
url += `?token=${userStore.token}`;
}
const { data, isLoading } = useSWR(url, fetcher);
return [data, isLoading];
}
const getKey = (pageIndex: number, previousPageData: any) => {
if (previousPageData && !previousPageData.content.length) return null;
let url: string = `${ENDPOINTS.collection.base}/${props.id}/releases/${pageIndex}`;
if (userStore.token) {
url += `?token=${userStore.token}`;
}
return url;
};
const [collectionInfo, collectionInfoIsLoading] = useFetchCollectionInfo();
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">
{collectionInfoIsLoading ? (
<Spinner />
) : (
<>
<div className="flex flex-col flex-wrap gap-4 px-2 sm:flex-row">
<CollectionInfoBasics
image={collectionInfo.collection.image}
title={collectionInfo.collection.title}
description={collectionInfo.collection.description}
authorAvatar={collectionInfo.collection.creator.avatar}
authorLogin={collectionInfo.collection.creator.login}
authorId={collectionInfo.collection.creator.id}
creationDate={collectionInfo.collection.creation_date}
updateDate={collectionInfo.collection.last_update_date}
/>
{userStore.token && !isLoading && (
<CollectionInfoLists
completed={collectionInfo.completed_count}
planned={collectionInfo.plan_count}
abandoned={collectionInfo.dropped_count}
delayed={collectionInfo.hold_on_count}
watching={collectionInfo.watching_count}
total={data[0].total_count}
/>
)}
</div>
{isLoading || !content || !isLoadingEnd ? (
<Spinner />
) : (
<ReleaseSection content={content} />
)}
</>
)}
</main>
);
};