diff --git a/modules/frontend/package-lock.json b/modules/frontend/package-lock.json index f5dde46..6a06afb 100644 --- a/modules/frontend/package-lock.json +++ b/modules/frontend/package-lock.json @@ -8,13 +8,10 @@ "name": "nyanimedb-frontend", "version": "0.0.0", "dependencies": { - "@heroicons/react": "^2.2.0", - "@tailwindcss/vite": "^4.1.17", "axios": "^1.12.2", "react": "^19.1.1", "react-dom": "^19.1.1", - "react-router-dom": "^7.9.4", - "tailwindcss": "^4.1.17" + "react-router-dom": "^7.9.4" }, "devDependencies": { "@eslint/js": "^9.36.0", @@ -340,6 +337,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -356,6 +354,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -372,6 +371,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -388,6 +388,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -404,6 +405,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -420,6 +422,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -436,6 +439,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -452,6 +456,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -468,6 +473,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -484,6 +490,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -500,6 +507,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -516,6 +524,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -532,6 +541,7 @@ "cpu": [ "mips64el" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -548,6 +558,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -564,6 +575,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -580,6 +592,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -596,6 +609,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -612,6 +626,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -628,6 +643,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -644,6 +660,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -660,6 +677,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -676,6 +694,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -692,6 +711,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -708,6 +728,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -724,6 +745,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -740,6 +762,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -906,15 +929,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@heroicons/react": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz", - "integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==", - "license": "MIT", - "peerDependencies": { - "react": ">= 16 || ^19.0.0-rc" - } - }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -971,6 +985,7 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -981,6 +996,7 @@ "version": "2.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -991,6 +1007,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -1000,12 +1017,14 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -1071,6 +1090,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1084,6 +1104,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1097,6 +1118,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1110,6 +1132,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1123,6 +1146,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1136,6 +1160,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1149,6 +1174,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1162,6 +1188,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1175,6 +1202,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1188,6 +1216,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1201,6 +1230,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1214,6 +1244,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1227,6 +1258,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1240,6 +1272,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1253,6 +1286,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1266,6 +1300,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1279,6 +1314,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1292,6 +1328,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1305,6 +1342,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1318,6 +1356,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1331,6 +1370,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1344,269 +1384,13 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ] }, - "node_modules/@tailwindcss/node": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.17.tgz", - "integrity": "sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==", - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.4", - "enhanced-resolve": "^5.18.3", - "jiti": "^2.6.1", - "lightningcss": "1.30.2", - "magic-string": "^0.30.21", - "source-map-js": "^1.2.1", - "tailwindcss": "4.1.17" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.17.tgz", - "integrity": "sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==", - "license": "MIT", - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.17", - "@tailwindcss/oxide-darwin-arm64": "4.1.17", - "@tailwindcss/oxide-darwin-x64": "4.1.17", - "@tailwindcss/oxide-freebsd-x64": "4.1.17", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.17", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.17", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.17", - "@tailwindcss/oxide-linux-x64-musl": "4.1.17", - "@tailwindcss/oxide-wasm32-wasi": "4.1.17", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.17", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.17" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.17.tgz", - "integrity": "sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.17.tgz", - "integrity": "sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.17.tgz", - "integrity": "sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.17.tgz", - "integrity": "sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.17.tgz", - "integrity": "sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.17.tgz", - "integrity": "sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.17.tgz", - "integrity": "sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.17.tgz", - "integrity": "sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.17.tgz", - "integrity": "sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.17.tgz", - "integrity": "sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.6.0", - "@emnapi/runtime": "^1.6.0", - "@emnapi/wasi-threads": "^1.1.0", - "@napi-rs/wasm-runtime": "^1.0.7", - "@tybys/wasm-util": "^0.10.1", - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.17.tgz", - "integrity": "sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.17.tgz", - "integrity": "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.17.tgz", - "integrity": "sha512-4+9w8ZHOiGnpcGI6z1TVVfWaX/koK7fKeSYF3qlYg2xpBtbteP2ddBxiarL+HVgfSJGeK5RIxRQmKm4rTJJAwA==", - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.1.17", - "@tailwindcss/oxide": "4.1.17", - "tailwindcss": "4.1.17" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7" - } - }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1656,6 +1440,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, "license": "MIT" }, "node_modules/@types/json-schema": { @@ -1669,7 +1454,7 @@ "version": "24.7.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.0.tgz", "integrity": "sha512-IbKooQVqUBrlzWTi79E8Fw78l8k1RNtlDDNWsFZs7XonuQSJ8oNYfEeclhprUldXISRMLzBpILuKgPlIxm+/Yw==", - "devOptional": true, + "dev": true, "license": "MIT", "peer": true, "dependencies": { @@ -2342,15 +2127,6 @@ "node": ">=0.4.0" } }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -2372,19 +2148,6 @@ "dev": true, "license": "ISC" }, - "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2434,6 +2197,7 @@ "version": "0.25.10", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", + "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -2853,6 +2617,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -2961,6 +2726,7 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, "license": "ISC" }, "node_modules/graphemer": { @@ -3118,15 +2884,6 @@ "dev": true, "license": "ISC" }, - "node_modules/jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3231,255 +2988,6 @@ "node": ">= 0.8.0" } }, - "node_modules/lightningcss": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", - "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.30.2", - "lightningcss-darwin-arm64": "1.30.2", - "lightningcss-darwin-x64": "1.30.2", - "lightningcss-freebsd-x64": "1.30.2", - "lightningcss-linux-arm-gnueabihf": "1.30.2", - "lightningcss-linux-arm64-gnu": "1.30.2", - "lightningcss-linux-arm64-musl": "1.30.2", - "lightningcss-linux-x64-gnu": "1.30.2", - "lightningcss-linux-x64-musl": "1.30.2", - "lightningcss-win32-arm64-msvc": "1.30.2", - "lightningcss-win32-x64-msvc": "1.30.2" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", - "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", - "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", - "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", - "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", - "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", - "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", - "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", - "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", - "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", - "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", - "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -3513,15 +3021,6 @@ "yallist": "^3.0.2" } }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -3610,6 +3109,7 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, "funding": [ { "type": "github", @@ -3749,6 +3249,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -3768,6 +3269,7 @@ "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, "funding": [ { "type": "opencollective", @@ -3935,6 +3437,7 @@ "version": "4.52.4", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz", "integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==", + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -4055,6 +3558,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -4086,29 +3590,11 @@ "node": ">=8" } }, - "node_modules/tailwindcss": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz", - "integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==", - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -4125,6 +3611,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -4142,6 +3629,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, "license": "MIT", "peer": true, "engines": { @@ -4247,7 +3735,7 @@ "version": "7.14.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz", "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/universalify": { @@ -4305,6 +3793,7 @@ "version": "7.1.9", "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.9.tgz", "integrity": "sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg==", + "dev": true, "license": "MIT", "peer": true, "dependencies": { @@ -4380,6 +3869,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -4397,6 +3887,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, "license": "MIT", "peer": true, "engines": { diff --git a/modules/frontend/package.json b/modules/frontend/package.json index cc468cf..f15ffd5 100644 --- a/modules/frontend/package.json +++ b/modules/frontend/package.json @@ -10,13 +10,10 @@ "preview": "vite preview" }, "dependencies": { - "@heroicons/react": "^2.2.0", - "@tailwindcss/vite": "^4.1.17", "axios": "^1.12.2", "react": "^19.1.1", "react-dom": "^19.1.1", - "react-router-dom": "^7.9.4", - "tailwindcss": "^4.1.17" + "react-router-dom": "^7.9.4" }, "devDependencies": { "@eslint/js": "^9.36.0", diff --git a/modules/frontend/src/api/index.ts b/modules/frontend/src/api/index.ts index 80ae491..f0d09ee 100644 --- a/modules/frontend/src/api/index.ts +++ b/modules/frontend/src/api/index.ts @@ -8,19 +8,16 @@ export { OpenAPI } from './core/OpenAPI'; export type { OpenAPIConfig } from './core/OpenAPI'; export type { cursor } from './models/cursor'; -export type { CursorObj } from './models/CursorObj'; export type { Image } from './models/Image'; -export type { ReleaseSeason } from './models/ReleaseSeason'; +export { ReleaseSeason } from './models/ReleaseSeason'; export type { Review } from './models/Review'; export type { Studio } from './models/Studio'; export type { Tag } from './models/Tag'; export type { Tags } from './models/Tags'; export type { Title } from './models/Title'; -export type { title_sort } from './models/title_sort'; -export type { TitleSort } from './models/TitleSort'; -export type { TitleStatus } from './models/TitleStatus'; +export { TitleStatus } from './models/TitleStatus'; export type { User } from './models/User'; export type { UserTitle } from './models/UserTitle'; -export type { UserTitleStatus } from './models/UserTitleStatus'; +export { UserTitleStatus } from './models/UserTitleStatus'; export { DefaultService } from './services/DefaultService'; diff --git a/modules/frontend/src/api/models/ReleaseSeason.ts b/modules/frontend/src/api/models/ReleaseSeason.ts index ad9f930..182b980 100644 --- a/modules/frontend/src/api/models/ReleaseSeason.ts +++ b/modules/frontend/src/api/models/ReleaseSeason.ts @@ -5,4 +5,9 @@ /** * Title release season */ -export type ReleaseSeason = 'winter' | 'spring' | 'summer' | 'fall'; +export enum ReleaseSeason { + WINTER = 'winter', + SPRING = 'spring', + SUMMER = 'summer', + FALL = 'fall', +} diff --git a/modules/frontend/src/api/models/TitleStatus.ts b/modules/frontend/src/api/models/TitleStatus.ts index 72e0261..811ece8 100644 --- a/modules/frontend/src/api/models/TitleStatus.ts +++ b/modules/frontend/src/api/models/TitleStatus.ts @@ -5,4 +5,8 @@ /** * Title status */ -export type TitleStatus = 'finished' | 'ongoing' | 'planned'; +export enum TitleStatus { + FINISHED = 'finished', + ONGOING = 'ongoing', + PLANNED = 'planned', +} diff --git a/modules/frontend/src/api/models/User.ts b/modules/frontend/src/api/models/User.ts index cd76dbe..541028e 100644 --- a/modules/frontend/src/api/models/User.ts +++ b/modules/frontend/src/api/models/User.ts @@ -6,11 +6,11 @@ export type User = { /** * Unique user ID (primary key) */ - id?: number; + id: number; /** * ID of the user avatar (references images table) */ - avatar_id?: number; + avatar_id?: number | null; /** * User email */ diff --git a/modules/frontend/src/api/models/UserTitleStatus.ts b/modules/frontend/src/api/models/UserTitleStatus.ts index 0a29626..20651fe 100644 --- a/modules/frontend/src/api/models/UserTitleStatus.ts +++ b/modules/frontend/src/api/models/UserTitleStatus.ts @@ -5,4 +5,9 @@ /** * User's title status */ -export type UserTitleStatus = 'finished' | 'planned' | 'dropped' | 'in-progress'; +export enum UserTitleStatus { + FINISHED = 'finished', + PLANNED = 'planned', + DROPPED = 'dropped', + IN_PROGRESS = 'in-progress', +} diff --git a/modules/frontend/src/api/services/DefaultService.ts b/modules/frontend/src/api/services/DefaultService.ts index 52321b8..b0ae54d 100644 --- a/modules/frontend/src/api/services/DefaultService.ts +++ b/modules/frontend/src/api/services/DefaultService.ts @@ -2,23 +2,17 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { CursorObj } from '../models/CursorObj'; import type { ReleaseSeason } from '../models/ReleaseSeason'; import type { Title } from '../models/Title'; -import type { TitleSort } from '../models/TitleSort'; import type { TitleStatus } from '../models/TitleStatus'; import type { User } from '../models/User'; import type { UserTitle } from '../models/UserTitle'; -import type { UserTitleStatus } from '../models/UserTitleStatus'; import type { CancelablePromise } from '../core/CancelablePromise'; import { OpenAPI } from '../core/OpenAPI'; import { request as __request } from '../core/request'; export class DefaultService { /** * Get titles - * @param cursor - * @param sort - * @param sortForward * @param word * @param status * @param rating @@ -27,13 +21,10 @@ export class DefaultService { * @param limit * @param offset * @param fields - * @returns any List of titles with cursor + * @returns Title List of titles * @throws ApiError */ public static getTitles( - cursor?: string, - sort?: TitleSort, - sortForward: boolean = true, word?: string, status?: TitleStatus, rating?: number, @@ -42,20 +33,11 @@ export class DefaultService { limit: number = 10, offset?: number, fields: string = 'all', - ): CancelablePromise<{ - /** - * List of titles - */ - data: Array; - cursor: CursorObj; - }> { + ): CancelablePromise<Array<Title>> { return __request(OpenAPI, { method: 'GET', url: '/titles', query: { - 'cursor': cursor, - 'sort': sort, - 'sort_forward': sortForward, 'word': word, 'status': status, 'rating': rating, @@ -129,13 +111,9 @@ export class DefaultService { * Get user titles * @param userId * @param cursor - * @param word - * @param status - * @param watchStatus - * @param rating - * @param releaseYear - * @param releaseSeason + * @param query * @param limit + * @param offset * @param fields * @returns UserTitle List of user titles * @throws ApiError @@ -143,30 +121,22 @@ export class DefaultService { public static getUsersTitles( userId: string, cursor?: string, - word?: string, - status?: TitleStatus, - watchStatus?: UserTitleStatus, - rating?: number, - releaseYear?: number, - releaseSeason?: ReleaseSeason, + query?: string, limit: number = 10, + offset?: number, fields: string = 'all', ): CancelablePromise<Array<UserTitle>> { return __request(OpenAPI, { method: 'GET', - url: '/users/{user_id}/titles/', + url: '/users/{user_id}/titles', path: { 'user_id': userId, }, query: { 'cursor': cursor, - 'word': word, - 'status': status, - 'watch_status': watchStatus, - 'rating': rating, - 'release_year': releaseYear, - 'release_season': releaseSeason, + 'query': query, 'limit': limit, + 'offset': offset, 'fields': fields, }, errors: { diff --git a/modules/frontend/src/api_/core/ApiError.ts b/modules/frontend/src/api_/core/ApiError.ts new file mode 100644 index 0000000..ec7b16a --- /dev/null +++ b/modules/frontend/src/api_/core/ApiError.ts @@ -0,0 +1,25 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiResult } from './ApiResult'; + +export class ApiError extends Error { + public readonly url: string; + public readonly status: number; + public readonly statusText: string; + public readonly body: any; + public readonly request: ApiRequestOptions; + + constructor(request: ApiRequestOptions, response: ApiResult, message: string) { + super(message); + + this.name = 'ApiError'; + this.url = response.url; + this.status = response.status; + this.statusText = response.statusText; + this.body = response.body; + this.request = request; + } +} diff --git a/modules/frontend/src/api_/core/ApiRequestOptions.ts b/modules/frontend/src/api_/core/ApiRequestOptions.ts new file mode 100644 index 0000000..93143c3 --- /dev/null +++ b/modules/frontend/src/api_/core/ApiRequestOptions.ts @@ -0,0 +1,17 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type ApiRequestOptions = { + readonly method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH'; + readonly url: string; + readonly path?: Record<string, any>; + readonly cookies?: Record<string, any>; + readonly headers?: Record<string, any>; + readonly query?: Record<string, any>; + readonly formData?: Record<string, any>; + readonly body?: any; + readonly mediaType?: string; + readonly responseHeader?: string; + readonly errors?: Record<number, string>; +}; diff --git a/modules/frontend/src/api_/core/ApiResult.ts b/modules/frontend/src/api_/core/ApiResult.ts new file mode 100644 index 0000000..ee1126e --- /dev/null +++ b/modules/frontend/src/api_/core/ApiResult.ts @@ -0,0 +1,11 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type ApiResult = { + readonly url: string; + readonly ok: boolean; + readonly status: number; + readonly statusText: string; + readonly body: any; +}; diff --git a/modules/frontend/src/api_/core/CancelablePromise.ts b/modules/frontend/src/api_/core/CancelablePromise.ts new file mode 100644 index 0000000..d70de92 --- /dev/null +++ b/modules/frontend/src/api_/core/CancelablePromise.ts @@ -0,0 +1,131 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export class CancelError extends Error { + + constructor(message: string) { + super(message); + this.name = 'CancelError'; + } + + public get isCancelled(): boolean { + return true; + } +} + +export interface OnCancel { + readonly isResolved: boolean; + readonly isRejected: boolean; + readonly isCancelled: boolean; + + (cancelHandler: () => void): void; +} + +export class CancelablePromise<T> implements Promise<T> { + #isResolved: boolean; + #isRejected: boolean; + #isCancelled: boolean; + readonly #cancelHandlers: (() => void)[]; + readonly #promise: Promise<T>; + #resolve?: (value: T | PromiseLike<T>) => void; + #reject?: (reason?: any) => void; + + constructor( + executor: ( + resolve: (value: T | PromiseLike<T>) => void, + reject: (reason?: any) => void, + onCancel: OnCancel + ) => void + ) { + this.#isResolved = false; + this.#isRejected = false; + this.#isCancelled = false; + this.#cancelHandlers = []; + this.#promise = new Promise<T>((resolve, reject) => { + this.#resolve = resolve; + this.#reject = reject; + + const onResolve = (value: T | PromiseLike<T>): void => { + if (this.#isResolved || this.#isRejected || this.#isCancelled) { + return; + } + this.#isResolved = true; + if (this.#resolve) this.#resolve(value); + }; + + const onReject = (reason?: any): void => { + if (this.#isResolved || this.#isRejected || this.#isCancelled) { + return; + } + this.#isRejected = true; + if (this.#reject) this.#reject(reason); + }; + + const onCancel = (cancelHandler: () => void): void => { + if (this.#isResolved || this.#isRejected || this.#isCancelled) { + return; + } + this.#cancelHandlers.push(cancelHandler); + }; + + Object.defineProperty(onCancel, 'isResolved', { + get: (): boolean => this.#isResolved, + }); + + Object.defineProperty(onCancel, 'isRejected', { + get: (): boolean => this.#isRejected, + }); + + Object.defineProperty(onCancel, 'isCancelled', { + get: (): boolean => this.#isCancelled, + }); + + return executor(onResolve, onReject, onCancel as OnCancel); + }); + } + + get [Symbol.toStringTag]() { + return "Cancellable Promise"; + } + + public then<TResult1 = T, TResult2 = never>( + onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null, + onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null + ): Promise<TResult1 | TResult2> { + return this.#promise.then(onFulfilled, onRejected); + } + + public catch<TResult = never>( + onRejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null + ): Promise<T | TResult> { + return this.#promise.catch(onRejected); + } + + public finally(onFinally?: (() => void) | null): Promise<T> { + return this.#promise.finally(onFinally); + } + + public cancel(): void { + if (this.#isResolved || this.#isRejected || this.#isCancelled) { + return; + } + this.#isCancelled = true; + if (this.#cancelHandlers.length) { + try { + for (const cancelHandler of this.#cancelHandlers) { + cancelHandler(); + } + } catch (error) { + console.warn('Cancellation threw an error', error); + return; + } + } + this.#cancelHandlers.length = 0; + if (this.#reject) this.#reject(new CancelError('Request aborted')); + } + + public get isCancelled(): boolean { + return this.#isCancelled; + } +} diff --git a/modules/frontend/src/api_/core/OpenAPI.ts b/modules/frontend/src/api_/core/OpenAPI.ts new file mode 100644 index 0000000..185e5c3 --- /dev/null +++ b/modules/frontend/src/api_/core/OpenAPI.ts @@ -0,0 +1,32 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ApiRequestOptions } from './ApiRequestOptions'; + +type Resolver<T> = (options: ApiRequestOptions) => Promise<T>; +type Headers = Record<string, string>; + +export type OpenAPIConfig = { + BASE: string; + VERSION: string; + WITH_CREDENTIALS: boolean; + CREDENTIALS: 'include' | 'omit' | 'same-origin'; + TOKEN?: string | Resolver<string> | undefined; + USERNAME?: string | Resolver<string> | undefined; + PASSWORD?: string | Resolver<string> | undefined; + HEADERS?: Headers | Resolver<Headers> | undefined; + ENCODE_PATH?: ((path: string) => string) | undefined; +}; + +export const OpenAPI: OpenAPIConfig = { + BASE: '/api/v1', + VERSION: '1.0.0', + WITH_CREDENTIALS: false, + CREDENTIALS: 'include', + TOKEN: undefined, + USERNAME: undefined, + PASSWORD: undefined, + HEADERS: undefined, + ENCODE_PATH: undefined, +}; diff --git a/modules/frontend/src/api_/core/request.ts b/modules/frontend/src/api_/core/request.ts new file mode 100644 index 0000000..1dc6fef --- /dev/null +++ b/modules/frontend/src/api_/core/request.ts @@ -0,0 +1,323 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import axios from 'axios'; +import type { AxiosError, AxiosRequestConfig, AxiosResponse, AxiosInstance } from 'axios'; +import FormData from 'form-data'; + +import { ApiError } from './ApiError'; +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiResult } from './ApiResult'; +import { CancelablePromise } from './CancelablePromise'; +import type { OnCancel } from './CancelablePromise'; +import type { OpenAPIConfig } from './OpenAPI'; + +export const isDefined = <T>(value: T | null | undefined): value is Exclude<T, null | undefined> => { + return value !== undefined && value !== null; +}; + +export const isString = (value: any): value is string => { + return typeof value === 'string'; +}; + +export const isStringWithValue = (value: any): value is string => { + return isString(value) && value !== ''; +}; + +export const isBlob = (value: any): value is Blob => { + return ( + typeof value === 'object' && + typeof value.type === 'string' && + typeof value.stream === 'function' && + typeof value.arrayBuffer === 'function' && + typeof value.constructor === 'function' && + typeof value.constructor.name === 'string' && + /^(Blob|File)$/.test(value.constructor.name) && + /^(Blob|File)$/.test(value[Symbol.toStringTag]) + ); +}; + +export const isFormData = (value: any): value is FormData => { + return value instanceof FormData; +}; + +export const isSuccess = (status: number): boolean => { + return status >= 200 && status < 300; +}; + +export const base64 = (str: string): string => { + try { + return btoa(str); + } catch (err) { + // @ts-ignore + return Buffer.from(str).toString('base64'); + } +}; + +export const getQueryString = (params: Record<string, any>): string => { + const qs: string[] = []; + + const append = (key: string, value: any) => { + qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`); + }; + + const process = (key: string, value: any) => { + if (isDefined(value)) { + if (Array.isArray(value)) { + value.forEach(v => { + process(key, v); + }); + } else if (typeof value === 'object') { + Object.entries(value).forEach(([k, v]) => { + process(`${key}[${k}]`, v); + }); + } else { + append(key, value); + } + } + }; + + Object.entries(params).forEach(([key, value]) => { + process(key, value); + }); + + if (qs.length > 0) { + return `?${qs.join('&')}`; + } + + return ''; +}; + +const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => { + const encoder = config.ENCODE_PATH || encodeURI; + + const path = options.url + .replace('{api-version}', config.VERSION) + .replace(/{(.*?)}/g, (substring: string, group: string) => { + if (options.path?.hasOwnProperty(group)) { + return encoder(String(options.path[group])); + } + return substring; + }); + + const url = `${config.BASE}${path}`; + if (options.query) { + return `${url}${getQueryString(options.query)}`; + } + return url; +}; + +export const getFormData = (options: ApiRequestOptions): FormData | undefined => { + if (options.formData) { + const formData = new FormData(); + + const process = (key: string, value: any) => { + if (isString(value) || isBlob(value)) { + formData.append(key, value); + } else { + formData.append(key, JSON.stringify(value)); + } + }; + + Object.entries(options.formData) + .filter(([_, value]) => isDefined(value)) + .forEach(([key, value]) => { + if (Array.isArray(value)) { + value.forEach(v => process(key, v)); + } else { + process(key, value); + } + }); + + return formData; + } + return undefined; +}; + +type Resolver<T> = (options: ApiRequestOptions) => Promise<T>; + +export const resolve = async <T>(options: ApiRequestOptions, resolver?: T | Resolver<T>): Promise<T | undefined> => { + if (typeof resolver === 'function') { + return (resolver as Resolver<T>)(options); + } + return resolver; +}; + +export const getHeaders = async (config: OpenAPIConfig, options: ApiRequestOptions, formData?: FormData): Promise<Record<string, string>> => { + const [token, username, password, additionalHeaders] = await Promise.all([ + resolve(options, config.TOKEN), + resolve(options, config.USERNAME), + resolve(options, config.PASSWORD), + resolve(options, config.HEADERS), + ]); + + const formHeaders = typeof formData?.getHeaders === 'function' && formData?.getHeaders() || {} + + const headers = Object.entries({ + Accept: 'application/json', + ...additionalHeaders, + ...options.headers, + ...formHeaders, + }) + .filter(([_, value]) => isDefined(value)) + .reduce((headers, [key, value]) => ({ + ...headers, + [key]: String(value), + }), {} as Record<string, string>); + + if (isStringWithValue(token)) { + headers['Authorization'] = `Bearer ${token}`; + } + + if (isStringWithValue(username) && isStringWithValue(password)) { + const credentials = base64(`${username}:${password}`); + headers['Authorization'] = `Basic ${credentials}`; + } + + if (options.body !== undefined) { + if (options.mediaType) { + headers['Content-Type'] = options.mediaType; + } else if (isBlob(options.body)) { + headers['Content-Type'] = options.body.type || 'application/octet-stream'; + } else if (isString(options.body)) { + headers['Content-Type'] = 'text/plain'; + } else if (!isFormData(options.body)) { + headers['Content-Type'] = 'application/json'; + } + } + + return headers; +}; + +export const getRequestBody = (options: ApiRequestOptions): any => { + if (options.body) { + return options.body; + } + return undefined; +}; + +export const sendRequest = async <T>( + config: OpenAPIConfig, + options: ApiRequestOptions, + url: string, + body: any, + formData: FormData | undefined, + headers: Record<string, string>, + onCancel: OnCancel, + axiosClient: AxiosInstance +): Promise<AxiosResponse<T>> => { + const source = axios.CancelToken.source(); + + const requestConfig: AxiosRequestConfig = { + url, + headers, + data: body ?? formData, + method: options.method, + withCredentials: config.WITH_CREDENTIALS, + withXSRFToken: config.CREDENTIALS === 'include' ? config.WITH_CREDENTIALS : false, + cancelToken: source.token, + }; + + onCancel(() => source.cancel('The user aborted a request.')); + + try { + return await axiosClient.request(requestConfig); + } catch (error) { + const axiosError = error as AxiosError<T>; + if (axiosError.response) { + return axiosError.response; + } + throw error; + } +}; + +export const getResponseHeader = (response: AxiosResponse<any>, responseHeader?: string): string | undefined => { + if (responseHeader) { + const content = response.headers[responseHeader]; + if (isString(content)) { + return content; + } + } + return undefined; +}; + +export const getResponseBody = (response: AxiosResponse<any>): any => { + if (response.status !== 204) { + return response.data; + } + return undefined; +}; + +export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void => { + const errors: Record<number, string> = { + 400: 'Bad Request', + 401: 'Unauthorized', + 403: 'Forbidden', + 404: 'Not Found', + 500: 'Internal Server Error', + 502: 'Bad Gateway', + 503: 'Service Unavailable', + ...options.errors, + } + + const error = errors[result.status]; + if (error) { + throw new ApiError(options, result, error); + } + + if (!result.ok) { + const errorStatus = result.status ?? 'unknown'; + const errorStatusText = result.statusText ?? 'unknown'; + const errorBody = (() => { + try { + return JSON.stringify(result.body, null, 2); + } catch (e) { + return undefined; + } + })(); + + throw new ApiError(options, result, + `Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}` + ); + } +}; + +/** + * Request method + * @param config The OpenAPI configuration object + * @param options The request options from the service + * @param axiosClient The axios client instance to use + * @returns CancelablePromise<T> + * @throws ApiError + */ +export const request = <T>(config: OpenAPIConfig, options: ApiRequestOptions, axiosClient: AxiosInstance = axios): CancelablePromise<T> => { + return new CancelablePromise(async (resolve, reject, onCancel) => { + try { + const url = getUrl(config, options); + const formData = getFormData(options); + const body = getRequestBody(options); + const headers = await getHeaders(config, options, formData); + + if (!onCancel.isCancelled) { + const response = await sendRequest<T>(config, options, url, body, formData, headers, onCancel, axiosClient); + const responseBody = getResponseBody(response); + const responseHeader = getResponseHeader(response, options.responseHeader); + + const result: ApiResult = { + url, + ok: isSuccess(response.status), + status: response.status, + statusText: response.statusText, + body: responseHeader ?? responseBody, + }; + + catchErrorCodes(options, result); + + resolve(result.body); + } + } catch (error) { + reject(error); + } + }); +}; diff --git a/modules/frontend/src/api_/index.ts b/modules/frontend/src/api_/index.ts new file mode 100644 index 0000000..f0d09ee --- /dev/null +++ b/modules/frontend/src/api_/index.ts @@ -0,0 +1,23 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export { ApiError } from './core/ApiError'; +export { CancelablePromise, CancelError } from './core/CancelablePromise'; +export { OpenAPI } from './core/OpenAPI'; +export type { OpenAPIConfig } from './core/OpenAPI'; + +export type { cursor } from './models/cursor'; +export type { Image } from './models/Image'; +export { ReleaseSeason } from './models/ReleaseSeason'; +export type { Review } from './models/Review'; +export type { Studio } from './models/Studio'; +export type { Tag } from './models/Tag'; +export type { Tags } from './models/Tags'; +export type { Title } from './models/Title'; +export { TitleStatus } from './models/TitleStatus'; +export type { User } from './models/User'; +export type { UserTitle } from './models/UserTitle'; +export { UserTitleStatus } from './models/UserTitleStatus'; + +export { DefaultService } from './services/DefaultService'; diff --git a/modules/frontend/src/api_/models/Image.ts b/modules/frontend/src/api_/models/Image.ts new file mode 100644 index 0000000..1317db7 --- /dev/null +++ b/modules/frontend/src/api_/models/Image.ts @@ -0,0 +1,10 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type Image = { + id?: number; + storage_type?: string; + image_path?: string; +}; + diff --git a/modules/frontend/src/api_/models/ReleaseSeason.ts b/modules/frontend/src/api_/models/ReleaseSeason.ts new file mode 100644 index 0000000..182b980 --- /dev/null +++ b/modules/frontend/src/api_/models/ReleaseSeason.ts @@ -0,0 +1,13 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +/** + * Title release season + */ +export enum ReleaseSeason { + WINTER = 'winter', + SPRING = 'spring', + SUMMER = 'summer', + FALL = 'fall', +} diff --git a/modules/frontend/src/api/models/CursorObj.ts b/modules/frontend/src/api_/models/Review.ts similarity index 66% rename from modules/frontend/src/api/models/CursorObj.ts rename to modules/frontend/src/api_/models/Review.ts index f54abb1..9b453b7 100644 --- a/modules/frontend/src/api/models/CursorObj.ts +++ b/modules/frontend/src/api_/models/Review.ts @@ -2,8 +2,4 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -export type CursorObj = { - id: number; - param?: string; -}; - +export type Review = Record<string, any>; diff --git a/modules/frontend/src/api_/models/Studio.ts b/modules/frontend/src/api_/models/Studio.ts new file mode 100644 index 0000000..062695a --- /dev/null +++ b/modules/frontend/src/api_/models/Studio.ts @@ -0,0 +1,12 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { Image } from './Image'; +export type Studio = { + id: number; + name: string; + poster?: Image; + description?: string; +}; + diff --git a/modules/frontend/src/api_/models/Tag.ts b/modules/frontend/src/api_/models/Tag.ts new file mode 100644 index 0000000..665c724 --- /dev/null +++ b/modules/frontend/src/api_/models/Tag.ts @@ -0,0 +1,8 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +/** + * A localized tag: keys are language codes (ISO 639-1), values are tag names + */ +export type Tag = Record<string, string>; diff --git a/modules/frontend/src/api/models/TitleSort.ts b/modules/frontend/src/api_/models/Tags.ts similarity index 60% rename from modules/frontend/src/api/models/TitleSort.ts rename to modules/frontend/src/api_/models/Tags.ts index 1c9385e..748f066 100644 --- a/modules/frontend/src/api/models/TitleSort.ts +++ b/modules/frontend/src/api_/models/Tags.ts @@ -2,7 +2,8 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ +import type { Tag } from './Tag'; /** - * Title sort order + * Array of localized tags */ -export type TitleSort = 'id' | 'year' | 'rating' | 'views'; +export type Tags = Array<Tag>; diff --git a/modules/frontend/src/api/models/title_sort.ts b/modules/frontend/src/api_/models/Title.ts similarity index 61% rename from modules/frontend/src/api/models/title_sort.ts rename to modules/frontend/src/api_/models/Title.ts index 69b01a7..4da7aa3 100644 --- a/modules/frontend/src/api/models/title_sort.ts +++ b/modules/frontend/src/api_/models/Title.ts @@ -2,5 +2,4 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { TitleSort } from './TitleSort'; -export type title_sort = TitleSort; +export type Title = Record<string, any>; diff --git a/modules/frontend/src/api_/models/TitleStatus.ts b/modules/frontend/src/api_/models/TitleStatus.ts new file mode 100644 index 0000000..811ece8 --- /dev/null +++ b/modules/frontend/src/api_/models/TitleStatus.ts @@ -0,0 +1,12 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +/** + * Title status + */ +export enum TitleStatus { + FINISHED = 'finished', + ONGOING = 'ongoing', + PLANNED = 'planned', +} diff --git a/modules/frontend/src/api_/models/User.ts b/modules/frontend/src/api_/models/User.ts new file mode 100644 index 0000000..541028e --- /dev/null +++ b/modules/frontend/src/api_/models/User.ts @@ -0,0 +1,35 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type User = { + /** + * Unique user ID (primary key) + */ + id: number; + /** + * ID of the user avatar (references images table) + */ + avatar_id?: number | null; + /** + * User email + */ + mail?: string; + /** + * Username (alphanumeric + _ or -) + */ + nickname: string; + /** + * Display name + */ + disp_name?: string; + /** + * User description + */ + user_desc?: string; + /** + * Timestamp when the user was created + */ + creation_date?: string; +}; + diff --git a/modules/frontend/src/api_/models/UserTitle.ts b/modules/frontend/src/api_/models/UserTitle.ts new file mode 100644 index 0000000..26d5ddc --- /dev/null +++ b/modules/frontend/src/api_/models/UserTitle.ts @@ -0,0 +1,5 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type UserTitle = Record<string, any>; diff --git a/modules/frontend/src/api_/models/UserTitleStatus.ts b/modules/frontend/src/api_/models/UserTitleStatus.ts new file mode 100644 index 0000000..20651fe --- /dev/null +++ b/modules/frontend/src/api_/models/UserTitleStatus.ts @@ -0,0 +1,13 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +/** + * User's title status + */ +export enum UserTitleStatus { + FINISHED = 'finished', + PLANNED = 'planned', + DROPPED = 'dropped', + IN_PROGRESS = 'in-progress', +} diff --git a/modules/frontend/src/api_/models/cursor.ts b/modules/frontend/src/api_/models/cursor.ts new file mode 100644 index 0000000..5788e14 --- /dev/null +++ b/modules/frontend/src/api_/models/cursor.ts @@ -0,0 +1,5 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type cursor = string; diff --git a/modules/frontend/src/api_/services/DefaultService.ts b/modules/frontend/src/api_/services/DefaultService.ts new file mode 100644 index 0000000..b0ae54d --- /dev/null +++ b/modules/frontend/src/api_/services/DefaultService.ts @@ -0,0 +1,148 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ReleaseSeason } from '../models/ReleaseSeason'; +import type { Title } from '../models/Title'; +import type { TitleStatus } from '../models/TitleStatus'; +import type { User } from '../models/User'; +import type { UserTitle } from '../models/UserTitle'; +import type { CancelablePromise } from '../core/CancelablePromise'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; +export class DefaultService { + /** + * Get titles + * @param word + * @param status + * @param rating + * @param releaseYear + * @param releaseSeason + * @param limit + * @param offset + * @param fields + * @returns Title List of titles + * @throws ApiError + */ + public static getTitles( + word?: string, + status?: TitleStatus, + rating?: number, + releaseYear?: number, + releaseSeason?: ReleaseSeason, + limit: number = 10, + offset?: number, + fields: string = 'all', + ): CancelablePromise<Array<Title>> { + return __request(OpenAPI, { + method: 'GET', + url: '/titles', + query: { + 'word': word, + 'status': status, + 'rating': rating, + 'release_year': releaseYear, + 'release_season': releaseSeason, + 'limit': limit, + 'offset': offset, + 'fields': fields, + }, + errors: { + 400: `Request params are not correct`, + 500: `Unknown server error`, + }, + }); + } + /** + * Get title description + * @param titleId + * @param fields + * @returns Title Title description + * @throws ApiError + */ + public static getTitles1( + titleId: number, + fields: string = 'all', + ): CancelablePromise<Title> { + return __request(OpenAPI, { + method: 'GET', + url: '/titles/{title_id}', + path: { + 'title_id': titleId, + }, + query: { + 'fields': fields, + }, + errors: { + 400: `Request params are not correct`, + 404: `Title not found`, + 500: `Unknown server error`, + }, + }); + } + /** + * Get user info + * @param userId + * @param fields + * @returns User User info + * @throws ApiError + */ + public static getUsers( + userId: string, + fields: string = 'all', + ): CancelablePromise<User> { + return __request(OpenAPI, { + method: 'GET', + url: '/users/{user_id}', + path: { + 'user_id': userId, + }, + query: { + 'fields': fields, + }, + errors: { + 400: `Request params are not correct`, + 404: `User not found`, + 500: `Unknown server error`, + }, + }); + } + /** + * Get user titles + * @param userId + * @param cursor + * @param query + * @param limit + * @param offset + * @param fields + * @returns UserTitle List of user titles + * @throws ApiError + */ + public static getUsersTitles( + userId: string, + cursor?: string, + query?: string, + limit: number = 10, + offset?: number, + fields: string = 'all', + ): CancelablePromise<Array<UserTitle>> { + return __request(OpenAPI, { + method: 'GET', + url: '/users/{user_id}/titles', + path: { + 'user_id': userId, + }, + query: { + 'cursor': cursor, + 'query': query, + 'limit': limit, + 'offset': offset, + 'fields': fields, + }, + errors: { + 400: `Request params are not correct`, + 500: `Unknown server error`, + }, + }); + } +} diff --git a/modules/frontend/src/components/ListView/ListView.tsx b/modules/frontend/src/components/ListView/ListView.tsx index e0e8ab9..77fea97 100644 --- a/modules/frontend/src/components/ListView/ListView.tsx +++ b/modules/frontend/src/components/ListView/ListView.tsx @@ -1,103 +1,52 @@ -import React, { useState, useEffect } from "react"; -import { Squares2X2Icon, Bars3Icon } from "@heroicons/react/24/solid"; -import type { CursorObj } from "../../api"; +import React from "react"; -export type ListViewProps<T> = { - fetchItems: (cursor: string, limit: number) => Promise<{ items: T[]; cursor: CursorObj}>; - renderItem: (item: T, layout: "square" | "horizontal") => React.ReactNode; - pageSize?: number; - searchPlaceholder?: string; - setSearch: any; -}; +interface ListViewProps<TItem> { + hook: ReturnType<typeof import("./useListView.tsx").useListView<TItem>>; + renderHorizontal: (item: TItem) => React.ReactNode; + renderSquare: (item: TItem) => React.ReactNode; +} -export function ListView<T>({ - fetchItems, - renderItem, - pageSize = 20, - searchPlaceholder = "Search...", -}: ListViewProps<T>) { - const [items, setItems] = useState<T[]>([]); - const [cursorObj, setCursorObj] = useState<CursorObj | undefined>(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<string | null>(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]); +export function ListView<TItem>({ + hook, + renderHorizontal, + renderSquare +}: ListViewProps<TItem>) { + const { items, search, setSearch, viewMode, setViewMode, loadMore, hasMore } = hook; return ( - <div className="w-full min-h-screen bg-gray-50 p-6 text-black flex flex-col items-center"> - <div className="w-full sm:w-4/5 flex gap-4 mb-8"> + <div> + {/* Search + Layout Switcher */} + <div style={{ display: "flex", gap: 8, marginBottom: 16 }}> <input - type="text" - placeholder={searchPlaceholder} - // value={search} + placeholder="Search..." + value={search} onChange={e => 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" /> - <button - className="p-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition" - onClick={() => - setLayout(prev => (prev === "square" ? "horizontal" : "square")) - }> - {layout === "square" ? <Squares2X2Icon className="w-6 h-6" /> : <Bars3Icon className="w-6 h-6" />} - </button> + + <button onClick={() => setViewMode("horizontal")}>Horizontal</button> + <button onClick={() => setViewMode("square")}>Square</button> </div> - {error && <div className="text-red-600 mb-6 font-medium">{error}</div>} - + {/* Items */} <div - className={`w-full sm:w-4/5 grid gap-6 ${ - layout === "square" ? "grid-cols-1 sm:grid-cols-2 lg:grid-cols-4" : "grid-cols-1" - }`} + style={{ + display: "grid", + gridTemplateColumns: viewMode === "square" ? "repeat(auto-fill, 160px)" : "1fr", + gap: 12 + }} > - {items.map(item => renderItem(item, layout))} + {items.map(item => + viewMode === "horizontal" + ? renderHorizontal(item) + : renderSquare(item) + )} </div> - {cursorObj && ( - <div className="mt-8 flex justify-center w-full sm:w-4/5"> - <button - className="px-6 py-3 bg-blue-600 text-white font-semibold rounded-lg hover:bg-blue-700 transition disabled:opacity-50 disabled:cursor-not-allowed" - onClick={() => loadItems(false)} - disabled={loadingMore} - > - {loadingMore ? "Loading..." : "Load More"} - </button> - </div> + {hasMore && ( + <button onClick={loadMore} style={{ marginTop: 16 }}> + Load More + </button> )} - - {loading && <div className="mt-20 font-medium">Loading...</div>} </div> ); -} +} \ No newline at end of file diff --git a/modules/frontend/src/components/ListView/useListView.tsx b/modules/frontend/src/components/ListView/useListView.tsx new file mode 100644 index 0000000..20c3597 --- /dev/null +++ b/modules/frontend/src/components/ListView/useListView.tsx @@ -0,0 +1,37 @@ +import { useState, useEffect } from "react"; +import type { FetchFunction } from "../../types/list"; + +export function useListView<TItem>(fetchFn: FetchFunction<TItem>) { + const [items, setItems] = useState<TItem[]>([]); + const [cursor, setCursor] = useState<string | undefined>(); + const [search, setSearch] = useState(""); + const [viewMode, setViewMode] = useState<"horizontal" | "square">("horizontal"); + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + loadItems(true); + }, [search]); + + const loadItems = async (reset = false) => { + setIsLoading(true); + const result = await fetchFn({ + search, + cursor: reset ? undefined : cursor, + }); + + setItems(prev => reset ? result.items : [...prev, ...result.items]); + setCursor(result.nextCursor); + setIsLoading(false); + }; + + return { + items, + search, + setSearch, + viewMode, + setViewMode, + loadMore: () => loadItems(), + hasMore: Boolean(cursor), + isLoading, + }; +} \ No newline at end of file diff --git a/modules/frontend/src/components/cards/TitleCardHorizontal.tsx b/modules/frontend/src/components/cards/TitleCardHorizontal.tsx index cde6037..c3a8159 100644 --- a/modules/frontend/src/components/cards/TitleCardHorizontal.tsx +++ b/modules/frontend/src/components/cards/TitleCardHorizontal.tsx @@ -9,13 +9,13 @@ export function TitleCardHorizontal({ title }: { title: Title }) { border: "1px solid #ddd", borderRadius: 8 }}> - {title.poster?.image_path && ( - <img src={title.poster.image_path} width={80} /> + {title.posterUrl && ( + <img src={title.posterUrl} width={80} /> )} <div> - <h3>{title.title_names["en"]}</h3> - <p>{title.release_year} · {title.release_season} · Rating: {title.rating}</p> - <p>Status: {title.title_status}</p> + <h3>{title.name}</h3> + <p>{title.year} · {title.season} · Rating: {title.rating}</p> + <p>Status: {title.status}</p> </div> </div> ); diff --git a/modules/frontend/src/components/cards/TitleCardSquare.tsx b/modules/frontend/src/components/cards/TitleCardSquare.tsx index e21c258..0fc0339 100644 --- a/modules/frontend/src/components/cards/TitleCardSquare.tsx +++ b/modules/frontend/src/components/cards/TitleCardSquare.tsx @@ -10,12 +10,12 @@ export function TitleCardSquare({ title }: { title: Title }) { borderRadius: 8, textAlign: "center" }}> - {title.poster?.image_path && ( - <img src={title.poster.image_path} width={140} /> + {title.posterUrl && ( + <img src={title.posterUrl} width={140} /> )} <div> - <h4>{title.title_names["en"]}</h4> - <small>{title.release_year} • {title.rating}</small> + <h4>{title.name}</h4> + <small>{title.year} • {title.rating}</small> </div> </div> ); diff --git a/modules/frontend/src/index.css b/modules/frontend/src/index.css index e20de02..08a3ac9 100644 --- a/modules/frontend/src/index.css +++ b/modules/frontend/src/index.css @@ -1,8 +1,68 @@ -@import "tailwindcss"; +:root { + font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; -html, body, #root { + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { margin: 0; - padding: 0; - width: 100%; - height: 100%; -} \ No newline at end of file + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/modules/frontend/src/pages/TitlesPage/TitlesPage.module.css b/modules/frontend/src/pages/TitlesPage/TitlesPage.module.css index f1d8c73..9cc728b 100644 --- a/modules/frontend/src/pages/TitlesPage/TitlesPage.module.css +++ b/modules/frontend/src/pages/TitlesPage/TitlesPage.module.css @@ -1 +1,59 @@ -@import "tailwindcss"; +.container { + padding: 24px; +} + +.header { + display: flex; + justify-content: space-between; + margin-bottom: 16px; +} + +.searchInput { + padding: 8px; + width: 240px; +} + +.list { + display: grid; + gap: 12px; +} + +.card { + display: flex; + padding: 10px; + border: 1px solid #ddd; + border-radius: 8px; + gap: 12px; +} + +.poster { + width: 80px; + height: 120px; + object-fit: cover; + border-radius: 4px; +} + +.posterPlaceholder { + width: 80px; + height: 120px; + background: #eee; + display: flex; + align-items: center; + justify-content: center; +} + +.cardInfo { + display: flex; + flex-direction: column; +} + +.loadMore { + margin-top: 16px; + padding: 8px 16px; +} + +.loader, +.error { + padding: 20px; + text-align: center; +} diff --git a/modules/frontend/src/pages/TitlesPage/TitlesPage.tsx b/modules/frontend/src/pages/TitlesPage/TitlesPage.tsx index b59f737..438d828 100644 --- a/modules/frontend/src/pages/TitlesPage/TitlesPage.tsx +++ b/modules/frontend/src/pages/TitlesPage/TitlesPage.tsx @@ -1,52 +1,114 @@ -import { ListView } from "../../components/ListView/ListView"; +import React, { useEffect, useState } from "react"; import { DefaultService } from "../../api/services/DefaultService"; -import { TitleCardSquare } from "../../components/cards/TitleCardSquare"; -import { TitleCardHorizontal } from "../../components/cards/TitleCardHorizontal"; -import type { Title } from "../../api"; -import { useState, useEffect } from "react"; +import type { Title } from "../../api/models/Title"; +import styles from "./TitlesPage.module.css"; -const PAGE_SIZE = 20; -export default function TitlesPage() { +const LIMIT = 20; + +const TitlesPage: React.FC = () => { + const [titles, setTitles] = useState<Title[]>([]); const [search, setSearch] = useState(""); + const [offset, setOffset] = useState(0); - const loadTitles = async (cursor: string, limit: number) => { - const result = await DefaultService.getTitles( - cursor, - undefined, - true, - search, - undefined, - undefined, - undefined, - undefined, - limit, - undefined, - 'all' - ); + const [loading, setLoading] = useState(true); + const [loadingMore, setLoadingMore] = useState(false); + const [error, setError] = useState<string | null>(null); - return { - items: result.data ?? [], - cursor: result.cursor ?? null, - }; + const fetchTitles = async (reset: boolean) => { + try { + if (reset) { + setLoading(true); + setOffset(0); + } else { + setLoadingMore(true); + } + + const result = await DefaultService.getTitles( + search || undefined, + undefined, // status + undefined, // rating + undefined, // release_year + undefined, // release_season + LIMIT, + reset ? 0 : offset, + "all" + ); + + if (reset) { + setTitles(result); + } else { + setTitles(prev => [...prev, ...result]); + } + + if (result.length > 0) { + setOffset(prev => prev + LIMIT); + } + + } catch (err) { + console.error(err); + setError("Failed to fetch titles."); + } finally { + setLoading(false); + setLoadingMore(false); + } }; - return ( - <div className="w-full min-h-screen bg-gray-50 p-6 text-black flex flex-col items-center"> - - <h1 className="text-4xl font-bold mb-6 text-center">Titles</h1> + useEffect(() => { + fetchTitles(true); + }, [search]); - <ListView<Title> - pageSize={PAGE_SIZE} - fetchItems={loadTitles} - searchPlaceholder="Search titles..." - renderItem={(title, layout) => - layout === "square" - ? <TitleCardSquare title={title} /> - : <TitleCardHorizontal title={title} /> - } - setSearch={setSearch} - /> + if (loading) return <div className={styles.loader}>Loading...</div>; + if (error) return <div className={styles.error}>{error}</div>; + + return ( + <div className={styles.container}> + <div className={styles.header}> + <h1>Titles</h1> + + <input + className={styles.searchInput} + placeholder="Search titles..." + value={search} + onChange={e => setSearch(e.target.value)} + /> + </div> + + <div className={styles.list}> + {titles.map((t) => ( + <div key={t.id} className={styles.card}> + {t.poster_id ? ( + <img + src={`/images/${t.poster_id}.png`} + alt="Poster" + className={styles.poster} + /> + ) : ( + <div className={styles.posterPlaceholder}>No Image</div> + )} + + <div className={styles.cardInfo}> + <h3 className={styles.titleName}>{t.name}</h3> + <p className={styles.meta}> + {t.release_year} • {t.release_season} + </p> + <p className={styles.rating}>Rating: {t.rating}</p> + <p className={styles.status}>{t.status}</p> + </div> + </div> + ))} + </div> + + {titles.length > 0 && ( + <button + className={styles.loadMore} + onClick={() => fetchTitles(false)} + disabled={loadingMore} + > + {loadingMore ? "Loading..." : "Load More"} + </button> + )} </div> ); -} +}; +export default TitlesPage; diff --git a/modules/frontend/vite.config.ts b/modules/frontend/vite.config.ts index 6c261e6..4cfbdd0 100644 --- a/modules/frontend/vite.config.ts +++ b/modules/frontend/vite.config.ts @@ -1,13 +1,9 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' -import tailwindcss from '@tailwindcss/vite' // https://vite.dev/config/ export default defineConfig({ - plugins: [ - react(), - tailwindcss() - ], + plugins: [react()], server: { host: '127.0.0.1', port: 8083,