diff --git a/app/App.tsx b/app/App.tsx index 34052c1..eaf472a 100644 --- a/app/App.tsx +++ b/app/App.tsx @@ -72,7 +72,7 @@ export const App = (props) => { className={`${inter.className} overflow-x-hidden dark:bg-[#0d1117] dark:text-white`} > -
+
{props.children}
0 && hours > 0 && minutes > 0) return `${dayDisplay}, ${hourDisplay}, ${minuteDisplay}`; + if (days > 0 && hours > 0 && minutes > 0) + return `${dayDisplay}, ${hourDisplay}, ${minuteDisplay}`; if (days > 0 && hours > 0) return `${dayDisplay}, ${hourDisplay}`; if (days > 0 && minutes > 0) return `${dayDisplay}, ${minuteDisplay}`; if (hours > 0 && minutes > 0) return `${hourDisplay}, ${minuteDisplay}`; @@ -294,65 +295,480 @@ const StatusList: Record = { announce: 3, }; -export async function _FetchHomePageReleases( - status: string, - token: string | null, - page: string | number = 0 -) { - let statusId: null | number = null; - let categoryId: null | number = null; - if (status == "films") { - categoryId = 2; - } else { - statusId = StatusList[status]; - } - - const body = { - country: null, - season: null, - sort: 0, - studio: null, - age_ratings: [], - category_id: categoryId, - end_year: null, - episode_duration_from: null, - episode_duration_to: null, +export const FilterCountry = ["Япония", "Китай", "Южная Корея"]; +export const FilterCategoryIdToString: Record = { + 1: "Сериал", + 2: "Полнометражный фильм", + 3: "OVA", + 4: "Дорама", +}; +export const FilterGenre = { + uncategorized: { + name: "Нет категории", + genres: [ + "авангард", + "гурман", + "драма", + "комедия", + "повседневность", + "приключения", + "романтика", + "сверхъестественное", + "спорт", + "тайна", + "триллер", + "ужасы", + "фантастика", + "фэнтези", + "экшен", + "эротика", + "этти", + ], + }, + audience: { + name: "Аудитория", + genres: [ + "детское", + "дзёсей", + "сэйнэн", + "сёдзё", + "сёдзё-ай", + "сёнен", + "сёнен-ай", + ], + }, + theme: { + name: "Тематика", + genres: [ + "CGDCT", + "антропоморфизм", + "боевые искусства", + "вампиры", + "взрослые персонажи", + "видеоигры", + "военное", + "выживание", + "гарем", + "гонки", + "городское фэнтези", + "гэг-юмор", + "детектив", + "жестокость", + "забота о детях", + "злодейка", + "игра с высокими ставками", + "идолы (жен.)", + "идолы (муж.)", + "изобразительное искусство", + "исполнительское искусство", + "исторический", + "исэкай", + "иясикэй", + "командный спорт", + "космос", + "кроссдрессинг", + "культура отаку", + "любовный многоугольник", + "магическая смена пола", + "махо-сёдзё", + "медицина", + "меха", + "мифология", + "музыка", + "образовательное", + "организованная преступность", + "пародия", + "питомцы", + "психологическое", + "путешествие во времени", + "работа", + "реверс-гарем", + "реинкарнация", + "романтический подтекст", + "самураи", + "спортивные единоборства", + "стратегические игры", + "супер сила", + "удостоено наград", + "хулиганы", + "школа", + "шоу-бизнес", + ], + }, +}; +export const FilterProfileListIdToString: Record = { + 0: "Избранное", + 1: "Смотрю", + 2: "В планах", + 3: "Просмотрено", + 4: "Отложено", + 5: "Брошено", +}; +export const FilterStudio = [ + "Неважно", + "A-1 Pictures", + "A.C.G.T", + "ACTAS, Inc", + "ACiD FiLM", + "AIC A.S.T.A", + "AIC PLUS", + "AIC Spirits", + "AIC", + "Animac", + "ANIMATE", + "Aniplex", + "ARMS", + "Artland", + "ARTMIC Studios", + "Asahi Production", + "Asia-Do", + "ASHI", + "Asread", + "Asmik Ace", + "Aubeck", + "BM Entertainment", + "Bandai Visua", + "Barnum Studio", + "Bee Train", + "BeSTACK", + "Blender Foundation", + "Bones", + "Brains Base", + "Bridge", + "Cinema Citrus", + "Chaos Project", + "Cherry Lips", + "David Production", + "Daume", + "Doumu", + "Dax International", + "DLE INC", + "Digital Frontier", + "Digital Works", + "Diomedea", + "DIRECTIONS Inc", + "Dogakobo", + "Dofus", + "Encourage Films", + "Feel", + "Fifth Avenue", + "Five Ways", + "Fuji TV", + "Foursome", + "GRAM Studio", + "G&G Entertainment", + "Gainax", + "GANSIS", + "Gathering", + "Gonzino", + "Gonzo", + "GoHands", + "Green Bunny", + "Group TAC", + "Hal Film Maker", + "Hasbro Studios", + "h.m.p", + "Himajin", + "Hoods Entertainment", + "Idea Factory", + "J.C.Staff", + "KANSAI", + "Kaname Production", + "Kitty Films", + "Knack", + "Kokusai Eigasha", + "KSS (студия)", + "Kyoto Animation", + "Lemon Heart", + "LMD", + "Madhouse Studios", + "Magic Bus", + "Manglobe Inc.", + "Manpuku Jinja", + "MAPPA", + "Milky", + "Minamimachi Bugyosho", + "Media Blasters", + "Mook Animation", + "Moonrock", + "MOVIC", + "Mushi Productions", + "Natural High", + "Nippon Animation", + "Nomad", + "Lerche", + "OB Planning", + "Office AO", + "Ordet", + "Oriental Light and Magic", + "OLM Inc.", + "P.A. Works", + "Palm Studio", + "Pastel", + "Phoenix Entertainment", + "Picture Magic", + "Pink", + "Pink Pineapple", + "Planet", + "Plum", + "PPM", + "Primastea", + "Production I.G", + "Project No.9", + "Radix", + "Rikuentai", + "Robot", + "Satelight", + "Seven", + "Seven Arcs", + "Shaft", + "Silver Link", + "Shinei Animation", + "Shogakukan Music & Digital Entertainment", + "Soft on Demand", + "Starchild Records", + "Studio 9 Maiami", + "Studio Tulip", + "Studio 4°C", + "Studio e.go!", + "Studio A.P.P.P", + "Studio Barcelona", + "Studio Blanc", + "Studio Comet", + "Studio Deen", + "Studio Fantasia", + "Studio Flag", + "Studio Gallop", + "Studio Ghibli", + "Studio Guts", + "Studio Gokumi", + "Studio Rikka", + "Studio Hibari", + "Studio Junio", + "Studio Khara", + "Studio Live", + "Studio Matrix", + "Studio Pierrot", + "Studio Egg", + "Sunrise", + "Synergy SP", + "Synergy Japan", + "Tatsunoko Production", + "Tele-Cartoon Japan", + "Telecom Animation Film", + "Tezuka Productions", + "The Answer Studio", + "TMS", + "TNK", + "Toei Animation", + "Tokyo Kids", + "TYO Animations", + "Transarts", + "Triangle Staff", + "Trinet Entertainment", + "Ufotable", + "Vega Entertainment", + "Victor Entertainment", + "Viewworks", + "White Fox", + "Wonder Farm", + "XEBEC-M2", + "Xebec", + "Yumeta Company", + "Zexcs", + "Zuiyo Eizo", + "8bit", +]; +export const FilterSource = [ + "Оригинал", + "Манга", + "Веб-манга", + "Енкома", + "Ранобэ", + "Новелла", + "Веб-новелла", + "Визуальная новелла", + "Игра", + "Карточная игра", + "Книга", + "Книга с картинками", + "Музыка", + "Радио", + "Более одного", + "Другое", +]; +export const FilterYear = Array.from({ length: 200 }, (_, i) => 1900 + i); // 1900-2100 years around now +export const FilterSeasonIdToString = { + 1: "Зима", + 2: "Весна", + 3: "Лето", + 4: "Осень", +}; +export const FilterEpisodeCount = [ + { + name: "Неважно", episodes_from: null, episodes_to: null, - genres: [], - profile_list_exclusions: [], - start_year: null, - status_id: statusId, - types: [], - is_genres_exclude_mode_enabled: false, - }; + }, + { + name: "От 1 до 12", + episodes_from: 1, + episodes_to: 12, + }, + { + name: "От 13 до 25", + episodes_from: 13, + episodes_to: 25, + }, + { + name: "От 26 до 100", + episodes_from: 26, + episodes_to: 100, + }, + { + name: "Больше 100", + episodes_from: 100, + episodes_to: null, + }, +]; +export const FilterEpisodeDuration = [ + { + name: "Неважно", + episode_duration_from: null, + episode_duration_to: null, + }, + { + name: "До 10 минут", + episode_duration_from: 1, + episode_duration_to: 10, + }, + { + name: "До 30 минут", + episode_duration_from: 1, + episode_duration_to: 30, + }, + { + name: "Более 30 минут", + episode_duration_from: 30, + episode_duration_to: null, + }, +]; +export const FilterStatusIdToString = { + 1: "Вышел", + 2: "Выходит", + 3: "Анонс", +}; +export const FilterAgeRatingToString = { + 1: "0+", + 2: "6+", + 3: "12+", + 4: "16+", + 5: "18+", +}; +export const FilterSortToString = { + 0: "По дате добавления", + 1: "По рейтингу", + 2: "По годам", + 3: "По популярности", +}; +export type Filter = { + country: null | string; + category_id: null | number; + genres: string[]; + is_genres_exclude_mode_enabled: boolean; + profile_list_exclusions: number[]; + types: number[]; // fetched from /type/all + studio: string[]; + source: null | string; + start_year: null | number; + end_year: null | number; + season: null | number; + episodes_from: null | number; + episodes_to: null | number; + episode_duration_from: null | number; + episode_duration_to: null | number; + status_id: null | number; + age_ratings: number[]; + sort: number; +}; + +export const FilterDefault: Filter = { + country: null, + season: null, + sort: 0, + source: null, + studio: null, + age_ratings: [], + category_id: null, + end_year: null, + episode_duration_from: null, + episode_duration_to: null, + episodes_from: null, + episodes_to: null, + genres: [], + is_genres_exclude_mode_enabled: false, + profile_list_exclusions: [], + start_year: null, + status_id: null, + types: [], +}; + +export async function FetchFilter( + { + country, + category_id, + genres, + is_genres_exclude_mode_enabled, + profile_list_exclusions, + types, + studio, + source, + start_year, + end_year, + season, + episodes_from, + episodes_to, + episode_duration_from, + episode_duration_to, + status_id, + age_ratings, + sort, + }: Filter, + page: number, + token: null | string +) { let url: string; url = `${ENDPOINTS.filter}/${page}`; if (token) { url += `?token=${token}`; } - const data: Object = fetch(url, { - method: "POST", - headers: HEADERS, - body: JSON.stringify(body), - }) - .then((response) => { - if (response.ok) { - return response.json(); - } else { - throw new Error("Error fetching data"); - } + const { data, error } = await fetchDataViaPost( + url, + JSON.stringify({ + country, + category_id, + genres, + is_genres_exclude_mode_enabled, + profile_list_exclusions, + types, + studio, + source, + start_year, + end_year, + season, + episodes_from, + episodes_to, + episode_duration_from, + episode_duration_to, + status_id, + age_ratings, + sort, }) - .then((data: Object) => { - return data; - }) - .catch((error) => { - console.log(error); - return null; - }); - return data; + ); + + return [data, error]; } export const BookmarksList = { diff --git a/app/discovery/popular/page.tsx b/app/discovery/popular/page.tsx new file mode 100644 index 0000000..f16fc9d --- /dev/null +++ b/app/discovery/popular/page.tsx @@ -0,0 +1,12 @@ +import { DiscoverPage } from "#/pages/Discover"; + +export const metadata = { + title: "Обзор - Популярное", + description: "Популярные релизы", +}; + +export const dynamic = "force-static"; + +export default function Discover() { + return ; +} diff --git a/app/pages/Index.tsx b/app/pages/Index.tsx index c40c057..ef6eef8 100644 --- a/app/pages/Index.tsx +++ b/app/pages/Index.tsx @@ -3,15 +3,22 @@ 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"; +import { FetchFilter } from "#/api/utils"; import { usePreferencesStore } from "#/store/preferences"; import { useRouter } from "next/navigation"; +import { + ListAnnounce, + ListFilms, + ListFinished, + ListLast, + ListOngoing, +} from "./IndexFilters"; export function IndexPage() { const token = useUserStore((state) => state.token); const preferenceStore = usePreferencesStore(); - const router = useRouter() + const router = useRouter(); const [isLoading, setIsLoading] = useState(true); const [lastReleasesData, setLastReleasesData] = useState(null); const [ongoingReleasesData, setOngoingReleasesData] = useState(null); @@ -21,7 +28,9 @@ export function IndexPage() { useEffect(() => { if (preferenceStore.params.skipToCategory.enabled) { - router.push(`/home/${preferenceStore.params.skipToCategory.homeCategory}`); + router.push( + `/home/${preferenceStore.params.skipToCategory.homeCategory}` + ); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -35,11 +44,19 @@ export function IndexPage() { 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); + const [lastReleases] = await FetchFilter(ListLast.filter, 0, token); + const [ongoingReleases] = await FetchFilter(ListOngoing.filter, 0, token); + const [announceReleases] = await FetchFilter( + ListAnnounce.filter, + 0, + token + ); + const [finishedReleases] = await FetchFilter( + ListFinished.filter, + 0, + token + ); + const [filmsReleases] = await FetchFilter(ListFilms.filter, 0, token); setLastReleasesData(lastReleases); setOngoingReleasesData(ongoingReleases); @@ -56,16 +73,12 @@ export function IndexPage() { return ( <> - {lastReleasesData ? ( + {lastReleasesData && ( - ) : ( -
- -
)} {finishedReleasesData && ( )} - {!isLoading && - !lastReleasesData && - !finishedReleasesData && - !ongoingReleasesData && - !announceReleasesData && ( -
-

Ошибка загрузки контента...

-
- )} + {isLoading && ( +
+ +
+ )} ); } diff --git a/app/pages/IndexFilters.tsx b/app/pages/IndexFilters.tsx new file mode 100644 index 0000000..2ea2d50 --- /dev/null +++ b/app/pages/IndexFilters.tsx @@ -0,0 +1,26 @@ +import { Filter, FilterDefault } from "#/api/utils"; + +export const ListLast = { + name: "Последнее", + filter: FilterDefault, +}; + +export const ListOngoing = { + name: "Онгоинги", + filter: { ...FilterDefault, status_id: 2 }, +}; + +export const ListAnnounce = { + name: "Анонсы", + filter: { ...FilterDefault, status_id: 3 }, +}; + +export const ListFinished = { + name: "Завершённые", + filter: { ...FilterDefault, status_id: 1 }, +}; + +export const ListFilms = { + name: "Фильмы", + filter: { ...FilterDefault, category_id: 2, status_id: null }, +};