From 27554eb3738859278817bfb9133b391fd3e0d7db Mon Sep 17 00:00:00 2001 From: Radiquum Date: Wed, 20 Aug 2025 05:50:49 +0500 Subject: [PATCH 1/8] change how filters work --- app/api/config.ts | 8 +- app/pages/Search.tsx | 878 ++++++++++++++++++++++++++----------------- 2 files changed, 541 insertions(+), 345 deletions(-) diff --git a/app/api/config.ts b/app/api/config.ts index 853a361..957a4c9 100644 --- a/app/api/config.ts +++ b/app/api/config.ts @@ -51,7 +51,13 @@ export const ENDPOINTS = { } }, filter: `${API_PREFIX}/filter`, - search: `${API_URL}/search`, + search: { + profileList: `${API_PREFIX}/search/profile/list/`, + profileHistory: `${API_PREFIX}/search/history/`, + profileFavoriteCollection: `${API_PREFIX}/search/favoriteCollections/`, + profiles: `${API_PREFIX}/search/profiles/`, + releases: `${API_PREFIX}/search/releases/`, + }, statistic: { addHistory: `${API_PREFIX}/history/add`, markWatched: `${API_PREFIX}/episode/watch`, diff --git a/app/pages/Search.tsx b/app/pages/Search.tsx index bba699e..b3878d6 100644 --- a/app/pages/Search.tsx +++ b/app/pages/Search.tsx @@ -1,196 +1,155 @@ "use client"; -import useSWRInfinite from "swr/infinite"; -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 "#/hooks/useScrollPosition"; -import { useRouter } from "next/navigation"; import { useSearchParams } from "next/navigation"; -import { useUserStore } from "../store/auth"; -import { Button, Dropdown, DropdownItem, Modal, ModalBody, ModalFooter, ModalHeader } from "flowbite-react"; -import { CollectionsSection } from "#/components/CollectionsSection/CollectionsSection"; -import { UserSection } from "#/components/UserSection/UserSection"; -import { useSWRfetcher } from "#/api/utils"; +import { useRouter } from "next/navigation"; -const ListsMapping = { - watching: { - name: "Смотрю", - id: 1, - }, - planned: { - name: "В планах", - id: 2, - }, - watched: { - name: "Просмотрено", - id: 3, - }, - delayed: { - name: "Отложено", - id: 4, - }, - abandoned: { - name: "Заброшено", - id: 5, - }, -}; +import { + Button, + Dropdown, + DropdownItem, + Modal, + ModalBody, + ModalFooter, + ModalHeader, +} from "flowbite-react"; +import { useUserStore } from "#/store/auth"; -const TagMapping = { - name: { - name: "Названию", - id: 0, +const whereMapping = [ + { + id: "releases", + label: "Релизах", + auth: false, }, - studio: { - name: "Студии", - id: 1, + { + id: "profiles", + label: "Профилях", + auth: false, }, - director: { - name: "Режиссёру", - id: 2, + { + id: "list", + label: "Списках", + auth: true, }, - author: { - name: "Автору", - id: 3, + { + id: "history", + label: "Истории", + auth: true, }, - tag: { - name: "Тегу", - id: 4, + { + id: "favorites", + label: "Избранном", + auth: true, }, -}; + { + id: "collections", + label: "Коллекциях", + auth: true, + }, +]; -const WhereMapping = { - releases: "Релизах", - list: "Списках", - history: "Истории", - favorites: "Избранном", - collections: "Коллекциях", - profiles: "Профилях", +const searchByMapping = { + releases: [ + { + id: "name", + label: "Названию", + value: 0, + }, + { + id: "studio", + label: "Студии", + value: 1, + }, + { + id: "director", + label: "Режиссёру", + value: 2, + }, + { + id: "author", + label: "Автору", + value: 3, + }, + { + id: "tag", + label: "Тегу", + value: 4, + }, + ], + list: [ + { + id: "watching", + label: "Смотрю", + value: 1, + }, + { + id: "planned", + label: "В планах", + value: 2, + }, + { + id: "watched", + label: "Просмотрено", + value: 3, + }, + { + id: "delayed", + label: "Отложено", + value: 4, + }, + { + id: "abandoned", + label: "Заброшено", + value: 5, + }, + ], + none: [{ id: "none", label: "Нет", value: 0 }], }; export function SearchPage() { const router = useRouter(); const searchParams = useSearchParams(); - const [query, setQuery] = useState(searchParams.get("q") || ""); - const [searchVal, setSearchVal] = useState(searchParams.get("q") || ""); - const [where, setWhere] = useState(searchParams.get("where") || "releases"); - const [searchBy, setSearchBy] = useState( - searchParams.get("searchBy") || "name" - ); - const [list, setList] = useState(searchParams.get("list") || "watching"); + const userStore = useUserStore(); + const [query, setQuery] = useState(searchParams.get("query") || ""); + const [params, setParams] = useState(null); const [filtersModalOpen, setFiltersModalOpen] = useState(false); - const userStore = useUserStore(); - - const getKey = (pageIndex: number, previousPageData: any) => { - if (where == "releases") { - if (previousPageData && !previousPageData.releases.length) return null; - } else { - if (previousPageData && !previousPageData.content.length) return null; - } - - const url = new URL("/api/search", window.location.origin); - url.searchParams.set("page", pageIndex.toString()); - - if (userStore.token) { - url.searchParams.set("token", userStore.token); - } - - if (where) { - url.searchParams.set("where", where); - } - - if (where == "list" && list && ListsMapping.hasOwnProperty(list)) { - url.searchParams.set("list", ListsMapping[list].id); - } - - url.searchParams.set("searchBy", TagMapping[searchBy].id); - - if (query) { - url.searchParams.set("q", query); - return url.toString(); - } - return; - }; - - const { data, error, isLoading, size, setSize } = useSWRInfinite( - getKey, - useSWRfetcher, - { initialSize: 2, revalidateFirstPage: false } - ); - - const [content, setContent] = useState(null); useEffect(() => { - if (data) { - let allReleases = []; - if (where == "releases") { - for (let i = 0; i < data.length; i++) { - allReleases.push(...data[i].releases); - } - } else { - for (let i = 0; i < data.length; i++) { - allReleases.push(...data[i].content); - } - } - setContent(allReleases); + let _parsed = null; + try { + _parsed = JSON.parse(searchParams.get("params")); + } catch { + _parsed = { + where: "releases", + searchBy: "name", + }; } + setParams(_parsed); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [data]); - - const scrollPosition = useScrollPosition(); - useEffect(() => { - if (scrollPosition >= 98 && scrollPosition <= 99) { - setSize(size + 1); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [scrollPosition]); - - function _executeSearch(value: string) { - const Params = new URLSearchParams(window.location.search); - Params.set("q", value); - const url = new URL(`/search?${Params.toString()}`, window.location.origin); - setContent(null); - setQuery(value); - router.push(url.toString()); - } + }, []); useEffect(() => { - if (searchVal && searchVal.length % 4 == 1) { - _executeSearch(searchVal.trim()); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [searchVal]); + if (!params) return; - if (error) - return ( -
-
-

Ошибка

-

- Произошла ошибка поиска. Попробуйте обновить страницу или зайдите - позже. -

-
-
- ); + const url = new URL(`/search`, window.location.origin); + url.searchParams.set("params", JSON.stringify(params)); + router.replace(url.toString()); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [params]); + + if (!params) return <>; return ( - <> -
-
{ - e.preventDefault(); - _executeSearch(searchVal.trim()); - }} - > +
+
+
-
+
setSearchVal(e.target.value)} + // value={searchVal} + // onChange={(e) => setSearchVal(e.target.value)} />
- +
-
- {data && data[0].related && } - {content ? - content.length > 0 ? - <> - {where == "collections" ? - - : where == "profiles" ? - - : - } - - :
- -

Странно, аниме не найдено, попробуйте другой запрос...

-
- : isLoading && ( -
- -
- ) - } - {!content && !isLoading && !query && ( -
- -

Введите ваш запрос что-бы найти любимый тайтл

-
- )} -
- {( - data && - data.length > 1 && - (where == "releases" ? - data[data.length - 1].releases.length == 25 - : data[data.length - 1].content.length == 25) - ) ? - - : ""} + +

query: {query}

+

params: {JSON.stringify(params)}

{}} + params={params} + setParams={setParams} /> - +
); } const FiltersModal = (props: { isOpen: boolean; setIsOpen: any; - where: string; - setWhere: any; - list: string; - setList: any; isAuth: boolean; - searchBy: string; - setSearchBy: any; setContent: any; + params: any; + setParams: any; }) => { - const router = useRouter(); - const [where, setWhere] = useState(props.where); - const [list, setList] = useState(props.list); - const [searchBy, setSearchBy] = useState(props.searchBy); - - function _cancel() { - setWhere(props.where); - setList(props.list); - setSearchBy(props.searchBy); - props.setIsOpen(false); - } - - function _accept() { - const Params = new URLSearchParams(window.location.search); - - if (props.where != where) { - Params.set("where", where); - props.setWhere(where); - } - - if (where == "list") { - Params.set("list", list); - props.setList(list); - } else { - Params.delete("list"); - } - - if (!["profiles", "collections"].includes(where)) { - Params.set("searchBy", searchBy); - props.setSearchBy(searchBy); - } else { - Params.delete("searchBy"); - props.setSearchBy("name"); - } - - props.setContent(null); - - const url = new URL(`/search?${Params.toString()}`, window.location.origin); - router.push(url.toString()); - } + if (!props.params) return <>; return ( - _cancel()}> + props.setIsOpen(false)}> Фильтры -
-
-

Искать в

- - {Object.keys(WhereMapping).map((item) => { - if ( - ["list", "history", "collections", "favorites"].includes( - item - ) && - !props.isAuth - ) { - return <>; - } else { - return ( - setWhere(item)} - key={`where--${item}`} +
+
+

Искать в

+ item.id == props.params.where).label + } + color="blue" + > + {whereMapping.map((item) => { + return item.auth && !props.isAuth ? + <> + : + searchByMapping[item.id] ? + props.setParams({ + where: item.id, + searchBy: searchByMapping[item.id][0].id, + }) + : props.setParams({ where: item.id, searchBy: "none" }) + } + key={`where--${item.id}`} > - {WhereMapping[item]} - - ); - } + {item.label} + ; })}
+ {searchByMapping[props.params.where] ? +
+

Искать по

+ item.id == props.params.searchBy + ).label + } + color="blue" + > + {searchByMapping[props.params.where].map((item) => { + return ( + + props.setParams({ + where: props.params.where, + searchBy: item.id, + }) + } + key={`searchBy--${item.id}`} + > + {item.label} + + ); + })} + +
+ : <>}
- {props.isAuth && where == "list" && ListsMapping.hasOwnProperty(list) ? -
-
-

Список

- - {Object.keys(ListsMapping).map((item) => { - return ( - setList(item)} - key={`list--${item}`} - > - {ListsMapping[item].name} - - ); - })} - -
-
- : ""} - {!["profiles", "collections"].includes(where) ? -
-
-

Искать по

- - {Object.keys(TagMapping).map((item) => { - return ( - setSearchBy(item)} - key={`tag--${item}`} - > - {TagMapping[item].name} - - ); - })} - -
-
- : ""} - -
- - -
-
+ ); }; + +// import useSWRInfinite from "swr/infinite"; +// import { ReleaseSection } from "#/components/ReleaseSection/ReleaseSection"; +// import { RelatedSection } from "#/components/RelatedSection/RelatedSection"; +// import { Spinner } from "#/components/Spinner/Spinner"; +// import { useScrollPosition } from "#/hooks/useScrollPosition"; +// import { useUserStore } from "../store/auth"; +// import { Button, Dropdown, DropdownItem, Modal, ModalBody, ModalFooter, ModalHeader } from "flowbite-react"; +// import { CollectionsSection } from "#/components/CollectionsSection/CollectionsSection"; +// import { UserSection } from "#/components/UserSection/UserSection"; +// import { useSWRfetcher } from "#/api/utils"; + +// export function SearchPage() { +// const router = useRouter(); + +// const [searchVal, setSearchVal] = useState(searchParams.get("q") || ""); +// const [where, setWhere] = useState(searchParams.get("where") || "releases"); + +// const [list, setList] = useState(searchParams.get("list") || "watching"); +// const [filtersModalOpen, setFiltersModalOpen] = useState(false); + +// + +// const getKey = (pageIndex: number, previousPageData: any) => { +// if (where == "releases") { +// if (previousPageData && !previousPageData.releases.length) return null; +// } else { +// if (previousPageData && !previousPageData.content.length) return null; +// } + +// const url = new URL("/api/search", window.location.origin); +// url.searchParams.set("page", pageIndex.toString()); + +// if (userStore.token) { +// url.searchParams.set("token", userStore.token); +// } + +// if (where) { +// url.searchParams.set("where", where); +// } + +// if (where == "list" && list && ListsMapping.hasOwnProperty(list)) { +// url.searchParams.set("list", ListsMapping[list].id); +// } + +// url.searchParams.set("searchBy", TagMapping[searchBy].id); + +// if (query) { +// url.searchParams.set("q", query); +// return url.toString(); +// } +// return; +// }; + +// const { data, error, isLoading, size, setSize } = useSWRInfinite( +// getKey, +// useSWRfetcher, +// { initialSize: 2, revalidateFirstPage: false } +// ); + +// const [content, setContent] = useState(null); +// useEffect(() => { +// if (data) { +// let allReleases = []; +// if (where == "releases") { +// for (let i = 0; i < data.length; i++) { +// allReleases.push(...data[i].releases); +// } +// } else { +// for (let i = 0; i < data.length; i++) { +// allReleases.push(...data[i].content); +// } +// } +// setContent(allReleases); +// } +// // eslint-disable-next-line react-hooks/exhaustive-deps +// }, [data]); + +// const scrollPosition = useScrollPosition(); +// useEffect(() => { +// if (scrollPosition >= 98 && scrollPosition <= 99) { +// setSize(size + 1); +// } +// // eslint-disable-next-line react-hooks/exhaustive-deps +// }, [scrollPosition]); + +// function _executeSearch(value: string) { +// const Params = new URLSearchParams(window.location.search); +// Params.set("q", value); +// const url = new URL(`/search?${Params.toString()}`, window.location.origin); +// setContent(null); +// setQuery(value); +// router.push(url.toString()); +// } + +// useEffect(() => { +// if (searchVal && searchVal.length % 4 == 1) { +// _executeSearch(searchVal.trim()); +// } +// // eslint-disable-next-line react-hooks/exhaustive-deps +// }, [searchVal]); + +// if (error) +// return ( +//
+//
+//

