mirror of
https://github.com/Radiquum/AniX.git
synced 2025-04-28 17:09:41 +05:00
Merge remote-tracking branch 'origin/feat_player'
This commit is contained in:
parent
bb437fe7ca
commit
25e31a7799
62 changed files with 1508 additions and 701 deletions
|
@ -1,16 +1,22 @@
|
|||
"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";
|
||||
import { ReleaseCourusel } from "#/components/ReleaseCourusel/ReleaseCourusel";
|
||||
import { Spinner } from "#/components/Spinner/Spinner";
|
||||
const fetcher = (...args: any) =>
|
||||
fetch([...args] as any).then((res) => res.json());
|
||||
import { useUserStore } from "#/store/auth";
|
||||
import { BookmarksList } from "#/api/utils";
|
||||
import { ENDPOINTS } from "#/api/config";
|
||||
|
||||
export function BookmarksPage() {
|
||||
const token = useUserStore((state) => state.token);
|
||||
|
||||
function useFetchReleases(list) {
|
||||
let url;
|
||||
url = `/api/bookmarks?list=${list}&token=${token}`;
|
||||
function useFetchReleases(listName: string) {
|
||||
let url: string;
|
||||
|
||||
if (token) {
|
||||
url = `${ENDPOINTS.user.bookmark}/all/${BookmarksList[listName]}/0?token=${token}`;
|
||||
}
|
||||
|
||||
const { data } = useSWR(url, fetcher);
|
||||
return [data];
|
||||
|
@ -58,15 +64,13 @@ export function BookmarksPage() {
|
|||
content={watchedData.content}
|
||||
/>
|
||||
)}
|
||||
{delayedData &&
|
||||
delayedData.content &&
|
||||
delayedData.content.length > 0 && (
|
||||
<ReleaseCourusel
|
||||
sectionTitle="Отложено"
|
||||
showAllLink="/bookmarks/delayed"
|
||||
content={delayedData.content}
|
||||
/>
|
||||
)}
|
||||
{delayedData && delayedData.content && delayedData.content.length > 0 && (
|
||||
<ReleaseCourusel
|
||||
sectionTitle="Отложено"
|
||||
showAllLink="/bookmarks/delayed"
|
||||
content={delayedData.content}
|
||||
/>
|
||||
)}
|
||||
{abandonedData &&
|
||||
abandonedData.content &&
|
||||
abandonedData.content.length > 0 && (
|
|
@ -1,34 +1,37 @@
|
|||
"use client";
|
||||
import useSWRInfinite from "swr/infinite";
|
||||
import { ReleaseSection } from "@/app/components/ReleaseSection/ReleaseSection";
|
||||
import { Spinner } from "@/app/components/Spinner/Spinner";
|
||||
import { ReleaseSection } from "#/components/ReleaseSection/ReleaseSection";
|
||||
import { Spinner } from "#/components/Spinner/Spinner";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useScrollPosition } from "@/app/hooks/useScrollPosition";
|
||||
import { useScrollPosition } from "#/hooks/useScrollPosition";
|
||||
import { useUserStore } from "../store/auth";
|
||||
import { Dropdown } from "flowbite-react";
|
||||
import { sort } from "./common";
|
||||
import { ENDPOINTS } from "#/api/config";
|
||||
import { BookmarksList, SortList } from "#/api/utils";
|
||||
|
||||
const fetcher = async (url) => {
|
||||
const fetcher = async (url: string) => {
|
||||
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;
|
||||
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 function BookmarksCategoryPage(props) {
|
||||
export function BookmarksCategoryPage(props: any) {
|
||||
const token = useUserStore((state) => state.token);
|
||||
const [selectedSort, setSelectedSort] = useState(0);
|
||||
const [isLoadingEnd, setIsLoadingEnd] = useState(false);
|
||||
|
||||
const getKey = (pageIndex, previousPageData) => {
|
||||
const getKey = (pageIndex: number, previousPageData: any) => {
|
||||
if (previousPageData && !previousPageData.content.length) return null;
|
||||
return `/api/bookmarks?list=${props.slug}&page=${pageIndex}&token=${token}&sort=${sort.values[selectedSort].value}`;
|
||||
if (token) {
|
||||
return `${ENDPOINTS.user.bookmark}/all/${BookmarksList[props.slug]}/${pageIndex}?token=${token}&sort=${sort.values[selectedSort].id}`;
|
||||
}
|
||||
};
|
||||
|
||||
const { data, error, isLoading, size, setSize } = useSWRInfinite(
|
|
@ -1,20 +1,20 @@
|
|||
"use client";
|
||||
import useSWRInfinite from "swr/infinite";
|
||||
import { ReleaseSection } from "@/app/components/ReleaseSection/ReleaseSection";
|
||||
import { Spinner } from "@/app/components/Spinner/Spinner";
|
||||
import { ReleaseSection } from "#/components/ReleaseSection/ReleaseSection";
|
||||
import { Spinner } from "#/components/Spinner/Spinner";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useScrollPosition } from "@/app/hooks/useScrollPosition";
|
||||
import { useScrollPosition } from "#/hooks/useScrollPosition";
|
||||
import { useUserStore } from "../store/auth";
|
||||
import { Dropdown } from "flowbite-react";
|
||||
import { sort } from "./common";
|
||||
import { ENDPOINTS } from "#/api/config";
|
||||
|
||||
const fetcher = async (url) => {
|
||||
const fetcher = async (url: string) => {
|
||||
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;
|
||||
const error = new Error(`An error occurred while fetching the data. status: ${res.status}`);
|
||||
error.message = await res.json();
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
@ -26,9 +26,11 @@ export function FavoritesPage() {
|
|||
const [selectedSort, setSelectedSort] = useState(0);
|
||||
const [isLoadingEnd, setIsLoadingEnd] = useState(false);
|
||||
|
||||
const getKey = (pageIndex, previousPageData) => {
|
||||
const getKey = (pageIndex: number, previousPageData: any) => {
|
||||
if (previousPageData && !previousPageData.content.length) return null;
|
||||
return `/api/favorites?page=${pageIndex}&token=${token}&sort=${sort.values[selectedSort].value}`;
|
||||
if (token) {
|
||||
return `${ENDPOINTS.user.favorite}/all/${pageIndex}?token=${token}&sort=${sort.values[selectedSort].id}`;
|
||||
}
|
||||
};
|
||||
|
||||
const { data, error, isLoading, size, setSize } = useSWRInfinite(
|
|
@ -1,18 +1,18 @@
|
|||
"use client";
|
||||
import useSWRInfinite from "swr/infinite";
|
||||
import { ReleaseSection } from "@/app/components/ReleaseSection/ReleaseSection";
|
||||
import { Spinner } from "@/app/components/Spinner/Spinner";
|
||||
import { ReleaseSection } from "#/components/ReleaseSection/ReleaseSection";
|
||||
import { Spinner } from "#/components/Spinner/Spinner";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useScrollPosition } from "@/app/hooks/useScrollPosition";
|
||||
import { useScrollPosition } from "#/hooks/useScrollPosition";
|
||||
import { useUserStore } from "../store/auth";
|
||||
import { ENDPOINTS } from "#/api/config";
|
||||
|
||||
const fetcher = async (url) => {
|
||||
const fetcher = async (url: string) => {
|
||||
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;
|
||||
const error = new Error(`An error occurred while fetching the data. status: ${res.status}`);
|
||||
error.message = await res.json();
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
@ -23,9 +23,11 @@ export function HistoryPage() {
|
|||
const token = useUserStore((state) => state.token);
|
||||
const [isLoadingEnd, setIsLoadingEnd] = useState(false);
|
||||
|
||||
const getKey = (pageIndex, previousPageData) => {
|
||||
const getKey = (pageIndex: number, previousPageData: any) => {
|
||||
if (previousPageData && !previousPageData.content.length) return null;
|
||||
return `/api/history?page=${pageIndex}&token=${token}`;
|
||||
if (token) {
|
||||
return `${ENDPOINTS.user.history}/${pageIndex}?token=${token}`;
|
||||
}
|
||||
};
|
||||
|
||||
const { data, error, isLoading, size, setSize } = useSWRInfinite(
|
|
@ -1,64 +0,0 @@
|
|||
"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 IndexPage() {
|
||||
const userStore = useUserStore((state) => state);
|
||||
const token = userStore.token;
|
||||
|
||||
function useFetchReleases(status) {
|
||||
let url;
|
||||
|
||||
url = `/api/home?status=${status}`;
|
||||
if (token) {
|
||||
url += `&token=${token}`;
|
||||
}
|
||||
const { data } = useSWR(url, fetcher);
|
||||
return [data];
|
||||
}
|
||||
|
||||
const [lastReleasesData] = useFetchReleases("last");
|
||||
const [finishedReleasesData] = useFetchReleases("finished");
|
||||
const [ongoingReleasesData] = useFetchReleases("ongoing");
|
||||
const [announceReleasesData] = useFetchReleases("announce");
|
||||
|
||||
return (
|
||||
<main className="container flex flex-col pt-2 pb-16 mx-auto sm:pt-4 sm:pb-0">
|
||||
{lastReleasesData ? (
|
||||
<ReleaseCourusel
|
||||
sectionTitle="Последние релизы"
|
||||
showAllLink="/home/last"
|
||||
content={lastReleasesData.content}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex items-center justify-center min-w-full min-h-screen">
|
||||
<Spinner />
|
||||
</div>
|
||||
)}
|
||||
{finishedReleasesData && (
|
||||
<ReleaseCourusel
|
||||
sectionTitle="Завершенные релизы"
|
||||
showAllLink="/home/finished"
|
||||
content={finishedReleasesData.content}
|
||||
/>
|
||||
)}
|
||||
{ongoingReleasesData && (
|
||||
<ReleaseCourusel
|
||||
sectionTitle="В эфире"
|
||||
showAllLink="/home/ongoing"
|
||||
content={ongoingReleasesData.content}
|
||||
/>
|
||||
)}
|
||||
{announceReleasesData && (
|
||||
<ReleaseCourusel
|
||||
sectionTitle="Анонсированные релизы"
|
||||
showAllLink="/home/announce"
|
||||
content={announceReleasesData.content}
|
||||
/>
|
||||
)}
|
||||
</main>
|
||||
);
|
||||
}
|
94
app/pages/Index.tsx
Normal file
94
app/pages/Index.tsx
Normal file
|
@ -0,0 +1,94 @@
|
|||
"use client";
|
||||
import { ReleaseCourusel } from "#/components/ReleaseCourusel/ReleaseCourusel";
|
||||
import { Spinner } from "#/components/Spinner/Spinner";
|
||||
import { useUserStore } from "#/store/auth";
|
||||
import { useState, useEffect } from "react";
|
||||
import { _FetchHomePageReleases } from "#/api/utils";
|
||||
|
||||
export function IndexPage() {
|
||||
const token = useUserStore((state) => state.token);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [lastReleasesData, setLastReleasesData] = useState(null);
|
||||
const [ongoingReleasesData, setOngoingReleasesData] = useState(null);
|
||||
const [finishedReleasesData, setFinishedReleasesData] = useState(null);
|
||||
const [announceReleasesData, setAnnounceReleasesData] = useState(null);
|
||||
const [filmsReleasesData, setFilmsReleasesData] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
async function _loadReleases() {
|
||||
setIsLoading(true);
|
||||
setLastReleasesData(null);
|
||||
setOngoingReleasesData(null);
|
||||
setFinishedReleasesData(null);
|
||||
setAnnounceReleasesData(null);
|
||||
setFilmsReleasesData(null);
|
||||
|
||||
const lastReleases = await _FetchHomePageReleases("last", token);
|
||||
const ongoingReleases = await _FetchHomePageReleases("ongoing", token);
|
||||
const finishedReleases = await _FetchHomePageReleases("finished", token);
|
||||
const announceReleases = await _FetchHomePageReleases("announce", token);
|
||||
const filmsReleases = await _FetchHomePageReleases("films", token);
|
||||
|
||||
setLastReleasesData(lastReleases);
|
||||
setOngoingReleasesData(ongoingReleases);
|
||||
setFinishedReleasesData(finishedReleases);
|
||||
setAnnounceReleasesData(announceReleases);
|
||||
setFilmsReleasesData(filmsReleases);
|
||||
setIsLoading(false);
|
||||
}
|
||||
_loadReleases();
|
||||
}, [token]);
|
||||
|
||||
return (
|
||||
<main className="container flex flex-col pt-2 pb-20 mx-auto sm:pt-4 sm:pb-0">
|
||||
{lastReleasesData ? (
|
||||
<ReleaseCourusel
|
||||
sectionTitle="Последние релизы"
|
||||
showAllLink="/home/last"
|
||||
content={lastReleasesData.content}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex items-center justify-center min-w-full min-h-screen">
|
||||
<Spinner />
|
||||
</div>
|
||||
)}
|
||||
{finishedReleasesData && (
|
||||
<ReleaseCourusel
|
||||
sectionTitle="Завершенные релизы"
|
||||
showAllLink="/home/finished"
|
||||
content={finishedReleasesData.content}
|
||||
/>
|
||||
)}
|
||||
{ongoingReleasesData && (
|
||||
<ReleaseCourusel
|
||||
sectionTitle="В эфире"
|
||||
showAllLink="/home/ongoing"
|
||||
content={ongoingReleasesData.content}
|
||||
/>
|
||||
)}
|
||||
{announceReleasesData && (
|
||||
<ReleaseCourusel
|
||||
sectionTitle="Анонсированные релизы"
|
||||
showAllLink="/home/announce"
|
||||
content={announceReleasesData.content}
|
||||
/>
|
||||
)}
|
||||
{filmsReleasesData && (
|
||||
<ReleaseCourusel
|
||||
sectionTitle="Фильмы"
|
||||
showAllLink="/home/films"
|
||||
content={filmsReleasesData.content}
|
||||
/>
|
||||
)}
|
||||
{!isLoading &&
|
||||
!lastReleasesData &&
|
||||
!finishedReleasesData &&
|
||||
!ongoingReleasesData &&
|
||||
!announceReleasesData && (
|
||||
<div className="flex items-center justify-center min-w-full min-h-screen">
|
||||
<h1 className="text-2xl">Ошибка загрузки контента...</h1>
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
);
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
"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 IndexCategoryPage(props) {
|
||||
const userStore = useUserStore((state) => state);
|
||||
const [isLoadingEnd, setIsLoadingEnd] = useState(false);
|
||||
const token = userStore.token;
|
||||
const getKey = (pageIndex, previousPageData) => {
|
||||
if (previousPageData && !previousPageData.content.length) return null;
|
||||
if (token) {
|
||||
return `/api/home?status=${props.slug}&page=${pageIndex}&token=${token}`;
|
||||
}
|
||||
return `/api/home?status=${props.slug}&page=${pageIndex}`;
|
||||
};
|
||||
|
||||
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);
|
||||
setIsLoadingEnd(true);
|
||||
}
|
||||
}, [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">
|
||||
{content && content.length > 0 ? (
|
||||
<ReleaseSection
|
||||
sectionTitle={props.SectionTitleMapping[props.slug]}
|
||||
content={content}
|
||||
/>
|
||||
) : !isLoadingEnd ? (
|
||||
<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>
|
||||
В списке {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>
|
||||
)}
|
||||
</main>
|
||||
);
|
||||
}
|
77
app/pages/IndexCategory.tsx
Normal file
77
app/pages/IndexCategory.tsx
Normal file
|
@ -0,0 +1,77 @@
|
|||
"use client";
|
||||
import { ReleaseSection } from "#/components/ReleaseSection/ReleaseSection";
|
||||
import { Spinner } from "#/components/Spinner/Spinner";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useScrollPosition } from "#/hooks/useScrollPosition";
|
||||
import { useUserStore } from "../store/auth";
|
||||
import { _FetchHomePageReleases } from "#/api/utils";
|
||||
|
||||
export function IndexCategoryPage(props) {
|
||||
const token = useUserStore((state) => state.token);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [content, setContent] = useState(null);
|
||||
const [page, setPage] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
async function _loadInitialReleases() {
|
||||
setIsLoading(true);
|
||||
setContent(null);
|
||||
|
||||
const data: any = await _FetchHomePageReleases(props.slug, token, page);
|
||||
|
||||
setContent(data.content);
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
_loadInitialReleases();
|
||||
}, [token]);
|
||||
|
||||
useEffect(() => {
|
||||
async function _loadNextReleasesPage() {
|
||||
const data: any = await _FetchHomePageReleases(props.slug, token, page);
|
||||
const newContent = [...content, ...data.content];
|
||||
setContent(newContent);
|
||||
}
|
||||
if (content) {
|
||||
_loadNextReleasesPage();
|
||||
}
|
||||
}, [page]);
|
||||
|
||||
const scrollPosition = useScrollPosition();
|
||||
useEffect(() => {
|
||||
if (scrollPosition == 98) {
|
||||
setPage(page + 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">
|
||||
{content && content.length > 0 ? (
|
||||
<ReleaseSection
|
||||
sectionTitle={props.SectionTitleMapping[props.slug]}
|
||||
content={content}
|
||||
/>
|
||||
) : 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>
|
||||
В списке {props.SectionTitleMapping[props.slug]} пока ничего нет...
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<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={() => setPage(page + 1)}
|
||||
>
|
||||
<span className="w-10 h-10 iconify mdi--plus"> </span>
|
||||
<span className="text-lg">Загрузить ещё</span>
|
||||
</button>
|
||||
</main>
|
||||
);
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
"use client";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useUserStore } from "@/app/store/auth";
|
||||
import { setJWT } from "@/app/api/utils";
|
||||
import { useUserStore } from "#/store/auth";
|
||||
import { setJWT } from "#/api/utils";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
export function LoginPage() {
|
||||
const [login, setLogin] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [remember, setRemember] = useState(false);
|
||||
const [remember, setRemember]: any = useState(false);
|
||||
const userStore = useUserStore();
|
||||
const router = useRouter();
|
||||
|
||||
|
@ -68,17 +68,17 @@ export function LoginPage() {
|
|||
htmlFor="email"
|
||||
className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
|
||||
>
|
||||
Эл. почта
|
||||
Логин или эл. почта
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
type="text"
|
||||
name="email"
|
||||
id="email"
|
||||
className="bg-gray-50 border border-gray-300 text-gray-900 rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="name@company.com"
|
||||
value={login}
|
||||
onChange={(e) => setLogin(e.target.value)}
|
||||
required=""
|
||||
required={true}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
|
@ -94,7 +94,7 @@ export function LoginPage() {
|
|||
id="password"
|
||||
placeholder="••••••••"
|
||||
className="bg-gray-50 border border-gray-300 text-gray-900 rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
required=""
|
||||
required={true}
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
|
@ -107,7 +107,7 @@ export function LoginPage() {
|
|||
aria-describedby="remember"
|
||||
type="checkbox"
|
||||
className="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-primary-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-primary-600 dark:ring-offset-gray-800"
|
||||
required=""
|
||||
required={true}
|
||||
value={remember}
|
||||
onChange={(e) => setRemember(e.target.checked)}
|
||||
/>
|
|
@ -1,14 +1,14 @@
|
|||
"use client";
|
||||
import { useUserStore } from "@/app/store/auth";
|
||||
import { useUserStore } from "#/store/auth";
|
||||
import { useEffect, useState } from "react";
|
||||
import { fetchDataViaGet } from "../api/utils";
|
||||
import { Spinner } from "../components/Spinner/Spinner";
|
||||
import { Avatar, Card, Button, Table } from "flowbite-react";
|
||||
import { Chip } from "../components/Chip/Chip";
|
||||
import { unixToDate, minutesToTime } from "../api/utils";
|
||||
import { ReleaseLink } from "../components/ReleaseLink/ReleaseLink";
|
||||
import { ReleaseCourusel } from "#/components/ReleaseCourusel/ReleaseCourusel";
|
||||
|
||||
export const ProfilePage = (props) => {
|
||||
export const ProfilePage = (props: any) => {
|
||||
const authUser = useUserStore((state) => state);
|
||||
const [user, setUser] = useState(null);
|
||||
const [isMyProfile, setIsMyProfile] = useState(false);
|
||||
|
@ -74,7 +74,7 @@ export const ProfilePage = (props) => {
|
|||
];
|
||||
|
||||
return (
|
||||
<main className="container flex flex-col gap-4 px-4 pt-4 pb-32 mx-auto overflow-hidden xl:flex-row sm:pb-4">
|
||||
<main className="container flex flex-col gap-4 px-4 pt-4 pb-32 mx-auto overflow-hidden sm:pb-4">
|
||||
<div className="flex flex-col gap-4">
|
||||
<Card className="max-w-full">
|
||||
<div className="flex gap-2">
|
||||
|
@ -111,20 +111,17 @@ export const ProfilePage = (props) => {
|
|||
>
|
||||
{socials.map((social) => {
|
||||
if (!social.nickname) return null;
|
||||
if (social.name == "discord") return (
|
||||
<Button
|
||||
color="light"
|
||||
key={social.name}
|
||||
as="a"
|
||||
>
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<span
|
||||
className={`iconify-color h-4 w-4 sm:h-6 sm:w-6 ${social.icon}`}
|
||||
></span>
|
||||
{social.nickname}
|
||||
</div>
|
||||
</Button>
|
||||
)
|
||||
if (social.name == "discord")
|
||||
return (
|
||||
<Button color="light" key={social.name} as="a">
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<span
|
||||
className={`iconify-color h-4 w-4 sm:h-6 sm:w-6 ${social.icon}`}
|
||||
></span>
|
||||
{social.nickname}
|
||||
</div>
|
||||
</Button>
|
||||
);
|
||||
return (
|
||||
<Button
|
||||
color="light"
|
||||
|
@ -278,15 +275,11 @@ export const ProfilePage = (props) => {
|
|||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<Card className="w-full max-w-full min-w-full">
|
||||
<h1>Недавно просмотренные</h1>
|
||||
<div className="grid justify-center sm:grid-cols-[repeat(auto-fit,minmax(300px,1fr))] grid-cols-[100%] gap-2 min-w-full">
|
||||
{user.history.map((release) => {
|
||||
return <ReleaseLink key={release.id} {...release} />;
|
||||
})}
|
||||
</div>
|
||||
</Card>
|
||||
<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>
|
||||
);
|
359
app/pages/Release.tsx
Normal file
359
app/pages/Release.tsx
Normal file
|
@ -0,0 +1,359 @@
|
|||
"use client";
|
||||
|
||||
import useSWR from "swr";
|
||||
import { Spinner } from "#/components/Spinner/Spinner";
|
||||
const fetcher = (...args: any) =>
|
||||
fetch([...args] as any).then((res) => res.json());
|
||||
import { useUserStore } from "#/store/auth";
|
||||
import { Card, Dropdown, Button } from "flowbite-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { unixToDate, getSeasonFromUnix, minutesToTime } from "#/api/utils";
|
||||
import { ReleaseLink } from "#/components/ReleaseLink/ReleaseLink";
|
||||
import { ReleasePlayer } from "#/components/ReleasePlayer/ReleasePlayer";
|
||||
import { ENDPOINTS } from "#/api/config";
|
||||
import { Table } from "flowbite-react";
|
||||
import { ReleaseInfoSearchLink } from "#/components/ReleaseInfo/ReleaseInfo.SearchLink";
|
||||
import Link from "next/link";
|
||||
|
||||
const lists = [
|
||||
{ list: 0, name: "Не смотрю" },
|
||||
{ list: 1, name: "Смотрю" },
|
||||
{ list: 2, name: "В планах" },
|
||||
{ list: 3, name: "Просмотрено" },
|
||||
{ list: 4, name: "Отложено" },
|
||||
{ list: 5, name: "Брошено" },
|
||||
];
|
||||
|
||||
const weekDay = [
|
||||
"_",
|
||||
"каждый понедельник",
|
||||
"каждый вторник",
|
||||
"каждую среду",
|
||||
"каждый четверг",
|
||||
"каждую пятницу",
|
||||
"каждую субботу",
|
||||
"каждое воскресенье",
|
||||
];
|
||||
|
||||
const YearSeason = ["_", "Зима", "Весна", "Лето", "Осень"];
|
||||
|
||||
const DropdownTheme = {
|
||||
floating: {
|
||||
target:
|
||||
"flex-1 bg-blue-600 enabled:hover:bg-blue-700 focus:ring-4 focus:outline-none focus:ring-blue-300 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800",
|
||||
},
|
||||
};
|
||||
|
||||
export const ReleasePage = (props: any) => {
|
||||
const token = useUserStore((state) => state.token);
|
||||
const [userList, setUserList] = useState(0);
|
||||
const [userFavorite, setUserFavorite] = useState(false);
|
||||
|
||||
function useFetch(id: number) {
|
||||
let url: string;
|
||||
|
||||
url = `/api/release/${id}`;
|
||||
if (token) {
|
||||
url += `?token=${token}`;
|
||||
}
|
||||
const { data, isLoading, error } = useSWR(url, fetcher);
|
||||
return [data, isLoading, error];
|
||||
}
|
||||
const [data, isLoading, error] = useFetch(props.id);
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
const el = document.getElementById("note");
|
||||
if (el) {
|
||||
el.innerHTML = data.release.note;
|
||||
}
|
||||
setUserList(data.release.profile_list_status || 0);
|
||||
setUserFavorite(data.release.is_favorite);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
function _addToFavorite() {
|
||||
if (data && token) {
|
||||
setUserFavorite(!userFavorite);
|
||||
if (userFavorite) {
|
||||
fetch(
|
||||
`${ENDPOINTS.user.favorite}/delete/${data.release.id}?token=${token}`
|
||||
);
|
||||
} else {
|
||||
fetch(
|
||||
`${ENDPOINTS.user.favorite}/add/${data.release.id}?token=${token}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _addToList(list: number) {
|
||||
if (data && token) {
|
||||
setUserList(list);
|
||||
fetch(
|
||||
`${ENDPOINTS.user.bookmark}/add/${list}/${data.release.id}?token=${token}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return data ? (
|
||||
<main className="container px-4 pt-4 pb-24 mx-auto sm:pb-4">
|
||||
<div className="grid grid-cols-[100%] lg:grid-cols-[70%_30%] gap-2 justify-center">
|
||||
<div className="[grid-column:1] flex flex-col gap-2">
|
||||
<Card className="lg:[grid-column:1]">
|
||||
<div className="flex flex-col w-full h-full gap-4 lg:flex-row">
|
||||
<img
|
||||
className="w-[285px] max-h-[385px] object-cover border border-gray-200 rounded-lg shadow-md dark:border-gray-700"
|
||||
src={data.release.image}
|
||||
alt=""
|
||||
></img>
|
||||
<div className="flex flex-col max-w-2xl gap-2 text-sm md:text-base">
|
||||
<div className="flex flex-col gap-1">
|
||||
{data.release.title_ru && (
|
||||
<p className="text-xl font-bold text-black md:text-2xl">
|
||||
{data.release.title_ru}
|
||||
</p>
|
||||
)}
|
||||
{data.release.title_original && (
|
||||
<p className="text-sm text-gray-500 md:text-base">
|
||||
{data.release.title_original}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{data.release.note && (
|
||||
<div className="py-2 bg-blue-100 border-l-4 border-blue-700 rounded-md ">
|
||||
<div id="note" className="ml-2"></div>
|
||||
</div>
|
||||
)}
|
||||
{data.release.description && <p>{data.release.description}</p>}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
{data.release.status.name.toLowerCase() != "анонс" && (
|
||||
<ReleasePlayer id={props.id} />
|
||||
)}
|
||||
</div>
|
||||
<div className="[grid-column:1] lg:[grid-column:2] flex flex-col gap-2">
|
||||
<Card className="order-2 lg:order-1">
|
||||
<Table>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell className="py-0">
|
||||
{data.release.country ? (
|
||||
data.release.country.toLowerCase() == "япония" ? (
|
||||
<span className="w-8 h-8 iconify-color twemoji--flag-for-japan"></span>
|
||||
) : (
|
||||
<span className="w-8 h-8 iconify-color twemoji--flag-for-china"></span>
|
||||
)
|
||||
) : (
|
||||
<span className="w-8 h-8 iconify-color twemoji--flag-for-united-nations "></span>
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell className="font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||
{data.release.country && data.release.country}
|
||||
{(data.release.aired_on_date != 0 || data.release.year) &&
|
||||
", "}
|
||||
{data.release.aired_on_date != 0 &&
|
||||
`${getSeasonFromUnix(data.release.aired_on_date)} `}
|
||||
{data.release.year && `${data.release.year} г.`}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell className="py-0">
|
||||
<span className="w-8 h-8 iconify-color mdi--animation-play "></span>
|
||||
</Table.Cell>
|
||||
<Table.Cell className="font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||
{data.release.episodes_released
|
||||
? data.release.episodes_released
|
||||
: "?"}
|
||||
{"/"}
|
||||
{data.release.episodes_total
|
||||
? data.release.episodes_total + " эп. "
|
||||
: "? эп. "}
|
||||
{data.release.duration != 0 &&
|
||||
`по ${minutesToTime(data.release.duration)}`}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell className="py-0">
|
||||
<span className="w-8 h-8 iconify-color mdi--calendar "></span>
|
||||
</Table.Cell>
|
||||
<Table.Cell className="font-medium text-gray-900 dark:text-white">
|
||||
{data.release.category.name}
|
||||
{", "}
|
||||
{data.release.broadcast == 0
|
||||
? data.release.status.name.toLowerCase()
|
||||
: `выходит ${weekDay[data.release.broadcast]}`}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell className="py-0">
|
||||
<span className="w-8 h-8 iconify-color mdi--people "></span>
|
||||
</Table.Cell>
|
||||
<Table.Cell className="font-medium text-gray-900 dark:text-white">
|
||||
{data.release.studio && (
|
||||
<>
|
||||
{"Студия: "}
|
||||
{data.release.studio
|
||||
.split(", ")
|
||||
.map((studio: string, index: number) => {
|
||||
return (
|
||||
<>
|
||||
{index > 0 && ", "}
|
||||
<ReleaseInfoSearchLink
|
||||
title={studio}
|
||||
searchBy={1}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
{(data.release.author || data.release.director) && ", "}
|
||||
</>
|
||||
)}
|
||||
{data.release.author && (
|
||||
<>
|
||||
{"Автор: "}
|
||||
<ReleaseInfoSearchLink
|
||||
title={data.release.author}
|
||||
searchBy={3}
|
||||
/>
|
||||
{data.release.director && ", "}
|
||||
</>
|
||||
)}
|
||||
{data.release.director && (
|
||||
<>
|
||||
{"Режиссёр: "}
|
||||
<ReleaseInfoSearchLink
|
||||
title={data.release.director}
|
||||
searchBy={2}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell className="py-0">
|
||||
<span className="w-8 h-8 iconify-color mdi--tag "></span>
|
||||
</Table.Cell>
|
||||
<Table.Cell className="font-medium text-gray-900 dark:text-white">
|
||||
{data.release.genres &&
|
||||
data.release.genres
|
||||
.split(", ")
|
||||
.map((genre: string, index: number) => {
|
||||
return (
|
||||
<>
|
||||
{index > 0 && ", "}
|
||||
<ReleaseInfoSearchLink
|
||||
title={genre}
|
||||
searchBy={4}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{data.release.status.name.toLowerCase() == "анонс" && (
|
||||
<Table.Row>
|
||||
<Table.Cell className="py-0">
|
||||
<span className="w-8 h-8 iconify-color mdi--clock "></span>
|
||||
</Table.Cell>
|
||||
<Table.Cell className="font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||
{data.release.aired_on_date != 0 ? (
|
||||
unixToDate(data.release.aired_on_date)
|
||||
) : data.release.year ? (
|
||||
<>
|
||||
{data.release.season && data.release.season != 0
|
||||
? `${YearSeason[data.release.season]} `
|
||||
: ""}
|
||||
{data.release.year && `${data.release.year} г.`}
|
||||
</>
|
||||
) : (
|
||||
"Скоро"
|
||||
)}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
)}
|
||||
</Table.Body>
|
||||
</Table>
|
||||
</Card>
|
||||
{token && (
|
||||
<Card className="order-1 lg:order-2">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Dropdown
|
||||
label={lists[userList].name}
|
||||
dismissOnClick={true}
|
||||
theme={DropdownTheme}
|
||||
>
|
||||
{lists.map((list) => (
|
||||
<Dropdown.Item
|
||||
key={list.list}
|
||||
onClick={() => _addToList(list.list)}
|
||||
>
|
||||
{list.name}
|
||||
</Dropdown.Item>
|
||||
))}
|
||||
</Dropdown>
|
||||
<Button
|
||||
className="bg-blue-600 enabled:hover:bg-blue-700 focus:ring-4 focus:outline-none focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
|
||||
onClick={() => {
|
||||
_addToFavorite();
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className={`iconify w-6 h-6 ${
|
||||
userFavorite ? "mdi--heart" : "mdi--heart-outline"
|
||||
}`}
|
||||
></span>
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
{data.release.related_releases.length > 0 && (
|
||||
<Card className="order-3">
|
||||
<div>
|
||||
<div className="flex justify-between py-2 border-b-2 border-black">
|
||||
<h1>Связанные релизы</h1>
|
||||
{data.release.related && (
|
||||
<Link href={`/related/${data.release.related.id}`}>
|
||||
<div className="flex items-center">
|
||||
<p className="hidden sm:block">Показать все</p>
|
||||
<span className="w-6 h-6 iconify mdi--arrow-right"></span>
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-1 mt-2">
|
||||
{data.release.related_releases.map((release) => {
|
||||
if (release.id == data.release.id) return null;
|
||||
return <ReleaseLink key={release.id} {...release} />;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
) : (
|
||||
<main className="flex h-[100dvh] w-full justify-center items-center">
|
||||
<Spinner />
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
{
|
||||
/* <Chip
|
||||
bg_color={
|
||||
data.release.grade.toFixed(1) == 0
|
||||
? "hidden"
|
||||
: data.release.grade.toFixed(1) < 2
|
||||
? "bg-red-500"
|
||||
: data.release.grade.toFixed(1) < 3
|
||||
? "bg-orange-500"
|
||||
: data.release.grade.toFixed(1) < 4
|
||||
? "bg-yellow-500"
|
||||
: "bg-green-500"
|
||||
}
|
||||
name={data.release.grade.toFixed(1)}
|
||||
/> */
|
||||
}
|
|
@ -1,21 +1,20 @@
|
|||
"use client";
|
||||
import useSWRInfinite from "swr/infinite";
|
||||
import { ReleaseSection } from "@/app/components/ReleaseSection/ReleaseSection";
|
||||
import { RelatedSection } from "@/app/components/RelatedSection/RelatedSection";
|
||||
import { Spinner } from "@/app/components/Spinner/Spinner";
|
||||
import { ReleaseSection } from "#/components/ReleaseSection/ReleaseSection";
|
||||
import { RelatedSection } from "#/components/RelatedSection/RelatedSection";
|
||||
import { Spinner } from "#/components/Spinner/Spinner";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useScrollPosition } from "@/app/hooks/useScrollPosition";
|
||||
import { useScrollPosition } from "#/hooks/useScrollPosition";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { useUserStore } from "../store/auth";
|
||||
|
||||
const fetcher = async (url) => {
|
||||
const fetcher = async (url: string) => {
|
||||
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;
|
||||
const error = new Error(`An error occurred while fetching the data. status: ${res.status}`);
|
||||
error.message = await res.json();
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
@ -26,18 +25,34 @@ export function SearchPage() {
|
|||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const [query, setQuery] = useState(searchParams.get("q") || null);
|
||||
const where = searchParams.get("where") || null
|
||||
const searchBy = searchParams.get("searchBy") || null
|
||||
const list = searchParams.get("list") || null
|
||||
|
||||
const token = useUserStore((state) => state.token);
|
||||
|
||||
const getKey = (pageIndex, previousPageData) => {
|
||||
const getKey = (pageIndex: number, previousPageData: any) => {
|
||||
if (previousPageData && !previousPageData.releases.length) return null;
|
||||
|
||||
const url = new URL("/api/search", window.location.origin);
|
||||
url.searchParams.set("page", pageIndex);
|
||||
url.searchParams.set("page", pageIndex.toString());
|
||||
|
||||
if (token) {
|
||||
url.searchParams.set("token", token);
|
||||
}
|
||||
|
||||
if (where) {
|
||||
url.searchParams.set("where", where);
|
||||
}
|
||||
|
||||
if (searchBy) {
|
||||
url.searchParams.set("searchBy", searchBy);
|
||||
}
|
||||
|
||||
if (list) {
|
||||
url.searchParams.set("list", list);
|
||||
}
|
||||
|
||||
if (query) {
|
||||
url.searchParams.set("q", query);
|
||||
return url.toString();
|
|
@ -5,26 +5,32 @@ export const sort = {
|
|||
{
|
||||
name: "По добавлению новых",
|
||||
value: "adding_descending",
|
||||
id: 1
|
||||
},
|
||||
{
|
||||
name: "По добавлению старых",
|
||||
value: "adding_ascending",
|
||||
id: 2
|
||||
},
|
||||
{
|
||||
name: "По дате выхода новых",
|
||||
value: "year_descending",
|
||||
id: 3
|
||||
},
|
||||
{
|
||||
name: "По дате выхода старых",
|
||||
value: "year_ascending",
|
||||
id: 4
|
||||
},
|
||||
{
|
||||
name: "По алфавиту А-Я",
|
||||
value: "alphabet_descending",
|
||||
id: 5
|
||||
},
|
||||
{
|
||||
name: "По алфавиту Я-А",
|
||||
value: "alphabet_ascending",
|
||||
id: 6
|
||||
},
|
||||
],
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue