diff --git a/modules/frontend/package-lock.json b/modules/frontend/package-lock.json index 40bb520..f5dde46 100644 --- a/modules/frontend/package-lock.json +++ b/modules/frontend/package-lock.json @@ -8,7 +8,6 @@ "name": "nyanimedb-frontend", "version": "0.0.0", "dependencies": { - "@headlessui/react": "^2.2.9", "@heroicons/react": "^2.2.0", "@tailwindcss/vite": "^4.1.17", "axios": "^1.12.2", @@ -31,9 +30,6 @@ "typescript": "~5.9.3", "typescript-eslint": "^8.45.0", "vite": "^7.1.7" - }, - "engines": { - "node": "20.x" } }, "node_modules/@apidevtools/json-schema-ref-parser": { @@ -910,79 +906,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@floating-ui/core": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", - "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", - "license": "MIT", - "dependencies": { - "@floating-ui/utils": "^0.2.10" - } - }, - "node_modules/@floating-ui/dom": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", - "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", - "license": "MIT", - "dependencies": { - "@floating-ui/core": "^1.7.3", - "@floating-ui/utils": "^0.2.10" - } - }, - "node_modules/@floating-ui/react": { - "version": "0.26.28", - "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz", - "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==", - "license": "MIT", - "dependencies": { - "@floating-ui/react-dom": "^2.1.2", - "@floating-ui/utils": "^0.2.8", - "tabbable": "^6.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@floating-ui/react-dom": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", - "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", - "license": "MIT", - "dependencies": { - "@floating-ui/dom": "^1.7.4" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@floating-ui/utils": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", - "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", - "license": "MIT" - }, - "node_modules/@headlessui/react": { - "version": "2.2.9", - "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.9.tgz", - "integrity": "sha512-Mb+Un58gwBn0/yWZfyrCh0TJyurtT+dETj7YHleylHk5od3dv2XqETPGWMyQ5/7sYN7oWdyM1u9MvC0OC8UmzQ==", - "license": "MIT", - "dependencies": { - "@floating-ui/react": "^0.26.16", - "@react-aria/focus": "^3.20.2", - "@react-aria/interactions": "^3.25.0", - "@tanstack/react-virtual": "^3.13.9", - "use-sync-external-store": "^1.5.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "react": "^18 || ^19 || ^19.0.0-rc", - "react-dom": "^18 || ^19 || ^19.0.0-rc" - } - }, "node_modules/@heroicons/react": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz", @@ -1134,103 +1057,6 @@ "node": ">= 8" } }, - "node_modules/@react-aria/focus": { - "version": "3.21.2", - "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.21.2.tgz", - "integrity": "sha512-JWaCR7wJVggj+ldmM/cb/DXFg47CXR55lznJhZBh4XVqJjMKwaOOqpT5vNN7kpC1wUpXicGNuDnJDN1S/+6dhQ==", - "license": "Apache-2.0", - "dependencies": { - "@react-aria/interactions": "^3.25.6", - "@react-aria/utils": "^3.31.0", - "@react-types/shared": "^3.32.1", - "@swc/helpers": "^0.5.0", - "clsx": "^2.0.0" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", - "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" - } - }, - "node_modules/@react-aria/interactions": { - "version": "3.25.6", - "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.25.6.tgz", - "integrity": "sha512-5UgwZmohpixwNMVkMvn9K1ceJe6TzlRlAfuYoQDUuOkk62/JVJNDLAPKIf5YMRc7d2B0rmfgaZLMtbREb0Zvkw==", - "license": "Apache-2.0", - "dependencies": { - "@react-aria/ssr": "^3.9.10", - "@react-aria/utils": "^3.31.0", - "@react-stately/flags": "^3.1.2", - "@react-types/shared": "^3.32.1", - "@swc/helpers": "^0.5.0" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", - "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" - } - }, - "node_modules/@react-aria/ssr": { - "version": "3.9.10", - "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.10.tgz", - "integrity": "sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==", - "license": "Apache-2.0", - "dependencies": { - "@swc/helpers": "^0.5.0" - }, - "engines": { - "node": ">= 12" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" - } - }, - "node_modules/@react-aria/utils": { - "version": "3.31.0", - "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.31.0.tgz", - "integrity": "sha512-ABOzCsZrWzf78ysswmguJbx3McQUja7yeGj6/vZo4JVsZNlxAN+E9rs381ExBRI0KzVo6iBTeX5De8eMZPJXig==", - "license": "Apache-2.0", - "dependencies": { - "@react-aria/ssr": "^3.9.10", - "@react-stately/flags": "^3.1.2", - "@react-stately/utils": "^3.10.8", - "@react-types/shared": "^3.32.1", - "@swc/helpers": "^0.5.0", - "clsx": "^2.0.0" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", - "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" - } - }, - "node_modules/@react-stately/flags": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@react-stately/flags/-/flags-3.1.2.tgz", - "integrity": "sha512-2HjFcZx1MyQXoPqcBGALwWWmgFVUk2TuKVIQxCbRq7fPyWXIl6VHcakCLurdtYC2Iks7zizvz0Idv48MQ38DWg==", - "license": "Apache-2.0", - "dependencies": { - "@swc/helpers": "^0.5.0" - } - }, - "node_modules/@react-stately/utils": { - "version": "3.10.8", - "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.8.tgz", - "integrity": "sha512-SN3/h7SzRsusVQjQ4v10LaVsDc81jyyR0DD5HnsQitm/I5WDpaSr2nRHtyloPFU48jlql1XX/S04T2DLQM7Y3g==", - "license": "Apache-2.0", - "dependencies": { - "@swc/helpers": "^0.5.0" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" - } - }, - "node_modules/@react-types/shared": { - "version": "3.32.1", - "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.32.1.tgz", - "integrity": "sha512-famxyD5emrGGpFuUlgOP6fVW2h/ZaF405G5KDi3zPHzyjAWys/8W6NAVJtNbkCkhedmvL0xOhvt8feGXyXaw5w==", - "license": "Apache-2.0", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" - } - }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.38", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.38.tgz", @@ -1524,15 +1350,6 @@ "win32" ] }, - "node_modules/@swc/helpers": { - "version": "0.5.17", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", - "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.8.0" - } - }, "node_modules/@tailwindcss/node": { "version": "4.1.17", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.17.tgz", @@ -1790,33 +1607,6 @@ "vite": "^5.2.0 || ^6 || ^7" } }, - "node_modules/@tanstack/react-virtual": { - "version": "3.13.12", - "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.12.tgz", - "integrity": "sha512-Gd13QdxPSukP8ZrkbgS2RwoZseTTbQPLnQEn7HY/rqtM+8Zt95f7xKC7N0EsKs7aoz0WzZ+fditZux+F8EzYxA==", - "license": "MIT", - "dependencies": { - "@tanstack/virtual-core": "3.13.12" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/@tanstack/virtual-core": { - "version": "3.13.12", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.12.tgz", - "integrity": "sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -2431,15 +2221,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -4305,12 +4086,6 @@ "node": ">=8" } }, - "node_modules/tabbable": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.3.0.tgz", - "integrity": "sha512-EIHvdY5bPLuWForiR/AN2Bxngzpuwn1is4asboytXtpTgsArc+WmSJKVLlhdh71u7jFcryDqB2A8lQvj78MkyQ==", - "license": "MIT" - }, "node_modules/tailwindcss": { "version": "4.1.17", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz", @@ -4402,12 +4177,6 @@ "typescript": ">=4.8.4" } }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -4532,15 +4301,6 @@ "punycode": "^2.1.0" } }, - "node_modules/use-sync-external-store": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", - "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, "node_modules/vite": { "version": "7.1.9", "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.9.tgz", diff --git a/modules/frontend/package.json b/modules/frontend/package.json index e0b65ba..cc468cf 100644 --- a/modules/frontend/package.json +++ b/modules/frontend/package.json @@ -10,7 +10,6 @@ "preview": "vite preview" }, "dependencies": { - "@headlessui/react": "^2.2.9", "@heroicons/react": "^2.2.0", "@tailwindcss/vite": "^4.1.17", "axios": "^1.12.2", diff --git a/modules/frontend/src/App.css b/modules/frontend/src/App.css index e69de29..b9d355d 100644 --- a/modules/frontend/src/App.css +++ b/modules/frontend/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/modules/frontend/src/App.tsx b/modules/frontend/src/App.tsx index 909ad6c..f67c37e 100644 --- a/modules/frontend/src/App.tsx +++ b/modules/frontend/src/App.tsx @@ -2,13 +2,10 @@ import React from "react"; import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; import UserPage from "./pages/UserPage/UserPage"; import TitlesPage from "./pages/TitlesPage/TitlesPage"; -import { Header } from "./components/Header/Header"; const App: React.FC = () => { - const username = "nihonium"; return ( -
} /> } /> diff --git a/modules/frontend/src/components/Header/Header.tsx b/modules/frontend/src/components/Header/Header.tsx deleted file mode 100644 index 98b1295..0000000 --- a/modules/frontend/src/components/Header/Header.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import React, { useState } from "react"; -import { Link } from "react-router-dom"; -import { Bars3Icon, XMarkIcon } from "@heroicons/react/24/solid"; - -type HeaderProps = { - username?: string; -}; - -export const Header: React.FC = ({ username }) => { - const [menuOpen, setMenuOpen] = useState(false); - - const toggleMenu = () => setMenuOpen(!menuOpen); - - return ( -
-
-
- - {/* Левый блок — логотип / название */} -
- - NyanimeDB - -
- - {/* Центр — ссылки на разделы (desktop) */} - - - {/* Правый блок — профиль */} -
- {username ? ( - - {username} - - ) : ( - - Login - - )} -
- - {/* Бургер для мобильного */} -
- -
- -
-
- - {/* Мобильное меню */} - {menuOpen && ( -
- -
- )} -
- ); -}; diff --git a/modules/frontend/src/components/LayoutSwitch/LayoutSwitch.tsx b/modules/frontend/src/components/LayoutSwitch/LayoutSwitch.tsx deleted file mode 100644 index 679feea..0000000 --- a/modules/frontend/src/components/LayoutSwitch/LayoutSwitch.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from "react"; -import { Squares2X2Icon, Bars3Icon } from "@heroicons/react/24/solid"; - -export type LayoutSwitchProps = { - layout: "square" | "horizontal" - setLayout: (value: React.SetStateAction<"square" | "horizontal">) => void -}; - -export function LayoutSwitch({ - layout, - setLayout -}: LayoutSwitchProps) { - - return ( -
- -
- ); -} diff --git a/modules/frontend/src/components/ListView/ListView.tsx b/modules/frontend/src/components/ListView/ListView.tsx index 67488c0..e0e8ab9 100644 --- a/modules/frontend/src/components/ListView/ListView.tsx +++ b/modules/frontend/src/components/ListView/ListView.tsx @@ -1,49 +1,103 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import { Squares2X2Icon, Bars3Icon } from "@heroicons/react/24/solid"; +import type { CursorObj } from "../../api"; export type ListViewProps = { - items: T[]; - layout: "square" | "horizontal"; + fetchItems: (cursor: string, limit: number) => Promise<{ items: T[]; cursor: CursorObj}>; renderItem: (item: T, layout: "square" | "horizontal") => React.ReactNode; - onLoadMore: () => void; - hasMore: boolean; - loadingMore: boolean; + pageSize?: number; + searchPlaceholder?: string; + setSearch: any; }; export function ListView({ - items, - layout, + fetchItems, renderItem, - onLoadMore, - hasMore, - loadingMore + pageSize = 20, + searchPlaceholder = "Search...", }: ListViewProps) { + const [items, setItems] = useState([]); + const [cursorObj, setCursorObj] = useState(undefined); + const [loading, setLoading] = useState(true); + const [loadingMore, setLoadingMore] = useState(false); + const [search, setSearch] = useState(""); + const [layout, setLayout] = useState<"square" | "horizontal">("horizontal"); + const [error, setError] = useState(null); + + const loadItems = async (reset: boolean = false) => { + try { + if (reset) { + setLoading(true); + setCursorObj(undefined); + } else { + setLoadingMore(true); + } + + const cursorStr = cursorObj ? btoa(JSON.stringify(cursorObj)) : "" + console.log("encoded cursor: " + cursorStr) + + const result = await fetchItems(cursorStr, pageSize); + + if (reset) setItems(result.items); + else setItems(prev => [...prev, ...result.items]); + + setCursorObj(result.cursor); + setError(null); + } catch (err: any) { + console.error(err); + setError("Failed to fetch items."); + } finally { + setLoading(false); + setLoadingMore(false); + } + }; + + useEffect(() => { + loadItems(true); + }, [search]); return ( -
- {/* Items */} +
+
+ setSearch(e.target.value)} + className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 text-black" + /> + +
+ + {error &&
{error}
} +
{items.map(item => renderItem(item, layout))}
- {/* Load More */} - {hasMore && ( -
+ {cursorObj && ( +
)} + + {loading &&
Loading...
}
); } diff --git a/modules/frontend/src/components/SearchBar/SearchBar.tsx b/modules/frontend/src/components/SearchBar/SearchBar.tsx deleted file mode 100644 index 87aee66..0000000 --- a/modules/frontend/src/components/SearchBar/SearchBar.tsx +++ /dev/null @@ -1,34 +0,0 @@ -type SearchBarProps = { - placeholder?: string; - search: string; - setSearch: (value: string) => void; -}; - -export function SearchBar({ - placeholder = "Search...", - search, - setSearch, -}: SearchBarProps) { - return ( -
- setSearch(e.target.value)} - className=" - w-full - px-4 - py-2 - border - border-gray-300 - rounded-lg - focus:outline-none - focus:ring-2 - focus:ring-blue-500 - text-black - " - /> -
- ); -} diff --git a/modules/frontend/src/components/TitlesSortBox/TitlesSortBox.tsx b/modules/frontend/src/components/TitlesSortBox/TitlesSortBox.tsx deleted file mode 100644 index f1f8195..0000000 --- a/modules/frontend/src/components/TitlesSortBox/TitlesSortBox.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import React, { useState } from "react"; -import type { TitleSort } from "../../api"; -import { ChevronDownIcon, ArrowUpIcon, ArrowDownIcon } from "@heroicons/react/24/solid"; - -type TitlesSortBoxProps = { - sort: TitleSort; - setSort: (value: TitleSort) => void; - sortForward: boolean; - setSortForward: (value: boolean) => void; -}; - -const SORT_OPTIONS: TitleSort[] = ["id", "rating", "year", "views"]; - -export function TitlesSortBox({ - sort, - setSort, - sortForward, - setSortForward, -}: TitlesSortBoxProps) { - const [open, setOpen] = useState(false); - - const toggleSortDirection = () => setSortForward(!sortForward); - const handleSortSelect = (newSort: TitleSort) => { - setSort(newSort); - setOpen(false); - }; - - return ( -
- {/* Левая часть — смена направления */} - - - {/* Правая часть — выбор параметра */} - - - {/* Dropdown */} - {open && ( -
    - {SORT_OPTIONS.map(option => ( -
  • - -
  • - ))} -
- )} -
- ); -} diff --git a/modules/frontend/src/index.css b/modules/frontend/src/index.css index 7b32a9b..e20de02 100644 --- a/modules/frontend/src/index.css +++ b/modules/frontend/src/index.css @@ -5,5 +5,4 @@ html, body, #root { padding: 0; width: 100%; height: 100%; - @apply text-black bg-white; } \ No newline at end of file diff --git a/modules/frontend/src/pages/TitlesPage/TitlesPage.tsx b/modules/frontend/src/pages/TitlesPage/TitlesPage.tsx index 0fec3c8..b59f737 100644 --- a/modules/frontend/src/pages/TitlesPage/TitlesPage.tsx +++ b/modules/frontend/src/pages/TitlesPage/TitlesPage.tsx @@ -1,154 +1,52 @@ -import { useEffect, useState } from "react"; import { ListView } from "../../components/ListView/ListView"; -import { SearchBar } from "../../components/SearchBar/SearchBar"; -import { TitlesSortBox } from "../../components/TitlesSortBox/TitlesSortBox"; import { DefaultService } from "../../api/services/DefaultService"; import { TitleCardSquare } from "../../components/cards/TitleCardSquare"; import { TitleCardHorizontal } from "../../components/cards/TitleCardHorizontal"; -import type { CursorObj, Title, TitleSort } from "../../api"; -import { LayoutSwitch } from "../../components/LayoutSwitch/LayoutSwitch"; - -const PAGE_SIZE = 10; +import type { Title } from "../../api"; +import { useState, useEffect } from "react"; +const PAGE_SIZE = 20; export default function TitlesPage() { - const [titles, setTitles] = useState([]); - const [nextPage, setNextPage] = useState([]); - const [cursor, setCursor] = useState(null); const [search, setSearch] = useState(""); - const [loading, setLoading] = useState(true); - const [loadingMore, setLoadingMore] = useState(false); - const [sort, setSort] = useState("id"); - const [sortForward, setSortForward] = useState(true); - const [layout, setLayout] = useState<"square" | "horizontal">("square"); - const fetchPage = async (cursorObj: CursorObj | null) => { - const cursorStr = cursorObj ? btoa(JSON.stringify(cursorObj)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '') : ""; + const loadTitles = async (cursor: string, limit: number) => { + const result = await DefaultService.getTitles( + cursor, + undefined, + true, + search, + undefined, + undefined, + undefined, + undefined, + limit, + undefined, + 'all' + ); - try { - const result = await DefaultService.getTitles( - cursorStr, - sort, - sortForward, - search.trim() || undefined, - undefined, - undefined, - undefined, - undefined, - PAGE_SIZE, - undefined, - "all" - ); - - if ((result === undefined) || !result.data?.length) { - return { items: [], nextCursor: null }; - } - return { - items: result.data ?? [], - nextCursor: result.cursor ?? null - }; - } catch (err: any) { - if (err.status === 204) { - return { items: [], nextCursor: null }; - } - throw err; - } + return { + items: result.data ?? [], + cursor: result.cursor ?? null, + }; }; - // Инициализация: загружаем сразу две страницы - useEffect(() => { - const initLoad = async () => { - setLoading(true); - setTitles([]); - setNextPage([]); - setCursor(null); - - const firstPage = await fetchPage(null); - const secondPage = firstPage.nextCursor ? await fetchPage(firstPage.nextCursor) : { items: [], nextCursor: null }; - - setTitles(firstPage.items); - setNextPage(secondPage.items); - setCursor(secondPage.nextCursor); - setLoading(false); - }; - - initLoad(); - }, [search, sort, sortForward]); - - -const handleLoadMore = async () => { - if (nextPage.length === 0) { - setLoadingMore(false); - return; - } - - setLoadingMore(true); - - setTitles(prev => [...prev, ...nextPage]); - setNextPage([]); - - // Подгружаем следующую страницу с сервера - if (cursor) { - try { - const next = await fetchPage(cursor); - if (next.items.length > 0) { - setNextPage(next.items); - } - setCursor(next.nextCursor); - } catch (err) { - console.error(err); - } - } - - // Любой сценарий – выключаем loadingMore - setLoadingMore(false); -}; - - - return ( -
+
+ +

Titles

-

Titles

- -
- - - -
- - {loading &&
Loading...
} - - {!loading && titles.length === 0 && ( -
No titles found.
- )} - - {titles.length > 0 && ( - <> - - items={titles} - layout={layout} - hasMore={!!cursor || nextPage.length > 1} - loadingMore={loadingMore} - onLoadMore={handleLoadMore} - renderItem={(title, layout) => - layout === "square" - ? - : - } - /> - - {!cursor && nextPage.length == 0 && ( -
- Результатов больше нет, было найдено {titles.length} тайтлов. -
- )} - - )} + + pageSize={PAGE_SIZE} + fetchItems={loadTitles} + searchPlaceholder="Search titles..." + renderItem={(title, layout) => + layout === "square" + ? + : + } + setSearch={setSearch} + />
); } + diff --git a/modules/frontend/vite.config.ts b/modules/frontend/vite.config.ts index 554d630..6c261e6 100644 --- a/modules/frontend/vite.config.ts +++ b/modules/frontend/vite.config.ts @@ -9,7 +9,7 @@ export default defineConfig({ tailwindcss() ], server: { - host: '0.0.0.0', + host: '127.0.0.1', port: 8083, }, })