Ошибка

+//

+// Произошла ошибка поиска. Попробуйте обновить страницу или зайдите +// позже. +//

+//
+//
+// ); + +// return ( +// <> +//
+//
{ +// e.preventDefault(); +// _executeSearch(searchVal.trim()); +// }} +// > + +//
+// +//
+//
+// {data && data[0].related && } +// {content ? +// content.length > 0 ? +// <> +// {where == "collections" ? +// +// : where == "profiles" ? +// +// : +// } +// +// :
+// +//

Странно, аниме не найдено, попробуйте другой запрос...

+//
+// : isLoading && ( +//
+// +//
+// ) +// } +// {!content && !isLoading && !query && ( +//
+// +//

Введите ваш запрос что-бы найти любимый тайтл

+//
+// )} +//
+// {( +// data && +// data.length > 1 && +// (where == "releases" ? +// data[data.length - 1].releases.length == 25 +// : data[data.length - 1].content.length == 25) +// ) ? +// +// : ""} +// +// +// ); +// } + +// const FiltersModal = (props: { +// isOpen: boolean; +// setIsOpen: any; +// where: string; +// setWhere: any; +// list: string; +// setList: any; +// isAuth: boolean; +// searchBy: string; +// setSearchBy: any; +// setContent: any; +// }) => { +// const router = useRouter(); +// const [where, setWhere] = useState(props.where); +// const [list, setList] = useState(props.list); +// const [searchBy, setSearchBy] = useState(props.searchBy); + +// function _cancel() { +// setWhere(props.where); +// setList(props.list); +// setSearchBy(props.searchBy); +// props.setIsOpen(false); +// } + +// function _accept() { +// const Params = new URLSearchParams(window.location.search); + +// if (props.where != where) { +// Params.set("where", where); +// props.setWhere(where); +// } + +// if (where == "list") { +// Params.set("list", list); +// props.setList(list); +// } else { +// Params.delete("list"); +// } + +// if (!["profiles", "collections"].includes(where)) { +// Params.set("searchBy", searchBy); +// props.setSearchBy(searchBy); +// } else { +// Params.delete("searchBy"); +// props.setSearchBy("name"); +// } + +// props.setContent(null); + +// const url = new URL(`/search?${Params.toString()}`, window.location.origin); +// router.push(url.toString()); +// } + +// return ( +// _cancel()}> +// Фильтры +// +//
+//
+//

Искать в

+// +// {Object.keys(WhereMapping).map((item) => { +// if ( +// ["list", "history", "collections", "favorites"].includes( +// item +// ) && +// !props.isAuth +// ) { +// return <>; +// } else { +// return ( +// setWhere(item)} +// key={`where--${item}`} +// > +// {WhereMapping[item]} +// +// ); +// } +// })} +// +//
+//
+// {props.isAuth && where == "list" && ListsMapping.hasOwnProperty(list) ? +//
+//
+//

Список

+// +// {Object.keys(ListsMapping).map((item) => { +// return ( +// setList(item)} +// key={`list--${item}`} +// > +// {ListsMapping[item].name} +// +// ); +// })} +// +//
+//
+// : ""} +// {!["profiles", "collections"].includes(where) ? +//
+//
+//

Искать по

+// +// {Object.keys(TagMapping).map((item) => { +// return ( +// setSearchBy(item)} +// key={`tag--${item}`} +// > +// {TagMapping[item].name} +// +// ); +// })} +// +//
+//
+// : ""} +//
+// +//
+// +// +//
+//
+//
+// ); +// }; From c8a7a38b522b507793ee14363d0337ec008980fc Mon Sep 17 00:00:00 2001 From: Radiquum Date: Fri, 22 Aug 2025 00:25:38 +0500 Subject: [PATCH 2/8] feat: add run-all services --- package.json | 1 + run-all.dev.js | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 run-all.dev.js diff --git a/package.json b/package.json index ec1be21..10c8582 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "private": true, "scripts": { "dev": "next dev", + "dev-with-services": "node ./run-all.dev.js", "build": "next build", "start": "next start", "lint": "next lint", diff --git a/run-all.dev.js b/run-all.dev.js new file mode 100644 index 0000000..13fbc9b --- /dev/null +++ b/run-all.dev.js @@ -0,0 +1,29 @@ +const { spawn } = require("child_process"); + +const npm = /^win/.test(process.platform) ? "npm.cmd" : "npm"; + +const client = spawn(npm, ["run", "dev"], { shell: true }); +const parser = spawn(npm, ["run", "serve"], { shell: true, cwd: "./player-parser" }); +const proxy = spawn(npm, ["run", "serve"], { shell: true, cwd: "./api-prox" }); + +const clientInfo = "\x1b[36m[client]\x1b[0m"; +const parserInfo = "\x1b[33m[parser]\x1b[0m"; +const proxyInfo = "\x1b[31m[proxy]\x1b[0m"; + +console.log(`${clientInfo} CMD: ${client.spawnargs.toString()}`); +console.log(`${clientInfo} PID: ${client.pid}`); +console.log(`${parserInfo} CMD: ${parser.spawnargs.toString()}`); +console.log(`${parserInfo} PID: ${parser.pid}`); +console.log(`${proxyInfo} CMD: ${proxy.spawnargs.toString()}`); +console.log(`${proxyInfo} PID: ${proxy.pid}`); +console.log(`\n`); + +client.stdout.on("data", (data) => + console.log(clientInfo, data.toString()) +); +parser.stdout.on("data", (data) => + console.log(parserInfo, data.toString()) +); +proxy.stdout.on("data", (data) => + console.log(proxyInfo, data.toString()) +); From ee68300501a05c1baaea4521d2ff59bab1e2ce6a Mon Sep 17 00:00:00 2001 From: Radiquum Date: Fri, 22 Aug 2025 00:41:17 +0500 Subject: [PATCH 3/8] fix: CORS headers for firefox --- api-prox/index.ts | 2 +- api-prox/shared.ts | 2 +- player-parser/index.ts | 2 +- player-parser/shared.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api-prox/index.ts b/api-prox/index.ts index 5603aa3..b653ef0 100644 --- a/api-prox/index.ts +++ b/api-prox/index.ts @@ -18,7 +18,7 @@ app.use(function (req, res, next) { res.header("Access-Control-Allow-Origin", req.headers.origin || "*"); res.header( "Access-Control-Allow-Headers", - "Origin, X-Requested-With, Content-Type, Accept, Sign" + "Origin, X-Requested-With, Content-Type, Accept, Sign, Allow, User-Agent" ); res.header("Access-Control-Allow-Methods", "GET,HEAD,POST,OPTIONS"); next(); diff --git a/api-prox/shared.ts b/api-prox/shared.ts index e50ed78..36144e0 100644 --- a/api-prox/shared.ts +++ b/api-prox/shared.ts @@ -1,7 +1,7 @@ export const corsHeaders = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": - "Origin, X-Requested-With, Content-Type, Accept, Sign", + "Origin, X-Requested-With, Content-Type, Accept, Sign, Allow, User-Agent", "Access-Control-Allow-Methods": "GET,HEAD,POST,OPTIONS", "Cache-Control": "no-cache", }; diff --git a/player-parser/index.ts b/player-parser/index.ts index 9f3cf5f..f98aab4 100644 --- a/player-parser/index.ts +++ b/player-parser/index.ts @@ -9,7 +9,7 @@ app.use(function (req, res, next) { res.header("Access-Control-Allow-Origin", req.headers.origin || "*"); res.header( "Access-Control-Allow-Headers", - "Origin, X-Requested-With, Content-Type, Accept" + "Origin, X-Requested-With, Content-Type, Accept, Allow, User-Agent" ); res.header("Access-Control-Allow-Methods", "GET,HEAD,POST,OPTIONS"); next(); diff --git a/player-parser/shared.ts b/player-parser/shared.ts index b03c5eb..a5e1a0a 100644 --- a/player-parser/shared.ts +++ b/player-parser/shared.ts @@ -1,6 +1,6 @@ export const corsHeaders = { "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept", + "Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept, Allow, User-Agent", "Access-Control-Allow-Methods": "GET,HEAD,POST,OPTIONS", "Cache-Control": "no-cache", }; From 5dd7483d93addcb1294139cb2ec5f32033e6f2c6 Mon Sep 17 00:00:00 2001 From: Radiquum Date: Fri, 22 Aug 2025 00:42:01 +0500 Subject: [PATCH 4/8] fix: get params in search --- app/pages/Search.tsx | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/app/pages/Search.tsx b/app/pages/Search.tsx index b3878d6..a88c9d7 100644 --- a/app/pages/Search.tsx +++ b/app/pages/Search.tsx @@ -115,16 +115,23 @@ export function SearchPage() { const [filtersModalOpen, setFiltersModalOpen] = useState(false); useEffect(() => { - let _parsed = null; - try { - _parsed = JSON.parse(searchParams.get("params")); - } catch { - _parsed = { + const queryParams = searchParams.get("params"); + + if (queryParams) { + try { + setParams(JSON.parse(queryParams)); + } catch (e) { + setParams({ + where: "releases", + searchBy: "name", + }); + } + } else { + setParams({ where: "releases", searchBy: "name", - }; + }); } - setParams(_parsed); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -244,7 +251,7 @@ const FiltersModal = (props: { }) : props.setParams({ where: item.id, searchBy: "none" }) } - key={`where--${item.id}`} + key={`filter--where--${item.id}`} > {item.label}
; @@ -273,7 +280,7 @@ const FiltersModal = (props: { searchBy: item.id, }) } - key={`searchBy--${item.id}`} + key={`filter--where--${props.params.where}--searchBy--${item.id}`} > {item.label} From b0dab3844e0068a15daa3a0cf9b6baaf806b7aa0 Mon Sep 17 00:00:00 2001 From: Radiquum Date: Fri, 22 Aug 2025 01:00:11 +0500 Subject: [PATCH 5/8] style: move buttons from modal to page --- app/pages/Search.tsx | 155 ++++++++++++++++--------------------------- 1 file changed, 58 insertions(+), 97 deletions(-) diff --git a/app/pages/Search.tsx b/app/pages/Search.tsx index a88c9d7..8f880b9 100644 --- a/app/pages/Search.tsx +++ b/app/pages/Search.tsx @@ -190,112 +190,73 @@ export function SearchPage() { Поиск
+
+
+ item.id == params.where).label}`} + color="light" + > + {whereMapping.map((item) => { + return item.auth && !userStore.isAuth ? + <> + : + searchByMapping[item.id] ? + setParams({ + where: item.id, + searchBy: searchByMapping[item.id][0].id, + }) + : setParams({ where: item.id, searchBy: "none" }) + } + key={`filter--where--${item.id}`} + > + {item.label} + ; + })} + +
+ {searchByMapping[params.where] ? +
+ item.id == params.searchBy + ).label}` + } + color="light" + > + {searchByMapping[params.where].map((item) => { + return ( + + setParams({ + where: params.where, + searchBy: item.id, + }) + } + key={`filter--where--${params.where}--searchBy--${item.id}`} + > + {item.label} + + ); + })} + +
+ : <>} +
-

