diff --git a/AGENTS.md b/AGENTS.md index 016e708..70eaa42 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,9 +21,10 @@ This is a modern frontend project template based on React 18, TypeScript, and Vi - **State Management**: Zustand / Redux Toolkit - **Routing**: React Router v6 - **UI Components**: Ant Design / Material-UI -- **Styling**: Tailwind CSS / Styled-components +- **Styling**: Tailwind CSS 4 with shadcn/ui component library - **Testing Framework**: Vitest + React Testing Library - **Code Quality**: ESLint + Prettier + Husky +- **UI Components**: Complete shadcn/ui component set (New York style) with Lucide icons ## Project Structure @@ -315,6 +316,54 @@ export default defineConfig({ }); ``` + +## Styling + +1. Use the shadcn/ui library unless the user specifies otherwise. +2. Avoid using indigo or blue colors unless specified in the user's request. +3. MUST generate responsive designs. +4. The Code Project is rendered on top of a white background. If a different background color is needed, use a wrapper element with a background color Tailwind class. + +--- + +## UI/UX Design Standards + +### Visual Design + +- **Color System**: Use Tailwind CSS built-in variables (`bg-primary`, `text-primary-foreground`, `bg-background`). +- **Color Restriction**: NO indigo or blue colors unless explicitly requested. +- **Theme Support**: Implement light/dark mode with `next-themes`. +- **Typography**: Consistent hierarchy with proper font weights and sizes. + +### Responsive Design (MANDATORY) + +- **Mobile-First**: Design for mobile, then enhance for desktop. +- **Breakpoints**: Use Tailwind responsive prefixes (`sm:`, `md:`, `lg:`, `xl:`). +- **Touch-Friendly**: Minimum 44px touch targets for interactive elements. + +### Layout (MANDATORY) + +- **Sticky Footer Required**: If a `footer` exists, it MUST stick to the bottom of the viewport when content is shorter than one screen height (no floating/empty gap below). +- **Natural Push on Overflow**: When content exceeds the viewport height, the footer MUST be pushed down naturally (never overlay or cover content). +- **Recommended Implementation (Tailwind)**: Use a root wrapper with `min-h-screen flex flex-col`, and apply `mt-auto` to the `footer`. +- **Mobile Safe Area**: On devices with safe areas (e.g., iOS), the footer MUST respect bottom safe area insets when applicable. + +### Accessibility (MANDATORY) + +- **Semantic HTML**: Use `main`, `header`, `nav`, `section`, `article`. +- **ARIA Support**: Proper roles, labels, and descriptions. +- **Screen Readers**: Use `sr-only` class for screen reader content. +- **Alt Text**: Descriptive alt text for all images. +- **Keyboard Navigation**: Ensure all elements are keyboard accessible. + +### Interactive Elements + +- **Loading States**: Show spinners/skeletons during async operations. +- **Error Handling**: Clear, actionable error messages. +- **Feedback**: Toast notifications for user actions. +- **Animations**: Subtle Framer Motion transitions (hover, focus, page transitions). +- **Hover Effects**: Interactive feedback on all clickable elements. + ## Common Issues ### Issue 1: Vite Development Server Slow Startup diff --git a/components.json b/components.json index 15addee..5ac2628 100644 --- a/components.json +++ b/components.json @@ -21,5 +21,7 @@ }, "menuColor": "default", "menuAccent": "subtle", - "registries": {} + "registries": { + "@acme": "https://acme.com/r/{name}.json" + } } diff --git a/package-lock.json b/package-lock.json index 3458ce7..41aba2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,6 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "react-router-dom": "^7.14.0", - "shadcn": "^4.2.0", "tailwind-merge": "^3.5.0", "tw-animate-css": "^1.4.0", "vite": "^6.2.0", @@ -34,6 +33,7 @@ "@vitest/ui": "^4.1.4", "autoprefixer": "^10.4.21", "jsdom": "^29.0.2", + "shadcn": "^4.5.0", "tailwindcss": "^4.1.14", "tsx": "^4.21.0", "typedoc": "^0.28.19", @@ -166,6 +166,7 @@ "version": "7.27.3", "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.27.3" @@ -194,6 +195,7 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", @@ -224,6 +226,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.28.5", @@ -267,6 +270,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.27.1" @@ -288,6 +292,7 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-member-expression-to-functions": "^7.28.5", @@ -305,6 +310,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.27.1", @@ -373,6 +379,7 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" @@ -388,6 +395,7 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" @@ -403,6 +411,7 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-transforms": "^7.28.6", @@ -449,6 +458,7 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", @@ -468,6 +478,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", @@ -747,6 +758,7 @@ "version": "1.60.2", "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.60.2.tgz", "integrity": "sha512-r4AznHUvfLONuWdoSIQtut6Ez/ym+lGXRtDvRaoAEMEhAmwSoK24jRsfR28vcb3ygWm7qeYOcbZolhtseJl6mA==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "commander": "^11.1.0", @@ -771,6 +783,7 @@ "version": "11.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=16" @@ -780,6 +793,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, "license": "MIT", "dependencies": { "cross-spawn": "^7.0.3", @@ -803,6 +817,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -815,6 +830,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=10.17.0" @@ -824,6 +840,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -836,6 +853,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.0.0" @@ -848,6 +866,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" @@ -863,12 +882,14 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, "license": "ISC" }, "node_modules/@dotenvx/dotenvx/node_modules/strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -878,6 +899,7 @@ "version": "0.2.6", "resolved": "https://registry.npmjs.org/@ecies/ciphers/-/ciphers-0.2.6.tgz", "integrity": "sha512-patgsRPKGkhhoBjETV4XxD0En4ui5fbX0hzayqI3M8tvNMGUoUvmyYAIWwlxBc1KX5cturfqByYdj5bYGRpN9g==", + "dev": true, "license": "MIT", "engines": { "bun": ">=1", @@ -1410,6 +1432,7 @@ "version": "1.19.13", "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.13.tgz", "integrity": "sha512-TsQLe4i2gvoTtrHje625ngThGBySOgSK3Xo2XRYOdqGN1teR8+I7vchQC46uLJi8OF62YTYA3AhSpumtkhsaKQ==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=18.14.1" @@ -1422,6 +1445,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -1431,6 +1455,7 @@ "version": "5.1.21", "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", + "dev": true, "license": "MIT", "dependencies": { "@inquirer/core": "^10.3.2", @@ -1452,6 +1477,7 @@ "version": "10.3.2", "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", + "dev": true, "license": "MIT", "dependencies": { "@inquirer/ansi": "^1.0.2", @@ -1479,6 +1505,7 @@ "version": "1.0.15", "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -1488,6 +1515,7 @@ "version": "3.0.10", "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -1550,6 +1578,7 @@ "version": "1.29.0", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", + "devOptional": true, "license": "MIT", "dependencies": { "@hono/node-server": "^1.19.9", @@ -1590,6 +1619,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "devOptional": true, "license": "MIT", "dependencies": { "mime-types": "^3.0.0", @@ -1603,6 +1633,7 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "devOptional": true, "license": "MIT", "dependencies": { "bytes": "^3.1.2", @@ -1627,6 +1658,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=18" @@ -1640,6 +1672,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.6.0" @@ -1649,6 +1682,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "devOptional": true, "license": "MIT", "dependencies": { "accepts": "^2.0.0", @@ -1692,6 +1726,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "devOptional": true, "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -1713,6 +1748,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -1722,6 +1758,7 @@ "version": "0.7.2", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "devOptional": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -1738,6 +1775,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -1747,6 +1785,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=18" @@ -1759,6 +1798,7 @@ "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -1768,6 +1808,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "devOptional": true, "license": "MIT", "dependencies": { "mime-db": "^1.54.0" @@ -1784,6 +1825,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -1793,6 +1835,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "devOptional": true, "license": "MIT", "dependencies": { "bytes": "~3.1.2", @@ -1808,6 +1851,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "devOptional": true, "license": "MIT", "dependencies": { "debug": "^4.4.3", @@ -1834,6 +1878,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "devOptional": true, "license": "MIT", "dependencies": { "encodeurl": "^2.0.0", @@ -1853,6 +1898,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "devOptional": true, "license": "MIT", "dependencies": { "content-type": "^1.0.5", @@ -1867,6 +1913,7 @@ "version": "0.41.3", "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.41.3.tgz", "integrity": "sha512-cXu86tF4VQVfwz8W1SPbhoRyHJkti6mjH/XJIxp40jhO4j2k1m4KYrEykxqWPkFF3vrK4rgQppBh//AwyGSXPA==", + "dev": true, "license": "MIT", "dependencies": { "@open-draft/deferred-promise": "^2.2.0", @@ -1884,6 +1931,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "dev": true, "license": "MIT", "engines": { "node": "^14.21.3 || >=16" @@ -1896,6 +1944,7 @@ "version": "1.9.7", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "dev": true, "license": "MIT", "dependencies": { "@noble/hashes": "1.8.0" @@ -1911,6 +1960,7 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, "license": "MIT", "engines": { "node": "^14.21.3 || >=16" @@ -1923,6 +1973,7 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", @@ -1936,6 +1987,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -1945,6 +1997,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", @@ -1958,12 +2011,14 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", + "dev": true, "license": "MIT" }, "node_modules/@open-draft/logger": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", + "dev": true, "license": "MIT", "dependencies": { "is-node-process": "^1.2.0", @@ -1974,6 +2029,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", + "dev": true, "license": "MIT" }, "node_modules/@polka/url": { @@ -2382,6 +2438,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true, "license": "MIT" }, "node_modules/@shikijs/engine-oniguruma": { @@ -2437,6 +2494,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -2713,6 +2771,7 @@ "version": "0.27.0", "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.27.0.tgz", "integrity": "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==", + "dev": true, "license": "MIT", "dependencies": { "fast-glob": "^3.3.3", @@ -2922,6 +2981,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.6.tgz", "integrity": "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==", + "dev": true, "license": "MIT" }, "node_modules/@types/unist": { @@ -2935,6 +2995,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/validate-npm-package-name/-/validate-npm-package-name-4.0.2.tgz", "integrity": "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==", + "dev": true, "license": "MIT" }, "node_modules/@vitejs/plugin-react": { @@ -3118,6 +3179,7 @@ "version": "8.18.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "devOptional": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -3134,6 +3196,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "devOptional": true, "license": "MIT", "dependencies": { "ajv": "^8.0.0" @@ -3151,6 +3214,7 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -3163,6 +3227,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -3178,6 +3243,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, "license": "Python-2.0" }, "node_modules/array-flatten": { @@ -3200,6 +3266,7 @@ "version": "0.16.1", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.0.1" @@ -3249,6 +3316,7 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, "license": "MIT", "engines": { "node": "18 || 20 || >=22" @@ -3348,6 +3416,7 @@ "version": "5.0.5", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" @@ -3360,6 +3429,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -3411,6 +3481,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, "license": "MIT", "dependencies": { "run-applescript": "^7.0.0" @@ -3464,6 +3535,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -3503,6 +3575,7 @@ "version": "5.6.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" @@ -3527,6 +3600,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, "license": "MIT", "dependencies": { "restore-cursor": "^5.0.0" @@ -3542,6 +3616,7 @@ "version": "2.9.2", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -3554,6 +3629,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, "license": "ISC", "engines": { "node": ">= 12" @@ -3563,6 +3639,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -3577,6 +3654,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -3586,12 +3664,14 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, "license": "MIT" }, "node_modules/cliui/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -3606,6 +3686,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -3618,6 +3699,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -3644,12 +3726,14 @@ "version": "13.0.3", "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", + "dev": true, "license": "MIT" }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -3662,12 +3746,14 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, "license": "MIT" }, "node_modules/commander": { "version": "14.0.3", "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true, "license": "MIT", "engines": { "node": ">=20" @@ -3719,6 +3805,7 @@ "version": "2.8.6", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "devOptional": true, "license": "MIT", "dependencies": { "object-assign": "^4", @@ -3736,6 +3823,7 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", + "dev": true, "license": "MIT", "dependencies": { "env-paths": "^2.2.1", @@ -3762,6 +3850,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "devOptional": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -3776,12 +3865,14 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "devOptional": true, "license": "ISC" }, "node_modules/cross-spawn/node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "devOptional": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -3811,6 +3902,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, "license": "MIT", "bin": { "cssesc": "bin/cssesc" @@ -3870,6 +3962,7 @@ "version": "1.7.2", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "dev": true, "license": "MIT", "peerDependencies": { "babel-plugin-macros": "^3.1.0" @@ -3884,6 +3977,7 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -3893,6 +3987,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "dev": true, "license": "MIT", "dependencies": { "bundle-name": "^4.1.0", @@ -3909,6 +4004,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -3921,6 +4017,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -3961,6 +4058,7 @@ "version": "8.0.4", "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" @@ -4005,6 +4103,7 @@ "version": "0.4.18", "resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.4.18.tgz", "integrity": "sha512-wG99Zcfcys9fZux7Cft8BAX/YrOJLJSZ3jyYPfhZHqN2E+Ffx+QXBDsv3gubEgPtV6dTzJMSQUwk1H98/t/0wQ==", + "dev": true, "license": "MIT", "dependencies": { "@ecies/ciphers": "^0.2.5", @@ -4034,6 +4133,7 @@ "version": "10.6.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, "license": "MIT" }, "node_modules/encodeurl": { @@ -4075,6 +4175,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -4084,6 +4185,7 @@ "version": "1.3.4", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" @@ -4187,6 +4289,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", @@ -4219,6 +4322,7 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "devOptional": true, "license": "MIT", "dependencies": { "eventsource-parser": "^3.0.1" @@ -4231,6 +4335,7 @@ "version": "3.0.6", "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=18.0.0" @@ -4240,6 +4345,7 @@ "version": "9.6.1", "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "dev": true, "license": "MIT", "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", @@ -4322,6 +4428,7 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.2.tgz", "integrity": "sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg==", + "devOptional": true, "license": "MIT", "dependencies": { "ip-address": "10.1.0" @@ -4361,12 +4468,14 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "devOptional": true, "license": "MIT" }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -4383,6 +4492,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "devOptional": true, "funding": [ { "type": "github", @@ -4399,6 +4509,7 @@ "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -4455,6 +4566,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "dev": true, "license": "MIT", "dependencies": { "is-unicode-supported": "^2.0.0" @@ -4470,6 +4582,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -4593,6 +4706,7 @@ "version": "11.3.4", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", @@ -4630,6 +4744,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-3.1.0.tgz", "integrity": "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==", + "dev": true, "license": "MIT" }, "node_modules/gaxios": { @@ -4673,6 +4788,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -4682,6 +4798,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -4718,6 +4835,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-own-enumerable-keys/-/get-own-enumerable-keys-1.0.0.tgz", "integrity": "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA==", + "dev": true, "license": "MIT", "engines": { "node": ">=14.16" @@ -4743,6 +4861,7 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, "license": "MIT", "dependencies": { "@sec-ant/readable-stream": "^0.4.1", @@ -4772,6 +4891,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -4828,6 +4948,7 @@ "version": "16.13.2", "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.13.2.tgz", "integrity": "sha512-5bJ+nf/UCpAjHM8i06fl7eLyVC9iuNAjm9qzkiu2ZGhM0VscSvS6WDPfAwkdkBuoXGM9FJSbKl6wylMwP9Ktig==", + "dev": true, "license": "MIT", "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" @@ -4861,12 +4982,14 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz", "integrity": "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==", + "dev": true, "license": "MIT" }, "node_modules/hono": { "version": "4.12.12", "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.12.tgz", "integrity": "sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=16.9.0" @@ -4922,6 +5045,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18.0" @@ -4943,6 +5067,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -4952,6 +5077,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -4974,6 +5100,7 @@ "version": "10.1.0", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "devOptional": true, "license": "MIT", "engines": { "node": ">= 12" @@ -4992,12 +5119,14 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, "license": "MIT" }, "node_modules/is-docker": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, "license": "MIT", "bin": { "is-docker": "cli.js" @@ -5013,6 +5142,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -5022,6 +5152,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5031,6 +5162,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -5043,6 +5175,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", + "dev": true, "license": "MIT", "engines": { "node": ">=20" @@ -5055,6 +5188,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, "license": "MIT", "dependencies": { "is-docker": "^3.0.0" @@ -5073,6 +5207,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -5085,12 +5220,14 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", + "dev": true, "license": "MIT" }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -5100,6 +5237,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-3.0.0.tgz", "integrity": "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -5112,6 +5250,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -5131,12 +5270,14 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "devOptional": true, "license": "MIT" }, "node_modules/is-regexp": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-3.1.0.tgz", "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -5149,6 +5290,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -5161,6 +5303,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -5173,6 +5316,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, "license": "MIT", "dependencies": { "is-inside-container": "^1.0.0" @@ -5188,6 +5332,7 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=18" @@ -5206,6 +5351,7 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.2.tgz", "integrity": "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==", + "devOptional": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -5221,6 +5367,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -5305,18 +5452,21 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "devOptional": true, "license": "MIT" }, "node_modules/json-schema-typed": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "devOptional": true, "license": "BSD-2-Clause" }, "node_modules/json5": { @@ -5335,6 +5485,7 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, "license": "MIT", "dependencies": { "universalify": "^2.0.0" @@ -5368,6 +5519,7 @@ "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -5626,6 +5778,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, "license": "MIT" }, "node_modules/linkify-it": { @@ -5642,6 +5795,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "dev": true, "license": "MIT", "dependencies": { "chalk": "^5.3.0", @@ -5658,6 +5812,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -5782,12 +5937,14 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, "license": "MIT" }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -5806,6 +5963,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -5819,6 +5977,7 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -5864,6 +6023,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -5873,6 +6033,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -5885,6 +6046,7 @@ "version": "10.2.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "brace-expansion": "^5.0.5" @@ -5900,6 +6062,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5966,6 +6129,7 @@ "version": "2.13.2", "resolved": "https://registry.npmjs.org/msw/-/msw-2.13.2.tgz", "integrity": "sha512-go2H1TIERKkC48pXiwec5l6sbNqYuvqOk3/vHGo1Zd+pq/H63oFawDQerH+WQdUw/flJFHDG7F+QdWMwhntA/A==", + "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -6010,6 +6174,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -6023,12 +6188,14 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, "license": "MIT" }, "node_modules/mute-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, "license": "ISC", "engines": { "node": "^18.17.0 || >=20.5.0" @@ -6109,6 +6276,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "dev": true, "license": "MIT", "dependencies": { "path-key": "^4.0.0", @@ -6125,6 +6293,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -6137,6 +6306,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6158,6 +6328,7 @@ "version": "1.1.33", "resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-1.1.33.tgz", "integrity": "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==", + "dev": true, "license": "MIT", "engines": { "node": ">= 10" @@ -6190,6 +6361,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "devOptional": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -6199,6 +6371,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, "license": "MIT", "dependencies": { "mimic-function": "^5.0.0" @@ -6214,6 +6387,7 @@ "version": "11.0.0", "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz", "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==", + "dev": true, "license": "MIT", "dependencies": { "default-browser": "^5.4.0", @@ -6234,6 +6408,7 @@ "version": "8.2.0", "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "dev": true, "license": "MIT", "dependencies": { "chalk": "^5.3.0", @@ -6257,6 +6432,7 @@ "version": "1.4.3", "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", + "dev": true, "license": "MIT" }, "node_modules/p-retry": { @@ -6276,6 +6452,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -6288,6 +6465,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", @@ -6306,6 +6484,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -6340,12 +6519,14 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, "license": "MIT" }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -6386,6 +6567,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=16.20.0" @@ -6423,6 +6605,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "dev": true, "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -6443,6 +6626,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", + "dev": true, "license": "MIT", "engines": { "node": ">=20" @@ -6455,6 +6639,7 @@ "version": "9.3.0", "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", + "dev": true, "license": "MIT", "dependencies": { "parse-ms": "^4.0.0" @@ -6470,6 +6655,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, "license": "MIT", "dependencies": { "kleur": "^3.0.3", @@ -6483,6 +6669,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -6564,6 +6751,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, "funding": [ { "type": "github", @@ -6689,6 +6877,7 @@ "version": "0.23.11", "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==", + "dev": true, "license": "MIT", "dependencies": { "ast-types": "^0.16.1", @@ -6705,6 +6894,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6714,6 +6904,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6729,6 +6920,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -6748,6 +6940,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, "license": "MIT", "dependencies": { "onetime": "^7.0.0", @@ -6773,12 +6966,14 @@ "version": "0.10.1", "resolved": "https://registry.npmjs.org/rettime/-/rettime-0.10.1.tgz", "integrity": "sha512-uyDrIlUEH37cinabq0AX4QbgV4HbFZ/gqoiunWQ1UqBtRvTTytwhNYjE++pO/MjPTZL5KQCf2bEoJ/BJNVQ5Kw==", + "dev": true, "license": "MIT" }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -6833,6 +7028,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "devOptional": true, "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -6849,6 +7045,7 @@ "version": "8.4.2", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "devOptional": true, "license": "MIT", "funding": { "type": "opencollective", @@ -6859,6 +7056,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -6871,6 +7069,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, "funding": [ { "type": "github", @@ -7011,9 +7210,10 @@ "license": "ISC" }, "node_modules/shadcn": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/shadcn/-/shadcn-4.2.0.tgz", - "integrity": "sha512-ZDuV340itidaUd4Gi1BxQX+Y7Ush6BHp6URZBM2RyxUUBZ6yFtOWIr4nVY+Ro+YRSpo82v7JrsmtcU5xoBCMJQ==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/shadcn/-/shadcn-4.5.0.tgz", + "integrity": "sha512-ZpNOz7IMI5aezbMEWNxBvl2aJ1ek6NuAMqpL/FUnk5IuRxERl8ohYEnqqAmhPOcur8RbGuCoqTZLQ3Oi4Xkf8A==", + "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.28.0", @@ -7059,6 +7259,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "devOptional": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -7071,6 +7272,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -7159,6 +7361,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, "license": "ISC", "engines": { "node": ">=14" @@ -7186,12 +7389,14 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, "license": "MIT" }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -7233,6 +7438,7 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -7245,12 +7451,14 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", + "dev": true, "license": "MIT" }, "node_modules/string-width": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^10.3.0", @@ -7268,6 +7476,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-5.0.0.tgz", "integrity": "sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "get-own-enumerable-keys": "^1.0.0", @@ -7285,6 +7494,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.2.2" @@ -7300,6 +7510,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -7309,6 +7520,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -7334,6 +7546,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "dev": true, "license": "MIT", "engines": { "node": ">=20" @@ -7375,6 +7588,7 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "dev": true, "license": "MIT" }, "node_modules/tinybench": { @@ -7424,6 +7638,7 @@ "version": "7.0.28", "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.28.tgz", "integrity": "sha512-+Zg3vWhRUv8B1maGSTFdev9mjoo8Etn2Ayfs4cnjlD3CsGkxXX4QyW3j2WJ0wdjYcYmy7Lx2RDsZMhgCWafKIw==", + "dev": true, "license": "MIT", "dependencies": { "tldts-core": "^7.0.28" @@ -7436,12 +7651,14 @@ "version": "7.0.28", "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.28.tgz", "integrity": "sha512-7W5Efjhsc3chVdFhqtaU0KtK32J37Zcr9RKtID54nG+tIpcY79CQK/veYPODxtD/LJ4Lue66jvrQzIX2Z2/pUQ==", + "dev": true, "license": "MIT" }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -7473,6 +7690,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "tldts": "^7.0.5" @@ -7498,6 +7716,7 @@ "version": "26.0.0", "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-26.0.0.tgz", "integrity": "sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug==", + "dev": true, "license": "MIT", "dependencies": { "@ts-morph/common": "~0.27.0", @@ -7508,6 +7727,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, "license": "MIT", "dependencies": { "json5": "^2.2.2", @@ -7557,6 +7777,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.5.0.tgz", "integrity": "sha512-PlBfpQwiUvGViBNX84Yxwjsdhd1TUlXr6zjX7eoirtCPIr08NAmxwa+fcYBTeRQxHo9YC9wwF3m9i700sHma8g==", + "dev": true, "license": "(MIT OR CC0-1.0)", "dependencies": { "tagged-tag": "^1.0.0" @@ -7609,7 +7830,7 @@ "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -7646,6 +7867,7 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -7658,6 +7880,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 10.0.0" @@ -7676,6 +7899,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/until-async/-/until-async-3.0.2.tgz", "integrity": "sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/kettanaito" @@ -7724,6 +7948,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, "license": "MIT" }, "node_modules/utils-merge": { @@ -7739,6 +7964,7 @@ "version": "7.0.2", "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-7.0.2.tgz", "integrity": "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==", + "dev": true, "license": "ISC", "engines": { "node": "^20.17.0 || >=22.9.0" @@ -8435,6 +8661,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, "license": "ISC", "dependencies": { "isexe": "^3.1.1" @@ -8467,6 +8694,7 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -8481,6 +8709,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8490,12 +8719,14 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, "license": "MIT" }, "node_modules/wrap-ansi/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -8510,6 +8741,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -8522,6 +8754,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "devOptional": true, "license": "ISC" }, "node_modules/ws": { @@ -8549,6 +8782,7 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz", "integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==", + "dev": true, "license": "MIT", "dependencies": { "is-wsl": "^3.1.0", @@ -8582,6 +8816,7 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -8613,6 +8848,7 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -8631,6 +8867,7 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -8640,6 +8877,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8649,12 +8887,14 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, "license": "MIT" }, "node_modules/yargs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -8669,6 +8909,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -8681,6 +8922,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/yocto-spinner/-/yocto-spinner-1.1.0.tgz", "integrity": "sha512-/BY0AUXnS7IKO354uLLA2eRcWiqDifEbd6unXCsOxkFDAkhgUL3PH9X2bFoaU0YchnDXsF+iKleeTLJGckbXfA==", + "dev": true, "license": "MIT", "dependencies": { "yoctocolors": "^2.1.1" @@ -8696,6 +8938,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -8708,6 +8951,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -8720,6 +8964,7 @@ "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "devOptional": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -8729,6 +8974,7 @@ "version": "3.25.2", "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "devOptional": true, "license": "ISC", "peerDependencies": { "zod": "^3.25.28 || ^4" diff --git a/package.json b/package.json index a62cc2c..99d0dc6 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,6 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "react-router-dom": "^7.14.0", - "shadcn": "^4.2.0", "tailwind-merge": "^3.5.0", "tw-animate-css": "^1.4.0", "vite": "^6.2.0", @@ -42,6 +41,7 @@ "@vitest/ui": "^4.1.4", "autoprefixer": "^10.4.21", "jsdom": "^29.0.2", + "shadcn": "^4.5.0", "tailwindcss": "^4.1.14", "tsx": "^4.21.0", "typedoc": "^0.28.19", diff --git a/src/App.tsx b/src/App.tsx index 993e4c0..01be37c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,7 +6,8 @@ import { useState, useMemo, useEffect } from 'react'; import { LayoutGroup } from 'motion/react'; import { BrowserRouter, Routes, Route, useNavigate, useSearchParams, useParams, useLocation } from 'react-router-dom'; -import Sidebar from './components/Sidebar'; +import AppSidebar from './components/sidebar/AppSidebar'; +import { SidebarProvider } from '@/components/ui/sidebar'; import BrowseView from './components/BrowseView'; import DashboardView from './components/DashboardView'; import DetailView from './components/DetailView'; @@ -23,6 +24,9 @@ import { MOCK_MEDIA, DETAIL_MEDIA } from './data'; import { Media, Staff, MediaCategory, UserSettings } from './types'; import { fetchAllMedia, fetchMediaById, fetchCastById, convertApiCastToStaff, fetchSettings, updateSettings } from './api'; import { ThemeProvider, useTheme } from './contexts/ThemeContext'; +import { Search, Plus, LayoutGrid, List, Filter } from 'lucide-react'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; import { CATEGORY_PATHS, PATH_TO_CATEGORY, DEFAULT_ENABLED_CATEGORIES, DEFAULT_SETTINGS } from './constants'; import { useAppStore } from './store/appStore'; @@ -210,7 +214,8 @@ function AppContent() { window.scrollTo({ top: 0, behavior: 'smooth' }); }; - const allMedia = useMemo(() => { + // All media from enabled categories (for cross-category search) + const allEnabledMedia = useMemo(() => { // Use API data if available, otherwise fall back to mock data let list: Media[] = []; @@ -228,9 +233,14 @@ function AppContent() { list.push(DETAIL_MEDIA); } + // Filter by enabled categories only (all enabled categories, not just active) + return list.filter(m => enabledCategories.includes(m.category)); + }, [enabledCategories, customMedia, apiMedia]); + + const allMedia = useMemo(() => { // Filter by active category AND ensure it's enabled - return list.filter(m => m.category === activeCategory && enabledCategories.includes(m.category)); - }, [activeCategory, enabledCategories, customMedia, apiMedia]); + return allEnabledMedia.filter(m => m.category === activeCategory); + }, [activeCategory, allEnabledMedia]); const handleAddMedia = async () => { // Reload all media from API to get the newly added item @@ -257,37 +267,55 @@ function AppContent() { const allStaff = useMemo(() => { const staff: Staff[] = []; - // Use API data if available, otherwise fall back to mock data - let baseList: Media[] = []; + const staffIds = new Set(); // Track unique staff to avoid duplicates - if (apiMedia.length > 0) { - // API has data, use it - baseList = [...apiMedia]; - } else { - // API is empty, use mock data as fallback - baseList = [...MOCK_MEDIA]; - } - - // Add custom media and detail media - baseList = [...baseList, ...customMedia]; - if (!baseList.find(m => m.id === DETAIL_MEDIA.id)) { - baseList.push(DETAIL_MEDIA); - } - - const enabledMedia = baseList.filter(m => enabledCategories.includes(m.category)); - - enabledMedia.forEach(media => { + // Use allEnabledMedia which already has enabled categories filtered + allEnabledMedia.forEach(media => { media.staff?.forEach(s => { - staff.push({ - ...s, - mediaId: media.id, - mediaTitle: media.title - }); + // Avoid duplicate staff entries + if (!staffIds.has(s.id)) { + staffIds.add(s.id); + staff.push({ + ...s, + mediaId: media.id, + mediaTitle: media.title + }); + } }); }); return staff; - }, [enabledCategories, customMedia, apiMedia]); + }, [allEnabledMedia]); + // Search across all enabled media (all categories) + const searchResultsMedia = useMemo(() => { + if (!searchQuery.trim()) return []; + const query = searchQuery.toLowerCase(); + return allEnabledMedia.filter(media => + media.title.toLowerCase().includes(query) || + media.year.toLowerCase().includes(query) || + media.genres?.some(g => g.toLowerCase().includes(query)) || + media.studios?.some(s => s.toLowerCase().includes(query)) || + media.description?.toLowerCase().includes(query) || + media.tags?.some(t => t.toLowerCase().includes(query)) || + media.developers?.some(d => d.toLowerCase().includes(query)) || + media.platforms?.some(p => p.toLowerCase().includes(query)) + ); + }, [allEnabledMedia, searchQuery]); + + // Search cast members + const searchResultsCast = useMemo(() => { + if (!searchQuery.trim()) return []; + const query = searchQuery.toLowerCase(); + return allStaff.filter(staff => + staff.name.toLowerCase().includes(query) || + staff.role.toLowerCase().includes(query) || + staff.bio?.toLowerCase().includes(query) || + staff.occupations?.some(o => o.toLowerCase().includes(query)) || + staff.characterName?.toLowerCase().includes(query) + ); + }, [allStaff, searchQuery]); + + // Legacy filteredMedia for backward compatibility (searches within current category) const filteredMedia = useMemo(() => { if (!searchQuery.trim()) return allMedia; const query = searchQuery.toLowerCase(); @@ -358,15 +386,98 @@ function AppContent() { navigate('/browse'); }; + // Calculate media counts for sidebar (all categories) + const mediaCounts = useMemo(() => { + const counts: Record = {}; + // Count all enabled categories using allEnabledMedia + enabledCategories.forEach(cat => { + counts[cat] = allEnabledMedia.filter(m => m.category === cat).length; + }); + // Add favorites count + counts['favorites'] = allEnabledMedia.filter(m => m.rating && m.rating >= 8).length; + // Add total count + counts['all'] = allEnabledMedia.length; + return counts; + }, [allEnabledMedia, enabledCategories]); + + // Calculate active filter based on current URL + const activeFilter = useMemo(() => { + const path = location.pathname; + // Map routes to filter IDs + const routeMap: Record = { + '/anime': 'anime', + '/movies': 'movies', + '/tv-series': 'tv-series', + '/music': 'music', + '/books': 'books', + '/adult': 'adult', + '/consoles': 'consoles', + '/games': 'games', + }; + if (routeMap[path]) return routeMap[path]; + if (searchParams.get('favorites') === 'true') return 'favorites'; + return undefined; + }, [location.pathname, searchParams]); + return ( -
- - -
+
+ + + +
+ {/* Header with Search and Add Media */} +
+
+ {/* Search Bar */} +
+
+ + handleSearch(e.target.value)} + className="w-full pl-10 pr-4 py-2 bg-[#1a1d26] border-white/10 rounded-lg text-white placeholder:text-gray-500 focus:border-[#e8466c]/50 focus:ring-[#e8466c]/20" + /> +
+
+ + {/* View Toggle and Add Button */} +
+
+ + +
+ + +
+
+
+ } /> {/* Footer */} -
+
-
-
- {settings?.pageTitle || 'omnyx'} +
+ {mediaCounts.all} total + + {mediaCounts.movies} Movies + {mediaCounts.series} Series + {mediaCounts.games} Games + {mediaCounts.adult} Adult + + {mediaCounts.favorites} Favorites
- -

- © 2026 Omnyx Media Discovery. All rights reserved. +

+ © 2026 MediaVault v1.0.0

+
); } diff --git a/src/components/BrowseView.tsx b/src/components/BrowseView.tsx index af13f20..8f93295 100644 --- a/src/components/BrowseView.tsx +++ b/src/components/BrowseView.tsx @@ -1,18 +1,13 @@ -import { Media, MediaCategory } from '@/types'; +import { Media, MediaCategory, Staff } from '@/types'; import MediaCard from './MediaCard'; -import MediaListItem from './MediaListItem'; -import { LayoutGrid, List, Star, ChevronLeft, ChevronRight, ArrowUpDown, Search, Monitor, Users, FolderTree, Tag } from 'lucide-react'; +import MediaTable from './MediaTable'; +import MediaFilters from './filters/MediaFilters'; +import { LayoutGrid, List, ChevronLeft, ChevronRight, User, Users } from 'lucide-react'; import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; import Loading from '@/components/ui/loading'; import React, { useState, useMemo, useEffect } from 'react'; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger -} from '@/components/ui/dropdown-menu'; import { cn } from '@/lib/utils'; -import { AnimatePresence } from 'motion/react'; interface BrowseViewProps { mediaList: Media[]; @@ -22,13 +17,26 @@ interface BrowseViewProps { gridItemSize?: number; onGridItemSizeChange?: (size: number) => void; loading?: boolean; + searchResultsCast?: Staff[]; + onCastClick?: (person: Staff) => void; + searchQuery?: string; } -export default function BrowseView({ mediaList, onMediaClick, activeCategory, itemsPerPage: initialItemsPerPage = 12, gridItemSize: initialGridItemSize = 5, onGridItemSizeChange, loading = false }: BrowseViewProps) { - const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid'); +export default function BrowseView({ + mediaList, + onMediaClick, + activeCategory, + itemsPerPage: initialItemsPerPage = 12, + gridItemSize: initialGridItemSize = 5, + onGridItemSizeChange, + loading = false, + searchResultsCast = [], + onCastClick, + searchQuery = '' +}: BrowseViewProps) { + const [viewMode, setViewMode] = useState<'grid' | 'list'>('list'); const [currentPage, setCurrentPage] = useState(1); const [itemsPerPage, setItemsPerPage] = useState(initialItemsPerPage); - const [sortBy, setSortBy] = useState('default'); const [gridItemSize, setGridItemSize] = useState(initialGridItemSize); // Sync itemsPerPage with prop when API settings are loaded @@ -53,14 +61,6 @@ export default function BrowseView({ mediaList, onMediaClick, activeCategory, it const [selectedCategory, setSelectedCategory] = useState(null); const [selectedSource, setSelectedSource] = useState(null); - // Extract unique values for filters - const allGenres = useMemo(() => Array.from(new Set(mediaList.flatMap(m => m.genres || []))), [mediaList]); - const allStudios = useMemo(() => Array.from(new Set(mediaList.flatMap(m => m.studios || []))), [mediaList]); - const allPlatforms = useMemo(() => Array.from(new Set(mediaList.flatMap(m => m.platforms || []))), [mediaList]); - const allDevelopers = useMemo(() => Array.from(new Set(mediaList.flatMap(m => m.developers || []))), [mediaList]); - const allCategories = useMemo(() => Array.from(new Set(mediaList.flatMap(m => m.series || []))), [mediaList]); - const allSources = useMemo(() => Array.from(new Set(mediaList.flatMap(m => m.source ? [m.source] : []))), [mediaList]); - const filteredMedia = useMemo(() => { return mediaList.filter(media => { if (selectedGenre && !media.genres?.includes(selectedGenre)) return false; @@ -76,21 +76,9 @@ export default function BrowseView({ mediaList, onMediaClick, activeCategory, it // Reset to first page when mediaList or filters change useEffect(() => { setCurrentPage(1); - }, [filteredMedia, sortBy]); - - const sortedMedia = useMemo(() => { - const list = [...filteredMedia]; - if (sortBy === 'title-asc') { - return list.sort((a, b) => a.title.localeCompare(b.title)); - } - if (sortBy === 'title-desc') { - return list.sort((a, b) => b.title.localeCompare(a.title)); - } - return list; - }, [filteredMedia, sortBy]); + }, [filteredMedia]); const gridColsClass = useMemo(() => { - // Map slider value (1-10) to grid columns const colsMap: Record = { 1: 'grid-cols-1 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4', 2: 'grid-cols-1 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4', @@ -106,12 +94,21 @@ export default function BrowseView({ mediaList, onMediaClick, activeCategory, it return `grid ${colsMap[gridItemSize] || colsMap[5]}`; }, [gridItemSize]); - const totalPages = Math.ceil(sortedMedia.length / itemsPerPage); + const totalPages = Math.ceil(filteredMedia.length / itemsPerPage); const paginatedMedia = useMemo(() => { const startIndex = (currentPage - 1) * itemsPerPage; - return sortedMedia.slice(startIndex, startIndex + itemsPerPage); - }, [sortedMedia, currentPage, itemsPerPage]); + return filteredMedia.slice(startIndex, startIndex + itemsPerPage); + }, [filteredMedia, currentPage, itemsPerPage]); + + const handleClearAll = () => { + setSelectedGenre(null); + setSelectedStudio(null); + setSelectedPlatform(null); + setSelectedDeveloper(null); + setSelectedCategory(null); + setSelectedSource(null); + }; const handlePrevPage = () => { setCurrentPage((prev) => Math.max(prev - 1, 1)); @@ -123,173 +120,72 @@ export default function BrowseView({ mediaList, onMediaClick, activeCategory, it window.scrollTo({ top: 0, behavior: 'smooth' }); }; + // Calculate favorite IDs + const favoriteIds = useMemo(() => { + return new Set(mediaList.filter(m => m.rating && m.rating >= 8).map(m => m.id)); + }, [mediaList]); + + // Check if we have search results + const hasSearchResults = searchQuery.trim().length > 0; + const hasCastResults = searchResultsCast.length > 0; + const hasMediaResults = mediaList.length > 0; + + // Pagination for cast results (show first 12) + const paginatedCast = useMemo(() => { + return searchResultsCast.slice(0, itemsPerPage); + }, [searchResultsCast, itemsPerPage]); + return ( -
+
{/* Filters Bar */} -
-
- {/* Genre Filter */} - - - - - - setSelectedGenre(null)}>All Genres - {allGenres.sort().map(genre => ( - setSelectedGenre(genre)}>{genre} - ))} - - - - {/* Studio Filter */} - - - - - - setSelectedStudio(null)}>All Studios - {allStudios.sort().map(studio => ( - setSelectedStudio(studio)}>{studio} - ))} - - - - {/* Platform Filter - Only for Games */} - {activeCategory === 'Games' && ( - - - - - - setSelectedPlatform(null)}>All Platforms - {allPlatforms.sort().map(platform => ( - setSelectedPlatform(platform)}>{platform} - ))} - - - )} - - {/* Developer Filter - Only for Games */} - {activeCategory === 'Games' && ( - - - - - - setSelectedDeveloper(null)}>All Developers - {allDevelopers.sort().map(developer => ( - setSelectedDeveloper(developer)}>{developer} - ))} - - - )} - - {/* Category Filter - Only for Games */} - {activeCategory === 'Games' && ( - - - - - - setSelectedCategory(null)}>--- Alle --- - {allCategories.sort().map(category => ( - setSelectedCategory(category)}>{category} - ))} - - - )} - - {/* Source Filter */} - {allSources.length > 0 && ( - - - - - - setSelectedSource(null)}>All Sources - {allSources.sort().map(source => ( - setSelectedSource(source)}>{source} - ))} - - - )} - - {(selectedGenre || selectedStudio || selectedPlatform || selectedDeveloper || selectedCategory || selectedSource) && ( - - )} -
+
+
- {/* Grid item size slider */} -
- Size - { - const newSize = Number(e.target.value); - setGridItemSize(newSize); - onGridItemSizeChange?.(newSize); - }} - className="w-24 h-2 bg-background rounded-lg appearance-none cursor-pointer accent-[#6d28d9]" - /> - {gridItemSize} -
- - - - - - - setSortBy('default')}>Default - setSortBy('title-asc')}>Title (A-Z) - setSortBy('title-desc')}>Title (Z-A) - - + {/* Grid item size slider - only show in grid mode */} + {viewMode === 'grid' && ( +
+ Size + { + const newSize = Number(e.target.value); + setGridItemSize(newSize); + onGridItemSizeChange?.(newSize); + }} + className="w-24 h-2 bg-[#0d0f14] rounded-lg appearance-none cursor-pointer accent-[#e8466c]" + /> + {gridItemSize} +
+ )} -
+ {/* View Toggle */} +
+ {/* Search Results Summary */} + {hasSearchResults && ( +
+
+ Search results for: + + "{searchQuery}" + +
+
+ {hasMediaResults && ( +
+ + {mediaList.length} media +
+ )} + {hasCastResults && ( +
+ + {searchResultsCast.length} cast +
+ )} +
+
+ )} + + {/* Results Count */} +
+

+ Showing {paginatedMedia.length} of {filteredMedia.length} results +

+
+ + {/* Cast Search Results */} + {hasSearchResults && hasCastResults && onCastClick && ( +
+
+ +

Cast Results

+ + {searchResultsCast.length} + +
+
+ {paginatedCast.map((person) => ( +
onCastClick(person)} + className="group cursor-pointer bg-[#1a1d26] rounded-lg p-3 border border-white/10 hover:border-[#e8466c]/50 transition-all duration-300 hover:bg-[#1f232c]" + > +
+
+ {person.photo ? ( + {person.name} + ) : ( +
+ +
+ )} +
+
+

+ {person.name} +

+

{person.role}

+ {person.filmography && person.filmography.length > 0 && ( +

+ {person.filmography.length} role{person.filmography.length !== 1 ? 's' : ''} +

+ )} +
+
+
+ ))} +
+ {searchResultsCast.length > itemsPerPage && ( +

+ +{searchResultsCast.length - itemsPerPage} more cast members +

+ )} +
+ )} + {/* Content */} {loading ? ( - ) : mediaList.length === 0 ? ( -
-
- + ) : mediaList.length === 0 && !hasCastResults ? ( +
+
+ 📁
-

No results found

+

No results found

Try adjusting your search or filters

+ ) : mediaList.length === 0 ? ( +
+

No media results found for this search

+
) : ( -
- - {paginatedMedia.map((media) => ( - viewMode === 'grid' ? ( + <> + {hasSearchResults && ( +
+ +

Media Results

+ + {mediaList.length} + +
+ )} + {viewMode === 'list' ? ( + + ) : ( +
+ {paginatedMedia.map((media) => ( - ) : ( - - ) - ))} - -
+ ))} +
+ )} + )} {/* Pagination Controls */} - {mediaList.length > 0 && ( -
+ {filteredMedia.length > 0 && ( +
- Items per page: + Items per page: @@ -372,16 +367,16 @@ export default function BrowseView({ mediaList, onMediaClick, activeCategory, it size="sm" onClick={handlePrevPage} disabled={currentPage === 1} - className="gap-2 font-bold border-border" + className="gap-2 font-bold border-white/10 bg-transparent text-gray-300 hover:bg-white/5 hover:text-white disabled:opacity-50" > Previous
- {currentPage} - of - {totalPages || 1} + {currentPage} + of + {totalPages || 1}
{/* Content Section */} -
- {/* Sidebar Info */} -
-
-

Personal Info

- -
-
-
- +
+ {/* Sidebar Info - Modern shadcn Design */} +
+ {/* Personal Info Card */} + + + +
+
-
-

Birth Date

-

{person.birthDate || 'Unknown'}

-
-
- -
-
- -
-
-

Birth Place

-

{person.birthPlace || 'Unknown'}

-
-
- -
-
- -
-
-

Known For

-

{person.role}

+ Personal Info + + + + {/* Birth Date */} +
+
+
+ +
+ Born
+ {person.birthDate || '—'} +
+ + + {/* Birth Place */} +
+
+
+ +
+ Origin +
+ + {person.birthPlace || '—'} + +
+ + + {/* Known For */} +
+
+
+ +
+ Role +
+ + {person.role} +
+ {/* Ethnicity - only if present */} {(person.ethnicity || person.adult_specifics?.ethnicity) && ( -
-
- + <> + +
+
+
+ +
+ Ethnicity +
+ + {person.adult_specifics?.ethnicity || person.ethnicity} +
-
-

Ethnicity

-

{person.adult_specifics?.ethnicity || person.ethnicity}

-
-
+ )} -
-
+ + -
-

Measurements

- -
- -
-
- -
-
-

Height

-

{person.adult_specifics?.height || person.height} cm

-
+ {/* Measurements Card - Only if data exists */} + {(person.adult_specifics?.height || person.height || person.adult_specifics?.weight || person.weight || + person.adult_specifics?.measurements || person.bust_size || person.hair_color || person.adult_specifics?.hair_color) && ( + + + +
+
- - - {(person.weight || person.adult_specifics?.weight) && ( -
-
- + Measurements + + + + {/* Height & Weight Grid */} + {(person.adult_specifics?.height || person.height || person.adult_specifics?.weight || person.weight) && ( + <> +
+ {(person.adult_specifics?.height || person.height) && ( +
+

Height

+

+ {person.adult_specifics?.height || person.height} + cm +

+
+ )} + {(person.adult_specifics?.weight || person.weight) && ( +
+

Weight

+

+ {person.adult_specifics?.weight || person.weight} + kg +

+
+ )}
-
-

Weight

-

{person.adult_specifics?.weight || person.weight} kg

-
-
+ + )} + {/* Measurements (Bust-Waist-Hip) */} {(person.adult_specifics?.measurements || person.bust_size || person.cup_size || person.waist_size || person.hip_size) && ( -
-
- -
-
-

Measurements

-

+ <> +

+

Figure

+

{person.adult_specifics?.measurements || ( <> - {person.bust_size && `${person.bust_size}`} - {person.cup_size && person.cup_size} - {person.bust_size || person.cup_size ? '-' : ''} - {person.waist_size && `${person.waist_size}`} - {person.waist_size ? '-' : ''} - {person.hip_size && `${person.hip_size}`} + {person.bust_size && {person.bust_size}{person.cup_size && {person.cup_size}}} + {(person.bust_size || person.cup_size) && person.waist_size && } + {person.waist_size && {person.waist_size}} + {person.hip_size && } + {person.hip_size && {person.hip_size}} )}

-
+ + )} - {(person.hair_color || person.adult_specifics?.hair_color) && ( -
-
- + {/* Hair & Eyes Grid */} +
+ {(person.hair_color || person.adult_specifics?.hair_color) && ( +
+
+ + Hair +
+

+ {person.adult_specifics?.hair_color || person.hair_color} +

-
-

Hair Color

-

{person.adult_specifics?.hair_color || person.hair_color}

+ )} + {(person.eye_color || person.adult_specifics?.eye_color) && ( +
+
+ + Eyes +
+

+ {person.adult_specifics?.eye_color || person.eye_color} +

-
- )} + )} +
- {(person.eye_color || person.adult_specifics?.eye_color) && ( -
-
- -
-
-

Eye Color

-

{person.adult_specifics?.eye_color || person.eye_color}

-
-
- )} - - {person.adult_specifics?.tattoos && ( -
-
- -
-
-

Tattoos

-

{person.adult_specifics.tattoos}

-
-
- )} - - {person.adult_specifics?.piercings && ( -
-
- -
-
-

Piercings

-

{person.adult_specifics.piercings}

-
-
- )} -
-
-
- - {/* Main Bio & Roles */} -
- {person.bio && ( -
-

- Biography -

-

- {person.bio} -

-
- )} - - {person.filmography && person.filmography.length > 0 && ( -
-

- - Characters -

-
- {person.filmography.map(item => ( -
-
- {item.title} -
-
-

Character

-

{item.characterName || item.role}

- - {item.category && ( - - {item.category} - + {/* Tattoos & Piercings */} + {(person.adult_specifics?.tattoos || person.adult_specifics?.piercings) && ( + <> + +
+ {person.adult_specifics?.tattoos && ( +
+

Tattoos

+

{person.adult_specifics.tattoos}

+
+ )} + {person.adult_specifics?.piercings && ( +
+

Piercings

+

{person.adult_specifics.piercings}

+
)}
-
- ))} -
-
+ + )} + + )} +
- {person.filmography && person.filmography.length > 0 && ( -
-
-

- - Filmography -

-
- - -
-
-
- {sortedFilmography.map(item => ( -
handleMediaClick(item.id.toString())} - className="group flex items-center gap-4 p-4 rounded-2xl bg-card border border-border/50 hover:border-[#6d28d9]/30 hover:shadow-lg hover:shadow-[#6d28d9]/10 transition-all duration-300 cursor-pointer" - > -
- {item.title} -
-
-

- {item.title} -

-

- {item.year || 'Unknown'} -

-
- - {item.role} - - {item.category && ( - - {item.category} - - )} -
+ {/* Main Bio & Roles - Wider */} +
+ + + {person.bio && ( + + + Biography + + )} + {person.filmography && person.filmography.length > 0 && ( + <> + + + Characters + + + + Filmography + + + )} + + + {person.bio && ( + + + + Biography + + +

+ {person.bio} +

+
+
+
+ )} + + {person.filmography && person.filmography.length > 0 && ( + <> + +
+ + {person.filmography.map((item, index) => ( + + handleMediaClick(item.id.toString())} + > + +
+ {item.title} +
+
+

Character

+

+ {item.characterName || item.role} +

+

{item.title}

+
+
+
+
+ ))} +
+
+
+ + + {/* Sort Toolbar */} +
+

+ {person.filmography.length} {person.filmography.length === 1 ? 'title' : 'titles'} +

+
+ + + + + + + Sort by + + + {sortOptions.map(option => ( + { + if (sortBy === option.value) { + setSortOrder(prev => prev === 'asc' ? 'desc' : 'asc'); + } else { + setSortBy(option.value); + setSortOrder('asc'); + } + }} + className="flex items-center justify-between text-xs" + > + + + {option.label} + + {sortBy === option.value && ( + sortOrder === 'asc' ? : + )} + + ))} + +
- ))} -
-
- )} + + {/* Filmography Grid */} +
+ + {sortedFilmography.map((item, index) => ( + + handleMediaClick(item.id.toString())} + className="group cursor-pointer hover:border-[#6d28d9]/30 hover:shadow-md transition-all duration-200 border-border/60" + > + +
+ {item.title} +
+
+

+ {item.title} +

+

+ {item.year || 'Unknown'} +

+
+ + {item.role} + + {item.category && ( + + {item.category} + + )} +
+
+
+
+
+ ))} +
+
+ + + )} +
diff --git a/src/components/CastView.tsx b/src/components/CastView.tsx index 945d82a..6e86bb9 100644 --- a/src/components/CastView.tsx +++ b/src/components/CastView.tsx @@ -1,10 +1,40 @@ import { Staff, MediaCategory } from '@/types'; import { useState, useMemo, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; -import { Search, ArrowUpDown, User, ChevronLeft, ChevronRight, X, Filter } from 'lucide-react'; +import { + Search, ArrowUpDown, User, ChevronLeft, ChevronRight, X, Filter, + LayoutGrid, Table2, Eye, Calendar, Star, ArrowUpAZ, ArrowDownAZ, + Briefcase, Film, Users, ChevronUp, ChevronDown +} from 'lucide-react'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; +import { Card, CardContent } from '@/components/ui/card'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, + DropdownMenuSeparator, +} from '@/components/ui/dropdown-menu'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/table'; +import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import Loading from '@/components/ui/loading'; import { motion, AnimatePresence } from 'motion/react'; import { cn } from '@/lib/utils'; @@ -30,14 +60,19 @@ export default function CastView({ onPersonClick, enabledCategories, itemsPerPag return (localStorage.getItem('castSortOrder') as 'asc' | 'desc') || 'desc'; }); const [filterOccupation, setFilterOccupation] = useState(() => { - return localStorage.getItem('castFilterOccupation') || ''; + const saved = localStorage.getItem('castFilterOccupation'); + return saved && saved !== '' ? saved : 'all'; }); const [filterMediaType, setFilterMediaType] = useState(() => { - return localStorage.getItem('castFilterMediaType') || ''; + const saved = localStorage.getItem('castFilterMediaType'); + return saved && saved !== '' ? saved : 'all'; }); const [currentPage, setCurrentPage] = useState(1); const [itemsPerPage, setItemsPerPage] = useState(initialItemsPerPage); - const [showFilters, setShowFilters] = useState(false); + const [viewMode, setViewMode] = useState<'grid' | 'table'>(() => { + return (localStorage.getItem('castViewMode') as 'grid' | 'table') || 'grid'; + }); + const [hoveredRow, setHoveredRow] = useState(null); // Sync itemsPerPage with prop when API settings are loaded useEffect(() => { @@ -71,11 +106,11 @@ export default function CastView({ onPersonClick, enabledCategories, itemsPerPag setSearchQuery(''); setSortBy('roleCount'); setSortOrder('desc'); - setFilterOccupation(''); - setFilterMediaType(''); + setFilterOccupation('all'); + setFilterMediaType('all'); }; - const hasActiveFilters = searchQuery || filterOccupation || filterMediaType || sortBy !== 'roleCount' || sortOrder !== 'desc'; + const hasActiveFilters = searchQuery || (filterOccupation && filterOccupation !== 'all') || (filterMediaType && filterMediaType !== 'all') || sortBy !== 'roleCount' || sortOrder !== 'desc'; useEffect(() => { const loadCast = async () => { @@ -110,12 +145,12 @@ export default function CastView({ onPersonClick, enabledCategories, itemsPerPag } // Filter by occupation - if (filterOccupation && !s.occupations?.includes(filterOccupation)) { + if (filterOccupation && filterOccupation !== 'all' && !s.occupations?.includes(filterOccupation)) { return false; } - + // Filter by media type - if (filterMediaType && !s.media_types?.includes(filterMediaType)) { + if (filterMediaType && filterMediaType !== 'all' && !s.media_types?.includes(filterMediaType)) { return false; } @@ -185,260 +220,537 @@ export default function CastView({ onPersonClick, enabledCategories, itemsPerPag window.scrollTo({ top: 0, behavior: 'smooth' }); }; - return ( -
-
-
-

- Cast & Staff -

-

Discover the people behind your favorite media

-
+ // Persist view mode + useEffect(() => { + localStorage.setItem('castViewMode', viewMode); + }, [viewMode]); -
-
- - setSearchQuery(e.target.value)} - className="pl-10 w-full md:w-[300px] bg-muted/50 backdrop-blur-sm border-none rounded-full h-11" - /> -
- - - {hasActiveFilters && ( - + + + + + + + + + {/* Count Badge */} + + {filteredStaff.length} {filteredStaff.length === 1 ? 'person' : 'people'} + +
+ + {/* Bottom Row: Filter Dropdowns */} +
+ {/* Sort Dropdown */} + + + + + + + Sort by + + + {sortOptions.map(option => ( + { + if (sortBy === option.value) { + setSortOrder(prev => prev === 'asc' ? 'desc' : 'asc'); + } else { + setSortBy(option.value); + setSortOrder('asc'); + } + }} + className="flex items-center justify-between" + > + + + {option.label} + + {sortBy === option.value && ( + sortOrder === 'asc' ? : + )} + + ))} + + + + {/* Occupation Filter */} + {uniqueOccupations.length > 0 && ( + + + + + + + Filter by Occupation + + + setFilterOccupation('all')}> + All Occupations + + {uniqueOccupations.map(occ => ( + setFilterOccupation(occ)}> + {occ} + + ))} + + + )} + + {/* Media Type Filter */} + {uniqueMediaTypes.length > 0 && ( + + + + + + + Filter by Media Type + + + setFilterMediaType('all')}> + All Media Types + + {uniqueMediaTypes.map(type => ( + setFilterMediaType(type)}> + {type} + + ))} + + + )} + + {/* Clear All */} + {hasActiveFilters && ( + + )} +
+ + {/* Active Filter Badges */} + {hasActiveFilters && ( +
+ {searchQuery && ( + setSearchQuery('')} + > + Search: {searchQuery} + + + )} + {filterOccupation && filterOccupation !== 'all' && ( + setFilterOccupation('all')} + > + {filterOccupation} + + + )} + {filterMediaType && filterMediaType !== 'all' && ( + setFilterMediaType('all')} + > + {filterMediaType} + + + )} + {(sortBy !== 'roleCount' || sortOrder !== 'desc') && ( + { setSortBy('roleCount'); setSortOrder('desc'); }} + > + Sort: {sortOptions.find(o => o.value === sortBy)?.label} + + + )} +
)}
-
- {showFilters && ( - -
-
- - -
-
- - -
-
- - -
-
-
- {searchQuery && ( - - Search: {searchQuery} - - - )} - {filterOccupation && ( - - Occupation: {filterOccupation} - - - )} - {filterMediaType && ( - - Media Type: {filterMediaType} - - - )} - {(sortBy !== 'name' || sortOrder !== 'asc') && ( - - Sort: {sortBy} ({sortOrder}) - - - )} -
-
- )} + {/* Content Area */} + {loading ? ( + + ) : filteredStaff.length === 0 ? ( + + +
+ +
+

No cast members found

+
+
+ ) : viewMode === 'grid' ? ( + /* Grid View - Modern Cards */ +
+ + {paginatedStaff.map((person) => ( + + onPersonClick(person)} + > + {/* Card Header with Avatar and Info */} +
+
+ + + + + + +
+

+ {person.name} +

+

+ {person.role} +

+
+ {person.filmography && person.filmography.length > 0 && ( + + + + + {person.filmography.length} + + + +

{person.filmography.length} roles

+
+
+ )} + {person.birthDate && ( + + + {new Date(person.birthDate).getFullYear()} + + )} +
+
+
+
- {loading ? ( - - ) : filteredStaff.length === 0 ? ( -
-
- + {/* Latest Role Section */} + {person.filmography && person.filmography.length > 0 && ( +
+
+
+ {person.filmography[0].title} +
+
+

Latest

+

{person.filmography[0].title}

+

{person.filmography[0].role}

+
+
+
+ )} + + + ))} +
-

No cast members found

-
- ) : ( -
- - {paginatedStaff.map((person) => ( - onPersonClick(person)} + ) : ( + /* Table View */ +
+ + + + + handleSort('name')} + > +
+ Name + {sortBy === 'name' && (sortOrder === 'asc' ? : )} +
+
+ handleSort('role')} + > +
+ Role + {sortBy === 'role' && (sortOrder === 'asc' ? : )} +
+
+ Latest Work + handleSort('roleCount')} + > +
+ Roles + {sortBy === 'roleCount' && (sortOrder === 'asc' ? : )} +
+
+ +
+
+ + + {paginatedStaff.map((person) => ( + setHoveredRow(person.id)} + onMouseLeave={() => setHoveredRow(null)} + onClick={() => onPersonClick(person)} + > + + + + + + + + + +
+ {person.name} + {person.birthDate && ( + + {new Date(person.birthDate).toLocaleDateString()} + + )} +
+
+ + + {person.role} + + + + {person.filmography && person.filmography.length > 0 ? ( +
+
+ {person.filmography[0].title} +
+
+

{person.filmography[0].title}

+

{person.filmography[0].role}

+
+
+ ) : ( + - + )} +
+ + {person.filmography ? ( + + {person.filmography.length} + + ) : ( + - + )} + + + + +
+ ))} +
+
+
+
+ )} + + {/* Pagination - Modern */} + {filteredStaff.length > 0 && ( +
+
+ Showing + + {Math.min((currentPage - 1) * itemsPerPage + 1, filteredStaff.length)}-{Math.min(currentPage * itemsPerPage, filteredStaff.length)} + + of + {filteredStaff.length} + items +
+ +
+ + +
+ + +
+ {currentPage} + / + {totalPages}
- {person.filmography && person.filmography.length > 0 && ( -
-
- {person.filmography[0].title} -
-
-

Latest Role

-

{person.filmography[0].title}

-

{person.filmography[0].role}

-
-
- )} - - ))} - -
- )} - - {/* Pagination Controls */} - {filteredStaff.length > 0 && ( -
-
- Items per page: - -
- -
- - -
- {currentPage} - of - {totalPages || 1} + +
- -
-
- )} -
+ )} +
+ ); } diff --git a/src/components/DashboardView.tsx b/src/components/DashboardView.tsx index 0c3490d..eb14999 100644 --- a/src/components/DashboardView.tsx +++ b/src/components/DashboardView.tsx @@ -1,9 +1,22 @@ import { Media, MediaCategory } from '@/types'; import MediaCard from './MediaCard'; -import { Film, Tv, Music, Book, Gamepad2, Users, Star, TrendingUp, Clock, Hash, Play, Award } from 'lucide-react'; +import { + Film, + Tv, + Gamepad2, + Users, + Heart, + FolderKanban, + Database, + Sparkles, + Clock, + ChevronRight, + Eye +} from 'lucide-react'; import { useMemo } from 'react'; import { motion } from 'motion/react'; import Loading from '@/components/ui/loading'; +import { useNavigate } from 'react-router-dom'; interface DashboardViewProps { mediaList: Media[]; @@ -12,253 +25,214 @@ interface DashboardViewProps { } export default function DashboardView({ mediaList, onMediaClick, loading = false }: DashboardViewProps) { + const navigate = useNavigate(); + // Calculate statistics const stats = useMemo(() => { - const totalMedia = mediaList.length; const categories = mediaList.reduce((acc, media) => { acc[media.category] = (acc[media.category] || 0) + 1; return acc; }, {} as Record); - const totalRating = mediaList.reduce((sum, media) => sum + (media.rating || 0), 0); - const avgRating = totalRating > 0 ? (totalRating / mediaList.filter(m => m.rating).length).toFixed(1) : '0.0'; - - const totalPlaytime = mediaList.reduce((sum, media) => sum + (media.playtime || 0), 0); - const totalPlayCount = mediaList.reduce((sum, media) => sum + (media.playCount || 0), 0); + const favoritesCount = mediaList.filter(m => m.rating && m.rating >= 8).length; return { - totalMedia, - categories, - avgRating, - totalPlaytime, - totalPlayCount + movies: categories['Movies'] || 0, + series: categories['TV Series'] || 0, + games: categories['Games'] || 0, + adult: categories['Adult'] || 0, + actors: new Set(mediaList.flatMap(m => m.staff?.map(s => s.id) || [])).size, + collections: 3, // Placeholder + favorites: favoritesCount }; }, [mediaList]); - // Get recently added media (sorted by some indicator - using index as proxy) + // Get recently added media const recentMedia = useMemo(() => { - return [...mediaList].slice(0, 8); + return [...mediaList].slice(0, 10); }, [mediaList]); - // Get top rated media - const topRatedMedia = useMemo(() => { + // Get favorites + const favoritesMedia = useMemo(() => { return [...mediaList] - .filter(m => m.rating && m.rating > 0) - .sort((a, b) => (b.rating || 0) - (a.rating || 0)) + .filter(m => m.rating && m.rating >= 8) .slice(0, 8); }, [mediaList]); - // Get most played media - const mostPlayedMedia = useMemo(() => { - return [...mediaList] - .filter(m => m.playCount && m.playCount > 0) - .sort((a, b) => (b.playCount || 0) - (a.playCount || 0)) - .slice(0, 8); - }, [mediaList]); - - // Category icons mapping - const categoryIcons: Record = { - 'Anime': Tv, - 'Movies': Film, - 'TV Series': Tv, - 'Music': Music, - 'Books': Book, - 'Games': Gamepad2, - 'Consoles': Gamepad2, - 'Adult': Users - }; - - // Category colors - const categoryColors: Record = { - 'Anime': 'bg-purple-500/10 text-purple-500 border-purple-500/20', - 'Movies': 'bg-blue-500/10 text-blue-500 border-blue-500/20', - 'TV Series': 'bg-green-500/10 text-green-500 border-green-500/20', - 'Music': 'bg-pink-500/10 text-pink-500 border-pink-500/20', - 'Books': 'bg-yellow-500/10 text-yellow-500 border-yellow-500/20', - 'Games': 'bg-red-500/10 text-red-500 border-red-500/20', - 'Consoles': 'bg-orange-500/10 text-orange-500 border-orange-500/20', - 'Adult': 'bg-gray-500/10 text-gray-500 border-gray-500/20' - }; - - const formatPlaytime = (minutes: number) => { - if (minutes < 60) return `${minutes}m`; - const hours = Math.floor(minutes / 60); - const mins = minutes % 60; - return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`; - }; + // Category card config + const categoryCards = [ + { + key: 'movies', + label: 'MOVIES', + count: stats.movies, + icon: Film, + color: 'from-blue-500/20 to-blue-600/10', + iconBg: 'bg-blue-500/20', + path: '/movies' + }, + { + key: 'series', + label: 'SERIES', + count: stats.series, + icon: Tv, + color: 'from-green-500/20 to-green-600/10', + iconBg: 'bg-green-500/20', + path: '/tv-series' + }, + { + key: 'games', + label: 'GAMES', + count: stats.games, + icon: Gamepad2, + color: 'from-purple-500/20 to-purple-600/10', + iconBg: 'bg-purple-500/20', + path: '/games' + }, + { + key: 'adult', + label: 'ADULT', + count: stats.adult, + icon: Eye, + color: 'from-rose-500/20 to-rose-600/10', + iconBg: 'bg-rose-500/20', + path: '/adult' + }, + { + key: 'actors', + label: 'ACTORS', + count: stats.actors, + icon: Users, + color: 'from-amber-500/20 to-amber-600/10', + iconBg: 'bg-amber-500/20', + path: '/cast' + }, + { + key: 'collections', + label: 'COLLECTIONS', + count: stats.collections, + icon: FolderKanban, + color: 'from-cyan-500/20 to-cyan-600/10', + iconBg: 'bg-cyan-500/20', + path: '/collections' + }, + ]; if (loading) { return ; } return ( -
- {/* Header */} -
-

- Dashboard -

-

Overview of your media collection

-
+
+ {/* Welcome Header */} + +
+
+ +
+

+ Welcome to MediaVault +

+
+

Your media library at a glance

+
{/* Stats Cards */} -
- -
-
-
- - Total -
-
{stats.totalMedia}
-
Media Items
-
- - - -
-
-
- - Average -
-
{stats.avgRating}
-
Rating
-
- + + {categoryCards.map((card, index) => { + const Icon = card.icon; + return ( + navigate(card.path)} + className={`relative overflow-hidden rounded-xl p-5 bg-gradient-to-br ${card.color} border border-white/5 hover:border-white/10 transition-all duration-300 cursor-pointer group`} + > +
+
+

{card.label}

+

{card.count}

+
+
+ +
+
+
+ ); + })} +
+ {/* Favorites Section */} + {favoritesMedia.length > 0 && ( -
-
-
- - Total -
-
{stats.totalPlayCount}
-
Play Count
-
- - - -
-
-
- - Total -
-
{formatPlaytime(stats.totalPlaytime)}
-
Playtime
-
- -
- - {/* Category Breakdown */} - -

- - Category Breakdown -

-
- {(Object.keys(stats.categories) as MediaCategory[]).map((category) => { - const Icon = categoryIcons[category]; - const count = stats.categories[category] || 0; - const percentage = stats.totalMedia > 0 ? ((count / stats.totalMedia) * 100).toFixed(1) : '0'; - - return ( -
- -
{category}
-
{count}
-
{percentage}%
+
navigate('/browse?favorites=true')} + className="relative overflow-hidden rounded-xl p-6 bg-gradient-to-r from-[#e8466c]/10 to-[#f47298]/5 border border-[#e8466c]/20 hover:border-[#e8466c]/30 transition-all duration-300 cursor-pointer group" + > +
+
+
+ +
+
+

FAVORITES

+

{favoritesMedia.length} items in your favorites

+
- ); - })} -
- +
+ View Favorites + +
+
+
+ + )} - {/* Recent Media */} + {/* Recently Added Section */} {recentMedia.length > 0 && ( -

- - Recent Additions -

-
+
+
+ +

Recently Added

+
+ +
+ +
{recentMedia.map((media) => ( - - ))} -
- - )} - - {/* Top Rated Media */} - {topRatedMedia.length > 0 && ( - -

- - Top Rated -

-
- {topRatedMedia.map((media) => ( - - ))} -
-
- )} - - {/* Most Played Media */} - {mostPlayedMedia.length > 0 && ( - -

- - Most Played -

-
- {mostPlayedMedia.map((media) => ( - + ))}
@@ -266,11 +240,11 @@ export default function DashboardView({ mediaList, onMediaClick, loading = false {/* Empty State */} {mediaList.length === 0 && ( -
-
- +
+
+
-

No media found

+

No media found

Start by adding media to your collection

)} diff --git a/src/components/DetailView.tsx b/src/components/DetailView.tsx index fcd3c81..462a04c 100644 --- a/src/components/DetailView.tsx +++ b/src/components/DetailView.tsx @@ -1,9 +1,21 @@ import { Media, Staff } from '@/types'; import { useNavigate } from 'react-router-dom'; import { useState } from 'react'; -import { ChevronLeft, Calendar, Clock } from 'lucide-react'; -import { Badge } from '@/components/ui/badge'; +import * as React from 'react'; +import { + ArrowLeft, Calendar, Clock, Play, Star, Users, Disc, Layers, + Tv, BookOpen, Gamepad2, Film, Music, Package, Heart, Bookmark, + MoreHorizontal, Share2, ExternalLink +} from 'lucide-react'; import { motion } from 'motion/react'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent } from '@/components/ui/card'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Separator } from '@/components/ui/separator'; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; +import { Progress } from '@/components/ui/progress'; import OverviewTab from './details/tabs/OverviewTab'; import CastTab from './details/tabs/CastTab'; import SeasonsTab from './details/tabs/SeasonsTab'; @@ -16,164 +28,392 @@ interface DetailViewProps { onPersonClick: (person: Staff) => void; } +const categoryIcons: Record = { + 'Anime': , + 'Movies': , + 'TV Series': , + 'Music': , + 'Books': , + 'Games': , + 'Consoles': , + 'Adult': , +}; + +const statusColors: Record = { + 'watching': 'bg-emerald-500/10 text-emerald-500 border-emerald-500/20', + 'reading': 'bg-emerald-500/10 text-emerald-500 border-emerald-500/20', + 'listening': 'bg-emerald-500/10 text-emerald-500 border-emerald-500/20', + 'playing': 'bg-emerald-500/10 text-emerald-500 border-emerald-500/20', + 'completed': 'bg-blue-500/10 text-blue-500 border-blue-500/20', + 'planned': 'bg-amber-500/10 text-amber-500 border-amber-500/20', + 'dropped': 'bg-red-500/10 text-red-500 border-red-500/20', + 'on-hold': 'bg-muted text-muted-foreground border-border', +}; + export default function DetailView({ media, allMedia, onPersonClick }: DetailViewProps) { const navigate = useNavigate(); - const [progress, setProgress] = useState(70.8); + const [progress] = useState(media.playCount ? Math.min(100, (media.playCount * 10)) : 0); const hasEpisodes = media.episodes && media.episodes.length > 0; const hasTracks = media.tracks && media.tracks.length > 0; const hasCast = media.staff && media.staff.length > 0; const hasFranchise = media.category === 'Games' && media.series && media.series.length > 0; - const tabs = [ - 'Overview', - ...(hasCast ? ['Cast'] : []), - 'Actions', - 'History', - ...(hasEpisodes ? ['Seasons'] : []), - ...(hasTracks ? ['Tracks'] : []), - ...(hasFranchise ? ['Series'] : []), - 'Reviews', - 'Suggestions', - 'Watch On' - ]; - const [activeTab, setActiveTab] = useState(tabs[0]); + // Determine default tab based on available content + const getDefaultTab = () => { + if (hasEpisodes) return 'seasons'; + if (hasTracks) return 'tracks'; + if (hasCast) return 'cast'; + return 'overview'; + }; + + const [activeTab, setActiveTab] = useState(getDefaultTab()); + + const tabItems = [ + { id: 'overview', label: 'Overview', icon: BookOpen, hidden: false }, + { id: 'cast', label: 'Cast', icon: Users, hidden: !hasCast }, + { id: 'seasons', label: 'Seasons', icon: Layers, hidden: !hasEpisodes }, + { id: 'tracks', label: 'Tracks', icon: Disc, hidden: !hasTracks }, + { id: 'series', label: 'Series', icon: Gamepad2, hidden: !hasFranchise }, + ].filter(tab => !tab.hidden); + + const statusBadgeClass = media.status ? statusColors[media.status] : 'bg-muted text-muted-foreground border-border'; return ( -
- {/* Banner */} -
- {media.title} -
+ +
+ {/* Hero Section - Full height from top behind transparent navbar */} +
+ {media.title} +
- -
+ {/* Back Button - z-50 to ensure clickable */} + - {/* Content */} -
-
- {/* Left Column: Cover Image */} -
- - {media.title} - + {/* Quick Actions - z-50 to ensure clickable */} +
+ + + + + Add to favorites + + + + + + Bookmark + + + + + + Share + + + + + + More options +
- {/* Right Column: Info */} -
- {/* Header with tags */} -
-

- {media.title} -

- {media.status && ( - - {media.status.toUpperCase()} - - )} - {media.completionStatus && ( - {media.completionStatus.toUpperCase()} - )} -
+ {/* Hero Content - pt-16 to account for navbar + buttons */} +
+
+ {/* Poster */} + + + + + {categoryIcons[media.category] || } + + + - {/* Show Details */} -
-
- - {media.year} -
-
- {media.status ? media.status.charAt(0).toUpperCase() + media.status.slice(1) : 'Unknown'} -
-
- - {media.playtime ? `${media.playtime}h` : '12h 30m'} -
-
- - {/* Progress Bar */} -
-
- Progress - {progress}% -
-
-
-
-
- - {/* Navigation Tabs */} -
- {tabs.map(tab => ( - - ))} +
+ {categoryIcons[media.category] && ( + + {categoryIcons[media.category]} + {media.category} + + )} + {media.type && ( + + {media.type} + + )} + {media.status && ( + + {media.status.charAt(0).toUpperCase() + media.status.slice(1)} + + )} + {media.completionStatus && ( + + {media.completionStatus} + + )} +
+ +

+ {media.title} +

+ +
+
+ + {media.year} +
+ {media.rating && ( +
+ + {media.rating.toFixed(1)} +
+ )} + {media.playtime && ( +
+ + {media.playtime}h played +
+ )} + {hasEpisodes && ( +
+ + {media.episodes!.length} episodes +
+ )} + {hasTracks && ( +
+ + {media.tracks!.length} tracks +
+ )} +
+ +
+ + {/* Primary Action */} + + + +
+
+
+ + {/* Content Section */} +
+
+ {/* Left Sidebar - Info Cards */} +
+ {/* Progress Card */} + {progress > 0 && ( + + +
+ Progress + {progress}% +
+ +
+
+ )} + + {/* Studios */} + {media.studios && media.studios.length > 0 && ( + + +

+
+ +
+ Studios +

+
+ {media.studios.map(studio => ( + + {studio} + + ))} +
+
+
+ )} + + {/* Platforms (for Games) */} + {media.platforms && media.platforms.length > 0 && ( + + +

+
+ +
+ Platforms +

+
+ {media.platforms.map(platform => ( + + {platform} + + ))} +
+
+
+ )} + + {/* Developers (for Games) */} + {media.developers && media.developers.length > 0 && ( + + +

+
+ +
+ Developers +

+
+ {media.developers.map(dev => ( + + {dev} + + ))} +
+
+
+ )} + + {/* Source */} + {media.source && ( + + +

+
+ +
+ Source +

+ + {media.source} + +
+
+ )}
- {/* Overview Tab */} - {activeTab === 'Overview' && } + {/* Main Content - Tabs */} +
+ + + {tabItems.map(tab => { + const Icon = tab.icon; + return ( + + + {tab.label} + + ); + })} + - {/* Cast Tab */} - {media.staff && media.staff.length > 0 && activeTab === 'Cast' && ( - - )} - {/* Seasons Tab */} - {media.episodes && media.episodes.length > 0 && activeTab === 'Seasons' && ( - - )} + + + - {/* Tracks Tab */} - {media.tracks && media.tracks.length > 0 && activeTab === 'Tracks' && ( - - )} + {hasCast && ( + + + + )} - {/* Series Tab */} - {media.category === 'Games' && media.series && media.series.length > 0 && activeTab === 'Series' && ( - window.location.href = `/${media.id}`} /> - )} + {hasEpisodes && ( + + + + )} + + {hasTracks && ( + + + + )} + + {hasFranchise && ( + + navigate(`/media/${m.id}`)} /> + + )} + +
-
+ ); } diff --git a/src/components/MediaCard.tsx b/src/components/MediaCard.tsx index efabba4..03b4117 100644 --- a/src/components/MediaCard.tsx +++ b/src/components/MediaCard.tsx @@ -1,15 +1,78 @@ -import { Media } from '@/types'; +import React, { useState } from 'react'; +import { Media, MediaCategory } from '@/types'; import { cn } from '@/lib/utils'; -import { motion } from 'motion/react'; -import { Star } from 'lucide-react'; +import { motion, AnimatePresence } from 'motion/react'; +import { + Star, + Heart, + Gamepad2, + Film, + Tv, + Eye, + Play, + Calendar, + Hash, + Trophy, +} from 'lucide-react'; +import { Card } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from '@/components/ui/tooltip'; +import { Button } from '@/components/ui/button'; +import { Separator } from '@/components/ui/separator'; interface MediaCardProps { key?: string; media: Media; onClick: (media: Media) => void; + showBadge?: boolean; + showFavorite?: boolean; + isFavorite?: boolean; + onFavoriteToggle?: (media: Media) => void; + variant?: 'default' | 'compact' | 'hero' | 'minimal'; } -export default function MediaCard({ media, onClick }: MediaCardProps) { +const categoryConfig: Record< + MediaCategory, + { label: string; variant: 'default' | 'secondary' | 'outline' | 'destructive'; icon: React.ElementType | null } +> = { + Anime: { label: 'ANIME', variant: 'secondary', icon: null }, + Movies: { label: 'MOVIE', variant: 'secondary', icon: Film }, + 'TV Series': { label: 'SERIES', variant: 'secondary', icon: Tv }, + Music: { label: 'MUSIC', variant: 'secondary', icon: null }, + Books: { label: 'BOOK', variant: 'secondary', icon: null }, + Games: { label: 'GAME', variant: 'secondary', icon: Gamepad2 }, + Consoles: { label: 'CONSOLE', variant: 'secondary', icon: null }, + Adult: { label: 'ADULT', variant: 'destructive', icon: Eye }, +}; + +const statusConfig: Record< + string, + { label: string; color: string; ringColor: string } +> = { + watching: { label: 'Watching', color: 'bg-blue-500', ringColor: 'ring-blue-500' }, + completed: { label: 'Completed', color: 'bg-green-500', ringColor: 'ring-green-500' }, + planned: { label: 'Planned', color: 'bg-gray-500', ringColor: 'ring-gray-500' }, + dropped: { label: 'Dropped', color: 'bg-red-500', ringColor: 'ring-red-500' }, + reading: { label: 'Reading', color: 'bg-amber-500', ringColor: 'ring-amber-500' }, + listening: { label: 'Listening', color: 'bg-purple-500', ringColor: 'ring-purple-500' }, + playing: { label: 'Playing', color: 'bg-indigo-500', ringColor: 'ring-indigo-500' }, + 'on-hold': { label: 'On Hold', color: 'bg-orange-500', ringColor: 'ring-orange-500' }, +}; + +export default function MediaCard({ + media, + onClick, + showBadge = true, + showFavorite = true, + isFavorite = false, + onFavoriteToggle, + variant = 'default' +}: MediaCardProps) { const statusColors = { watching: 'bg-blue-500', completed: 'bg-green-500', @@ -44,64 +107,446 @@ export default function MediaCard({ media, onClick }: MediaCardProps) { '1/1': 'aspect-[1/1]', }[getAspectRatio()]; - return ( - ) => { + e.stopPropagation(); + onFavoriteToggle?.(media); + }; + + const formatPlayCount = (count?: number) => { + if (!count) return null; + if (count === 1) return '1x played'; + if (count < 1000) return `${count}x played`; + return `${(count / 1000).toFixed(1)}k played`; + }; + + const renderRating = () => { + if (!media.rating) return null; + const stars = Math.floor(media.rating / 2); + return ( + + + +
+
+ {[...Array(5)].map((_, i) => ( + + ))} +
+ {media.rating.toFixed(1)} +
+
+ +

Rating: {media.rating}/10

+
+
+
+ ); + }; + + const renderCategoryBadge = () => { + if (!showBadge || !categoryInfo) return null; + return ( + + {CategoryIcon && } + {categoryInfo.label} + + ); + }; + + const renderFavoriteButton = () => { + if (!showFavorite) return null; + return ( + + + + + + ); + }; + + const renderStatusIndicator = () => { + if (!media.status) return null; + const status = statusConfig[media.status]; + return ( + + + +
+ + +

Status: {status.label}

+
+ + + ); + }; + + const renderCompactVariant = () => ( + onClick(media)} - whileHover={{ y: -8, scale: 1.02 }} - transition={{ duration: 0.3, ease: "easeOut" }} + onMouseEnter={() => setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + whileHover={{ y: -2 }} + transition={{ duration: 0.2, ease: 'easeOut' }} > -
- {media.title} - - {/* Gradient Overlay */} -
- - {/* Rating Badge */} - {media.rating && ( -
- - {media.rating} -
+ - )} - - {/* Glow Effect on Hover */} -
-
-
-

- {media.title} -

-
-

- {media.year} -

- {media.genres && media.genres.length > 0 && ( - <> - -

- {media.genres[0]} -

- - )} + > +
+ {media.title}
-
+
+ {renderCategoryBadge()} + {renderFavoriteButton()} + {renderStatusIndicator()} +
+

{media.title}

+

{media.year}

+
+ ); + + const renderMinimalVariant = () => ( + onClick(media)} + onMouseEnter={() => setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + whileHover={{ y: -2 }} + transition={{ duration: 0.2, ease: 'easeOut' }} + > + +
+ {media.title} +
+
+ {showFavorite && ( + + )} +
+

{media.title}

+
+ {media.year} + {media.rating && ( + <> + + {media.rating.toFixed(1)} + + )} +
+
+ + + ); + + const renderDefaultVariant = () => ( + + + + onClick(media)} + onMouseEnter={() => setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + whileHover={{ y: -4 }} + transition={{ duration: 0.2, ease: 'easeOut' }} + > + +
+ {media.title} +
+ +
+ + {renderCategoryBadge()} + {renderFavoriteButton()} + {renderStatusIndicator()} + +
+

+ {media.title} +

+ +
+
{renderRating()}
+
+ + + +
+ + + {media.year} + + {media.playCount && media.playCount > 0 && ( + <> + + + + {formatPlayCount(media.playCount)} + + + )} + {media.studios && media.studios.length > 0 && ( + <> + + + {media.studios[0]} + + + )} +
+ + {media.genres && media.genres.length > 0 && ( +
+ {media.genres.slice(0, 2).map((genre) => ( + + {genre} + + ))} + {media.genres.length > 2 && ( + + +{media.genres.length - 2} + + )} +
+ )} +
+ + {isHovered && ( + + )} + + + + +
+

{media.title}

+ {media.description && ( +

{media.description}

+ )} +
+ {media.category} + {media.year && ( + <> + + {media.year} + + )} + {media.rating && ( + <> + + {media.rating}/10 + + )} +
+
+
+ + + ); + + const renderHeroVariant = () => ( + onClick(media)} + onMouseEnter={() => setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + whileHover={{ y: -4 }} + transition={{ duration: 0.2, ease: 'easeOut' }} + > + +
+ {media.title} +
+ +
+ + {renderCategoryBadge()} + {renderFavoriteButton()} + {renderStatusIndicator()} + +
+ {media.rating && ( + + + {media.rating.toFixed(1)}/10 + + )} + +

+ {media.title} +

+ + {media.description && ( +

{media.description}

+ )} + +
+ + + {media.year} + + {media.playCount && media.playCount > 0 && ( + + + {formatPlayCount(media.playCount)} + + )} +
+ + {media.genres && media.genres.length > 0 && ( +
+ {media.genres.slice(0, 4).map((genre) => ( + + {genre} + + ))} +
+ )} +
+ + {isHovered && ( + + )} + + + ); + + const renderVariant = () => { + switch (variant) { + case 'compact': + return renderCompactVariant(); + case 'minimal': + return renderMinimalVariant(); + case 'hero': + return renderHeroVariant(); + default: + return renderDefaultVariant(); + } + }; + + return renderVariant(); } diff --git a/src/components/MediaListItem.tsx b/src/components/MediaListItem.tsx index 38262cc..cd6a983 100644 --- a/src/components/MediaListItem.tsx +++ b/src/components/MediaListItem.tsx @@ -1,101 +1,114 @@ -import { Media } from '@/types'; +import React from 'react'; +import { Media, MediaCategory } from '@/types'; import { cn } from '@/lib/utils'; import { motion } from 'motion/react'; -import { Star, Play, Bookmark } from 'lucide-react'; -import { Button } from '@/components/ui/button'; +import { Star, Heart, Gamepad2, Film, Tv, Eye } from 'lucide-react'; interface MediaListItemProps { key?: string; media: Media; onClick: (media: Media) => void; + isFavorite?: boolean; + onFavoriteToggle?: (media: Media) => void; } -export default function MediaListItem({ media, onClick }: MediaListItemProps) { - const statusColors = { - watching: 'bg-blue-500', - completed: 'bg-green-500', - planned: 'bg-gray-500', - dropped: 'bg-red-500', - reading: 'bg-amber-500', - listening: 'bg-purple-500', - playing: 'bg-indigo-500', - 'on-hold': 'bg-orange-500', - }; +const categoryConfig: Record = { + 'Anime': { label: 'ANIME', color: 'text-purple-300', bgColor: 'bg-purple-500/30', icon: null }, + 'Movies': { label: 'MOVIE', color: 'text-blue-300', bgColor: 'bg-blue-500/30', icon: Film }, + 'TV Series': { label: 'SERIES', color: 'text-green-300', bgColor: 'bg-green-500/30', icon: Tv }, + 'Music': { label: 'MUSIC', color: 'text-pink-300', bgColor: 'bg-pink-500/30', icon: null }, + 'Books': { label: 'BOOK', color: 'text-yellow-300', bgColor: 'bg-yellow-500/30', icon: null }, + 'Games': { label: 'GAME', color: 'text-indigo-300', bgColor: 'bg-indigo-500/30', icon: Gamepad2 }, + 'Consoles': { label: 'CONSOLE', color: 'text-orange-300', bgColor: 'bg-orange-500/30', icon: null }, + 'Adult': { label: 'ADULT', color: 'text-rose-300', bgColor: 'bg-rose-500/30', icon: Eye }, +}; - const getAspectRatio = () => { - if (media.aspectRatio) return media.aspectRatio; - switch (media.category) { - case 'Music': return '1/1'; - case 'Games': - case 'Adult': return '16/9'; - default: return '2/3'; - } - }; +export default function MediaListItem({ media, onClick, isFavorite = false, onFavoriteToggle }: MediaListItemProps) { + const categoryInfo = categoryConfig[media.category]; + const CategoryIcon = categoryInfo?.icon; - const aspectRatioClass = { - '2/3': 'w-24 h-32', - '16/9': 'w-48 h-27', // 16:9 ratio for w-48 is approx h-27 - '1/1': 'w-24 h-24', - }[getAspectRatio()]; + const handleFavoriteClick = (e: React.MouseEvent) => { + e.stopPropagation(); + onFavoriteToggle?.(media); + }; return ( onClick(media)} > -
- {media.title} -
- {media.status && ( -
- )} -
- -
-
-

- {media.title} -

- ({media.year}) + {/* TITLE Column: Poster + Title + Rating (like screenshot 2) */} +
+ {/* Poster Thumbnail */} +
+ {media.title}
-
-
- - {media.rating || 'N/A'} -
-
- {media.genres?.slice(0, 3).join(' • ') || 'Anime'} + {/* Title + Rating stacked */} +
+

+ {media.title} +

+
+ + + {media.rating?.toFixed(1) || '-'} +
- -

- {media.description || "No description available for this title."} -

-
- - + {/* TYPE Column */} +
+ + {CategoryIcon && } + {categoryInfo.label} + +
+ + {/* GENRE Column */} +
+ + {media.genres?.slice(0, 2).join(', ') || '-'} + +
+ + {/* YEAR Column */} +
+ {media.year} +
+ + {/* PLAYS Column */} +
+ {media.playCount || 0} +
+ + {/* FAVORITE Column (Heart) */} +
+
); diff --git a/src/components/MediaTable.tsx b/src/components/MediaTable.tsx new file mode 100644 index 0000000..d55d981 --- /dev/null +++ b/src/components/MediaTable.tsx @@ -0,0 +1,266 @@ +import React, { useState, useMemo } from 'react'; +import { Media, MediaCategory } from '@/types'; +import { cn } from '@/lib/utils'; +import { motion } from 'motion/react'; +import { + Star, + Heart, + Gamepad2, + Film, + Tv, + Eye, + Music, + BookOpen, + Monitor, + ArrowUpDown, + ArrowUp, + ArrowDown +} from 'lucide-react'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/table'; + +interface MediaTableProps { + mediaList: Media[]; + onMediaClick: (media: Media) => void; + onFavoriteToggle?: (media: Media) => void; + favoriteIds?: Set; +} + +type SortField = 'title' | 'category' | 'genre' | 'rating' | 'year' | 'plays'; +type SortDirection = 'asc' | 'desc'; + +const categoryConfig: Record = { + 'Anime': { label: 'ANIME', color: 'text-purple-300', bgColor: 'bg-purple-500/30', icon: null }, + 'Movies': { label: 'MOVIE', color: 'text-blue-300', bgColor: 'bg-blue-500/30', icon: Film }, + 'TV Series': { label: 'SERIES', color: 'text-green-300', bgColor: 'bg-green-500/30', icon: Tv }, + 'Music': { label: 'MUSIC', color: 'text-pink-300', bgColor: 'bg-pink-500/30', icon: Music }, + 'Books': { label: 'BOOK', color: 'text-yellow-300', bgColor: 'bg-yellow-500/30', icon: BookOpen }, + 'Games': { label: 'GAME', color: 'text-indigo-300', bgColor: 'bg-indigo-500/30', icon: Gamepad2 }, + 'Consoles': { label: 'CONSOLE', color: 'text-orange-300', bgColor: 'bg-orange-500/30', icon: Monitor }, + 'Adult': { label: 'ADULT', color: 'text-rose-300', bgColor: 'bg-rose-500/30', icon: Eye }, +}; + +export default function MediaTable({ + mediaList, + onMediaClick, + onFavoriteToggle, + favoriteIds = new Set() +}: MediaTableProps) { + const [sortField, setSortField] = useState('title'); + const [sortDirection, setSortDirection] = useState('asc'); + + const handleSort = (field: SortField) => { + if (sortField === field) { + setSortDirection(prev => prev === 'asc' ? 'desc' : 'asc'); + } else { + setSortField(field); + setSortDirection('asc'); + } + }; + + const sortedMedia = useMemo(() => { + const sorted = [...mediaList]; + sorted.sort((a, b) => { + let comparison = 0; + + switch (sortField) { + case 'title': + comparison = a.title.localeCompare(b.title); + break; + case 'category': + comparison = a.category.localeCompare(b.category); + break; + case 'genre': + const genreA = a.genres?.[0] || ''; + const genreB = b.genres?.[0] || ''; + comparison = genreA.localeCompare(genreB); + break; + case 'rating': + comparison = (b.rating || 0) - (a.rating || 0); + break; + case 'year': + comparison = b.year.localeCompare(a.year); + break; + case 'plays': + comparison = (b.playCount || 0) - (a.playCount || 0); + break; + } + + return sortDirection === 'asc' ? comparison : -comparison; + }); + return sorted; + }, [mediaList, sortField, sortDirection]); + + const SortIcon = ({ field }: { field: SortField }) => { + if (sortField !== field) { + return ; + } + return sortDirection === 'asc' + ? + : ; + }; + + const handleFavoriteClick = (e: React.MouseEvent, media: Media) => { + e.stopPropagation(); + onFavoriteToggle?.(media); + }; + + return ( +
+ + + + handleSort('title')} + > +
+ Title +
+
+ handleSort('category')} + > +
+ Type +
+
+ handleSort('genre')} + > +
+ Genre +
+
+ handleSort('rating')} + > +
+ Rating +
+
+ handleSort('year')} + > +
+ Year +
+
+ handleSort('plays')} + > +
+ Plays +
+
+ +
+
+ + {sortedMedia.map((media) => { + const categoryInfo = categoryConfig[media.category]; + const CategoryIcon = categoryInfo?.icon; + const isFavorite = favoriteIds.has(media.id); + + return ( + onMediaClick(media)} + > + {/* Title Cell with Poster */} + +
+
+ {media.title} +
+
+
+ {media.title} +
+
+
+
+ + {/* Type Badge */} + + + {CategoryIcon && } + {categoryInfo.label} + + + + {/* Genre */} + + + {media.genres?.join(', ') || '-'} + + + + {/* Rating */} + +
+ + + {media.rating?.toFixed(1) || '-'} + +
+
+ + {/* Year */} + + {media.year} + + + {/* Plays */} + + {media.playCount || 0} + + + {/* Favorite */} + + + +
+ ); + })} +
+
+
+ ); +} diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 964c84e..0f7a51f 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -1,86 +1,58 @@ import { useState } from 'react'; -import { NavLink, useLocation } from 'react-router-dom'; +import { NavLink, useLocation, useNavigate } from 'react-router-dom'; import { LayoutDashboard, - BookOpen, - Film, - Tv, - Gamepad2, + Library, Users, - Tag, - Music as MusicIcon, - Monitor, - Eye, - Dumbbell, - Calendar, FolderKanban, + Database, Settings, Sun, LogOut, - ChevronDown, - ChevronRight, Menu, X, - Plus + Plus, + Film, + Tv, + Gamepad2, + Heart, + Eye, + Flame, + Clock, + ChevronRight } from 'lucide-react'; import { cn } from '@/lib/utils'; import { useTheme } from '@/contexts/ThemeContext'; import { MediaCategory } from '@/types'; -import { CATEGORY_PATHS } from '@/constants'; interface SidebarProps { enabledCategories: MediaCategory[]; onToggleCategory: (category: MediaCategory) => void; pageTitle?: string; + mediaCounts?: { + all: number; + movies: number; + series: number; + games: number; + adult: number; + favorites: number; + }; + activeFilter?: string; + onFilterChange?: (filter: string) => void; } -export default function Sidebar({ enabledCategories, onToggleCategory, pageTitle }: SidebarProps) { - const [isMediaExpanded, setIsMediaExpanded] = useState(true); +export default function Sidebar({ + enabledCategories, + onToggleCategory, + pageTitle, + mediaCounts = { all: 24, movies: 8, series: 6, games: 6, adult: 4, favorites: 11 }, + activeFilter = 'all', + onFilterChange +}: SidebarProps) { const [isMobileOpen, setIsMobileOpen] = useState(false); const { theme, setTheme } = useTheme(); const location = useLocation(); - - - const categoryIcons: Record = { - 'Audio Book': , - 'Book': , - 'Movie': , - 'Music': , - 'Show': , - 'Video Game': , - 'Consoles': , - 'Adult': , - 'Groups': , - 'People': , - 'Genres': - }; - - const navItems = [ - { icon: , label: 'Dashboard', path: '/' }, - { - icon: , - label: 'Media', - hasSubmenu: true, - submenu: [ - ...(enabledCategories.includes('Anime') ? [{ label: 'Anime', path: '/anime' }] : []), - ...(enabledCategories.includes('Books') ? [{ label: 'Book', path: '/books' }] : []), - ...(enabledCategories.includes('Movies') ? [{ label: 'Movie', path: '/movies' }] : []), - ...(enabledCategories.includes('Music') ? [{ label: 'Music', path: '/music' }] : []), - ...(enabledCategories.includes('TV Series') ? [{ label: 'Show', path: '/tv-series' }] : []), - ...(enabledCategories.includes('Games') ? [{ label: 'Video Game', path: '/games' }] : []), - ...(enabledCategories.includes('Consoles') ? [{ label: 'Consoles', path: '/consoles' }] : []), - ...(enabledCategories.includes('Adult') ? [{ label: 'Adult', path: '/adult' }] : []), - { label: 'People', path: '/cast' }, - { label: 'Genres', path: '/browse' } - ].filter(Boolean) - }, - //{ icon: , label: 'Fitness', path: '/fitness' }, - //{ icon: , label: 'Calendar', path: '/calendar' }, - //{ icon: , label: 'Collections', path: '/collections' }, - { icon: , label: 'Add Media', path: '/add' }, - { icon: , label: 'Settings', path: '/settings' }, - { icon: , label: 'Import', path: '/import' } - ]; + const navigate = useNavigate(); const toggleTheme = () => { setTheme(theme === 'dark' ? 'light' : 'dark'); @@ -90,6 +62,36 @@ export default function Sidebar({ enabledCategories, onToggleCategory, pageTitle console.log('Logout clicked'); }; + const handleFilterClick = (filter: string) => { + onFilterChange?.(filter); + if (filter === 'all') { + navigate('/browse'); + } else if (filter === 'movies') { + navigate('/movies'); + } else if (filter === 'series') { + navigate('/tv-series'); + } else if (filter === 'games') { + navigate('/games'); + } else if (filter === 'adult') { + navigate('/adult'); + } else if (filter === 'favorites') { + navigate('/browse?favorites=true'); + } + }; + + const handleQuickFilter = (filter: string) => { + if (filter === 'most-played') { + navigate('/browse?sort=plays'); + } else if (filter === 'recently-added') { + navigate('/browse?sort=recent'); + } + }; + + const isActive = (path: string) => { + if (path === '/') return location.pathname === '/'; + return location.pathname.startsWith(path); + }; + return ( <> {/* Mobile menu button */} @@ -111,100 +113,299 @@ export default function Sidebar({ enabledCategories, onToggleCategory, pageTitle {/* Sidebar */} diff --git a/src/components/details/tabs/CastTab.tsx b/src/components/details/tabs/CastTab.tsx index cb7fa6c..2716bc0 100644 --- a/src/components/details/tabs/CastTab.tsx +++ b/src/components/details/tabs/CastTab.tsx @@ -1,5 +1,11 @@ import { Staff } from '@/types'; import { useState } from 'react'; +import { Card, CardContent } from '@/components/ui/card'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { Users, ChevronDown, ChevronUp, User } from 'lucide-react'; +import { motion } from 'motion/react'; interface CastTabProps { staff: Staff[]; @@ -7,40 +13,93 @@ interface CastTabProps { } export default function CastTab({ staff, onPersonClick }: CastTabProps) { - const [castLimit, setCastLimit] = useState(6); - const [showAllCast, setShowAllCast] = useState(false); + const [showAll, setShowAll] = useState(false); + const displayLimit = 8; - const displayedCast = showAllCast ? staff : (staff?.slice(0, castLimit) || []); - const hasMoreCast = (staff?.length || 0) > castLimit; + const displayedCast = showAll ? staff : staff.slice(0, displayLimit); + const hasMore = staff.length > displayLimit; return ( -
-

Acting

-
- {displayedCast.map(person => ( -
onPersonClick(person)} - > -
- {person.name} -
-

{person.name}

-

{person.characterName || person.role}

+
+ {/* Header */} +
+
+
+
- ))} - {hasMoreCast && ( - - )} +

+ Cast & Crew +

+ + {staff.length} + +
-
+ + {/* Cast Grid */} +
+ {displayedCast.map((person, index) => ( + + onPersonClick(person)} + > + +
+ + + + + + +
+

+ {person.name} +

+

+ {person.characterName || person.role} +

+
+
+
+
+
+ ))} +
+ + {/* Show More/Less Button */} + {hasMore && ( +
+ +
+ )} +
); } diff --git a/src/components/details/tabs/OverviewTab.tsx b/src/components/details/tabs/OverviewTab.tsx index 40c404f..b1a31ae 100644 --- a/src/components/details/tabs/OverviewTab.tsx +++ b/src/components/details/tabs/OverviewTab.tsx @@ -1,5 +1,7 @@ import { Media } from '@/types'; import { Badge } from '@/components/ui/badge'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { BookOpen, Tag } from 'lucide-react'; interface OverviewTabProps { media: Media; @@ -7,21 +9,84 @@ interface OverviewTabProps { export default function OverviewTab({ media }: OverviewTabProps) { return ( - <> - {/* Genre Tags */} -
- {media.genres?.map(genre => ( - - {genre} - - ))} -
+
+ {/* Genres */} + {media.genres && media.genres.length > 0 && ( + + + +
+ +
+ Genres +
+
+ +
+ {media.genres.map(genre => ( + + {genre} + + ))} +
+
+
+ )} + + {/* Tags */} + {media.tags && media.tags.length > 0 && ( + + + +
+ +
+ Tags +
+
+ +
+ {media.tags.map(tag => ( + + {tag} + + ))} +
+
+
+ )} {/* Description */} -
- + + + +
+ +
+ Synopsis +
+
+ + {media.description ? ( +
+ ) : ( +

+ No description available. +

+ )} + + +
); } diff --git a/src/components/details/tabs/SeasonsTab.tsx b/src/components/details/tabs/SeasonsTab.tsx index cdaaac0..dbc6844 100644 --- a/src/components/details/tabs/SeasonsTab.tsx +++ b/src/components/details/tabs/SeasonsTab.tsx @@ -1,10 +1,11 @@ import { Episode } from '@/types'; import { useState, useMemo, useEffect } from 'react'; -import { Search, MoreHorizontal, ListFilter, ChevronDown } from 'lucide-react'; +import { Search, Play, Clock, Calendar, ChevronDown, Tv } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Input } from '@/components/ui/input'; -import { Separator } from '@/components/ui/separator'; +import { Card, CardContent, CardHeader } from '@/components/ui/card'; +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; interface SeasonsTabProps { episodes: Episode[]; @@ -51,82 +52,133 @@ export default function SeasonsTab({ episodes }: SeasonsTabProps) { }; return ( -
-
-
-
- {episodes.length} Episode{episodes.length !== 1 ? 's' : ''} -
-
- {Object.keys(episodesBySeason).length} Season{Object.keys(episodesBySeason).length !== 1 ? 's' : ''} -
-
+
+ {/* Header */} +
-
- - +
+
- - +

+ Episodes +

+ + {episodes.length} + + + {Object.keys(episodesBySeason).length} Season{Object.keys(episodesBySeason).length !== 1 ? 's' : ''} + +
+
+ +
-
+ {/* Seasons */} +
{Object.keys(episodesBySeason) .map(Number) .sort((a, b) => a - b) .map(season => ( -
- - {expandedSeasons.has(season) && ( -
- {episodesBySeason[season].map(episode => ( -
-
-
- {episode.title} -
-
-
-
-

- E{episode.episode_number} • {episode.title} -

- {episode.air_date} • {episode.duration}m -
-

- {episode.description} -

-
+ toggleSeason(season)} + > + + + +
+
+

Season {season}

+ + {episodesBySeason[season].length} Episode{episodesBySeason[season].length !== 1 ? 's' : ''} +
- +
- ))} -
- )} -
+ + + + +
+ {episodesBySeason[season].map((episode, index) => ( +
+
+ {/* Thumbnail */} +
+ {episode.thumbnail ? ( + {episode.title} + ) : ( +
+ +
+ )} +
+
+ +
+
+
+ + {/* Info */} +
+
+
+

+ Episode {episode.episode_number} +

+

+ {episode.title} +

+ {episode.description && ( +

+ {episode.description} +

+ )} +
+
+ {episode.duration > 0 && ( +
+ + {episode.duration}m +
+ )} + {episode.air_date && ( +
+ + {episode.air_date} +
+ )} +
+
+
+
+
+ ))} +
+
+
+ + ))}
-
+
); } diff --git a/src/components/details/tabs/SeriesTab.tsx b/src/components/details/tabs/SeriesTab.tsx index 890163c..296b7b4 100644 --- a/src/components/details/tabs/SeriesTab.tsx +++ b/src/components/details/tabs/SeriesTab.tsx @@ -1,5 +1,7 @@ import { Media } from '@/types'; -import MediaCard from '../../MediaCard'; +import { Badge } from '@/components/ui/badge'; +import { Card, CardContent } from '@/components/ui/card'; +import { Gamepad2, Layers } from 'lucide-react'; interface SeriesTabProps { media: Media; @@ -20,35 +22,85 @@ export default function SeriesTab({ media, allMedia, onMediaClick }: SeriesTabPr if (seriesGames.length === 0) { return ( -
-

Series

-

No other games found in the same series.

-
+
+
+
+ +
+

+ Series +

+
+ + + +

+ No other games found in the same series. +

+
+
+
); } return ( -
-

Series

-
- {media.series?.map((s) => ( - - {s} - - ))} +
+ {/* Header */} +
+
+
+ +
+

+ Series +

+ + {seriesGames.length} + +
+
+ {media.series?.map((s) => ( + + {s} + + ))} +
-
+ + {/* Games Grid */} +
{seriesGames.map((game) => ( - onMediaClick(game)} - /> + > +
+ {game.title} +
+ +

+ {game.title} +

+

+ {game.year} +

+
+ ))}
-
+
); } diff --git a/src/components/details/tabs/TracksTab.tsx b/src/components/details/tabs/TracksTab.tsx index f804a70..db12414 100644 --- a/src/components/details/tabs/TracksTab.tsx +++ b/src/components/details/tabs/TracksTab.tsx @@ -1,49 +1,84 @@ import { Track } from '@/types'; -import { Search, MoreHorizontal, ListFilter } from 'lucide-react'; -import { Button } from '@/components/ui/button'; +import { Search, Play, Disc, Clock } from 'lucide-react'; +import { Badge } from '@/components/ui/badge'; import { Input } from '@/components/ui/input'; +import { Card, CardContent } from '@/components/ui/card'; interface TracksTabProps { tracks: Track[]; } export default function TracksTab({ tracks }: TracksTabProps) { + const formatDuration = (seconds: number | null) => { + if (!seconds) return '—'; + const mins = Math.floor(seconds / 60); + const secs = seconds % 60; + return `${mins}:${secs.toString().padStart(2, '0')}`; + }; + return ( -
-
-
-
- {tracks.length} Track{tracks.length !== 1 ? 's' : ''} -
-
+
+ {/* Header */} +
-
- - +
+
- - +

+ Tracks +

+ + {tracks.length} + +
+
+ +
-
- {tracks.map(track => ( -
- {track.track_number} -
-

- {track.title} -

-

{track.artist}

-
- {track.duration ? `${track.duration}m` : '-'} + {/* Tracks List */} + + +
+ {tracks.map((track, index) => ( +
+ {/* Track Number / Play Button */} +
+ + {track.track_number} + +
+ +
+
+ + {/* Track Info */} +
+

+ {track.title} +

+

+ {track.artist} +

+
+ + {/* Duration */} +
+ + {formatDuration(track.duration)} +
+
+ ))}
- ))} -
-
+ + +
); } diff --git a/src/components/filters/MediaFilters.tsx b/src/components/filters/MediaFilters.tsx new file mode 100644 index 0000000..d5e7989 --- /dev/null +++ b/src/components/filters/MediaFilters.tsx @@ -0,0 +1,375 @@ +import React from 'react'; +import { Media, MediaCategory } from '@/types'; +import { cn } from '@/lib/utils'; +import { + Star, + Building2, + Monitor, + Users, + FolderTree, + Database, + X +} from 'lucide-react'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, + DropdownMenuSeparator, + DropdownMenuGroup +} from '@/components/ui/dropdown-menu'; +import { Badge } from '@/components/ui/badge'; + +interface FilterOption { + label: string; + value: string; + count?: number; +} + +interface MediaFiltersProps { + mediaList: Media[]; + activeCategory: MediaCategory; + selectedGenre: string | null; + selectedStudio: string | null; + selectedPlatform: string | null; + selectedDeveloper: string | null; + selectedCategory: string | null; + selectedSource: string | null; + onGenreChange: (value: string | null) => void; + onStudioChange: (value: string | null) => void; + onPlatformChange: (value: string | null) => void; + onDeveloperChange: (value: string | null) => void; + onCategoryChange: (value: string | null) => void; + onSourceChange: (value: string | null) => void; + onClearAll: () => void; +} + +export default function MediaFilters({ + mediaList, + activeCategory, + selectedGenre, + selectedStudio, + selectedPlatform, + selectedDeveloper, + selectedCategory, + selectedSource, + onGenreChange, + onStudioChange, + onPlatformChange, + onDeveloperChange, + onCategoryChange, + onSourceChange, + onClearAll +}: MediaFiltersProps) { + // Extract unique filter values + const genres = React.useMemo(() => + Array.from(new Set(mediaList.flatMap(m => m.genres || []))).sort(), + [mediaList] + ); + + const studios = React.useMemo(() => + Array.from(new Set(mediaList.flatMap(m => m.studios || []))).sort(), + [mediaList] + ); + + const platforms = React.useMemo(() => + Array.from(new Set(mediaList.flatMap(m => m.platforms || []))).sort(), + [mediaList] + ); + + const developers = React.useMemo(() => + Array.from(new Set(mediaList.flatMap(m => m.developers || []))).sort(), + [mediaList] + ); + + const categories = React.useMemo(() => + Array.from(new Set(mediaList.flatMap(m => m.series || []))).sort(), + [mediaList] + ); + + const sources = React.useMemo(() => + Array.from(new Set(mediaList.map(m => m.source).filter(Boolean))).sort() as string[], + [mediaList] + ); + + const hasActiveFilters = selectedGenre || selectedStudio || selectedPlatform || + selectedDeveloper || selectedCategory || selectedSource; + + // Get available filters based on category + const getAvailableFilters = () => { + const baseFilters = ['genre']; + + switch (activeCategory) { + case 'Movies': + case 'TV Series': + return [...baseFilters, 'studio']; + case 'Games': + return [...baseFilters, 'platform', 'developer', 'category']; + case 'Adult': + return [...baseFilters, 'studio']; + default: + return baseFilters; + } + }; + + const availableFilters = getAvailableFilters(); + + return ( +
+ {/* Genre Filter - Always available */} + + + + {selectedGenre || 'Genres'} + + + + Filter by Genre + + + onGenreChange(null)}> + All Genres + + {genres.map(genre => ( + onGenreChange(genre)}> + {genre} + + ))} + + + + {/* Studio Filter - For Movies/Series/Adult */} + {availableFilters.includes('studio') && studios.length > 0 && ( + + + + {selectedStudio || 'Studios'} + + + + Filter by Studio + + + onStudioChange(null)}> + All Studios + + {studios.map(studio => ( + onStudioChange(studio)}> + {studio} + + ))} + + + )} + + {/* Platform Filter - For Games */} + {availableFilters.includes('platform') && platforms.length > 0 && ( + + + + {selectedPlatform || 'Platforms'} + + + + Filter by Platform + + + onPlatformChange(null)}> + All Platforms + + {platforms.map(platform => ( + onPlatformChange(platform)}> + {platform} + + ))} + + + )} + + {/* Developer Filter - For Games */} + {availableFilters.includes('developer') && developers.length > 0 && ( + + + + {selectedDeveloper || 'Developers'} + + + + Filter by Developer + + + onDeveloperChange(null)}> + All Developers + + {developers.map(developer => ( + onDeveloperChange(developer)}> + {developer} + + ))} + + + )} + + {/* Category/Series Filter - For Games */} + {availableFilters.includes('category') && categories.length > 0 && ( + + + + {selectedCategory || 'Series'} + + + + Filter by Series + + + onCategoryChange(null)}> + All Series + + {categories.map(category => ( + onCategoryChange(category)}> + {category} + + ))} + + + )} + + {/* Source Filter */} + {sources.length > 0 && ( + + + + {selectedSource || 'Source'} + + + + Filter by Source + + + onSourceChange(null)}> + All Sources + + {sources.map(source => ( + onSourceChange(source)}> + {source} + + ))} + + + )} + + {/* Clear All Filters */} + {hasActiveFilters && ( + + )} + + {/* Active Filter Badges */} + {hasActiveFilters && ( +
+ {selectedGenre && ( + onGenreChange(null)} + > + {selectedGenre} + + )} + {selectedStudio && ( + onStudioChange(null)} + > + {selectedStudio} + + )} + {selectedPlatform && ( + onPlatformChange(null)} + > + {selectedPlatform} + + )} + {selectedDeveloper && ( + onDeveloperChange(null)} + > + {selectedDeveloper} + + )} + {selectedCategory && ( + onCategoryChange(null)} + > + {selectedCategory} + + )} + {selectedSource && ( + onSourceChange(null)} + > + {selectedSource} + + )} +
+ )} +
+ ); +} diff --git a/src/components/sidebar/AppSidebar.tsx b/src/components/sidebar/AppSidebar.tsx new file mode 100644 index 0000000..c21f056 --- /dev/null +++ b/src/components/sidebar/AppSidebar.tsx @@ -0,0 +1,331 @@ +import { useLocation, useNavigate, NavLink } from 'react-router-dom'; +import { cn } from '@/lib/utils'; +import { useTheme } from '@/contexts/ThemeContext'; +import { MediaCategory } from '@/types'; +import { + LayoutDashboard, + Library, + Users, + FolderKanban, + Database, + Settings, + Sun, + Moon, + LogOut, + Film, + Tv, + Gamepad2, + Heart, + Eye, + Flame, + Clock, + User, + Music, + BookOpen, + Monitor, + Download, +} from 'lucide-react'; + +// shadcn/ui sidebar components +import { + Sidebar, + SidebarContent, + SidebarFooter, + SidebarGroup, + SidebarGroupContent, + SidebarGroupLabel, + SidebarHeader, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, + SidebarRail, + useSidebar, +} from '@/components/ui/sidebar'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { Button } from '@/components/ui/button'; + +interface AppSidebarProps { + enabledCategories: MediaCategory[]; + onToggleCategory: (category: MediaCategory) => void; + pageTitle?: string; + mediaCounts?: Record; + activeFilter?: string; + onFilterChange?: (filter: string) => void; + user?: { + name: string; + email: string; + avatar?: string; + }; +} + +export default function AppSidebar({ + enabledCategories, + pageTitle = 'MediaVault', + mediaCounts = {}, + activeFilter, + onFilterChange, + user, +}: AppSidebarProps) { + const { theme, setTheme } = useTheme(); + const location = useLocation(); + const navigate = useNavigate(); + + const toggleTheme = () => { + setTheme(theme === 'dark' ? 'light' : 'dark'); + }; + + const handleLogout = () => { + console.log('Logout clicked'); + }; + + // Category config with icons, colors and routes + const categoryConfig: Record = { + 'Anime': { icon: Tv, label: 'Anime', route: '/anime', color: 'text-purple-400' }, + 'Movies': { icon: Film, label: 'Movies', route: '/movies', color: 'text-blue-400' }, + 'TV Series': { icon: Tv, label: 'Series', route: '/tv-series', color: 'text-green-400' }, + 'Music': { icon: Music, label: 'Music', route: '/music', color: 'text-pink-400' }, + 'Books': { icon: BookOpen, label: 'Books', route: '/books', color: 'text-yellow-400' }, + 'Adult': { icon: Eye, label: 'Adult', route: '/adult', color: 'text-rose-400' }, + 'Consoles': { icon: Monitor, label: 'Consoles', route: '/consoles', color: 'text-orange-400' }, + 'Games': { icon: Gamepad2, label: 'Games', route: '/games', color: 'text-indigo-400' }, + }; + + const handleFilterClick = (filter: string) => { + onFilterChange?.(filter); + if (filter === 'favorites') { + navigate('/browse?favorites=true'); + return; + } + // Find route for category + const config = categoryConfig[filter as MediaCategory]; + if (config) { + navigate(config.route); + } + }; + + const handleQuickFilter = (filter: string) => { + const routes: Record = { + 'most-played': '/browse?sort=plays', + 'recently-added': '/browse?sort=recent', + }; + navigate(routes[filter] || '/browse'); + }; + + const isActive = (path: string) => { + if (path === '/') return location.pathname === '/'; + return location.pathname.startsWith(path); + }; + + // Build category routes for Library isActive check + const categoryRoutes = enabledCategories.map(cat => categoryConfig[cat].route); + + const mainNavItems = [ + { to: '/', icon: LayoutDashboard, label: 'Dashboard', isActive: isActive('/') }, + { to: '/browse', icon: Library, label: 'Library', isActive: isActive('/browse') || categoryRoutes.some(route => isActive(route)) }, + { to: '/cast', icon: Users, label: 'Actors', isActive: isActive('/cast') }, + //{ to: '/collections', icon: FolderKanban, label: 'Collections', isActive: isActive('/collections') }, + { to: '/import', icon: Download, label: 'Import', isActive: isActive('/import') }, + //{ to: '/sources', icon: Database, label: 'Sources', isActive: isActive('/sources') }, + { to: '/settings', icon: Settings, label: 'Settings', isActive: isActive('/settings') }, + ]; + + // Build media type filters from enabled categories + const mediaTypeFilters = enabledCategories.map(cat => { + const config = categoryConfig[cat]; + return { + id: cat.toLowerCase().replace(/\s+/g, '-'), + icon: config.icon, + label: config.label, + count: mediaCounts[cat] || 0, + color: config.color, + category: cat, + }; + }); + + return ( + + + +
+ +
+ {pageTitle} +
+
+ + + {/* Main Navigation */} + + + Navigation + + + + {mainNavItems.map((item) => ( + + + + + {item.label} + + + + ))} + + + + + {/* Media Type Filters */} + + + Media Type + + + + {mediaTypeFilters.map((filter) => { + const isFilterActive = activeFilter === filter.id; + return ( + + handleFilterClick(filter.category)} + isActive={isFilterActive} + className={cn( + 'transition-colors w-full justify-start gap-2', + isFilterActive + ? 'bg-[#e8466c]/10 text-[#e8466c] hover:bg-[#e8466c]/20' + : 'text-gray-400 hover:bg-white/5 hover:text-white' + )} + > + + {filter.label} + + {filter.count} + + + + ); + })} + + + + + {/* Quick Filters */} + + + Quick Filters + + + + + handleQuickFilter('most-played')} + className="text-gray-400 hover:bg-white/5 hover:text-white w-full justify-start gap-2" + > + + Most Played + + + + handleQuickFilter('recently-added')} + className="text-gray-400 hover:bg-white/5 hover:text-white w-full justify-start gap-2" + > + + Recently Added + + + + + + + + + {/* Theme Toggle */} + + + {/* User Profile */} + {user ? ( +
+ + + + {user.name + .split(' ') + .map((n) => n[0]) + .join('') + .toUpperCase()} + + +
+

{user.name}

+

{user.email}

+
+
+ ) : ( +
+ + + + + +
+

Guest

+

Not logged in

+
+
+ )} + + {/* Logout */} + +
+ + +
+ ); +} diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx new file mode 100644 index 0000000..5db9310 --- /dev/null +++ b/src/components/ui/avatar.tsx @@ -0,0 +1,109 @@ +"use client" + +import * as React from "react" +import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar" + +import { cn } from "@/lib/utils" + +function Avatar({ + className, + size = "default", + ...props +}: AvatarPrimitive.Root.Props & { + size?: "default" | "sm" | "lg" +}) { + return ( + + ) +} + +function AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) { + return ( + + ) +} + +function AvatarFallback({ + className, + ...props +}: AvatarPrimitive.Fallback.Props) { + return ( + + ) +} + +function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) { + return ( + svg]:hidden", + "group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2", + "group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2", + className + )} + {...props} + /> + ) +} + +function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AvatarGroupCount({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3", + className + )} + {...props} + /> + ) +} + +export { + Avatar, + AvatarImage, + AvatarFallback, + AvatarGroup, + AvatarGroupCount, + AvatarBadge, +} diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx new file mode 100644 index 0000000..40cac5f --- /dev/null +++ b/src/components/ui/card.tsx @@ -0,0 +1,103 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Card({ + className, + size = "default", + ...props +}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) { + return ( +
img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl", + className + )} + {...props} + /> + ) +} + +function CardHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardDescription({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardAction({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardAction, + CardDescription, + CardContent, +} diff --git a/src/components/ui/collapsible.tsx b/src/components/ui/collapsible.tsx new file mode 100644 index 0000000..4b242f7 --- /dev/null +++ b/src/components/ui/collapsible.tsx @@ -0,0 +1,19 @@ +import { Collapsible as CollapsiblePrimitive } from "@base-ui/react/collapsible" + +function Collapsible({ ...props }: CollapsiblePrimitive.Root.Props) { + return +} + +function CollapsibleTrigger({ ...props }: CollapsiblePrimitive.Trigger.Props) { + return ( + + ) +} + +function CollapsibleContent({ ...props }: CollapsiblePrimitive.Panel.Props) { + return ( + + ) +} + +export { Collapsible, CollapsibleTrigger, CollapsibleContent } diff --git a/src/components/ui/progress.tsx b/src/components/ui/progress.tsx new file mode 100644 index 0000000..ebefd5d --- /dev/null +++ b/src/components/ui/progress.tsx @@ -0,0 +1,36 @@ +import * as React from "react" +import { cn } from "@/lib/utils" + +interface ProgressProps extends React.HTMLAttributes { + value?: number + max?: number +} + +const Progress = React.forwardRef( + ({ className, value = 0, max = 100, ...props }, ref) => { + const percentage = Math.min(100, Math.max(0, (value / max) * 100)) + + return ( +
+
+
+ ) + } +) +Progress.displayName = "Progress" + +export { Progress } diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx new file mode 100644 index 0000000..56a7734 --- /dev/null +++ b/src/components/ui/select.tsx @@ -0,0 +1,199 @@ +import * as React from "react" +import { Select as SelectPrimitive } from "@base-ui/react/select" + +import { cn } from "@/lib/utils" +import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react" + +const Select = SelectPrimitive.Root + +function SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) { + return ( + + ) +} + +function SelectValue({ className, ...props }: SelectPrimitive.Value.Props) { + return ( + + ) +} + +function SelectTrigger({ + className, + size = "default", + children, + ...props +}: SelectPrimitive.Trigger.Props & { + size?: "sm" | "default" +}) { + return ( + + {children} + + } + /> + + ) +} + +function SelectContent({ + className, + children, + side = "bottom", + sideOffset = 4, + align = "center", + alignOffset = 0, + alignItemWithTrigger = true, + ...props +}: SelectPrimitive.Popup.Props & + Pick< + SelectPrimitive.Positioner.Props, + "align" | "alignOffset" | "side" | "sideOffset" | "alignItemWithTrigger" + >) { + return ( + + + + + {children} + + + + + ) +} + +function SelectLabel({ + className, + ...props +}: SelectPrimitive.GroupLabel.Props) { + return ( + + ) +} + +function SelectItem({ + className, + children, + ...props +}: SelectPrimitive.Item.Props) { + return ( + + + {children} + + + } + > + + + + ) +} + +function SelectSeparator({ + className, + ...props +}: SelectPrimitive.Separator.Props) { + return ( + + ) +} + +function SelectScrollUpButton({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function SelectScrollDownButton({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +export { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectScrollDownButton, + SelectScrollUpButton, + SelectSeparator, + SelectTrigger, + SelectValue, +} diff --git a/src/components/ui/sheet.tsx b/src/components/ui/sheet.tsx new file mode 100644 index 0000000..13281f5 --- /dev/null +++ b/src/components/ui/sheet.tsx @@ -0,0 +1,136 @@ +import * as React from "react" +import { Dialog as SheetPrimitive } from "@base-ui/react/dialog" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { XIcon } from "lucide-react" + +function Sheet({ ...props }: SheetPrimitive.Root.Props) { + return +} + +function SheetTrigger({ ...props }: SheetPrimitive.Trigger.Props) { + return +} + +function SheetClose({ ...props }: SheetPrimitive.Close.Props) { + return +} + +function SheetPortal({ ...props }: SheetPrimitive.Portal.Props) { + return +} + +function SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) { + return ( + + ) +} + +function SheetContent({ + className, + children, + side = "right", + showCloseButton = true, + ...props +}: SheetPrimitive.Popup.Props & { + side?: "top" | "right" | "bottom" | "left" + showCloseButton?: boolean +}) { + return ( + + + + {children} + {showCloseButton && ( + + } + > + + Close + + )} + + + ) +} + +function SheetHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function SheetFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) { + return ( + + ) +} + +function SheetDescription({ + className, + ...props +}: SheetPrimitive.Description.Props) { + return ( + + ) +} + +export { + Sheet, + SheetTrigger, + SheetClose, + SheetContent, + SheetHeader, + SheetFooter, + SheetTitle, + SheetDescription, +} diff --git a/src/components/ui/sidebar.tsx b/src/components/ui/sidebar.tsx new file mode 100644 index 0000000..e2a75a6 --- /dev/null +++ b/src/components/ui/sidebar.tsx @@ -0,0 +1,723 @@ +"use client" + +import * as React from "react" +import { mergeProps } from "@base-ui/react/merge-props" +import { useRender } from "@base-ui/react/use-render" +import { cva, type VariantProps } from "class-variance-authority" + +import { useIsMobile } from "@/hooks/use-mobile" +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Separator } from "@/components/ui/separator" +import { + Sheet, + SheetContent, + SheetDescription, + SheetHeader, + SheetTitle, +} from "@/components/ui/sheet" +import { Skeleton } from "@/components/ui/skeleton" +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip" +import { PanelLeftIcon } from "lucide-react" + +const SIDEBAR_COOKIE_NAME = "sidebar_state" +const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7 +const SIDEBAR_WIDTH = "16rem" +const SIDEBAR_WIDTH_MOBILE = "18rem" +const SIDEBAR_WIDTH_ICON = "3rem" +const SIDEBAR_KEYBOARD_SHORTCUT = "b" + +type SidebarContextProps = { + state: "expanded" | "collapsed" + open: boolean + setOpen: (open: boolean) => void + openMobile: boolean + setOpenMobile: (open: boolean) => void + isMobile: boolean + toggleSidebar: () => void +} + +const SidebarContext = React.createContext(null) + +function useSidebar() { + const context = React.useContext(SidebarContext) + if (!context) { + throw new Error("useSidebar must be used within a SidebarProvider.") + } + + return context +} + +function SidebarProvider({ + defaultOpen = true, + open: openProp, + onOpenChange: setOpenProp, + className, + style, + children, + ...props +}: React.ComponentProps<"div"> & { + defaultOpen?: boolean + open?: boolean + onOpenChange?: (open: boolean) => void +}) { + const isMobile = useIsMobile() + const [openMobile, setOpenMobile] = React.useState(false) + + // This is the internal state of the sidebar. + // We use openProp and setOpenProp for control from outside the component. + const [_open, _setOpen] = React.useState(defaultOpen) + const open = openProp ?? _open + const setOpen = React.useCallback( + (value: boolean | ((value: boolean) => boolean)) => { + const openState = typeof value === "function" ? value(open) : value + if (setOpenProp) { + setOpenProp(openState) + } else { + _setOpen(openState) + } + + // This sets the cookie to keep the sidebar state. + document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}` + }, + [setOpenProp, open] + ) + + // Helper to toggle the sidebar. + const toggleSidebar = React.useCallback(() => { + return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open) + }, [isMobile, setOpen, setOpenMobile]) + + // Adds a keyboard shortcut to toggle the sidebar. + React.useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if ( + event.key === SIDEBAR_KEYBOARD_SHORTCUT && + (event.metaKey || event.ctrlKey) + ) { + event.preventDefault() + toggleSidebar() + } + } + + window.addEventListener("keydown", handleKeyDown) + return () => window.removeEventListener("keydown", handleKeyDown) + }, [toggleSidebar]) + + // We add a state so that we can do data-state="expanded" or "collapsed". + // This makes it easier to style the sidebar with Tailwind classes. + const state = open ? "expanded" : "collapsed" + + const contextValue = React.useMemo( + () => ({ + state, + open, + setOpen, + isMobile, + openMobile, + setOpenMobile, + toggleSidebar, + }), + [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar] + ) + + return ( + +
+ {children} +
+
+ ) +} + +function Sidebar({ + side = "left", + variant = "sidebar", + collapsible = "offcanvas", + className, + children, + dir, + ...props +}: React.ComponentProps<"div"> & { + side?: "left" | "right" + variant?: "sidebar" | "floating" | "inset" + collapsible?: "offcanvas" | "icon" | "none" +}) { + const { isMobile, state, openMobile, setOpenMobile } = useSidebar() + + if (collapsible === "none") { + return ( +
+ {children} +
+ ) + } + + if (isMobile) { + return ( + + + + Sidebar + Displays the mobile sidebar. + +
{children}
+
+
+ ) + } + + return ( +
+ {/* This is what handles the sidebar gap on desktop */} +
+ +
+ ) +} + +function SidebarTrigger({ + className, + onClick, + ...props +}: React.ComponentProps) { + const { toggleSidebar } = useSidebar() + + return ( + + ) +} + +function SidebarRail({ className, ...props }: React.ComponentProps<"button">) { + const { toggleSidebar } = useSidebar() + + return ( +