query: {query}

params: {JSON.stringify(params)}

- {}} - params={params} - setParams={setParams} - />
); } -const FiltersModal = (props: { - isOpen: boolean; - setIsOpen: any; - isAuth: boolean; - setContent: any; - params: any; - setParams: any; -}) => { - if (!props.params) return <>; - - return ( - props.setIsOpen(false)}> - Фильтры - -
-
-

Искать в

- item.id == props.params.where).label - } - color="blue" - > - {whereMapping.map((item) => { - return item.auth && !props.isAuth ? - <> - : - searchByMapping[item.id] ? - props.setParams({ - where: item.id, - searchBy: searchByMapping[item.id][0].id, - }) - : props.setParams({ where: item.id, searchBy: "none" }) - } - key={`filter--where--${item.id}`} - > - {item.label} - ; - })} - -
- {searchByMapping[props.params.where] ? -
-

Искать по

- item.id == props.params.searchBy - ).label - } - color="blue" - > - {searchByMapping[props.params.where].map((item) => { - return ( - - props.setParams({ - where: props.params.where, - searchBy: item.id, - }) - } - key={`filter--where--${props.params.where}--searchBy--${item.id}`} - > - {item.label} - - ); - })} - -
- : <>} -
-
- -
- ); -}; - // import useSWRInfinite from "swr/infinite"; // import { ReleaseSection } from "#/components/ReleaseSection/ReleaseSection"; // import { RelatedSection } from "#/components/RelatedSection/RelatedSection"; From f2f628add0dfe24dec7838cf0433583142cd53af Mon Sep 17 00:00:00 2001 From: Radiquum Date: Fri, 22 Aug 2025 04:01:44 +0500 Subject: [PATCH 6/8] feat: Refactor: search #12 --- api-prox/index.ts | 2 +- api-prox/shared.ts | 2 +- app/api/config.ts | 11 +- app/pages/Search.tsx | 555 ++++++++++++++----------------------------- 4 files changed, 192 insertions(+), 378 deletions(-) diff --git a/api-prox/index.ts b/api-prox/index.ts index b653ef0..7bcd03c 100644 --- a/api-prox/index.ts +++ b/api-prox/index.ts @@ -18,7 +18,7 @@ app.use(function (req, res, next) { res.header("Access-Control-Allow-Origin", req.headers.origin || "*"); res.header( "Access-Control-Allow-Headers", - "Origin, X-Requested-With, Content-Type, Accept, Sign, Allow, User-Agent" + "Origin, X-Requested-With, Content-Type, Accept, Sign, Allow, User-Agent, Api-Version" ); res.header("Access-Control-Allow-Methods", "GET,HEAD,POST,OPTIONS"); next(); diff --git a/api-prox/shared.ts b/api-prox/shared.ts index 36144e0..f241fd3 100644 --- a/api-prox/shared.ts +++ b/api-prox/shared.ts @@ -1,7 +1,7 @@ export const corsHeaders = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": - "Origin, X-Requested-With, Content-Type, Accept, Sign, Allow, User-Agent", + "Origin, X-Requested-With, Content-Type, Accept, Sign, Allow, User-Agent, Api-Version", "Access-Control-Allow-Methods": "GET,HEAD,POST,OPTIONS", "Cache-Control": "no-cache", }; diff --git a/app/api/config.ts b/app/api/config.ts index 957a4c9..317ea1f 100644 --- a/app/api/config.ts +++ b/app/api/config.ts @@ -52,11 +52,12 @@ export const ENDPOINTS = { }, filter: `${API_PREFIX}/filter`, search: { - profileList: `${API_PREFIX}/search/profile/list/`, - profileHistory: `${API_PREFIX}/search/history/`, - profileFavoriteCollection: `${API_PREFIX}/search/favoriteCollections/`, - profiles: `${API_PREFIX}/search/profiles/`, - releases: `${API_PREFIX}/search/releases/`, + profileList: `${API_PREFIX}/search/profile/list`, + profileHistory: `${API_PREFIX}/search/history`, + profileFavoriteCollection: `${API_PREFIX}/search/favoriteCollections`, + profileFavorites: `${API_PREFIX}/search/favorites`, + profiles: `${API_PREFIX}/search/profiles`, + releases: `${API_PREFIX}/search/releases`, }, statistic: { addHistory: `${API_PREFIX}/history/add`, diff --git a/app/pages/Search.tsx b/app/pages/Search.tsx index 8f880b9..47d61d3 100644 --- a/app/pages/Search.tsx +++ b/app/pages/Search.tsx @@ -4,16 +4,35 @@ import { useState, useEffect } from "react"; import { useSearchParams } from "next/navigation"; import { useRouter } from "next/navigation"; -import { - Button, - Dropdown, - DropdownItem, - Modal, - ModalBody, - ModalFooter, - ModalHeader, -} from "flowbite-react"; +import { Dropdown, DropdownItem } from "flowbite-react"; import { useUserStore } from "#/store/auth"; +import { ENDPOINTS } from "#/api/config"; +import { tryCatchAPI } from "#/api/utils"; +import useSWRInfinite from "swr/infinite"; +import { Spinner } from "#/components/Spinner/Spinner"; +import { ReleaseSection } from "#/components/ReleaseSection/ReleaseSection"; +import { UserSection } from "#/components/UserSection/UserSection"; +import { CollectionsSection } from "#/components/CollectionsSection/CollectionsSection"; +import { useScrollPosition } from "#/hooks/useScrollPosition"; +import { RelatedSection } from "#/components/RelatedSection/RelatedSection"; + +const postFetcher = async (url: string, payload: string) => { + const { data, error } = await tryCatchAPI( + fetch(url, { + method: "POST", + headers: { + "Api-Version": "v2", + "Content-Type": "application/json", + }, + body: payload, + }) + ); + + if (error) { + throw error; + } + return data; +}; const whereMapping = [ { @@ -112,7 +131,9 @@ export function SearchPage() { const userStore = useUserStore(); const [query, setQuery] = useState(searchParams.get("query") || ""); const [params, setParams] = useState(null); - const [filtersModalOpen, setFiltersModalOpen] = useState(false); + const [content, setContent] = useState(null); + + const [HeaderH, setHeaderH] = useState(null); useEffect(() => { const queryParams = searchParams.get("params"); @@ -132,6 +153,11 @@ export function SearchPage() { searchBy: "name", }); } + + if (window) { + setHeaderH(document.querySelector("header").clientHeight); + } + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -139,17 +165,115 @@ export function SearchPage() { if (!params) return; const url = new URL(`/search`, window.location.origin); + url.searchParams.set("query", query); url.searchParams.set("params", JSON.stringify(params)); router.replace(url.toString()); + setContent(null); // eslint-disable-next-line react-hooks/exhaustive-deps }, [params]); + useEffect(() => { + setContent(null); + + const url = new URL(`/search`, window.location.origin); + url.searchParams.set("query", query); + url.searchParams.set("params", JSON.stringify(params)); + router.replace(url.toString()); + }, [query]); + + const getKey = (pageIndex: number, previousPageData: any) => { + if (!params) return null; + if (!query) return null; + + if (previousPageData) { + if (params.where == "releases") { + if (!previousPageData.releases.length) return null; + } else { + if (!previousPageData.content.length) return null; + } + } + + let url = null; + switch (params.where) { + case "releases": + url = `${ENDPOINTS.search.releases}/${pageIndex}`; + break; + case "profiles": + url = `${ENDPOINTS.search.profiles}/${pageIndex}`; + break; + case "list": + const list = searchByMapping[params.where].find( + (item) => item.id == params.searchBy + ); + if (!list) break; + url = `${ENDPOINTS.search.profileList}/${list.value}/${pageIndex}`; + break; + case "history": + url = `${ENDPOINTS.search.profileHistory}/${pageIndex}`; + break; + case "favorites": + url = `${ENDPOINTS.search.profileFavorites}/${pageIndex}`; + break; + case "collections": + url = `${ENDPOINTS.search.profileFavoriteCollection}/${pageIndex}`; + break; + } + + if (userStore.token) { + url += `?token=${userStore.token}`; + } + + let searchBy = null; + const _sbym = searchByMapping[params.where]; + if (_sbym) { + searchBy = _sbym.find((item) => item.id == params.searchBy).value; + } else { + searchBy = searchByMapping["none"][0].value; + } + + return [url, JSON.stringify({ query, searchBy })]; + }; + + const { data, error, isLoading, size, setSize, mutate } = useSWRInfinite( + getKey, + ([url, payload]) => postFetcher(url, payload), + { initialSize: 2 } + ); + + useEffect(() => { + if (data) { + let _content = []; + if (params.where == "releases") { + for (let i = 0; i < data.length; i++) { + _content.push(...data[i].releases); + } + } else { + for (let i = 0; i < data.length; i++) { + _content.push(...data[i].content); + } + } + setContent(_content); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [data]); + + const scrollPosition = useScrollPosition(); + useEffect(() => { + if (scrollPosition >= 98 && scrollPosition <= 99) { + setSize(size + 1); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [scrollPosition]); + if (!params) return <>; return (
-
-
+
+
-
-
+
+
item.id == params.where).label}`} color="light" + className="w-full" > {whereMapping.map((item) => { return item.auth && !userStore.isAuth ? @@ -217,17 +336,18 @@ export function SearchPage() {
{searchByMapping[params.where] ? -
+
item.id == params.searchBy - ).label}` - } + ).label + }`} color="light" + className="w-full" > {searchByMapping[params.where].map((item) => { return ( @@ -251,350 +371,43 @@ export function SearchPage() {
-

query: {query}

-

params: {JSON.stringify(params)}

+
+ {error ? +
+
+

Произошла ошибка поиска

+
+
+ : <>} + + {data && data[0].related && } + {content ? + content.length > 0 ? + params.where == "profiles" ? + + : params.where == "collections" ? + + : + :
+ +

Странно, аниме не найдено, попробуйте другой запрос...

+
+ + : <>} + + {!content && !isLoading && !query && ( +
+ +

Введите ваш запрос что-бы найти любимый тайтл

+
+ )} + + {isLoading ? +
+ +
+ : ""} +
); } - -// import useSWRInfinite from "swr/infinite"; -// import { ReleaseSection } from "#/components/ReleaseSection/ReleaseSection"; -// import { RelatedSection } from "#/components/RelatedSection/RelatedSection"; -// import { Spinner } from "#/components/Spinner/Spinner"; -// import { useScrollPosition } from "#/hooks/useScrollPosition"; -// import { useUserStore } from "../store/auth"; -// import { Button, Dropdown, DropdownItem, Modal, ModalBody, ModalFooter, ModalHeader } from "flowbite-react"; -// import { CollectionsSection } from "#/components/CollectionsSection/CollectionsSection"; -// import { UserSection } from "#/components/UserSection/UserSection"; -// import { useSWRfetcher } from "#/api/utils"; - -// export function SearchPage() { -// const router = useRouter(); - -// const [searchVal, setSearchVal] = useState(searchParams.get("q") || ""); -// const [where, setWhere] = useState(searchParams.get("where") || "releases"); - -// const [list, setList] = useState(searchParams.get("list") || "watching"); -// const [filtersModalOpen, setFiltersModalOpen] = useState(false); - -// - -// const getKey = (pageIndex: number, previousPageData: any) => { -// if (where == "releases") { -// if (previousPageData && !previousPageData.releases.length) return null; -// } else { -// if (previousPageData && !previousPageData.content.length) return null; -// } - -// const url = new URL("/api/search", window.location.origin); -// url.searchParams.set("page", pageIndex.toString()); - -// if (userStore.token) { -// url.searchParams.set("token", userStore.token); -// } - -// if (where) { -// url.searchParams.set("where", where); -// } - -// if (where == "list" && list && ListsMapping.hasOwnProperty(list)) { -// url.searchParams.set("list", ListsMapping[list].id); -// } - -// url.searchParams.set("searchBy", TagMapping[searchBy].id); - -// if (query) { -// url.searchParams.set("q", query); -// return url.toString(); -// } -// return; -// }; - -// const { data, error, isLoading, size, setSize } = useSWRInfinite( -// getKey, -// useSWRfetcher, -// { initialSize: 2, revalidateFirstPage: false } -// ); - -// const [content, setContent] = useState(null); -// useEffect(() => { -// if (data) { -// let allReleases = []; -// if (where == "releases") { -// for (let i = 0; i < data.length; i++) { -// allReleases.push(...data[i].releases); -// } -// } else { -// for (let i = 0; i < data.length; i++) { -// allReleases.push(...data[i].content); -// } -// } -// setContent(allReleases); -// } -// // eslint-disable-next-line react-hooks/exhaustive-deps -// }, [data]); - -// const scrollPosition = useScrollPosition(); -// useEffect(() => { -// if (scrollPosition >= 98 && scrollPosition <= 99) { -// setSize(size + 1); -// } -// // eslint-disable-next-line react-hooks/exhaustive-deps -// }, [scrollPosition]); - -// function _executeSearch(value: string) { -// const Params = new URLSearchParams(window.location.search); -// Params.set("q", value); -// const url = new URL(`/search?${Params.toString()}`, window.location.origin); -// setContent(null); -// setQuery(value); -// router.push(url.toString()); -// } - -// useEffect(() => { -// if (searchVal && searchVal.length % 4 == 1) { -// _executeSearch(searchVal.trim()); -// } -// // eslint-disable-next-line react-hooks/exhaustive-deps -// }, [searchVal]); - -// if (error) -// return ( -//
-//
-//

Ошибка

-//

-// Произошла ошибка поиска. Попробуйте обновить страницу или зайдите -// позже. -//

-//
-//
-// ); - -// return ( -// <> -//
-//
{ -// e.preventDefault(); -// _executeSearch(searchVal.trim()); -// }} -// > - -//
-// -//
-//
-// {data && data[0].related && } -// {content ? -// content.length > 0 ? -// <> -// {where == "collections" ? -// -// : where == "profiles" ? -// -// : -// } -// -// :
-// -//

Странно, аниме не найдено, попробуйте другой запрос...

-//
-// : isLoading && ( -//
-// -//
-// ) -// } -// {!content && !isLoading && !query && ( -//
-// -//

Введите ваш запрос что-бы найти любимый тайтл

-//
-// )} -//
-// {( -// data && -// data.length > 1 && -// (where == "releases" ? -// data[data.length - 1].releases.length == 25 -// : data[data.length - 1].content.length == 25) -// ) ? -// -// : ""} -// -// -// ); -// } - -// const FiltersModal = (props: { -// isOpen: boolean; -// setIsOpen: any; -// where: string; -// setWhere: any; -// list: string; -// setList: any; -// isAuth: boolean; -// searchBy: string; -// setSearchBy: any; -// setContent: any; -// }) => { -// const router = useRouter(); -// const [where, setWhere] = useState(props.where); -// const [list, setList] = useState(props.list); -// const [searchBy, setSearchBy] = useState(props.searchBy); - -// function _cancel() { -// setWhere(props.where); -// setList(props.list); -// setSearchBy(props.searchBy); -// props.setIsOpen(false); -// } - -// function _accept() { -// const Params = new URLSearchParams(window.location.search); - -// if (props.where != where) { -// Params.set("where", where); -// props.setWhere(where); -// } - -// if (where == "list") { -// Params.set("list", list); -// props.setList(list); -// } else { -// Params.delete("list"); -// } - -// if (!["profiles", "collections"].includes(where)) { -// Params.set("searchBy", searchBy); -// props.setSearchBy(searchBy); -// } else { -// Params.delete("searchBy"); -// props.setSearchBy("name"); -// } - -// props.setContent(null); - -// const url = new URL(`/search?${Params.toString()}`, window.location.origin); -// router.push(url.toString()); -// } - -// return ( -// _cancel()}> -// Фильтры -// -//
-//
-//

Искать в

-// -// {Object.keys(WhereMapping).map((item) => { -// if ( -// ["list", "history", "collections", "favorites"].includes( -// item -// ) && -// !props.isAuth -// ) { -// return <>; -// } else { -// return ( -// setWhere(item)} -// key={`where--${item}`} -// > -// {WhereMapping[item]} -// -// ); -// } -// })} -// -//
-//
-// {props.isAuth && where == "list" && ListsMapping.hasOwnProperty(list) ? -//
-//
-//

Список

-// -// {Object.keys(ListsMapping).map((item) => { -// return ( -// setList(item)} -// key={`list--${item}`} -// > -// {ListsMapping[item].name} -// -// ); -// })} -// -//
-//
-// : ""} -// {!["profiles", "collections"].includes(where) ? -//
-//
-//

Искать по

-// -// {Object.keys(TagMapping).map((item) => { -// return ( -// setSearchBy(item)} -// key={`tag--${item}`} -// > -// {TagMapping[item].name} -// -// ); -// })} -// -//
-//
-// : ""} -//
-// -//
-// -// -//
-//
-//
-// ); -// }; From 6e385654398ea3334c7ac0f5787e1e7bffacbb62 Mon Sep 17 00:00:00 2001 From: Radiquum Date: Fri, 22 Aug 2025 04:35:06 +0500 Subject: [PATCH 7/8] Feature Request: meta tags for share link in social networks #15 --- app/api/config.ts | 2 +- app/api/search/route.ts | 74 ------------------- app/collection/[id]/page.tsx | 4 +- .../RelatedSection/RelatedSection.tsx | 2 +- app/pages/Search.tsx | 4 +- app/profile/[id]/bookmarks/[slug]/page.tsx | 4 +- app/profile/[id]/bookmarks/page.tsx | 4 +- app/profile/[id]/collections/page.tsx | 6 +- app/profile/[id]/page.tsx | 4 +- app/related/[id]/page.tsx | 10 +-- app/release/[id]/collections/page.tsx | 6 +- app/release/[id]/page.tsx | 4 +- public/changelog/3.8.0.md | 8 ++ 13 files changed, 33 insertions(+), 99 deletions(-) delete mode 100644 app/api/search/route.ts create mode 100644 public/changelog/3.8.0.md diff --git a/app/api/config.ts b/app/api/config.ts index 317ea1f..9f3a2ba 100644 --- a/app/api/config.ts +++ b/app/api/config.ts @@ -1,4 +1,4 @@ -export const CURRENT_APP_VERSION = "3.7.0"; +export const CURRENT_APP_VERSION = "3.8.0"; import { env } from "next-runtime-env"; const NEXT_PUBLIC_API_URL = env("NEXT_PUBLIC_API_URL") || null; diff --git a/app/api/search/route.ts b/app/api/search/route.ts deleted file mode 100644 index d07dea9..0000000 --- a/app/api/search/route.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { NextResponse } from "next/server"; -import { NextRequest } from "next/server"; -import { fetchDataViaPost } from "../utils"; -import { ENDPOINTS } from "../config"; - -export async function GET(request: NextRequest) { - const page = parseInt(request.nextUrl.searchParams.get("page")) || 0; - const query = decodeURI(request.nextUrl.searchParams.get("q")) || null; - const token = request.nextUrl.searchParams.get("token") || null; - - const where = request.nextUrl.searchParams.get("where") || "releases"; - const searchBy = parseInt(request.nextUrl.searchParams.get("searchBy")) || 0; - const list = parseInt(request.nextUrl.searchParams.get("list")) || null; - - let url: URL; - - if (where == "list") { - if (!list) { - return NextResponse.json( - { message: "List ID required" }, - { status: 400 } - ); - } - if (!token) { - return NextResponse.json({ message: "token required" }, { status: 400 }); - } - url = new URL(`${ENDPOINTS.search}/profile/list/${list}/${page}`); - } else if (where == "history") { - if (!token) { - return NextResponse.json({ message: "token required" }, { status: 400 }); - } - url = new URL(`${ENDPOINTS.search}/history/${page}`); - } else if (where == "favorites") { - if (!token) { - return NextResponse.json({ message: "token required" }, { status: 400 }); - } - url = new URL(`${ENDPOINTS.search}/favorites/${page}`); - } else if (where == "collections") { - if (!token) { - return NextResponse.json({ message: "token required" }, { status: 400 }); - } - url = new URL(`${ENDPOINTS.search}/favoriteCollections/${page}`); - } else if (where == "profiles") { - url = new URL(`${ENDPOINTS.search}/profiles/${page}`); - } else { - url = new URL(`${ENDPOINTS.search}/releases/${page}`); - } - - if (token) { - url.searchParams.set("token", token); - } - const body = { query, searchBy }; - - const { data, error } = await fetchDataViaPost( - url.toString(), - JSON.stringify(body), - true - ); - if (error) { - return new Response(JSON.stringify(error), { - status: 500, - headers: { - "Content-Type": "application/json", - }, - }); - } - - return new Response(JSON.stringify(data), { - status: 200, - headers: { - "Content-Type": "application/json", - }, - }); -} diff --git a/app/collection/[id]/page.tsx b/app/collection/[id]/page.tsx index f70d285..976efb8 100644 --- a/app/collection/[id]/page.tsx +++ b/app/collection/[id]/page.tsx @@ -1,7 +1,7 @@ import { ViewCollectionPage } from "#/pages/ViewCollection"; import { fetchDataViaGet } from "#/api/utils"; import type { Metadata, ResolvingMetadata } from "next"; -export const dynamic = "force-static"; +import { API_URL } from "#/api/config"; export async function generateMetadata( { params }, @@ -9,7 +9,7 @@ export async function generateMetadata( ): Promise { const id = params.id; const { data, error } = await fetchDataViaGet( - `https://api.anixart.tv/collection/${id}` + `${API_URL}/collection/${id}` ); const previousOG = (await parent).openGraph; diff --git a/app/components/RelatedSection/RelatedSection.tsx b/app/components/RelatedSection/RelatedSection.tsx index 1ed7c43..368793d 100644 --- a/app/components/RelatedSection/RelatedSection.tsx +++ b/app/components/RelatedSection/RelatedSection.tsx @@ -15,7 +15,7 @@ export const RelatedSection = (props: any) => {
{props.images.map((item, index) => { return ( -
+
item.id == params.where).label}`} color="light" - className="w-full" + className="w-full lg:w-fit" > {whereMapping.map((item) => { return item.auth && !userStore.isAuth ? @@ -347,7 +347,7 @@ export function SearchPage() { ).label }`} color="light" - className="w-full" + className="w-full lg:w-fit" > {searchByMapping[params.where].map((item) => { return ( diff --git a/app/profile/[id]/bookmarks/[slug]/page.tsx b/app/profile/[id]/bookmarks/[slug]/page.tsx index a3fb3e1..525159e 100644 --- a/app/profile/[id]/bookmarks/[slug]/page.tsx +++ b/app/profile/[id]/bookmarks/[slug]/page.tsx @@ -1,7 +1,7 @@ import { BookmarksCategoryPage } from "#/pages/BookmarksCategory"; import { fetchDataViaGet } from "#/api/utils"; import type { Metadata, ResolvingMetadata } from "next"; -export const dynamic = 'force-static'; +import { API_URL } from "#/api/config"; const SectionTitleMapping = { watching: "Смотрю", @@ -17,7 +17,7 @@ export async function generateMetadata( ): Promise { const id: string = params.id; const { data, error } = await fetchDataViaGet( - `https://api.anixart.tv/profile/${id}` + `${API_URL}/profile/${id}` ); const previousOG = (await parent).openGraph; diff --git a/app/profile/[id]/bookmarks/page.tsx b/app/profile/[id]/bookmarks/page.tsx index afd66f5..fc2848d 100644 --- a/app/profile/[id]/bookmarks/page.tsx +++ b/app/profile/[id]/bookmarks/page.tsx @@ -1,7 +1,7 @@ import { BookmarksPage } from "#/pages/Bookmarks"; import { fetchDataViaGet } from "#/api/utils"; import type { Metadata, ResolvingMetadata } from "next"; -export const dynamic = "force-static"; +import { API_URL } from "#/api/config"; export async function generateMetadata( { params }, @@ -9,7 +9,7 @@ export async function generateMetadata( ): Promise { const id: string = params.id; const { data, error } = await fetchDataViaGet( - `https://api.anixart.tv/profile/${id}` + `${API_URL}/profile/${id}` ); const previousOG = (await parent).openGraph; diff --git a/app/profile/[id]/collections/page.tsx b/app/profile/[id]/collections/page.tsx index ce018fd..2fb0bf3 100644 --- a/app/profile/[id]/collections/page.tsx +++ b/app/profile/[id]/collections/page.tsx @@ -1,7 +1,7 @@ import { CollectionsFullPage } from "#/pages/CollectionsFull"; import { fetchDataViaGet } from "#/api/utils"; import type { Metadata, ResolvingMetadata } from "next"; -export const dynamic = "force-static"; +import { API_URL } from "#/api/config"; export async function generateMetadata( { params }, @@ -9,7 +9,7 @@ export async function generateMetadata( ): Promise { const id: string = params.id; const { data, error } = await fetchDataViaGet( - `https://api.anixart.tv/profile/${id}` + `${API_URL}/profile/${id}` ); const previousOG = (await parent).openGraph; @@ -38,7 +38,7 @@ export async function generateMetadata( export default async function Collections({ params }) { const { data, error } = await fetchDataViaGet( - `https://api.anixart.tv/profile/${params.id}` + `${API_URL}/profile/${params.id}` ); if (error) { diff --git a/app/profile/[id]/page.tsx b/app/profile/[id]/page.tsx index 0c28386..12424a1 100644 --- a/app/profile/[id]/page.tsx +++ b/app/profile/[id]/page.tsx @@ -1,7 +1,7 @@ import { ProfilePage } from "#/pages/Profile"; import { fetchDataViaGet } from "#/api/utils"; import type { Metadata, ResolvingMetadata } from "next"; -export const dynamic = "force-static"; +import { API_URL } from "#/api/config"; export async function generateMetadata( { params }, @@ -9,7 +9,7 @@ export async function generateMetadata( ): Promise { const id: string = params.id; const { data, error } = await fetchDataViaGet( - `https://api.anixart.tv/profile/${id}` + `${API_URL}/profile/${id}` ); const previousOG = (await parent).openGraph; diff --git a/app/related/[id]/page.tsx b/app/related/[id]/page.tsx index 3ce2b33..61bc966 100644 --- a/app/related/[id]/page.tsx +++ b/app/related/[id]/page.tsx @@ -1,7 +1,7 @@ import { RelatedPage } from "#/pages/Related"; import { fetchDataViaGet } from "#/api/utils"; import type { Metadata, ResolvingMetadata } from "next"; -export const dynamic = 'force-static'; +import { API_URL } from "#/api/config"; const _getData = async (url: string) => { const { data, error } = await fetchDataViaGet(url); @@ -12,7 +12,7 @@ export async function generateMetadata({ params }, parent: ResolvingMetadata): P const id:string = params.id; const previousOG = (await parent).openGraph; - const [ related, relatedError ] = await _getData(`https://api.anixart.tv/related/${id}/0`); + const [ related, relatedError ] = await _getData(`${API_URL}/related/${id}/0`); if (relatedError || related.content.length == 0) { return { title: "Ошибка", @@ -20,7 +20,7 @@ export async function generateMetadata({ params }, parent: ResolvingMetadata): P }; }; - const [ firstRelease, firstReleaseError ] = await _getData(`https://api.anixart.tv/release/${related.content[0].id}`); + const [ firstRelease, firstReleaseError ] = await _getData(`${API_URL}/release/${related.content[0].id}`); if (firstReleaseError) { return { title: "Ошибка", @@ -46,7 +46,7 @@ export async function generateMetadata({ params }, parent: ResolvingMetadata): P export default async function Related({ params }) { const id: string = params.id; - const [ related, relatedError ] = await _getData(`https://api.anixart.tv/related/${id}/0`); + const [ related, relatedError ] = await _getData(`${API_URL}/related/${id}/0`); if (relatedError || related.content.length == 0) { return
@@ -56,7 +56,7 @@ export default async function Related({ params }) {
}; - const [ firstRelease, firstReleaseError ] = await _getData(`https://api.anixart.tv/release/${related.content[0].id}`); + const [ firstRelease, firstReleaseError ] = await _getData(`${API_URL}/release/${related.content[0].id}`); if (firstReleaseError) { return
diff --git a/app/release/[id]/collections/page.tsx b/app/release/[id]/collections/page.tsx index b9dcded..28f1892 100644 --- a/app/release/[id]/collections/page.tsx +++ b/app/release/[id]/collections/page.tsx @@ -1,7 +1,7 @@ import { CollectionsFullPage } from "#/pages/CollectionsFull"; import { fetchDataViaGet } from "#/api/utils"; import type { Metadata, ResolvingMetadata } from "next"; -export const dynamic = "force-static"; +import { API_URL } from "#/api/config"; export async function generateMetadata( { params }, @@ -9,7 +9,7 @@ export async function generateMetadata( ): Promise { const id = params.id; const { data, error } = await fetchDataViaGet( - `https://api.anixart.tv/release/${id}` + `${API_URL}/release/${id}` ); const previousOG = (await parent).openGraph; @@ -38,7 +38,7 @@ export async function generateMetadata( export default async function Collections({ params }) { const { data, error } = await fetchDataViaGet( - `https://api.anixart.tv/release/${params.id}` + `${API_URL}/release/${params.id}` ); if (error) { diff --git a/app/release/[id]/page.tsx b/app/release/[id]/page.tsx index ba70944..d11f353 100644 --- a/app/release/[id]/page.tsx +++ b/app/release/[id]/page.tsx @@ -1,7 +1,7 @@ import { ReleasePage } from "#/pages/Release"; import { fetchDataViaGet } from "#/api/utils"; import type { Metadata, ResolvingMetadata } from "next"; -export const dynamic = "force-static"; +import { API_URL } from "#/api/config"; export async function generateMetadata( { params }, @@ -9,7 +9,7 @@ export async function generateMetadata( ): Promise { const id = params.id; const { data, error } = await fetchDataViaGet( - `https://api.anixart.tv/release/${id}` + `${API_URL}/release/${id}` ); const previousOG = (await parent).openGraph; diff --git a/public/changelog/3.8.0.md b/public/changelog/3.8.0.md new file mode 100644 index 0000000..a2c50b9 --- /dev/null +++ b/public/changelog/3.8.0.md @@ -0,0 +1,8 @@ +# 3.8.0 + +## Изменено + +- Фильтры на странице поиска были перемещены рядом с полем ввода +- ТЕХ: механизм запросов поиска стал работать так-же как и остальные запросы +- Динамическая метадата (пред просмотра) снова включена +- ТЕХ: генераторы метадаты (пред просмотра) используют ссылку API (API_URL) с конфига \ No newline at end of file From 96904bcb9c778877ae456df5832aba92b93f5c19 Mon Sep 17 00:00:00 2001 From: Radiquum Date: Fri, 22 Aug 2025 04:46:18 +0500 Subject: [PATCH 8/8] force-static for non-dynamic metadata --- app/bookmarks/page.tsx | 2 ++ app/collections/create/page.tsx | 2 ++ app/collections/favorites/page.tsx | 2 ++ app/collections/page.tsx | 2 ++ app/favorites/page.tsx | 2 ++ app/history/page.tsx | 2 ++ app/login/page.tsx | 2 ++ app/menu/page.tsx | 2 ++ app/page.tsx | 2 ++ app/search/page.tsx | 2 ++ 10 files changed, 20 insertions(+) diff --git a/app/bookmarks/page.tsx b/app/bookmarks/page.tsx index 56a5985..eaf51fa 100644 --- a/app/bookmarks/page.tsx +++ b/app/bookmarks/page.tsx @@ -2,6 +2,8 @@ export const metadata = { title: "Закладки", }; +export const dynamic = "force-static"; + import { BookmarksPage } from "#/pages/Bookmarks"; export default function Index() { return ; diff --git a/app/collections/create/page.tsx b/app/collections/create/page.tsx index 67b95bf..796b6cd 100644 --- a/app/collections/create/page.tsx +++ b/app/collections/create/page.tsx @@ -5,6 +5,8 @@ export const metadata = { description: "Создание новой коллекции", }; +export const dynamic = "force-static"; + export default function Collections() { return ; } diff --git a/app/collections/favorites/page.tsx b/app/collections/favorites/page.tsx index 628070e..0a0496b 100644 --- a/app/collections/favorites/page.tsx +++ b/app/collections/favorites/page.tsx @@ -5,6 +5,8 @@ export const metadata = { description: "Просмотр избранных коллекций", }; +export const dynamic = "force-static"; + export default function Collections() { return ; } diff --git a/app/collections/page.tsx b/app/collections/page.tsx index cd68b69..10ba0ab 100644 --- a/app/collections/page.tsx +++ b/app/collections/page.tsx @@ -5,6 +5,8 @@ export const metadata = { description: "Просмотр и управление коллекциями", } +export const dynamic = "force-static"; + export default function Collections() { return ; } diff --git a/app/favorites/page.tsx b/app/favorites/page.tsx index b2f5394..cc12a08 100644 --- a/app/favorites/page.tsx +++ b/app/favorites/page.tsx @@ -4,6 +4,8 @@ export const metadata = { import { FavoritesPage } from "#/pages/Favorites"; +export const dynamic = "force-static"; + export default function Index() { return ; } diff --git a/app/history/page.tsx b/app/history/page.tsx index 2815aa3..21bac4b 100644 --- a/app/history/page.tsx +++ b/app/history/page.tsx @@ -4,6 +4,8 @@ export const metadata = { import { HistoryPage } from "#/pages/History"; +export const dynamic = "force-static"; + export default function Index() { return ; } diff --git a/app/login/page.tsx b/app/login/page.tsx index dae1809..cca130c 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -5,6 +5,8 @@ export const metadata = { description: "Вход в аккаунт anixart", } +export const dynamic = "force-static"; + export default function Login() { return ; } diff --git a/app/menu/page.tsx b/app/menu/page.tsx index 1350fbc..61305cc 100644 --- a/app/menu/page.tsx +++ b/app/menu/page.tsx @@ -4,6 +4,8 @@ export const metadata = { import { MenuPage } from "#/pages/MobileMenuPage"; +export const dynamic = "force-static"; + export default function Index() { return ; } diff --git a/app/page.tsx b/app/page.tsx index b54ae5b..6731436 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,5 +1,7 @@ import { IndexPage } from "./pages/Index"; +export const dynamic = "force-static"; + export default function Index() { return ; } diff --git a/app/search/page.tsx b/app/search/page.tsx index 13b9981..3e70827 100644 --- a/app/search/page.tsx +++ b/app/search/page.tsx @@ -5,6 +5,8 @@ export const metadata = { description: "Поиск аниме релизов", }; +export const dynamic = "force-static"; + export default function Search() { return ; }