commit c5048761021a42f0539d3e8886229c7fcd536a78 Author: Vaibhav Tupe Date: Tue Mar 10 14:47:34 2026 +0530 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..24134da --- /dev/null +++ b/.gitignore @@ -0,0 +1,95 @@ +# =============================== +# Dependencies +# =============================== +node_modules/ +vendor/ + +# =============================== +# Build / Output +# =============================== +dist/ +build/ +target/ +out/ + +# =============================== +# Environment files +# =============================== +.env +.env.local +.env.development +.env.production +.env.test + +# =============================== +# Logs +# =============================== +*.log +npm-debug.log* +yarn-debug.log* +pnpm-debug.log* + +# =============================== +# Runtime / Temp files +# =============================== +tmp/ +temp/ +.cache/ + +# =============================== +# Python +# =============================== +__pycache__/ +*.py[cod] +*.pyo +.venv/ +venv/ + +# =============================== +# Java / JVM +# =============================== +*.class +*.jar +*.war + +# =============================== +# OS generated files +# =============================== +.DS_Store +Thumbs.db + +# =============================== +# IDE / Editor +# =============================== +.idea/ +.vscode/ +*.suo +*.ntvs* +*.njsproj +*.sln + +# =============================== +# Uploads / Media (IMPORTANT) +# =============================== +uploads/ +public/uploads/ +storage/ +media/ + +# =============================== +# Large media files +# =============================== +*.mp4 +*.mov +*.avi +*.flv +*.wmv +*.tiff +*.psd +*.zip +*.rar + +# =============================== +# Git +# =============================== +.git/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3234636 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM node:20 + +WORKDIR /vaishnaviecommerce + + + +COPY package*.json ./ +RUN npm install + +COPY . . + +RUN npm run build + + +EXPOSE 5173 + +CMD [ "npm", "run","preview" ] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..18bc70e --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# React + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..4fa125d --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,29 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{js,jsx}'], + extends: [ + js.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + parserOptions: { + ecmaVersion: 'latest', + ecmaFeatures: { jsx: true }, + sourceType: 'module', + }, + }, + rules: { + 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], + }, + }, +]) diff --git a/index.html b/index.html new file mode 100644 index 0000000..c547d91 --- /dev/null +++ b/index.html @@ -0,0 +1,15 @@ + + + + + + + + + Vaishnavi Collection + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..aa56da9 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,4028 @@ +{ + "name": "ecommerce-web", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ecommerce-web", + "version": "0.0.0", + "dependencies": { + "@reduxjs/toolkit": "^2.11.1", + "clsx": "^2.1.1", + "framer-motion": "^12.23.25", + "lucide-react": "^0.556.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-icons": "^5.5.0", + "react-redux": "^9.2.0", + "react-router-dom": "^7.10.1", + "swiper": "^12.1.0" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@types/react": "^19.2.5", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "autoprefixer": "^10.4.22", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "postcss": "^8.5.6", + "tailwindcss": "^3.4.18", + "vite": "^7.2.4" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", + "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "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", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "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" + } + }, + "node_modules/@nodelib/fs.walk": { + "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", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@reduxjs/toolkit": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.1.tgz", + "integrity": "sha512-HjhlEREguAyBTGNzRlGNiDHGQ2EjLSPWwdhhpoEqHYy8hWak3Dp6/fU72OfqVsiMb8S6rbfPsWUF24fxpilrVA==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^11.0.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.47.tgz", + "integrity": "sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", + "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", + "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", + "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.1.tgz", + "integrity": "sha512-WQfkSw0QbQ5aJ2CHYw23ZGkqnRwqKHD/KYsMeTkZzPT4Jcf0DcBxBtwMJxnu6E7oxw5+JC6ZAiePgh28uJ1HBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.5", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.47", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "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" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "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/autoprefixer": { + "version": "10.4.22", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.22.tgz", + "integrity": "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.27.0", + "caniuse-lite": "^1.0.30001754", + "fraction.js": "^5.3.4", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.4.tgz", + "integrity": "sha512-ZCQ9GEWl73BVm8bu5Fts8nt7MHdbt5vY9bP6WGnUh+r3l8M7CgfyTlwsgCbMC66BNxPr6Xoce3j66Ms5YUQTNA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "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" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001759", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001759.tgz", + "integrity": "sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "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" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "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": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "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" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.266", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.266.tgz", + "integrity": "sha512-kgWEglXvkEfMH7rxP5OSZZwnaDWT7J9EoZCujhnpLbfi0bbNtRkgdX2E3gt0Uer11c61qCYktB3hwkAS325sJg==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", + "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.1", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.24.tgz", + "integrity": "sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "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==", + "dev": 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", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "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" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/framer-motion": { + "version": "12.23.25", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.25.tgz", + "integrity": "sha512-gUHGl2e4VG66jOcH0JHhuJQr6ZNwrET9g31ZG0xdXzT0CznP7fHX4P8Bcvuc4MiUB90ysNnWX2ukHRIggkl6hQ==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.23.23", + "motion-utils": "^12.23.6", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/ignore": { + "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" + } + }, + "node_modules/immer": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.0.1.tgz", + "integrity": "sha512-naDCyggtcBWANtIrjQEajhhBEuL9b0Zg4zmlWK2CzS6xCWSE39/vvf4LqnMjUAWHBhot4m9MHCM/Z+mfWhUkiA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/import-fresh": { + "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", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "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" + } + }, + "node_modules/is-glob": { + "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" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "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" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "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/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.556.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.556.0.tgz", + "integrity": "sha512-iOb8dRk7kLaYBZhR2VlV1CeJGxChBgUthpSP8wom9jfj79qovgG6qcSdiy6vkoREKPnbUYzJsCn4o4PtG3Iy+A==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "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" + } + }, + "node_modules/micromatch": { + "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", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/motion-dom": { + "version": "12.23.23", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz", + "integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.23.6" + } + }, + "node_modules/motion-utils": { + "version": "12.23.6", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz", + "integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "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" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "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", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.2.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz", + "integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.1.tgz", + "integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.1" + } + }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.10.1.tgz", + "integrity": "sha512-gHL89dRa3kwlUYtRQ+m8NmxGI6CgqN+k4XyGjwcFoQwwCWF6xXpOCUlDovkXClS0d0XJN/5q7kc5W3kiFEd0Yw==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.10.1.tgz", + "integrity": "sha512-JNBANI6ChGVjA5bwsUIwJk7LHKmqB4JYnYfzFwyp2t12Izva11elds2jx7Yfoup2zssedntwU0oZ5DEmk5Sdaw==", + "license": "MIT", + "dependencies": { + "react-router": "7.10.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "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" + } + }, + "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", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", + "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.53.3", + "@rollup/rollup-android-arm64": "4.53.3", + "@rollup/rollup-darwin-arm64": "4.53.3", + "@rollup/rollup-darwin-x64": "4.53.3", + "@rollup/rollup-freebsd-arm64": "4.53.3", + "@rollup/rollup-freebsd-x64": "4.53.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", + "@rollup/rollup-linux-arm64-gnu": "4.53.3", + "@rollup/rollup-linux-arm64-musl": "4.53.3", + "@rollup/rollup-linux-loong64-gnu": "4.53.3", + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-musl": "4.53.3", + "@rollup/rollup-linux-s390x-gnu": "4.53.3", + "@rollup/rollup-linux-x64-gnu": "4.53.3", + "@rollup/rollup-linux-x64-musl": "4.53.3", + "@rollup/rollup-openharmony-arm64": "4.53.3", + "@rollup/rollup-win32-arm64-msvc": "4.53.3", + "@rollup/rollup-win32-ia32-msvc": "4.53.3", + "@rollup/rollup-win32-x64-gnu": "4.53.3", + "@rollup/rollup-win32-x64-msvc": "4.53.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "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", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/swiper": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/swiper/-/swiper-12.1.0.tgz", + "integrity": "sha512-BD4CpAOOyEvZ2f0CDx362ea+vmOwukVcmbsQx+0BhRIaBUz8wvcCd//E7RFmvBZCrfyqXCHUVqmgUwts6ywlxw==", + "funding": [ + { + "type": "patreon", + "url": "https://www.patreon.com/swiperjs" + }, + { + "type": "open_collective", + "url": "http://opencollective.com/swiper" + } + ], + "license": "MIT", + "engines": { + "node": ">= 4.7.0" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz", + "integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "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" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz", + "integrity": "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util-deprecate": { + "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/vite": { + "version": "7.2.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.7.tgz", + "integrity": "sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", + "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..feff671 --- /dev/null +++ b/package.json @@ -0,0 +1,38 @@ +{ + "name": "ecommerce-web", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint .", + "preview": "vite preview --host 0.0.0.0 --port 5173" + }, + "dependencies": { + "@reduxjs/toolkit": "^2.11.1", + "clsx": "^2.1.1", + "framer-motion": "^12.23.25", + "lucide-react": "^0.556.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-icons": "^5.5.0", + "react-redux": "^9.2.0", + "react-router-dom": "^7.10.1", + "swiper": "^12.1.0" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@types/react": "^19.2.5", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "autoprefixer": "^10.4.22", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "postcss": "^8.5.6", + "tailwindcss": "^3.4.18", + "vite": "^7.2.4" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..2e7af2b --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/public/Frame (1).png b/public/Frame (1).png new file mode 100644 index 0000000..1af84ef Binary files /dev/null and b/public/Frame (1).png differ diff --git a/public/Frame.png b/public/Frame.png new file mode 100644 index 0000000..108f682 Binary files /dev/null and b/public/Frame.png differ diff --git a/public/add-to-cart.png b/public/add-to-cart.png new file mode 100644 index 0000000..4040b52 Binary files /dev/null and b/public/add-to-cart.png differ diff --git a/public/default-product.png b/public/default-product.png new file mode 100644 index 0000000..2105d96 Binary files /dev/null and b/public/default-product.png differ diff --git a/public/empty-wishlist.png b/public/empty-wishlist.png new file mode 100644 index 0000000..2105d96 Binary files /dev/null and b/public/empty-wishlist.png differ diff --git a/public/favicon.png b/public/favicon.png new file mode 100644 index 0000000..1b315c1 Binary files /dev/null and b/public/favicon.png differ diff --git a/public/featured-product.avif b/public/featured-product.avif new file mode 100644 index 0000000..057dae0 Binary files /dev/null and b/public/featured-product.avif differ diff --git a/public/hero1.png b/public/hero1.png new file mode 100644 index 0000000..4a758d3 Binary files /dev/null and b/public/hero1.png differ diff --git a/public/hero2.png b/public/hero2.png new file mode 100644 index 0000000..225d6bb Binary files /dev/null and b/public/hero2.png differ diff --git a/public/hero3.png b/public/hero3.png new file mode 100644 index 0000000..bcd03fb Binary files /dev/null and b/public/hero3.png differ diff --git a/public/hero4.png b/public/hero4.png new file mode 100644 index 0000000..66204ca Binary files /dev/null and b/public/hero4.png differ diff --git a/public/hero5.jpg b/public/hero5.jpg new file mode 100644 index 0000000..682b92d Binary files /dev/null and b/public/hero5.jpg differ diff --git a/public/logo-small.png b/public/logo-small.png new file mode 100644 index 0000000..991e384 Binary files /dev/null and b/public/logo-small.png differ diff --git a/public/logo-small2.png b/public/logo-small2.png new file mode 100644 index 0000000..4a84608 Binary files /dev/null and b/public/logo-small2.png differ diff --git a/public/logo.png b/public/logo.png new file mode 100644 index 0000000..474691f Binary files /dev/null and b/public/logo.png differ diff --git a/public/most-loved-banner.png b/public/most-loved-banner.png new file mode 100644 index 0000000..a6649c8 Binary files /dev/null and b/public/most-loved-banner.png differ diff --git a/public/not-found.jpg b/public/not-found.jpg new file mode 100644 index 0000000..338d339 Binary files /dev/null and b/public/not-found.jpg differ diff --git a/public/order-success.png b/public/order-success.png new file mode 100644 index 0000000..8fa6455 Binary files /dev/null and b/public/order-success.png differ diff --git a/public/rating.png b/public/rating.png new file mode 100644 index 0000000..50a93b5 Binary files /dev/null and b/public/rating.png differ diff --git a/public/vite.svg b/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/App.jsx b/src/App.jsx new file mode 100644 index 0000000..506a78f --- /dev/null +++ b/src/App.jsx @@ -0,0 +1,23 @@ +import React from "react"; +import Navbar from "./components/navbar/Navbar"; +import Footer from "./components/layout/Footer"; +import AppRoutes from "./routes/AppRoutes"; +import TopPromoBanner from "./components/navbar/TopPromoBanner"; +import ScrollToTop from "./components/ui/ScrollToTop"; + +const App = () => { + return ( +
+ + + {/* */} +
+ + +
+ {/*
*/} +
+ ); +}; + +export default App; diff --git a/src/app/store.js b/src/app/store.js new file mode 100644 index 0000000..75d9491 --- /dev/null +++ b/src/app/store.js @@ -0,0 +1,51 @@ +import { configureStore } from "@reduxjs/toolkit"; +import userReducer from "../features/user/userSlice"; +import authReducer from "../features/auth/authSlice"; +import { authApi } from "../features/auth/authApi"; +import { wishlistApi } from "../features/wishlist/wishlistApi"; +import cartReducer from "../features/cart/cartSlice"; +import { cartApi } from "../features/cart/cartAPI"; +import { addressApi } from "../features/address/addressApi"; +// import authReducer from "../features/address/addressApi"; +import { categoriesApi } from "../features/categories/categoryApi"; +import { productApi } from "../features/products/productsAPI"; +import recentlyViewedReducer from "../features/recentlyViewed/recentlyViewedApi"; +import { ordersApi } from "../features/orders/ordersApi"; +import { wardrobeApi } from "../features/wardrobe/wardrobeApi"; +import { couponsApi } from "../features/coupons/couponsApi"; + +export const store = configureStore({ + reducer: { + user: userReducer, + auth: authReducer, + cart: cartReducer, + [authApi.reducerPath]: authApi.reducer, + [ordersApi.reducerPath]: ordersApi.reducer, + [wishlistApi.reducerPath]: wishlistApi.reducer, + [cartApi.reducerPath]: cartApi.reducer, + [addressApi.reducerPath]: addressApi.reducer, + // [categoryApi.reducerPath]: categoryApi.reducer, + [categoriesApi.reducerPath]: categoriesApi.reducer, + + [productApi.reducerPath]: productApi.reducer, + [wardrobeApi.reducerPath]: wardrobeApi.reducer, + // [categoriesApi.reducerPath]: categoriesApi.reducer, + recentlyViewed: recentlyViewedReducer, + [couponsApi.reducerPath]: couponsApi.reducer, + }, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware() + .concat(authApi.middleware) + .concat(ordersApi.middleware) + .concat(wishlistApi.middleware) + .concat(cartApi.middleware) + .concat(addressApi.middleware) + // .concat(categoryApi.middleware) + .concat(categoriesApi.middleware) + + .concat(productApi.middleware) + .concat(wardrobeApi.middleware) + .concat(couponsApi.middleware), +}); + +export default store; diff --git a/src/assets/react.svg b/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/Filters/FilterPanel.jsx b/src/components/Filters/FilterPanel.jsx new file mode 100644 index 0000000..c7df2b3 --- /dev/null +++ b/src/components/Filters/FilterPanel.jsx @@ -0,0 +1,210 @@ +import React, { useState } from "react"; + +const sizes = ["S", "M", "L", "XL", "Free Size", "3XL", "4XL"]; +const colors = [ + "Red", + "Black", + "Green", + "Brown", + "Maroon", + "Gold", + "Fawn", + "Peach", + "Wine", + "Yellow", +]; +const designs = ["Print", "Ambiodry", "Stone Work", "Weave", "Zari"]; +const fabrics = ["Silk", "Tissue", "Velvet"]; + +const tabs = ["Size", "Color", "Price", "Design", "Fabric", "Availability"]; + +const FilterPanel = () => { + const [activeTab, setActiveTab] = useState("Size"); + + const [selectedSizes, setSelectedSizes] = useState([]); + const [selectedColors, setSelectedColors] = useState([]); + const [priceRange, setPriceRange] = useState([0, 5000]); + const [selectedDesigns, setSelectedDesigns] = useState([]); + const [selectedFabrics, setSelectedFabrics] = useState([]); + const [inStockOnly, setInStockOnly] = useState(false); + + const toggleSelection = (item, list, setList) => { + list.includes(item) + ? setList(list.filter((i) => i !== item)) + : setList([...list, item]); + }; + + return ( +
+ {/* LEFT TABS */} +
+ {tabs.map((tab) => ( + + ))} +
+ + {/* RIGHT CONTENT */} +
+
+ {/* SIZE */} + {activeTab === "Size" && ( +
+ {sizes.map((size) => ( + + ))} +
+ )} + + {/* COLOR */} + {activeTab === "Color" && ( +
+ {colors.map((color) => ( +
+ toggleSelection(color, selectedColors, setSelectedColors) + } + className={`w-14 h-14 rounded-full cursor-pointer border-4 shadow + ${ + selectedColors.includes(color) + ? "border-primary scale-105" + : "border-gray-300" + }`} + style={{ backgroundColor: color.toLowerCase() }} + /> + ))} +
+ )} + + {/* PRICE */} + {activeTab === "Price" && ( +
+ {/* Min Label */} + + Min: ₹0 + + + {/* Max Label */} + + Max: ₹{priceRange[1]} + + + {/* Slider */} + setPriceRange([0, Number(e.target.value)])} + className="w-full accent-primary" + /> + + {/* Apply Button */} + +
+ )} + + {/* DESIGN */} + {activeTab === "Design" && ( +
+ {designs.map((design) => ( + + ))} +
+ )} + + {/* FABRIC */} + {activeTab === "Fabric" && ( +
+ {fabrics.map((fabric) => ( + + ))} +
+ )} + + {/* AVAILABILITY */} + {activeTab === "Availability" && ( + + )} +
+ + {/* BOTTOM APPLY BUTTON */} +
+ +
+
+
+ ); +}; + +export default FilterPanel; diff --git a/src/components/Offerings/OfferingCard.jsx b/src/components/Offerings/OfferingCard.jsx new file mode 100644 index 0000000..f8a240a --- /dev/null +++ b/src/components/Offerings/OfferingCard.jsx @@ -0,0 +1,48 @@ +import { motion } from "framer-motion"; + +const OfferingCard = ({ icon, title }) => { + return ( + +
+ {/* Icon */} +
+ {icon} +
+ + {/* Title */} +

+ {title} +

+
+
+ ); +}; + +export default OfferingCard; diff --git a/src/components/Offerings/OfferingsSection.jsx b/src/components/Offerings/OfferingsSection.jsx new file mode 100644 index 0000000..f7c05c4 --- /dev/null +++ b/src/components/Offerings/OfferingsSection.jsx @@ -0,0 +1,31 @@ +import OfferingCard from "./OfferingCard"; +import { offeringsData } from "../../data/offeringsData"; + +const OfferingsSection = () => { + return ( +
+
+ +

+ Our Unique Offerings +

+ +

+ Experience premium services crafted to elevate your shopping journey. +

+ +
+ {offeringsData.map((item) => ( + + ))} +
+
+
+ ); +}; + +export default OfferingsSection; diff --git a/src/components/common/Badge.jsx b/src/components/common/Badge.jsx new file mode 100644 index 0000000..02c58b1 --- /dev/null +++ b/src/components/common/Badge.jsx @@ -0,0 +1,9 @@ +const Badge = ({ children, className = "" }) => { + return ( + + {children} + + ); +}; + +export default Badge; diff --git a/src/components/common/Button.jsx b/src/components/common/Button.jsx new file mode 100644 index 0000000..b9fa2b7 --- /dev/null +++ b/src/components/common/Button.jsx @@ -0,0 +1,18 @@ +const Button = ({ children, className = "", ...props }) => { + return ( + + ); +}; + +export default Button; diff --git a/src/components/common/Card.jsx b/src/components/common/Card.jsx new file mode 100644 index 0000000..e69de29 diff --git a/src/components/common/Checkbox.jsx b/src/components/common/Checkbox.jsx new file mode 100644 index 0000000..528f549 --- /dev/null +++ b/src/components/common/Checkbox.jsx @@ -0,0 +1,37 @@ +// const Checkbox = ({ label, ...props }) => { +// return ( +// +// ); +// }; + +// export default Checkbox; + + + +const Checkbox = ({ label, ...props }) => { + return ( + + ); +}; + +export default Checkbox; diff --git a/src/components/common/EmptyState.jsx b/src/components/common/EmptyState.jsx new file mode 100644 index 0000000..1c65d74 --- /dev/null +++ b/src/components/common/EmptyState.jsx @@ -0,0 +1,66 @@ +// import React from "react"; + +// const EmptyState = ({ +// title = "No products found", +// description = "Try adjusting filters or check back later", +// icon = "🛍️", +// }) => { +// return ( +//
+//
{icon}
+ +//

+// {title} +//

+ +//

+// {description} +//

+//
+// ); +// }; + +// export default EmptyState; + +import React from "react"; +import clsx from "clsx"; + +const VARIANTS = { + default: { + title: "text-gray-800", + desc: "text-[#f2f2f2]", + }, + error: { + title: "text-red-600", + desc: "text-[#f2f2f2]", + }, + warning: { + title: "text-yellow-600", + desc: "text-[#f2f2f2]", + }, +}; + +const EmptyState = ({ + title, + description, + icon = "📦", + variant = "default", +}) => { + return ( +
+
{icon}
+ +

+ {title} +

+ +

+ {description} +

+
+ ); +}; + +export default EmptyState; diff --git a/src/components/common/Input.jsx b/src/components/common/Input.jsx new file mode 100644 index 0000000..94a889b --- /dev/null +++ b/src/components/common/Input.jsx @@ -0,0 +1,15 @@ +const Input = ({ label, className = "", ...props }) => { + return ( +
+ {label && } + +
+ ); +}; + +export default Input; diff --git a/src/components/common/Loader.jsx b/src/components/common/Loader.jsx new file mode 100644 index 0000000..3133338 --- /dev/null +++ b/src/components/common/Loader.jsx @@ -0,0 +1,7 @@ +const Loader = () => ( +
+
+
+); + +export default Loader; diff --git a/src/components/common/Modal.jsx b/src/components/common/Modal.jsx new file mode 100644 index 0000000..e69de29 diff --git a/src/components/common/ProductGridSkeleton.jsx b/src/components/common/ProductGridSkeleton.jsx new file mode 100644 index 0000000..8cfcef9 --- /dev/null +++ b/src/components/common/ProductGridSkeleton.jsx @@ -0,0 +1,14 @@ +import React from "react"; +import ProductCardSkeleton from "./ProductCardSkeleton"; + +const ProductGridSkeleton = ({ count = 8 }) => { + return ( +
+ {Array.from({ length: count }).map((_, i) => ( + + ))} +
+ ); +}; + +export default ProductGridSkeleton; diff --git a/src/components/common/Select.jsx b/src/components/common/Select.jsx new file mode 100644 index 0000000..e69de29 diff --git a/src/components/common/Table.jsx b/src/components/common/Table.jsx new file mode 100644 index 0000000..a0edca6 --- /dev/null +++ b/src/components/common/Table.jsx @@ -0,0 +1,27 @@ +const Table = ({ columns = [], data = [] }) => { + return ( + + + + {columns.map((col) => ( + + ))} + + + + + {data.map((row, i) => ( + + {Object.values(row).map((item, index) => ( + + ))} + + ))} + +
+ {col} +
{item}
+ ); +}; + +export default Table; diff --git a/src/components/home/Category/Category.jsx b/src/components/home/Category/Category.jsx new file mode 100644 index 0000000..0047076 --- /dev/null +++ b/src/components/home/Category/Category.jsx @@ -0,0 +1,18 @@ +import React from "react"; +// import CategoryCard from "./CategoryCard"; +import CategoryCarousel from "./CategoryCarousel"; + +const Category = () => { + return ( +
+ {/* Curved top */} +
+ +
+ +
+
+ ); +}; + +export default Category; diff --git a/src/components/home/Category/CategoryCard.jsx b/src/components/home/Category/CategoryCard.jsx new file mode 100644 index 0000000..cee6c4b --- /dev/null +++ b/src/components/home/Category/CategoryCard.jsx @@ -0,0 +1,51 @@ +import React from "react"; +import { Link } from "react-router-dom"; + +const CategoryCard = ({ category }) => { + const parent = category.grandParent || "all"; + const categorySlug = category.parent || category.slug; + const subCategory = category.slug; + + return ( + +
+ + {/* Image */} + {category.name} + + {/* Gradient Overlay */} +
+ + {/* Content */} +
+

+ {category.name} +

+
+
+ + ); +}; + +export default CategoryCard; diff --git a/src/components/home/Category/CategoryCarousel.jsx b/src/components/home/Category/CategoryCarousel.jsx new file mode 100644 index 0000000..1493de2 --- /dev/null +++ b/src/components/home/Category/CategoryCarousel.jsx @@ -0,0 +1,78 @@ +import React from "react"; +import { Swiper, SwiperSlide } from "swiper/react"; +import { EffectCoverflow, Navigation } from "swiper/modules"; +import "swiper/css"; +import "swiper/css/effect-coverflow"; +import "swiper/css/navigation"; +import { ChevronLeft, ChevronRight } from "lucide-react"; +import CategoryCard from "./CategoryCard"; +import { useGetCategoriesQuery } from "../../../features/categories/categoryApi"; + +const CategoryCarousel = () => { + const { data, isLoading, isError } = useGetCategoriesQuery(); + console.log("CATEGORY API DATA 👉", data); + + if (isLoading) { + return ( +
+ Loading categories... +
+ ); + } + + if (isError) { + return ( +
+ Failed to load categories +
+ ); + } + + return ( +
+

+ Shop by Category +

+ + {/* Navigation */} +
+ +
+
+ +
+ + + {data?.data?.map((cat) => ( + + + + ))} + +
+ ); +}; + +export default CategoryCarousel; diff --git a/src/components/home/Category/CategoryGrid.jsx b/src/components/home/Category/CategoryGrid.jsx new file mode 100644 index 0000000..9caf672 --- /dev/null +++ b/src/components/home/Category/CategoryGrid.jsx @@ -0,0 +1,64 @@ +import React from "react"; +import { useParams, useNavigate } from "react-router-dom"; +import { useGetCategoryColorsQuery } from "../../../features/categories/categoryApi"; + +const CategoryGrid = () => { + const { category, subCategory } = useParams(); + const navigate = useNavigate(); + + // use current category slug + const categorySlug = subCategory || category; + + const { data, isLoading } = useGetCategoryColorsQuery(categorySlug, { + skip: !categorySlug, + }); + + const colors = data?.data || []; + const message = data?.message; + + if (isLoading) return null; + + return ( +
+ {colors.length === 0 ? ( +

+ {message || "No colors available for this category"} +

+ ) : ( +
+ {colors.map((color) => ( +
+ navigate(`/products/${categorySlug}?color=${color.slug}`) + } + className="flex-shrink-0 flex flex-col items-center cursor-pointer group transition-transform hover:scale-105 w-28 sm:w-auto" + > + {/* Circle */} +
+
+ {color.name} +
+
+ + {/* Name */} +

+ {color.name} +

+
+ ))} +
+ )} +
+ ); +}; + +export default CategoryGrid; + diff --git a/src/components/home/Category/index.js b/src/components/home/Category/index.js new file mode 100644 index 0000000..b49ef48 --- /dev/null +++ b/src/components/home/Category/index.js @@ -0,0 +1 @@ +export { default } from "./Category"; diff --git a/src/components/home/HeroSection/HeroSection.jsx b/src/components/home/HeroSection/HeroSection.jsx new file mode 100644 index 0000000..1902c40 --- /dev/null +++ b/src/components/home/HeroSection/HeroSection.jsx @@ -0,0 +1,130 @@ +import React, { useState, useEffect } from "react"; + +const slides = [ + { + image: "/hero1.png", + title: "LIMITED EDITION", + subtitle: "Premium Menswear Collection", + cta1: "Shop Collection", + cta2: "Explore Luxury", + }, + { + image: "/hero2.png", + title: "THE ROYAL LOOK", + subtitle: "Crafted for Modern Kings", + cta1: "Shop Sherwanis", + cta2: "View Collection", + }, + { + image: "/hero3.png", + title: "BLACK • GOLD EDITION", + subtitle: "Timeless Elegance & Precision", + cta1: "Discover Now", + cta2: "View Collection", + }, + { + image: "/hero4.png", + title: "BLACK • GOLD EDITION", + subtitle: "Timeless Elegance & Precision", + cta1: "Discover Now", + cta2: "View Collection", + }, +]; + +const HeroSection = () => { + const [current, setCurrent] = useState(0); + + useEffect(() => { + const interval = setInterval(() => { + setCurrent((prev) => (prev + 1) % slides.length); + }, 6000); + return () => clearInterval(interval); + }, []); + + return ( +
+ {slides.map((slide, index) => ( +
+ hero + + {/* Dark Cinematic Overlay */} +
+ + {/* Brand-Colored Spotlight */} +
+ + {/* Content */} +
+
+

+ {slide.title} +

+ +

+ {slide.subtitle} +

+ +
+ {/* Primary Button (Luxury Gold-Brown) */} + + + {/* Outline Button */} + +
+ + {/* Accent Bar */} +
+
+
+
+ ))} + + {/* Navigation Dots */} +
+ {slides.map((_, idx) => ( +
setCurrent(idx)} + className={`w-3 h-3 rounded-full cursor-pointer border border-primary-default transition-all + ${ + idx === current + ? "bg-primary-default shadow-[0_0_10px_rgba(176,127,93,0.8)]" + : "bg-transparent" + } + `} + >
+ ))} +
+
+ ); +}; + +export default HeroSection; diff --git a/src/components/home/Home.jsx b/src/components/home/Home.jsx new file mode 100644 index 0000000..50e5007 --- /dev/null +++ b/src/components/home/Home.jsx @@ -0,0 +1,23 @@ +import React from "react"; +import HeroSection from "./HeroSection/HeroSection"; +import Category from "./Category/Category"; +import MostLoved from "./MostLoved/MostLoved"; +import NewGems from "./NewGems/NewGems"; +import OfferingsSection from "../Offerings/OfferingsSection"; +import RecentlyViewed from "../recentlyViewed/RecentlyViewed"; +import RecommendedForYou from "./RecommendedForYou/RecommendedForYou"; +// import TopPromoBanner from "../navbar/TopPromoBanner"; + +const Home = () => ( +
+ + + + + + + +
+); + +export default Home; diff --git a/src/components/home/MostLoved/MostLoved.jsx b/src/components/home/MostLoved/MostLoved.jsx new file mode 100644 index 0000000..02a1841 --- /dev/null +++ b/src/components/home/MostLoved/MostLoved.jsx @@ -0,0 +1,62 @@ +import React from "react"; +import { useGetMostLovedProductsQuery } from "../../../features/products/productsAPI"; +import MostLovedCard from "./MostLovedCard"; +import ProductGridSkeleton from "../../skeletons/ProductGridSkeleton"; +import EmptyState from "../../common/EmptyState"; +import { getRTKErrorMessage } from "../../../utils/rtkError"; + +const MostLoved = () => { + const { + data: products = [], + isLoading, + isError, + error, + } = useGetMostLovedProductsQuery(8); + + return ( +
+
+
+ Most Loved +
+ {/* 🔥 HEADER */} +
+ + ❤️ Trending Picks + +

+ Most Loved Labels +

+
+ + {/* ⏳ LOADING */} + {isLoading && } + + {/* ❌ ERROR */} + {isError && ( + + )} + + {/* ❤️ PRODUCTS */} + {!isLoading && products.length > 0 && ( +
+ {products.map((product) => ( + + ))} +
+ )} +
+
+ ); +}; + +export default MostLoved; diff --git a/src/components/home/MostLoved/MostLovedCard.jsx b/src/components/home/MostLoved/MostLovedCard.jsx new file mode 100644 index 0000000..671c6ed --- /dev/null +++ b/src/components/home/MostLoved/MostLovedCard.jsx @@ -0,0 +1,177 @@ +// import React from "react"; +// import { Link } from "react-router-dom"; + +// const MostLovedCard = ({ product }) => { +// const image = +// product?.images?.gallery?.[0] || +// product?.images?.primary || +// "/placeholder-product.png"; + +// const discount = product?.compareAtPrice +// ? Math.round( +// ((product.compareAtPrice - product.basePrice) / +// product.compareAtPrice) * +// 100, +// ) +// : 0; + +// return ( +// +// {/* Image */} +//
+// {product.name} + +// {/* Soft Gradient */} +//
+ +// {/* Discount */} +// {discount > 0 && ( +//
+// −{discount}% +//
+// )} +//
+ +// {/* Content */} +//
+//

+// {product.name} +//

+ +// {/* Price */} +//
+// +// ₹{product.basePrice} +// +// {discount > 0 && ( +// +// ₹{product.compareAtPrice} +// +// )} +//
+ +// {/* Hover Hint */} +//
+//
+// +// ); +// }; + +// export default MostLovedCard; + + + +import React from "react"; +import { Link } from "react-router-dom"; + +const MostLovedCard = ({ product }) => { + const image = + product?.images?.gallery?.[0] || + product?.images?.primary || + "/placeholder-product.png"; + + const hasOffer = + product?.compareAtPrice && + product?.compareAtPrice > product?.basePrice; + + const discount = hasOffer + ? Math.round( + ((product.compareAtPrice - product.basePrice) / + product.compareAtPrice) * + 100 + ) + : 0; + + return ( + + {/* IMAGE */} +
+ {product.name} + + {/* DARK GRADIENT */} +
+ + {/* 🔥 OFFER BADGE (ONLY IF DISCOUNT) */} + {hasOffer && ( +
+ {discount}% OFF +
+ )} + + {/* ❤️ MOST LOVED TAG */} +
+ ❤️ Loved +
+
+ + {/* CONTENT */} +
+

+ {product.name} +

+ + {/* DESCRIPTION */} +

+ {product.shortDescription || product.description} +

+ + {/* PRICE */} +
+ + ₹{product.basePrice} + + + {hasOffer && ( + + ₹{product.compareAtPrice} + + )} +
+ + {/* SUBTLE HOVER LINE */} +
+
+ + ); +}; + +export default MostLovedCard; diff --git a/src/components/home/NewGems/NewArrivalCard.jsx b/src/components/home/NewGems/NewArrivalCard.jsx new file mode 100644 index 0000000..5cbbd45 --- /dev/null +++ b/src/components/home/NewGems/NewArrivalCard.jsx @@ -0,0 +1,187 @@ +// import React from "react"; +// import { Link } from "react-router-dom"; + +// const NewArrivalCard = ({ product }) => { +// const image = +// product?.variants?.[0]?.images?.[0] || +// product?.images?.primary || +// "/placeholder-product.png"; + +// const price = product?.variants?.[0]?.price || product?.basePrice; + +// const discount = +// product?.compareAtPrice && product.compareAtPrice > price +// ? Math.round( +// ((product.compareAtPrice - price) / product.compareAtPrice) * 100 +// ) +// : 0; + +// return ( +// +// {/* Image */} +//
+// {product.name} + +// {/* Soft gradient */} +//
+ +// {/* NEW badge */} +//
+// NEW +//
+ +// {/* Discount */} +// {discount > 0 && ( +//
+// −{discount}% +//
+// )} +//
+ +// {/* Content */} +//
+//

+// {product.name} +//

+ +// {/* Price */} +//
+// ₹{price} + +// {discount > 0 && ( +// +// ₹{product.compareAtPrice} +// +// )} +//
+ +// {/* Minimal hover accent */} +//
+//
+// +// ); +// }; + +// export default NewArrivalCard; + + + + +import React from "react"; +import { Link } from "react-router-dom"; + +const NewArrivalCard = ({ product }) => { + const image = + product?.variants?.[0]?.images?.[0] || + product?.images?.gallery?.[0] || + product?.images?.primary || + "/placeholder-product.png"; + + const price = product?.variants?.[0]?.price || product?.basePrice; + + const hasDiscount = + product?.compareAtPrice && product.compareAtPrice > price; + + const discount = hasDiscount + ? Math.round( + ((product.compareAtPrice - price) / product.compareAtPrice) * 100 + ) + : 0; + + return ( + + {/* IMAGE */} +
+ {product.name} + + {/* GRADIENT OVERLAY */} +
+ + {/* BADGES */} +
+ {/* NEW */} + + NEW IN + + + {/* DISCOUNT */} + {hasDiscount && ( + + {discount}% OFF + + )} +
+
+ + {/* CONTENT */} +
+

+ {product.name} +

+ + {/* DESCRIPTION */} +

+ {product.shortDescription || product.description} +

+ + {/* PRICE */} +
+ ₹{price} + + {hasDiscount && ( + + ₹{product.compareAtPrice} + + )} +
+ + {/* CTA HINT */} +
+ View Product + +
+
+ + ); +}; + +export default NewArrivalCard; diff --git a/src/components/home/NewGems/NewGems.jsx b/src/components/home/NewGems/NewGems.jsx new file mode 100644 index 0000000..1fed59d --- /dev/null +++ b/src/components/home/NewGems/NewGems.jsx @@ -0,0 +1,61 @@ +import React from "react"; +import { useGetNewArrivalsQuery } from "../../../features/products/productsAPI"; +import NewArrivalCard from "./NewArrivalCard"; +import ProductGridSkeleton from "../../skeletons/ProductGridSkeleton"; +import EmptyState from "../../common/EmptyState"; + +const NewGems = () => { + const { data, isLoading, isError } = useGetNewArrivalsQuery(8); + + const products = data?.data || []; + + return ( +
+
+ {/* Banner Image Centered & Smaller */} +
+ Most Loved +
+ + {/* Heading */} +
+

+ New Arrivals +

+

Fresh picks just for you

+
+ + {/* ✅ Loading */} + {isLoading && } + + {/* ❌ Error */} + {isError && ( + + )} + + + + {/* ✅ Products */} + {!isLoading && !isError && ( +
+ {products.map((product) => ( + + ))} +
+ )} +
+ {/*
*/} + + ); +}; + +export default NewGems; diff --git a/src/components/home/RecommendedForYou/RecommendedForYou.jsx b/src/components/home/RecommendedForYou/RecommendedForYou.jsx new file mode 100644 index 0000000..b407386 --- /dev/null +++ b/src/components/home/RecommendedForYou/RecommendedForYou.jsx @@ -0,0 +1,157 @@ +// import React from "react"; +// import { useGetPersonalizedRecommendationsQuery } from "../../../features/products/productsAPI"; + +// const RecommendedForYou = ({ limit = 10 }) => { +// const token = localStorage.getItem("token"); +// const { data: products = [], isLoading } = +// useGetPersonalizedRecommendationsQuery(limit, { +// skip: !token, // skip query if not logged in +// }); + +// if (!token) return null; // hide if user not logged in +// if (isLoading) +// return

Loading recommendations...

; +// if (!products.length) +// return

No recommendations found.

; + +// return ( +//
+//
+// {/*

+// Recommended for You +//

*/} +//
+//

+// Recommended for You +//

+//

+// Continue shopping from where you left off +//

+//
+//
+// {products.map((product) => ( +//
+// {product.name} +//

{product.name}

+//

+// {product.description.slice(0, 60)}... +//

+//

+// ₹{product.basePrice} +//

+//
+// ))} +//
+//
+//
+// ); +// }; + +// export default RecommendedForYou; + +import { Link } from "react-router-dom"; +import { Swiper, SwiperSlide } from "swiper/react"; +import { Navigation, Pagination } from "swiper/modules"; +import { useGetPersonalizedRecommendationsQuery } from "../../../features/products/productsAPI"; + +import "swiper/css"; +import "swiper/css/navigation"; +import "swiper/css/pagination"; +import "../../../styles/recommendedSwiper.css"; + +const RecommendedForYou = ({ limit = 10 }) => { + const token = localStorage.getItem("token"); + + const { data: products = [], isLoading } = + useGetPersonalizedRecommendationsQuery(limit, { + skip: !token, + }); + + if (!token) return null; + if (isLoading) + return

Loading...

; + if (!products.length) return null; + + return ( +
+ {/* Heading */} +
+

+ Recommended for You +

+

+ Continue shopping from where you left off +

+
+ + + {products.map((product) => ( + + + {/* Image */} +
+ {product.isFeatured && ( + + Featured + + )} + + {product.name} +
+ + {/* Content */} +
+

+ {product.name} +

+

+ ₹{product.basePrice} +

+
+ +
+ ))} +
+
+ ); +}; + +export default RecommendedForYou; diff --git a/src/components/layout/Footer.jsx b/src/components/layout/Footer.jsx new file mode 100644 index 0000000..996733b --- /dev/null +++ b/src/components/layout/Footer.jsx @@ -0,0 +1,166 @@ +import { Link } from "react-router-dom"; +import { Facebook, Instagram, Youtube, Twitter } from "lucide-react"; + +const Footer = () => { + return ( +
+ {/* Newsletter */} +
+

+ Join Our Style Circle +

+ +

+ Be the first to know about new gems & exclusive ethnic collections. +

+ + {/* Responsive Input */} +
+ + + +
+
+ + {/* Main Footer Links */} +
+
+

Shop

+
    +
  • + + Sarees + +
  • +
  • + + Bandhani + +
  • +
  • + + Patola + +
  • +
  • + + Lehengas + +
  • +
+
+ +
+

+ Customer Care +

+
    +
  • + + Contact Us + +
  • +
  • + + FAQs + +
  • +
  • + + Return & Exchange + +
  • +
  • + + Shipping Info + +
  • +
+
+ +
+

Information

+
    +
  • + + About Us + +
  • +
  • + + Blog + +
  • +
  • + + Privacy Policy + +
  • +
  • + + Terms & Conditions + +
  • +
+
+ + {/* Social Section */} +
+

Follow Us

+ +
+
+ + {/* Bottom Strip */} +
+

+ © {new Date().getFullYear()} + Vaishnavi Collections. All + Rights Reserved. +

+

Crafted with ❤️ for your ethnic elegance.

+
+
+ ); +}; + +export default Footer; diff --git a/src/components/navbar/BellIcon.jsx b/src/components/navbar/BellIcon.jsx new file mode 100644 index 0000000..8e98080 --- /dev/null +++ b/src/components/navbar/BellIcon.jsx @@ -0,0 +1,18 @@ +import React from "react"; +import { Bell } from "lucide-react"; +import { Link } from "react-router-dom"; + +const BellIcon = ({ notificationCount = 0 }) => { + return ( + + + {notificationCount > 0 && ( + + {notificationCount} + + )} + + ); +}; + +export default BellIcon; diff --git a/src/components/navbar/CartIcon.jsx b/src/components/navbar/CartIcon.jsx new file mode 100644 index 0000000..b593253 --- /dev/null +++ b/src/components/navbar/CartIcon.jsx @@ -0,0 +1,46 @@ +// import React from "react"; +// import { ShoppingCart } from "lucide-react"; +// import { Link } from "react-router-dom"; + +// const CartIcon = ({ itemCount = 0 }) => { +// return ( +// +// +// {itemCount > 0 && ( +// +// {itemCount} +// +// )} +// +// ); +// }; + +// export default CartIcon; + + + +import { ShoppingCart } from "lucide-react"; +import { Link } from "react-router-dom"; +import { useGetCartItemsQuery } from "../../features/cart/cartAPI"; + +const CartIcon = ({ className = "" }) => { + const { data } = useGetCartItemsQuery(); + + const cartCount = data?.data?.items?.length || 0; + + return ( + + + + {cartCount > 0 && ( + + {cartCount} + + )} + + ); +}; + +export default CartIcon; diff --git a/src/components/navbar/Logo.jsx b/src/components/navbar/Logo.jsx new file mode 100644 index 0000000..561f282 --- /dev/null +++ b/src/components/navbar/Logo.jsx @@ -0,0 +1,21 @@ +import { Link } from "react-router-dom"; + +const Logo = () => ( + + {/* Desktop Full Logo */} + E-Shop Logo + + {/* Mobile Small Logo */} + E-Shop Logo + +); + +export default Logo; diff --git a/src/components/navbar/MenuLinks.jsx b/src/components/navbar/MenuLinks.jsx new file mode 100644 index 0000000..5efda84 --- /dev/null +++ b/src/components/navbar/MenuLinks.jsx @@ -0,0 +1,144 @@ +import { Link } from "react-router-dom"; +import { useState } from "react"; +import { useGetCategoryTreeQuery } from "../../features/categories/categoryApi"; + +const navItems = [ + { id: "home", label: "Home", href: "/" }, + { id: "collections", label: "Collections", href: "#", hasDropdown: true }, + { id: "wardrobe", label: "My Wardrobe", href: "/wardrobe" }, + { id: "calendar", label: "Style Calendar", href: "#" }, + { id: "trending", label: "Trending", href: "/trending" }, +]; + +// const megaMenu = { +// men: [ +// { title: "Casual Wear", items: ["T-Shirts", "Polos", "Shirts", "Jackets"] }, +// { title: "Formal Wear", items: ["Shirts", "Trousers", "Blazers"] }, +// { +// title: "Indian & Festive", +// items: ["Kurtas", "Kurta Sets", "Nehru Jackets"], +// }, +// { title: "Winterwear", items: ["Sweaters", "Jackets", "Thermals"] }, +// ], +// women: [ +// { title: "Westernwear", items: ["Dresses", "Tops", "Shrugs", "Jeans"] }, +// // { title: "Indian & Fusion", items: ["Kurtas", "Ethnic Skirts", "Sarees"] }, +// { +// title: "Indian & Fusion", +// items: [ +// { label: "Sarees", href: "/sarees" }, +// { label: "Badhni", href: "/badhni" }, +// { label: "Patola", href: "/patola" }, +// { label: "Lehengas", href: "/lehenga" }, +// ], +// }, +// { title: "Winterwear", items: ["Sweaters", "Thermals"] }, +// { title: "Athleisure", items: ["Sports Bra", "Tights", "Track Pants"] }, +// ], +// kids: [ +// { title: "Girls", items: ["Dresses", "T-Shirts", "Jeans", "Ethnic Wear"] }, +// { title: "Boys", items: ["Shirts", "Shorts", "Jeans", "Winterwear"] }, +// ], +// accessories: [ +// { title: "Footwear", items: ["Shoes", "Sandals"] }, +// { title: "Accessories", items: ["Belts", "Wallets", "Caps"] }, +// ], +// }; + +const MenuLinks = () => { + // const [megaMenu, setMegaMenu] = useState(null); + const [activeDropdown, setActiveDropdown] = useState(null); + // const [activeTab, setActiveTab] = useState("men"); + + const { data } = useGetCategoryTreeQuery(); + const categories = data?.data || []; + + const [activeTab, setActiveTab] = useState("men"); + + // find active category object + const activeCategory = categories.find((cat) => cat.slug === activeTab); + + return ( +
+ {navItems.map((item) => ( +
item.hasDropdown && setActiveDropdown(item.id)} + onMouseLeave={() => item.hasDropdown && setActiveDropdown(null)} + > + + {item.label} + + + + {/* Dropdown */} + {/* Dropdown */} + {item.hasDropdown && activeDropdown === item.id && ( + <> + {/* Invisible Hover Bridge (prevents dropdown from closing) */} +
+ +
+ {/* Tabs */} +
+ {categories.map((cat) => ( + + ))} +
+ + {/* Mega Menu Grid */} +
+ {activeCategory?.children?.map((section) => ( +
+

+ {section.name} +

+ +
    + {section.children?.map((sub) => ( +
  • + + {sub.name} + +
  • + ))} +
+
+ ))} +
+
+ + )} +
+ ))} +
+ ); +}; + +export default MenuLinks; diff --git a/src/components/navbar/MobileMenu.jsx b/src/components/navbar/MobileMenu.jsx new file mode 100644 index 0000000..d299c38 --- /dev/null +++ b/src/components/navbar/MobileMenu.jsx @@ -0,0 +1,132 @@ +import { useState } from "react"; +import { ChevronDown, ChevronUp } from "lucide-react"; +import { Link } from "react-router-dom"; + +const MobileMenu = ({ isOpen, closeMenu, navItems, megaMenu }) => { + const [openSection, setOpenSection] = useState(null); + + if (!isOpen) return null; + + const toggle = (key) => { + setOpenSection(openSection === key ? null : key); + }; + + return ( +
+
+ {/* MAIN NAV ITEMS */} + {navItems.map((item) => ( +
+ {/* If dropdown exists */} + {item.hasDropdown ? ( + <> + + + {/* DROPDOWN MEGA MENU */} +
+ {/* MEN */} + + + {/* WOMEN */} + + + {/* KIDS */} + + + {/* ACCESSORIES */} + +
+ + ) : ( + + {item.label} + + )} +
+ ))} +
+
+ ); +}; + +export default MobileMenu; + +/* --------------------------------------------- + Sub Menu Accordion (MEN / WOMEN / KIDS...) +---------------------------------------------- */ +const AccordionBlock = ({ title, items, closeMenu }) => { + const [open, setOpen] = useState(false); + + return ( +
+ + +
+ {items.map((block, idx) => ( +
+

{block.title}

+ +
+ {block.items.map((item, index) => ( + + {item.label} + + ))} +
+
+ ))} +
+
+ ); +}; diff --git a/src/components/navbar/Navbar.jsx b/src/components/navbar/Navbar.jsx new file mode 100644 index 0000000..f7675da --- /dev/null +++ b/src/components/navbar/Navbar.jsx @@ -0,0 +1,145 @@ +import React, { useState } from "react"; +import Logo from "./Logo"; +import MenuLinks from "./MenuLinks"; +import CartIcon from "./CartIcon"; +import UserIcon from "./UserIcon"; +import BellIcon from "./BellIcon"; +import MobileMenu from "./MobileMenu"; +import SearchBar from "./SearchBar"; +import TopPromoBanner from "./TopPromoBanner"; +import { Menu, X, Heart } from "lucide-react"; +import { Link } from "react-router-dom"; +import { useGetCategoryTreeQuery } from "../../features/categories/categoryApi"; +import { formatMegaMenu } from "../../utils/formatMegaMenu"; + + +// ---------------------------- +// NAV ITEMS +// ---------------------------- +const navItems = [ + { id: "home", label: "Home", href: "/" }, + { id: "collections", label: "Collections", href: "#", hasDropdown: true }, + { id: "wardrobe", label: "My Wardrobe", href: "/wardrobe" }, + { id: "calendar", label: "Style Calendar", href: "#" }, + { id: "trending", label: "Trending", href: "#" }, +]; + +// ---------------------------- +// MEGA MENU DATA +// ---------------------------- +// const megaMenu = { +// men: [ +// { title: "Casual Wear", items: ["T-Shirts", "Polos", "Shirts", "Jackets"] }, +// { title: "Formal Wear", items: ["Shirts", "Trousers", "Blazers"] }, +// { +// title: "Indian & Festive", +// items: ["Kurtas", "Kurta Sets", "Nehru Jackets"], +// }, +// { title: "Winterwear", items: ["Sweaters", "Jackets", "Thermals"] }, +// ], +// women: [ +// { title: "Westernwear", items: ["Dresses", "Tops", "Shrugs", "Jeans"] }, +// { +// title: "Indian & Fusion", +// items: [ +// { label: "Sarees", href: "/sarees" }, +// { label: "Badhni", href: "/badhni" }, +// { label: "Patola", href: "/patola" }, +// { label: "Lehengas", href: "/lehenga" }, +// ], +// }, +// { title: "Winterwear", items: ["Sweaters", "Thermals"] }, +// { title: "Athleisure", items: ["Sports Bra", "Tights", "Track Pants"] }, +// ], +// kids: [ +// { title: "Girls", items: ["Dresses", "T-Shirts", "Jeans", "Ethnic Wear"] }, +// { title: "Boys", items: ["Shirts", "Shorts", "Jeans", "Winterwear"] }, +// ], +// accessories: [ +// { title: "Footwear", items: ["Shoes", "Sandals"] }, +// { title: "Accessories", items: ["Belts", "Wallets", "Caps"] }, +// ], +// }; + +const Navbar = () => { + const [menuOpen, setMenuOpen] = useState(false); + const toggleMenu = () => setMenuOpen(!menuOpen); + + // const [menuOpen, setMenuOpen] = useState(false); + + const { data } = useGetCategoryTreeQuery(); + + const megaMenu = data ? formatMegaMenu(data.data) : {}; + + // const toggleMenu = () => setMenuOpen(!menuOpen); + + return ( + + ); +}; + +export default Navbar; diff --git a/src/components/navbar/SearchBar.jsx b/src/components/navbar/SearchBar.jsx new file mode 100644 index 0000000..d937e6a --- /dev/null +++ b/src/components/navbar/SearchBar.jsx @@ -0,0 +1,20 @@ +import { Search } from "lucide-react"; + +const SearchBar = () => { + return ( +
+
+ + +
+
+ ); +}; + +export default SearchBar; diff --git a/src/components/navbar/TopPromoBanner.jsx b/src/components/navbar/TopPromoBanner.jsx new file mode 100644 index 0000000..c97dba2 --- /dev/null +++ b/src/components/navbar/TopPromoBanner.jsx @@ -0,0 +1,124 @@ +// import React, { useState, useEffect } from "react"; +// import { Sparkles, Play, HelpCircle, ChevronRight } from "lucide-react"; + +// const lines = [ +// "🎉 Limited Time Offer – Extra 15% OFF!", +// "🔥 Shop New Winter Collection Now!", +// "💝 Use Code: FASHION15 & Save More!", +// ]; + +// const TopPromoBanner = () => { +// const [current, setCurrent] = useState(0); + +// useEffect(() => { +// const interval = setInterval(() => { +// setCurrent((prev) => (prev + 1) % lines.length); +// }, 2500); +// return () => clearInterval(interval); +// }, []); + +// return ( +//
+// {/* Shiny moving light */} +//
+ +//
+ +// {/* LEFT — Rotating Text */} +//
+// +//

+// {lines[current]} +//

+//
+ +// {/* RIGHT — Icons / Menu */} +//
+// + +// + +// +//
+//
+//
+// ); +// }; + +// export default TopPromoBanner; + + + +import React, { useState, useEffect } from "react"; +import { Sparkles, Play, HelpCircle, ChevronRight } from "lucide-react"; + +const lines = [ + "🎉 Limited Time Offer – Extra 15% OFF!", + "🔥 Shop New Winter Collection Now!", + "💝 Use Code: FASHION15 & Save More!", +]; + +const TopPromoBanner = () => { + const [current, setCurrent] = useState(0); + + useEffect(() => { + const interval = setInterval(() => { + setCurrent((prev) => (prev + 1) % lines.length); + }, 2500); + return () => clearInterval(interval); + }, []); + + return ( +
+ {/* Shiny moving light */} +
+ +
+ + + {/* LEFT — Rotating Text */} +
+ +

+ {lines[current]} +

+
+ + {/* RIGHT — MOBILE: ONLY ICONS */} +
+ + + +
+ + {/* RIGHT — DESKTOP: FULL TEXT MENU */} +
+ + + + + +
+ +
+
+ ); +}; + +export default TopPromoBanner; diff --git a/src/components/navbar/UserIcon.jsx b/src/components/navbar/UserIcon.jsx new file mode 100644 index 0000000..e367031 --- /dev/null +++ b/src/components/navbar/UserIcon.jsx @@ -0,0 +1,80 @@ +import React, { useState } from "react"; +import { User } from "lucide-react"; +import { useSelector, useDispatch } from "react-redux"; +import { Link, useNavigate } from "react-router-dom"; +import { logout } from "../../features/auth/authSlice"; + +const UserIcon = () => { + const dispatch = useDispatch(); + const navigate = useNavigate(); + const { user } = useSelector((state) => state.auth); + const [open, setOpen] = useState(false); + + const handleLogout = () => { + dispatch(logout()); + localStorage.removeItem("token"); + localStorage.removeItem("refreshToken"); + localStorage.removeItem("user"); + setOpen(false); + }; + + const handleUserClick = () => { + if (!user) { + navigate("/login"); + return; + } + setOpen(!open); + }; + + return ( +
+ {/* CLICKABLE SECTION */} +
+ + + {user && ( + + {user.name} + + )} +
+ + {/* DROPDOWN */} + {user && open && ( +
+
+

+ {`${user.firstName || ""} ${user.lastName || ""}`.trim()} +

+
+ + + Profile + + + + My Order + + + +
+ )} +
+ ); +}; + +export default UserIcon; diff --git a/src/components/navbar/Wishlist.jsx b/src/components/navbar/Wishlist.jsx new file mode 100644 index 0000000..6c7cd75 --- /dev/null +++ b/src/components/navbar/Wishlist.jsx @@ -0,0 +1,353 @@ +// import React, { useState } from "react"; +// import { +// useGetWishlistQuery, +// useRemoveFromWishlistMutation, +// } from "../../features/wishlist/wishlistApi"; +// import WishlistEmpty from "./WishlistEmpty"; +// import AddToCart from "../../pages/Cart/AddToCart"; +// import { useAddToCartMutation } from "../../features/cart/cartAPI"; +// import { useNavigate } from "react-router-dom"; + +// const Wishlist = () => { +// const navigate = useNavigate(); +// const [addToCart] = useAddToCartMutation(); +// const { data, isLoading, isError } = useGetWishlistQuery(); +// const [removeFromWishlist] = useRemoveFromWishlistMutation(); +// const [confirmModal, setConfirmModal] = useState({ +// show: false, +// productId: null, +// productName: "", +// }); + +// const wishlistItems = data?.data?.wishlist || []; + +// // Loading +// if (isLoading) +// return ( +//

+// Loading your wishlist... +//

+// ); + +// if (isError) return ; + +// // Empty +// if (wishlistItems.length === 0) return ; + +// const handleRemove = async (productId) => { +// try { +// await removeFromWishlist(productId).unwrap(); +// setConfirmModal({ show: false, productId: null, productName: "" }); +// } catch (error) { +// console.log("Remove failed", error); +// } +// }; + +// const fallbackImage = "/default-product.png"; + +// return ( +//
+//

+// My Wishlist +//

+ +//
+// {wishlistItems.map((item) => { +// const image = +// item?.product?.images?.gallery?.[0] || +// item?.product?.variants?.[0]?.images?.[0] || +// fallbackImage; + +// return ( +//
+// {/* Product Image */} +//
+// (e.target.src = fallbackImage)} +// alt={item?.product?.name || "Product"} +// className="object-cover h-full w-full transition-transform duration-500 group-hover:scale-105" +// /> + +// {/* Remove Button */} +// +//
+ +// {/* Product Info */} +//
+//

+// {item?.product?.name || "Unknown Product"} +//

+ +// {/* Price */} +// {item?.product?.basePrice && ( +//

+// ₹ {item.product.basePrice.toLocaleString()} +//

+// )} + +// {/* Added Date */} +//

+// Added on{" "} +// +// {new Date(item.createdAt).toLocaleDateString()} +// +//

+ +// {/* Add To Cart Component */} + +// {/* Buttons */} +//
+// +//
+//
+//
+// ); +// })} +//
+ +// {/* Confirmation Modal */} +// {confirmModal.show && ( +//
+//
+//

+// Are you sure you want to delete
+// +// {confirmModal.productName} +// {" "} +// from your wishlist? +//

+//
+// +// +//
+//
+//
+// )} +//
+// ); +// }; + +// export default Wishlist; + +import React, { useState } from "react"; +import { + useGetWishlistQuery, + useRemoveFromWishlistMutation, +} from "../../features/wishlist/wishlistApi"; +import { useAddToCartMutation } from "../../features/cart/cartAPI"; +import WishlistEmpty from "./WishlistEmpty"; +import { useNavigate } from "react-router-dom"; + +const Wishlist = () => { + const navigate = useNavigate(); + + const { data, isLoading, isError } = useGetWishlistQuery(); + const [addToCart] = useAddToCartMutation(); + const [removeFromWishlist] = useRemoveFromWishlistMutation(); + + const [confirm, setConfirm] = useState(null); + + const wishlist = data?.data?.wishlist || []; + const fallbackImage = "/default-product.png"; + + if (isLoading) { + return ( +
+ Loading wishlist… +
+ ); + } + + if (isError || wishlist.length === 0) { + return ; + } + + const handleAddToCart = async (item) => { + const token = localStorage.getItem("token"); + + if (!token) { + alert("Please login first to add product to cart"); + navigate("/login"); + return; + } + + try { + await addToCart({ + productId: item.productId, + quantity: 1, + }).unwrap(); + + await removeFromWishlist(item.productId).unwrap(); + navigate("/cart"); + } catch (err) { + console.error(err); + } + }; + + return ( +
+

+ My Wishlist ({wishlist.length} items) +

+ +
+ {wishlist.map((item) => { + const image = + item?.product?.images?.gallery?.[0] || + item?.product?.variants?.[0]?.images?.[0] || + fallbackImage; + + return ( +
+ {/* Image */} +
+ (e.target.src = fallbackImage)} + alt={item?.product?.name} + className="h-full w-full object-contain p-6" + /> + + {/* Remove */} + +
+ + {/* Content */} +
+

+ {item?.product?.category || "Product"} +

+ +

+ {item?.product?.name} +

+ +
+

+ ₹ {item?.product?.basePrice?.toLocaleString()} +

+ + +
+
+
+ ); + })} +
+ + {/* Confirm Modal */} + {confirm && ( +
+
+

+ Remove item? +

+

+ Are you sure you want to remove{" "} + + {confirm.product?.name} + {" "} + from your wishlist? +

+ +
+ + +
+
+
+ )} +
+ ); +}; + +export default Wishlist; diff --git a/src/components/navbar/WishlistEmpty.jsx b/src/components/navbar/WishlistEmpty.jsx new file mode 100644 index 0000000..525f857 --- /dev/null +++ b/src/components/navbar/WishlistEmpty.jsx @@ -0,0 +1,26 @@ +import React from "react"; +import { Link } from "react-router-dom"; +import Button from "../common/Button"; + +const WishlistEmpty = () => { + return ( +
+ Empty Wishlist +

+ Your Wishlist is Empty +

+

+ Tap the heart icon on products to save them for later. +

+ + + +
+ ); +}; + +export default WishlistEmpty; diff --git a/src/components/order/CancelOrderButton.jsx b/src/components/order/CancelOrderButton.jsx new file mode 100644 index 0000000..8fa4f8f --- /dev/null +++ b/src/components/order/CancelOrderButton.jsx @@ -0,0 +1,36 @@ +import { useCancelOrderMutation } from "../../features/orders/ordersApi"; + +const CancelOrderButton = ({ orderId, status }) => { + const [cancelOrder, { isLoading }] = useCancelOrderMutation(); + + const isCancelable = ["PENDING", "CONFIRMED"].includes(status); + + const handleCancel = async () => { + if (!window.confirm("Are you sure you want to cancel this order?")) return; + + try { + await cancelOrder(orderId).unwrap(); + alert("Order cancelled successfully"); + } catch (err) { + alert(err?.data?.message || "Failed to cancel order"); + } + }; + + return ( + + ); +}; + +export default CancelOrderButton; diff --git a/src/components/order/DeliveryAndPriceCard.jsx b/src/components/order/DeliveryAndPriceCard.jsx new file mode 100644 index 0000000..3015343 --- /dev/null +++ b/src/components/order/DeliveryAndPriceCard.jsx @@ -0,0 +1,134 @@ +import { FaHome, FaUser, FaInfoCircle } from "react-icons/fa"; + +const DeliveryAndPriceCard = ({ + order, + setShowAddressModal, + setShowUserModal, +}) => { + return ( +
+ {/* Delivery Details */} +
+

+ Delivery details +

+ + {/* Address */} +
setShowAddressModal(true)} + > +
+ +

+ {order.address.addressLine1}, {order.address.addressLine2},{" "} + {order.address.city}... +

+
+ {">"} +
+ + {/* User */} +
setShowUserModal(true)} + > +
+ +

+ {order.address.firstName} {order.address.lastName}{" "} + {order.address.phone} +

+
+ {">"} +
+
+ + {/* Price Details */} +
+

Price details

+ +
+
+ Listing price + + ₹{Number(order.listingPrice || order.subtotal).toFixed(2)} + +
+
+ + Special price{" "} + + + + ₹{Number(order.totalAmount).toFixed(2)} + +
+
+ Shipping price + + ₹{Number(order.shippingAmount || order.shippingAmount).toFixed(2)} + +
+ +
+ Discount price + + ₹{Number(order.discountAmount || order.discountAmount).toFixed(2)} + +
+ {order.taxAmount && ( +
+ Total fees + ₹{Number(order.taxAmount).toFixed(2)} +
+ )} + {order.discountAmount > 0 && ( +
+ Other discount + -₹{Number(order.discountAmount).toFixed(2)} +
+ )} +
+ +
+ Total amount + + ₹{Number(order.totalAmount).toFixed(2)} + +
+ + {/* Payment Method */} +
+ +
+ Payment method + + + + + + Cash On Delivery + +
+
+ ); +}; + +export default DeliveryAndPriceCard; diff --git a/src/components/order/MyOrders.jsx b/src/components/order/MyOrders.jsx new file mode 100644 index 0000000..13bf522 --- /dev/null +++ b/src/components/order/MyOrders.jsx @@ -0,0 +1,457 @@ +// import React, { useState } from "react"; +// import { useGetOrdersQuery } from "../../features/orders/ordersApi"; + +// const statusColor = { +// PENDING: "bg-yellow-100 text-yellow-800", +// PAID: "bg-blue-100 text-blue-800", +// SHIPPED: "bg-purple-100 text-purple-800", +// DELIVERED: "bg-green-100 text-green-800", +// CANCELLED: "bg-red-100 text-red-800", +// RETURNED: "bg-gray-100 text-gray-800", +// }; + +// // LEFT FILTERS +// const OrderFilters = ({ filters, setFilters }) => { +// const toggleStatus = (status) => { +// if (filters.status.includes(status)) { +// setFilters({ +// ...filters, +// status: filters.status.filter((s) => s !== status), +// }); +// } else { +// setFilters({ ...filters, status: [...filters.status, status] }); +// } +// }; + +// return ( +//
+//

Filter Orders

+ +//
+//

Status

+//
+// {["PENDING", "ON_THE_WAY", "DELIVERED", "CANCELLED", "RETURNED"].map( +// (status) => ( +// +// ) +// )} +//
+//
+ +//
+//

Order Time

+// +//
+//
+// ); +// }; + +// // ORDER CARD +// const OrderCard = ({ order }) => { +// return ( +//
+// {/* Header */} +//
+//
+//

Order ID

+//

{order.orderNumber}

+//

+// {new Date(order.createdAt).toLocaleString()} +//

+//
+ +//
+// +// {order.status.replace("_", " ")} +// +// +// ₹{Number(order.totalAmount).toFixed(2)} +// +//
+//
+ +// {/* Items */} +//
+// {order.items.map((item) => ( +//
+//
+//

{item.productName}

+//

Qty: {item.quantity}

+//
+//

+// ₹{Number(item.price).toFixed(2)} +//

+//
+// ))} +//
+ +// {/* Footer */} +//
+//
+//

Shipping Address

+//

+// {order.address.firstName} {order.address.lastName},{" "} +// {order.address.addressLine1}, {order.address.city},{" "} +// {order.address.state} - {order.address.postalCode} +//

+//
+ +//
+//

+// Payment:{" "} +// {order.paymentMethod} +//

+//

+// Payment Status:{" "} +// {order.paymentStatus} +//

+//
+//
+//
+// ); +// }; + +// // MAIN COMPONENT +// const MyOrders = () => { +// const { data, isLoading, isError } = useGetOrdersQuery({ +// page: 1, +// limit: 50, +// }); + +// const [filters, setFilters] = useState({ +// status: [], +// time: "30_DAYS", +// }); + +// const orders = data?.orders || []; + +// const filteredOrders = orders.filter((order) => { +// if (filters.status.length && !filters.status.includes(order.status)) return false; + +// const orderDate = new Date(order.createdAt); +// const now = new Date(); + +// if (filters.time === "30_DAYS" && (now - orderDate) / (1000 * 60 * 60 * 24) > 30) +// return false; +// if (filters.time === "2024" && orderDate.getFullYear() !== 2024) return false; +// if (filters.time === "2023" && orderDate.getFullYear() !== 2023) return false; + +// return true; +// }); + +// if (isLoading) +// return ( +//
+// Loading orders... +//
+// ); + +// if (isError || !orders.length) +// return ( +//
+// No orders found +//
+// ); + +// return ( +//
+//

My Orders

+ +//
+// {/* Filters */} +// + +// {/* Orders */} +//
+// {filteredOrders.length === 0 ? ( +//
+// No orders match your filters +//
+// ) : ( +// filteredOrders.map((order) => ) +// )} +//
+//
+//
+// ); +// }; + +// export default MyOrders; + +import React, { useState } from "react"; +import { useGetOrdersQuery } from "../../features/orders/ordersApi"; +import { useNavigate } from "react-router-dom"; +import { statusColor } from "../../constants/statusColor"; + +// const statusColor = { +// PENDING: "bg-yellow-100 text-yellow-800", +// PAID: "bg-blue-100 text-blue-800", +// SHIPPED: "bg-purple-100 text-purple-800", +// DELIVERED: "bg-green-100 text-green-800", +// CANCELLED: "bg-red-100 text-red-800", +// RETURNED: "bg-gray-100 text-gray-800", +// }; + +// LEFT FILTERS +const OrderFilters = ({ filters, setFilters }) => { + const toggleStatus = (status) => { + if (filters.status.includes(status)) { + setFilters({ + ...filters, + status: filters.status.filter((s) => s !== status), + }); + } else { + setFilters({ ...filters, status: [...filters.status, status] }); + } + }; + + return ( +
+

Filter Orders

+ +
+

Status

+
+ {["PENDING", "ON_THE_WAY", "DELIVERED", "CANCELLED", "RETURNED"].map( + (status) => ( + + ) + )} +
+
+ +
+

Order Time

+ +
+
+ ); +}; + +// ORDER CARD +const OrderCard = ({ order }) => { + const navigate = useNavigate(); + return ( +
navigate(`/my-orders/${order.id}`)} + > + {/* Header */} +
+
+

Order ID

+

{order.orderNumber}

+

+ {new Date(order.createdAt).toLocaleString()} +

+
+ +
+ + {order.status.replace("_", " ")} + + + ₹{Number(order.totalAmount).toFixed(2)} + +
+
+ + {/* Items */} +
+ {order.items.map((item) => ( +
+
+

{item.productName}

+

Qty: {item.quantity}

+
+

+ ₹{Number(item.price).toFixed(2)} +

+
+ ))} +
+ + {/* Footer */} +
+
+

Shipping Address

+

+ {order.address.firstName} {order.address.lastName},{" "} + {order.address.addressLine1}, {order.address.city},{" "} + {order.address.state} - {order.address.postalCode} +

+
+ +
+

+ Payment: {order.paymentMethod} +

+

+ Payment Status:{" "} + {order.paymentStatus} +

+
+
+
+ ); +}; + +// MAIN COMPONENT +const MyOrders = () => { + const { data, isLoading, isError } = useGetOrdersQuery({ + page: 1, + limit: 50, + }); + + const [filters, setFilters] = useState({ + status: [], + time: "30_DAYS", + }); + + const [searchQuery, setSearchQuery] = useState(""); + + const orders = data?.orders || []; + + const filteredOrders = orders + .filter((order) => { + if (filters.status.length && !filters.status.includes(order.status)) + return false; + + const orderDate = new Date(order.createdAt); + const now = new Date(); + + if ( + filters.time === "30_DAYS" && + (now - orderDate) / (1000 * 60 * 60 * 24) > 30 + ) + return false; + if (filters.time === "2024" && orderDate.getFullYear() !== 2024) + return false; + if (filters.time === "2023" && orderDate.getFullYear() !== 2023) + return false; + + return true; + }) + .filter((order) => { + // SEARCH FILTER + if (!searchQuery) return true; + const q = searchQuery.toLowerCase(); + const inOrderId = order.orderNumber.toLowerCase().includes(q); + const inCustomer = `${order.address.firstName} ${order.address.lastName}` + .toLowerCase() + .includes(q); + const inProduct = order.items.some((item) => + item.productName.toLowerCase().includes(q) + ); + return inOrderId || inCustomer || inProduct; + }); + + if (isLoading) + return ( +
+ Loading orders... +
+ ); + + if (isError || !orders.length) + return ( +
+ No orders found +
+ ); + + return ( +
+

My Orders

+ + {/* SEARCH BAR */} + {/* SEARCH BAR */} +
+
+ setSearchQuery(e.target.value)} + placeholder="Search by Order ID, Product, or Customer" + className="flex-1 border border-gray-300 rounded-l-xl px-4 py-3 shadow-sm focus:outline-none focus:ring-1 focus:ring-orange-700" + /> + +
+
+ +
+ {/* Filters */} + + + {/* Orders */} +
+ {filteredOrders.length === 0 ? ( +
+ No orders match your filters +
+ ) : ( + filteredOrders.map((order) => ( + + )) + )} +
+
+
+ ); +}; + +export default MyOrders; diff --git a/src/components/order/OrderDetails.jsx b/src/components/order/OrderDetails.jsx new file mode 100644 index 0000000..c9a5b5b --- /dev/null +++ b/src/components/order/OrderDetails.jsx @@ -0,0 +1,13 @@ +const OrderDetails = ({ order }) => { + return ( +
+

Order ID: {order.orderId}

+

Total Amount: ₹{order.total}

+

Payment: {order.paymentMethod}

+

Address: {order.address}

+

ETA: {order.eta}

+
+ ); +}; + +export default OrderDetails; diff --git a/src/components/order/OrderDetailsPage.jsx b/src/components/order/OrderDetailsPage.jsx new file mode 100644 index 0000000..97e6c84 --- /dev/null +++ b/src/components/order/OrderDetailsPage.jsx @@ -0,0 +1,395 @@ +import React, { useState } from "react"; +import { skipToken } from "@reduxjs/toolkit/query"; +import { useParams, Link } from "react-router-dom"; +import { + useGetOrderByIdQuery, + useGetOrderTrackingQuery, +} from "../../features/orders/ordersApi"; +import { + FaBoxOpen, + FaShippingFast, + FaTruck, + FaCheckCircle, + FaMapMarkerAlt, +} from "react-icons/fa"; + +import { statusColor } from "../../constants/statusColor"; +import { FaInfoCircle, FaUser } from "react-icons/fa"; +import DeliveryAndPriceCard from "./DeliveryAndPriceCard"; +import CancelOrderButton from "./CancelOrderButton"; + +const getStepIcon = (title) => { + const lower = title.toLowerCase(); + + if (lower.includes("placed") || lower.includes("confirmed")) return "📦"; + if (lower.includes("shipped")) return "🚚"; + if (lower.includes("out")) return "🚛"; + if (lower.includes("delivered")) return "✅"; + + return "📦"; +}; + +const OrderDetailsPage = () => { + const { id } = useParams(); + const { data, isLoading, isError } = useGetOrderByIdQuery(id); + const [showAddressModal, setShowAddressModal] = useState(false); + const [showUserModal, setShowUserModal] = useState(false); + const [showAllUpdates, setShowAllUpdates] = useState(false); + + const order = data?.data?.order; + + const { data: trackingData } = useGetOrderTrackingQuery( + order + ? { + orderId: order.id, + pincode: order.address?.postalCode, + shippingMethod: "STANDARD", + } + : skipToken, + ); + + const fullTimeline = trackingData?.data?.tracking?.timeline || []; + + const visibleTimeline = showAllUpdates + ? fullTimeline + : fullTimeline.slice(0, 3); + + // ⛔ Returns must come AFTER all hooks + if (isLoading) + return

Loading order...

; + + if (isError) + return ( +

+ Failed to load order. +

+ ); + if (!order) return null; + + const orderSteps = ["PENDING", "SHIPPED", "DELIVERED"]; + const currentStepIndex = orderSteps.indexOf(order.status); + + return ( +
+ + ← Back to Orders + + +

+ {order.orderNumber} +

+

+ {new Date(order.createdAt).toLocaleString("en-IN", { + day: "2-digit", + month: "2-digit", + year: "numeric", + })} + + + {order.status.replace("_", " ")} + +

+ +
+ {/* LEFT: Order Items + Track + Actions */} + {/* LEFT: Order Items + Track + Actions */} +
+ {/* Payment / Total Section */} + {/*
+

+ Pay online for a smooth doorstep experience +

+ +
*/} + + {/* Order Items */} +
+ {order.items.map((item) => ( +
+
+

+ {item.productName} +

+

+ {item.quantity}, {item.color || "Black"} +

+

+ Seller: {item.seller || "N/A"} +

+
+ {item.productName} +
+ ))} +

+ ₹{Number(order.totalAmount).toFixed(2)}{" "} + + {order.offer || "1 offer"} + +

+
+ {fullTimeline.length > 0 && ( +
+ {/* Header */} +
+

+ 📦 Track Your Order +

+ + {fullTimeline.length > 3 && ( + + )} +
+ + {/* Timeline Wrapper */} +
+ {/* ✅ PERFECT CENTERED LINE */} + +
    + {visibleTimeline.map((step, index) => { + const isCompleted = step.completed; + + const isCurrent = + !step.completed && fullTimeline[index - 1]?.completed; + + return ( +
  • + {/* ✅ Connector Line (NOT for last step) */} + {index !== visibleTimeline.length - 1 && ( +
    + )} + {/* Icon Circle */} +
    + {getStepIcon(step.title)} +
    + + {/* Content */} +
    +
    +

    + {step.title} +

    + + {step.timestamp && ( + + {new Date(step.timestamp).toLocaleString( + "en-IN", + { + day: "2-digit", + month: "short", + hour: "2-digit", + minute: "2-digit", + }, + )} + + )} +
    + +

    + {step.description} +

    + + {/* Current Badge */} + {isCurrent && ( + + In Progress + + )} + + {/* Delivered UI */} + {step.title.toLowerCase().includes("delivered") && + step.completed && ( +
    + 🎉 Your package has been delivered + successfully! +
    + )} +
    +
  • + ); + })} +
+
+
+ )} +
+ + {/* */} + {/* */} + +
+ {/* Cancel Order */} + + + {/* Chat with Us */} + +
+
+
+ + {/* RIGHT: Delivery Address + User Details */} + {/* */} + +
+ + {/* ADDRESS MODAL */} + {showAddressModal && ( +
+
+ {/* Header */} +
+

+ Delivery Address +

+ +
+ + {/* Content */} +
+ {/* Address Card */} +
+

+ {order.address.addressLine1} + {order.address.addressLine2 && + `, ${order.address.addressLine2}`} +

+

+ {order.address.city}, {order.address.state} –{" "} + + {order.address.postalCode} + +

+

{order.address.country}

+
+
+
+
+ )} + + {/* USER MODAL */} + {showUserModal && ( +
+
+ {/* Header */} +
+

+ User Details +

+ +
+ + {/* Content */} +
+
+ {/* Name */} +
+
+ {order.address.firstName?.[0]} + {order.address.lastName?.[0]} +
+
+

Full Name

+

+ {order.address.firstName} {order.address.lastName} +

+
+
+ +
+ + {/* Phone */} +
+

Phone

+

+ {order.address.phone} +

+
+ + {/* Email */} +
+

Email

+

+ {order.address.email || "N/A"} +

+
+
+
+
+
+ )} +
+ ); +}; + +export default OrderDetailsPage; diff --git a/src/components/order/OrderFilters.jsx b/src/components/order/OrderFilters.jsx new file mode 100644 index 0000000..3fcb969 --- /dev/null +++ b/src/components/order/OrderFilters.jsx @@ -0,0 +1,72 @@ +const OrderFilters = ({ filters, setFilters }) => { + return ( +
+ {/* Order Status */} +
+

Order Status

+ {["ON_THE_WAY", "DELIVERED", "CANCELLED", "RETURNED"].map((status) => ( + + ))} +
+ + {/* Order Time */} +
+

Order Time

+ + + + + + +
+
+ ); +}; + +export default OrderFilters; diff --git a/src/components/order/OrderSuccessCard.jsx b/src/components/order/OrderSuccessCard.jsx new file mode 100644 index 0000000..51862cf --- /dev/null +++ b/src/components/order/OrderSuccessCard.jsx @@ -0,0 +1,18 @@ +import { CheckCircle } from "lucide-react"; + +const OrderSuccessCard = ({ children }) => { + return ( +
+ +

+ Order Placed Successfully 🎉 +

+

+ Thank you for your purchase. Your order has been confirmed. +

+ {children} +
+ ); +}; + +export default OrderSuccessCard; diff --git a/src/components/order/OrderTrackingTimeline.jsx b/src/components/order/OrderTrackingTimeline.jsx new file mode 100644 index 0000000..a4f99a6 --- /dev/null +++ b/src/components/order/OrderTrackingTimeline.jsx @@ -0,0 +1,251 @@ +// import { CheckCircle, Truck, Package } from "lucide-react"; + +// const steps = [ +// { +// id: 1, +// title: "Order Confirmed", +// desc: "We have received your order", +// icon: CheckCircle, +// }, +// { +// id: 2, +// title: "Shipped", +// desc: "Your order is on the way", +// icon: Truck, +// }, +// { +// id: 3, +// title: "Out for Delivery", +// desc: "Courier is near your location", +// icon: Package, +// }, +// { +// id: 4, +// title: "Delivered", +// desc: "Package delivered successfully", +// icon: CheckCircle, +// }, +// ]; + +// const OrderTrackingTimeline = ({ timeline = [] }) => { +// if (!timeline.length) { +// return ( +//
+// No tracking updates available. +//
+// ); +// } + +// return ( +//
+// {timeline.map((step, index) => { +// const isActive = step.completed; + +// return ( +//
+// {/* Vertical Line */} +// {index !== timeline.length - 1 && ( +//
+// )} + +// {/* Icon */} +//
+// {step.icon || "📦"} +//
+ +// {/* Content */} +//
+//

{step.title}

+//

+// {step.description} +//

+// {step.timestamp && ( +//

+// {new Date(step.timestamp).toLocaleString()} +//

+// )} +//
+//
+// ); +// })} +//
+// ); +// }; + +// export default OrderTrackingTimeline; + +// OrderTrackingTimeline.jsx - YOUR BRAND COLORS + STUNNING DESIGN 🎨 + +import React, { useState, useEffect } from "react"; +import { + Check, + Package, + Clock, + MapPin, + Sparkles, + TrendingUp, +} from "lucide-react"; + +const OrderTrackingTimeline = ({ timeline = [] }) => { + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + }, []); + + if (!timeline.length) { + return ( +
+
+
+
+ +
+

+ Awaiting Updates +

+

+ Your tracking information will appear here +

+
+
+ ); + } + + const completedCount = timeline.filter((s) => s.completed).length; + const progress = (completedCount / timeline.length) * 100; + + return ( +
+ {/* Compact Progress Header */} +
+
+
+
+ +
+
+

+ Delivery Progress +

+

Real-time tracking

+
+
+ +
+
+ {Math.round(progress)}% +
+
+
+ + {/* Slim Progress Bar */} +
+
+
+
+ + {/* Timeline */} +
+ {timeline.map((step, index) => { + const isCompleted = step.completed; + const isLast = index === timeline.length - 1; + + return ( +
+ {/* Connector Line */} + {!isLast && ( +
+
+
+ )} + + {/* Card */} +
+ {/* Icon */} +
+ {isCompleted ? ( + + ) : ( + + {step.icon || "📦"} + + )} +
+ + {/* Content */} +
+

+ {step.title} +

+ +

+ {step.description} +

+ + {step.timestamp && ( +

+ {new Date(step.timestamp).toLocaleString("en-IN")} +

+ )} +
+
+
+ ); + })} +
+
+ ); +}; + +export default OrderTrackingTimeline; diff --git a/src/components/order/SuccessActions.jsx b/src/components/order/SuccessActions.jsx new file mode 100644 index 0000000..d62d003 --- /dev/null +++ b/src/components/order/SuccessActions.jsx @@ -0,0 +1,28 @@ +import { Truck, Home } from "lucide-react"; +import { useNavigate } from "react-router-dom"; + +const SuccessActions = ({ orderId }) => { + const navigate = useNavigate(); + + return ( +
+ + + +
+ ); +}; + +export default SuccessActions; diff --git a/src/components/order/TrackingStep.jsx b/src/components/order/TrackingStep.jsx new file mode 100644 index 0000000..6804fe7 --- /dev/null +++ b/src/components/order/TrackingStep.jsx @@ -0,0 +1,67 @@ +// const TrackingStep = ({ icon: Icon, label, active }) => { +// return ( +//
+// +// +// {label} +// +//
+// ); +// }; + +// export default TrackingStep; + + + +const TrackingStep = ({ icon: Icon, label, active, current }) => { + return ( +
+ {/* Icon Circle */} +
+ +
+ + {/* Content */} +
+ + {label} + + + {current && ( + + Current Status + + )} +
+
+ ); +}; + +export default TrackingStep; diff --git a/src/components/product/PinCodeCheck.jsx b/src/components/product/PinCodeCheck.jsx new file mode 100644 index 0000000..ba33438 --- /dev/null +++ b/src/components/product/PinCodeCheck.jsx @@ -0,0 +1,109 @@ +// import { useState } from "react"; + +// const PinCodeCheck = () => { +// const [pin, setPin] = useState(""); +// const [isChecked, setIsChecked] = useState(false); + +// const handleCheck = () => { +// // Simulate pin code check +// setIsChecked(true); +// }; + +// return ( +//
+//

Check Delivery & Exchange

+//
+// setPin(e.target.value)} +// className="border px-4 py-2 rounded-lg flex-1" +// /> +// +//
+ +// {isChecked && ( +//
+//
+// Logo +// Free delivery within 2-3 days +//
+//
Easy Exchange in 10 days
+//
+// )} +//
+// ); +// }; + +// export default PinCodeCheck; + + + + +import { useState } from "react"; +import { MapPin, Truck, RotateCcw } from "lucide-react"; + +const PinCodeCheck = () => { + const [pin, setPin] = useState(""); + const [isChecked, setIsChecked] = useState(false); + + const handleCheck = () => { + setIsChecked(true); + }; + + return ( +
+ {/* Title */} +

+ + Check Delivery & Exchange +

+ + {/* Input + Button */} +
+ setPin(e.target.value)} + className="border border-gray-300 px-4 py-3 rounded-lg flex-1 focus:ring-2 focus:ring-primary-default focus:border-primary-default outline-none transition" + /> + +
+ + {/* Result Box */} + {isChecked && ( +
+ {/* Delivery */} +
+ + + Free delivery within 2–3 days + +
+ + {/* Exchange */} +
+ + + Easy Exchange within 10 days + +
+
+ )} +
+ ); +}; + +export default PinCodeCheck; diff --git a/src/components/product/ProductCategoryPage.jsx b/src/components/product/ProductCategoryPage.jsx new file mode 100644 index 0000000..6fd4f94 --- /dev/null +++ b/src/components/product/ProductCategoryPage.jsx @@ -0,0 +1,292 @@ +import React, { useState } from "react"; +import { useParams, Link } from "react-router-dom"; +import Pagination from "../ui/Pagination"; +import Breadcrumb from "../ui/Breadcrumb"; +import CategoryGrid from "../home/Category/CategoryGrid"; +import SareeFilters from "../sarees/SareeFilters"; +import { useGetProductsByCategoryQuery } from "../../features/products/productsAPI"; +import { + useAddToWishlistMutation, + useRemoveFromWishlistMutation, + useGetWishlistQuery, +} from "../../features/wishlist/wishlistApi"; + +import { Heart, Eye } from "lucide-react"; + +const ProductCategoryPage = () => { + const { parent, category, subCategory } = useParams(); + const categorySlug = subCategory || category; + + const { data: wishlistData } = useGetWishlistQuery(); + const [addToWishlist] = useAddToWishlistMutation(); + const [removeFromWishlist] = useRemoveFromWishlistMutation(); + + const wishlistIds = + wishlistData?.data?.wishlist?.map( + (item) => item.product?._id || item.productId, + ) || + wishlistData?.data?.products?.map((item) => item._id) || + []; + + const [currentPage, setCurrentPage] = useState(1); + const itemsPerPage = 12; + + const { data, isLoading, isError } = useGetProductsByCategoryQuery({ + categorySlug, + limit: itemsPerPage, + skip: (currentPage - 1) * itemsPerPage, + }); + + const handleWishlist = async (e, productId) => { + e.preventDefault(); + e.stopPropagation(); + + try { + if (wishlistIds.includes(productId)) { + await removeFromWishlist(productId).unwrap(); + } else { + await addToWishlist(productId).unwrap(); + } + } catch (error) { + console.error("Wishlist error:", error); + } + }; + + const calculateDiscount = (basePrice, comparePrice) => { + if (!comparePrice || comparePrice <= basePrice) return 0; + return Math.round(((comparePrice - basePrice) / comparePrice) * 100); + }; + + // Calculate number of offers (mock data - you can replace with real logic) + const getOffersCount = () => { + return Math.floor(Math.random() * 3) + 1; // Random 1-3 offers + }; + + if (isLoading) { + return ( +
+
+
+

Loading products...

+
+
+ ); + } + + if (isError) { + return ( +
+
+

+ Unable to load products +

+

Please try again later

+
+
+ ); + } + + const products = data?.data?.products || []; + const totalProducts = data?.data?.totalCount || products.length; + const totalPages = Math.ceil(totalProducts / itemsPerPage); + + return ( +
+ {/* Header */} +
+
+ + + +
+
+
+ +

+ {categorySlug + .replace(/-/g, " ") + .replace(/\b\w/g, (char) => char.toUpperCase())}{" "} + Collection +

+ +

+ Explore our newest and most loved products curated just for you. +

+ + {/* Filters */} + +
+
+ + {/* Products Grid */} +
+ {products.length > 0 ? ( +
+ {products.map((product) => { + const discount = calculateDiscount( + product.basePrice, + product.compareAtPrice, + ); + const offersCount = getOffersCount(); + + return ( + + {/* Image Container */} +
+ {product.name} + + {/* Badges */} +
+ {product.isFeatured && ( +
+ NEW +
+ )} +
+ +
+ {/* Offers Badge */} + {offersCount > 0 && ( +
+ {offersCount} OFFER{offersCount > 1 ? "S" : ""} FOR + YOU +
+ )} +
+ + {/* Action Buttons */} +
+ {/* Quick View */} + + {/* Wishlist */} + +
+
+ + {/* Product Info */} +
+ {/* Brand/Category */} +

+ {product.brand || categorySlug.replace(/-/g, " ")} +

+ + {/* Product Name */} +

+ {product.name} +

+ +

+ {product.description} +

+ + {/* Price */} +
+ + ₹{product.basePrice.toLocaleString()} + + {product.compareAtPrice && ( + + ₹{product.compareAtPrice.toLocaleString()} + + )} + {/* Discount */} + {discount > 0 && ( +

+ {discount}% OFF +

+ )} +
+ + {/* Color Variants */} + {product.variants?.length > 0 && ( +
+ {product.variants.slice(0, 5).map((variant, idx) => { + const colorValue = + variant.color?.toLowerCase() || "#e5e7eb"; + return ( +
+ ); + })} + {product.variants.length > 5 && ( + + +{product.variants.length - 5} + + )} +
+ )} +
+ + ); + })} +
+ ) : ( +
+
+ + + +
+

+ No products found +

+

Try adjusting your filters

+
+ )} + + {/* Pagination */} + {totalPages > 1 && ( +
+ +
+ )} +
+
+ ); +}; + +export default ProductCategoryPage; diff --git a/src/components/product/ProductDeclaration.jsx b/src/components/product/ProductDeclaration.jsx new file mode 100644 index 0000000..ef516be --- /dev/null +++ b/src/components/product/ProductDeclaration.jsx @@ -0,0 +1,12 @@ +const ProductDeclaration = () => ( +
+

Product Declaration

+

+ Manufacturer: Vedant Fashions Limited +
+ Address: Paridhan Garment Park, 19, Canal South Road, SDF-1, 4th Floor, A501-A502, Kolkata, West Bengal 700015, India +

+
+); + +export default ProductDeclaration; diff --git a/src/components/product/ProductDetails.jsx b/src/components/product/ProductDetails.jsx new file mode 100644 index 0000000..55fb5ca --- /dev/null +++ b/src/components/product/ProductDetails.jsx @@ -0,0 +1,117 @@ +import { useParams } from "react-router-dom"; +import { useEffect } from "react"; +import { useDispatch } from "react-redux"; +import Breadcrumb from "../../components/ui/Breadcrumb"; +import ProductImages from "./ProductImages"; +import ProductInfo from "./ProductInfo"; +import ScrollToTop from "../ui/ScrollToTop"; +import { RatingPage } from "../../pages/RatingPage"; +import { useGetProductBySlugQuery } from "../../features/products/productsAPI"; +import { + useAddToWishlistMutation, + useRemoveFromWishlistMutation, + useGetWishlistQuery, +} from "../../features/wishlist/wishlistApi"; +import { addRecentProduct } from "../../features/recentlyViewed/recentlyViewedApi"; +import RecommendedProducts from "./RecommendedProducts"; +import RecommendedForYou from "../home/RecommendedForYou/RecommendedForYou"; + +const ProductDetails = () => { + const { slug } = useParams(); + const dispatch = useDispatch(); + + const { data, isLoading, isError } = useGetProductBySlugQuery(slug); + const { data: wishlistData } = useGetWishlistQuery(); + + const [addToWishlist] = useAddToWishlistMutation(); + const [removeFromWishlist] = useRemoveFromWishlistMutation(); + // const product = data?.data?.product; + + // ✅ Track product view with proper error handling + useEffect(() => { + if (data?.data?.product) { + const product = data.data.product; + + // ✅ Ensure all required fields exist and are valid + const productToSave = { + id: product._id, + name: product.name || "Unnamed Product", + image: + product.images?.primary || + (Array.isArray(product.images?.gallery) && + product.images.gallery[0]) || + product.image || + "/placeholder.jpg", + price: + product.basePrice != null && !isNaN(product.basePrice) + ? parseFloat(product.basePrice) + : 0, + url: `/product/${slug}`, + }; + console.log("Recently viewed product:", productToSave); + + // ✅ Debug log to see what's being saved + console.log("Saving product to recently viewed:", productToSave); + + dispatch(addRecentProduct(productToSave)); + } + }, [data, dispatch, slug]); + + if (isLoading) return

Loading product...

; + if (isError || !data?.data?.product) + return

Product not found

; + + const product = data.data.product; + + const wishlistIds = + wishlistData?.data?.wishlist?.map( + (item) => item.product?._id || item.productId, + ) || []; + + const isWishlisted = wishlistIds.includes(product._id); + + const toggleWishlist = async () => { + try { + if (isWishlisted) { + await removeFromWishlist(product._id).unwrap(); + } else { + await addToWishlist(product._id).unwrap(); + } + } catch (err) { + console.error("Wishlist error:", err); + } + }; + + return ( +
+ + +
+
+ + +
+ +
+ +
+
+ + + +
+ +
+
+ ); +}; + +export default ProductDetails; diff --git a/src/components/product/ProductDetailsSection.jsx b/src/components/product/ProductDetailsSection.jsx new file mode 100644 index 0000000..a9dbdea --- /dev/null +++ b/src/components/product/ProductDetailsSection.jsx @@ -0,0 +1,70 @@ +const ProductDetailsSection = ({ product }) => { + // Prepare weight & dimensions display + const weight = product.weight?.value + ? `${product.weight.value} ${product.weight.unit}` + : null; + const dimensions = + product.dimensions?.length && + product.dimensions?.width && + product.dimensions?.height + ? `${product.dimensions.length} x ${product.dimensions.width} x ${product.dimensions.height} ${product.dimensions.unit}` + : null; + + return ( +
+

Product Details

+ +
+ {/* Weight */} + {weight && ( +
+

Weight

+

{weight}

+
+ )} + + {/* Dimensions */} + {dimensions && ( +
+

Dimensions

+

{dimensions}

+
+ )} + + {/* Tags */} + {product.tags?.length > 0 && ( +
+

Tags

+

{product.tags.join(", ")}

+
+ )} + + {/* Meta Keywords */} + {product.metaKeywords?.length > 0 && ( +
+

Meta Keywords

+

{product.metaKeywords.join(", ")}

+
+ )} +
+ + {/* Description */} +
+

Description

+

+ {product.description} +

+
+ + {/* Disclaimer */} +
+

Disclaimer

+

+ Product color may slightly vary due to photographic lighting sources or screen settings. +

+
+
+ ); +}; + +export default ProductDetailsSection; diff --git a/src/components/product/ProductImages.jsx b/src/components/product/ProductImages.jsx new file mode 100644 index 0000000..765a8f7 --- /dev/null +++ b/src/components/product/ProductImages.jsx @@ -0,0 +1,120 @@ +// import { useState } from "react"; +// import { Heart } from "lucide-react"; + +// const ProductImages = ({ product }) => { +// const [mainImg, setMainImg] = useState( +// product?.images ? product.images[0] : product?.image +// ); + +// return ( +//
+ +// {/* Thumbnails */} +//
+// {product.images?.map((img, idx) => ( +// setMainImg(img)} +// className="w-20 h-20 md:w-24 md:h-24 object-cover rounded-xl border cursor-pointer hover:scale-105 transition-all" +// /> +// ))} +//
+ +// {/* Main Image */} +//
+// {product.title} + +// {/* Discount Badge */} +// {product.discount && ( +// +// {product.discount}% OFF +// +// )} + +// {/* Wishlist */} +// +//
+//
+// ); +// }; + +// export default ProductImages; + +import { useState } from "react"; +import { Heart } from "lucide-react"; + +const ProductImages = ({ product, isWishlisted, onToggleWishlist }) => { + const images = Array.isArray(product?.images?.gallery) + ? product.images.gallery + : []; + + const [mainImg, setMainImg] = useState(images[0]); + + if (!images.length) { + return ( +
+

No images available

+
+ ); + } + + return ( +
+ {/* Thumbnails */} +
+ {images.map((img, idx) => ( + setMainImg(img)} + className={`w-20 h-20 md:w-24 md:h-24 object-cover rounded-xl border cursor-pointer + ${mainImg === img ? "border-black scale-105" : "border-gray-200"} + transition-all`} + /> + ))} +
+ + {/* Main Image */} +
+ {product.name} + + {/* Discount Badge */} + {product.discount && ( + + {product.discount}% OFF + + )} + + {/* Wishlist */} + {/* */} + {/* Wishlist */} + +
+
+ ); +}; + +export default ProductImages; diff --git a/src/components/product/ProductInfo.jsx b/src/components/product/ProductInfo.jsx new file mode 100644 index 0000000..f413583 --- /dev/null +++ b/src/components/product/ProductInfo.jsx @@ -0,0 +1,163 @@ +import { useState } from "react"; +import { Heart } from "lucide-react"; +import { useNavigate } from "react-router-dom"; +import PinCodeCheck from "./PinCodeCheck"; +import ProductDetailsSection from "./ProductDetailsSection"; +import ProductDeclaration from "./ProductDeclaration"; +import Accordion from "../ui/Accordion"; +import ShippingReturns from "./ShippingReturns"; +import { useAddToCartMutation } from "../../features/cart/cartAPI"; + +const ProductInfo = ({ product, isWishlisted, onToggleWishlist }) => { + const navigate = useNavigate(); + const [quantity, setQuantity] = useState(1); + const [addToCart, { isLoading }] = useAddToCartMutation(); + + const handleQuantity = (type) => { + if (type === "inc") setQuantity((prev) => prev + 1); + else if (type === "dec" && quantity > 1) setQuantity((prev) => prev - 1); + }; + + const handleAddToCart = async () => { + const token = localStorage.getItem("token"); + + // 🚨 USER NOT LOGGED IN + if (!token) { + alert("Please login first to add product to cart"); + navigate("/login"); + return; + } + + try { + await addToCart({ + productId: product._id, + quantity, + }).unwrap(); + + alert("Item added to cart ✅"); + } catch (error) { + console.error("Add to cart failed:", error); + alert("Failed to add item ❌"); + } + }; + + return ( +
+

+ {product.name} +

+ +

+ {product.description || "No description available."} +

+ + {/* Price */} +
+

₹{product.basePrice}

+ {/*

+ ₹{product.compareAtPrice} +

*/} +
+ + {/* Colors */} + {product.colors?.length > 0 && ( +
+ Colors: + {product.colors.map((c, idx) => ( + + ))} +
+ )} + + {/* Quantity Selector */} +
+ Quantity: +
+ + {quantity} + +
+
+ + {/* Add to Cart */} + {/*
*/} +
+ + + {/* */} + +
+ + {/* Accordion Sections */} +
+ + + + + {/* + + */} + + + + +
+
+ ); +}; + +export default ProductInfo; diff --git a/src/components/product/RecommendedProducts.jsx b/src/components/product/RecommendedProducts.jsx new file mode 100644 index 0000000..cc34c2c --- /dev/null +++ b/src/components/product/RecommendedProducts.jsx @@ -0,0 +1,84 @@ +import { useParams, Link } from "react-router-dom"; +import { useGetProductRecommendationsQuery } from "../../features/products/productsAPI"; + +import { Swiper, SwiperSlide } from "swiper/react"; +import { Navigation, Pagination } from "swiper/modules"; + +import "swiper/css"; +import "swiper/css/navigation"; +import "swiper/css/pagination"; +import "../../styles/recommendedSwiper.css"; + +const RecommendedProducts = () => { + const { slug } = useParams(); + + const { data: recommendations, isLoading } = + useGetProductRecommendationsQuery({ slug, limit: 12 }); + + if (isLoading) + return

Loading...

; + + if (!recommendations?.length) return null; + + return ( +
+

+ You may also like this! +

+ + {recommendations.map((product) => ( + + + {/* Image */} +
+ {product.isFeatured && ( + + Featured + + )} + + {product.name} +
+ + {/* Content */} +
+

+ {product.name} +

+

+ ₹{product.basePrice} +

+
+ +
+ ))} +
+
+ ); +}; + +export default RecommendedProducts; diff --git a/src/components/product/ShippingReturns.jsx b/src/components/product/ShippingReturns.jsx new file mode 100644 index 0000000..9318fe7 --- /dev/null +++ b/src/components/product/ShippingReturns.jsx @@ -0,0 +1,12 @@ +const ShippingReturns = () => ( +
+

Shipping & Returns

+

+ All ready-to-ship products are shipped within 48 hours of placing the order. + Standard delivery dates are provided in the order confirmation email. Easy Exchange/Return Policy is available for all garments except Accessories. +

+

For details, visit Shipping & Returns page.

+
+); + +export default ShippingReturns; diff --git a/src/components/profile/ChangePassword.jsx b/src/components/profile/ChangePassword.jsx new file mode 100644 index 0000000..0839f73 --- /dev/null +++ b/src/components/profile/ChangePassword.jsx @@ -0,0 +1,108 @@ +import { useState } from "react"; +import { useChangePasswordMutation } from "../../features/auth/authApi"; +// import toast from "../ui/"; // optional: for toast notifications + +const ChangePassword = ({setActiveTab }) => { + const [currentPassword, setCurrentPassword] = useState(""); + const [newPassword, setNewPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + const [changePassword, { isLoading }] = useChangePasswordMutation(); + + const handleSubmit = async (e) => { + e.preventDefault(); + + if (newPassword !== confirmPassword) { + // toast.error("New password and confirm password do not match"); + return; + } + + try { + await changePassword({ + currentPassword, + newPassword, + }).unwrap(); + + // toast.success("Password changed successfully!"); + setCurrentPassword(""); + setNewPassword(""); + setConfirmPassword(""); + } catch (err) { + console.error(err); + // toast.error(err?.data?.message || "Failed to change password"); + } + }; + + return ( +
+

+ Change Password +

+
+
+ + setCurrentPassword(e.target.value)} + placeholder="Enter current password" + className="w-full border border-gray-300 p-3 rounded-lg focus:outline-none focus:ring-2 focus:ring-orange-900" + required + /> +
+ +
+ + setNewPassword(e.target.value)} + placeholder="Enter new password" + className="w-full border border-gray-300 p-3 rounded-lg focus:outline-none focus:ring-2 focus:ring-orange-900" + required + /> +
+ +
+ + setConfirmPassword(e.target.value)} + placeholder="Confirm new password" + className="w-full border border-gray-300 p-3 rounded-lg focus:outline-none focus:ring-2 focus:ring-orange-900" + required + /> +
+ + {/* */} +
+ + + +
+
+
+ ); +}; + +export default ChangePassword; diff --git a/src/components/profile/EditProfilePage.jsx b/src/components/profile/EditProfilePage.jsx new file mode 100644 index 0000000..d61d84e --- /dev/null +++ b/src/components/profile/EditProfilePage.jsx @@ -0,0 +1,134 @@ +import { useState, useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import { + useGetProfileQuery, + useUpdateProfileMutation, +} from "../../features/auth/authApi"; + +const EditProfilePage = ({ setActiveTab }) => { + const navigate = useNavigate(); + const { data: profileData, isLoading } = useGetProfileQuery(); + const [updateProfile, { isLoading: updating }] = useUpdateProfileMutation(); + + const [form, setForm] = useState({ + firstName: "", + lastName: "", + email: "", + phone: "", + username: "", + avatar: null, + }); + + // Populate form when data is loaded + useEffect(() => { + if (profileData?.data?.user) { + const { firstName, lastName, email, phone, avatar, username } = + profileData.data.user; + setForm({ firstName, lastName, email, phone, avatar, username }); + } + }, [profileData]); + + const handleChange = (e) => { + setForm({ ...form, [e.target.name]: e.target.value }); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + try { + await updateProfile(form).unwrap(); + alert("Profile updated successfully!"); + + setActiveTab("profile"); + } catch (err) { + console.error(err); + alert("Failed to update profile"); + } + }; + + if (isLoading) return

Loading profile...

; + + return ( +
+

Edit Profile

+
+
+ + +
+ +
+ + + +
+ + + + {/* Avatar Upload (optional) */} +
+ + setForm({ ...form, avatar: e.target.files[0] })} + className="w-full border rounded-lg p-2" + /> +
+ +
+ + +
+
+
+ ); +}; + +export default EditProfilePage; diff --git a/src/components/profile/OrdersSkeleton.jsx b/src/components/profile/OrdersSkeleton.jsx new file mode 100644 index 0000000..2a772a9 --- /dev/null +++ b/src/components/profile/OrdersSkeleton.jsx @@ -0,0 +1,29 @@ +const OrdersSkeleton = () => { + return ( +
+ {[1, 2, 3].map((i) => ( +
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+ ))} +
+ ); +}; + +export default OrdersSkeleton; diff --git a/src/components/profile/OrdersTab.jsx b/src/components/profile/OrdersTab.jsx new file mode 100644 index 0000000..9a4d382 --- /dev/null +++ b/src/components/profile/OrdersTab.jsx @@ -0,0 +1,215 @@ +// import { Package, Calendar, IndianRupee } from "lucide-react"; +// import { useGetOrdersQuery } from "../../features/orders/ordersApi"; +// import OrdersSkeleton from "./OrdersSkeleton"; + +// const OrdersTab = () => { +// const { data, isLoading, isError } = useGetOrdersQuery({}); + +// if (isLoading) return ; + +// if (isError) +// return ( +//

+// Failed to load orders +//

+// ); + +// const orders = data?.orders ?? []; + +// if (!orders.length) { +// return ( +//
+// +//

No orders found

+//
+// ); +// } + +// return ( +//
+// {orders.map((order) => ( +//
+// {/* Header */} +//
+//
+//

Order ID

+//

{order.orderNumber}

+//
+ +// +// {order.status} +// +//
+ +// {/* Items */} +//
+// {order.items.map((item) => ( +//
+//
+//

{item.productName}

+//

Qty: {item.quantity}

+//
+//

₹{item.price}

+//
+// ))} +//
+ +// {/* Footer */} +//
+//
+// +// {new Date(order.createdAt).toLocaleDateString()} +//
+ +//
+// +// {order.totalAmount} +//
+//
+//
+// ))} +//
+// ); +// }; + +// export default OrdersTab; + +import { + Package, + Calendar, + IndianRupee, + Truck, + CheckCircle, + XCircle, +} from "lucide-react"; +import { useGetOrdersQuery } from "../../features/orders/ordersApi"; +import OrdersSkeleton from "./OrdersSkeleton"; + +// Status colors +const statusColors = { + PENDING: "bg-yellow-100 text-yellow-800", + SHIPPED: "bg-blue-100 text-blue-800", + DELIVERED: "bg-green-100 text-green-800", + CANCELLED: "bg-red-100 text-red-800", +}; + +const OrdersTab = () => { + const { data, isLoading, isError } = useGetOrdersQuery({}); + + if (isLoading) return ; + + if (isError) + return ( +

+ Failed to load orders +

+ ); + + const orders = data?.orders ?? []; + + if (!orders.length) + return ( +
+ +

No orders found

+
+ ); + + return ( +
+ {orders.map((order) => ( +
+ {/* Header */} +
+
+

Order ID

+

{order.orderNumber}

+

+ Payment: {order.paymentMethod} +

+
+ + {order.status} + +
+ + {/* Items */} +
+ {order.items.map((item) => ( +
+
+

+ {item.productName} +

+

+ Qty: {item.quantity} +

+
+

+ ₹{Number(item.price) * item.quantity} +

+
+ ))} +
+ + {/* Footer */} +
+
+ + {new Date(order.createdAt).toLocaleDateString()} +
+ +
+
+ + {order.totalAmount} +
+ + {/* Dynamic status icons */} + {order.status === "SHIPPED" && ( +
+ Tracking +
+ )} + {order.status === "DELIVERED" && ( +
+ Delivered +
+ )} + {order.status === "CANCELLED" && ( +
+ Cancelled +
+ )} +
+
+
+ ))} +
+ ); +}; + +export default OrdersTab; diff --git a/src/components/profile/ProfileActions.jsx b/src/components/profile/ProfileActions.jsx new file mode 100644 index 0000000..1e5d1ca --- /dev/null +++ b/src/components/profile/ProfileActions.jsx @@ -0,0 +1,20 @@ +import { Link } from "react-router-dom"; + +const ProfileActions = ({setActiveTab }) => ( +
+ + +
+); + +export default ProfileActions; diff --git a/src/components/profile/ProfileContent.jsx b/src/components/profile/ProfileContent.jsx new file mode 100644 index 0000000..d7ca41d --- /dev/null +++ b/src/components/profile/ProfileContent.jsx @@ -0,0 +1,88 @@ +import ProfileHeader from "./ProfileHeader"; +import ProfileInfoCard from "./ProfileInfoCard"; +import ProfileStats from "./ProfileStats"; +import ProfileActions from "./ProfileActions"; +import Wishlist from "../navbar/Wishlist"; +import Addresses from "./addresses/Addresses"; +import AddAddressForm from "./addresses/AddAddressForm"; +import AddressSection from "./addresses/AddressSection"; +import OrdersTab from "./OrdersTab"; + +import EditProfilePage from "./EditProfilePage"; +import ChangePassword from "./ChangePassword"; + +const ProfileContent = ({ activeTab, setActiveTab, user }) => { + return ( +
+ {activeTab === "profile" && ( + <> + + +
+ + + + + +
+ + + + {/* */} + + + {/* {activeTab === "orders" && } */} + + )} + + {activeTab === "edit-profile" && ( + + )} + + {activeTab === "change-password" && ( + +)} + + {activeTab === "wishlist" && ( +
+

+ My Wishlist +

+ +
+ )} + + {activeTab === "orders" && ( +
+

+ My Orders +

+ +
+ )} + + {activeTab === "address" && } +
+ ); +}; + +export default ProfileContent; diff --git a/src/components/profile/ProfileHeader.jsx b/src/components/profile/ProfileHeader.jsx new file mode 100644 index 0000000..9ad63e2 --- /dev/null +++ b/src/components/profile/ProfileHeader.jsx @@ -0,0 +1,27 @@ +import { motion } from "framer-motion"; + +const ProfileHeader = ({ user }) => { + if (!user) return null; // or show a loader/fallback + + return ( + + Profile +
+

+ {`${user.firstName || ""} ${user.lastName || ""}`} +

+

{user.email || ""}

+
+
+ ); +}; + +export default ProfileHeader; diff --git a/src/components/profile/ProfileInfoCard.jsx b/src/components/profile/ProfileInfoCard.jsx new file mode 100644 index 0000000..bfff65e --- /dev/null +++ b/src/components/profile/ProfileInfoCard.jsx @@ -0,0 +1,8 @@ +const ProfileInfoCard = ({ title, value }) => ( +
+ {title} + {value || "Not provided"} +
+); + +export default ProfileInfoCard; diff --git a/src/components/profile/ProfilePage.jsx b/src/components/profile/ProfilePage.jsx new file mode 100644 index 0000000..2a647d0 --- /dev/null +++ b/src/components/profile/ProfilePage.jsx @@ -0,0 +1,51 @@ +import { useState } from "react"; +import { useDispatch } from "react-redux"; +import { useNavigate } from "react-router-dom"; +import { useGetProfileQuery } from "../../features/auth/authApi"; +// import { useDispatch } from "react-redux"; +import { logout, setCredentials } from "../../features/auth/authSlice"; +import ProfileSidebar from "./ProfileSidebar"; +import ProfileContent from "./ProfileContent"; + +const ProfilePage = () => { + const dispatch = useDispatch(); + const navigate = useNavigate(); + const { data, isLoading, isError } = useGetProfileQuery(); + const [activeTab, setActiveTab] = useState("profile"); // <--- state lifted + + if (isLoading) return

Loading profile...

; + if (isError) + return ( +

Failed to load profile

+ ); + + const user = data?.data?.user; + + const handleLogout = () => { + dispatch(logout()); + localStorage.clear(); + navigate("/"); + }; + + return ( +
+ {/* Sidebar */} + + + {/* Content */} + {/* */} + {/* Content */} + +
+ ); +}; + +export default ProfilePage; diff --git a/src/components/profile/ProfileSidebar.jsx b/src/components/profile/ProfileSidebar.jsx new file mode 100644 index 0000000..086581d --- /dev/null +++ b/src/components/profile/ProfileSidebar.jsx @@ -0,0 +1,63 @@ +import { User, Heart, MapPin, LogOut, Menu, ShoppingBag } from "lucide-react"; +import { useState } from "react"; + +const ProfileSidebar = ({ activeTab, setActiveTab, onLogout }) => { + const [openMobile, setOpenMobile] = useState(false); + + const tabs = [ + { id: "profile", label: "My Profile", icon: }, + { id: "wishlist", label: "My Wishlist", icon: }, + { id: "address", label: "Address", icon: }, + { id: "orders", label: "Orders", icon: }, + ]; + + return ( + <> + {/* Mobile Toggle */} +
+

Profile

+ +
+ + {/* Sidebar */} +
+ {tabs.map((tab) => ( +
{ + setActiveTab(tab.id); + setOpenMobile(false); // close mobile menu + }} + > + {tab.icon} + {tab.label} +
+ ))} + +
+ + Logout +
+
+ + ); +}; + +export default ProfileSidebar; diff --git a/src/components/profile/ProfileStats.jsx b/src/components/profile/ProfileStats.jsx new file mode 100644 index 0000000..a933826 --- /dev/null +++ b/src/components/profile/ProfileStats.jsx @@ -0,0 +1,20 @@ +const ProfileStats = ({ orders, wishlist, reviews }) => { + const stats = [ + { label: "Orders", value: orders.length }, + { label: "Wishlist", value: wishlist.length }, + { label: "Reviews", value: reviews.length }, + ]; + + return ( +
+ {stats.map((stat, i) => ( +
+

{stat.value}

+

{stat.label}

+
+ ))} +
+ ); +}; + +export default ProfileStats; diff --git a/src/components/profile/addresses/AddAddressForm.jsx b/src/components/profile/addresses/AddAddressForm.jsx new file mode 100644 index 0000000..1ec09af --- /dev/null +++ b/src/components/profile/addresses/AddAddressForm.jsx @@ -0,0 +1,82 @@ +import { useAddAddressMutation } from "../../../features/auth/authApi"; +import { useState } from "react"; + +const AddAddressForm = () => { + const [addAddress, { isLoading }] = useAddAddressMutation(); + + const [formData, setFormData] = useState({ + type: "SHIPPING", + isDefault: false, + firstName: "", + lastName: "", + company: "", + addressLine1: "", + addressLine2: "", + city: "", + state: "", + postalCode: "", + country: "", + phone: "", + }); + + const handleChange = (e) => { + const { name, value, type, checked } = e.target; + setFormData({ + ...formData, + [name]: type === "checkbox" ? checked : value, + }); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + try { + await addAddress(formData).unwrap(); + alert("Address added successfully"); + } catch (err) { + console.error(err); + alert("Failed to add address"); + } + }; + + return ( +
+

Add New Address

+ +
+ + + + +
+ + + + +
+ + + +
+ + + +
+ + +
+ + +
+ ); +}; + +export default AddAddressForm; diff --git a/src/components/profile/addresses/AddressCard.jsx b/src/components/profile/addresses/AddressCard.jsx new file mode 100644 index 0000000..e4d90bf --- /dev/null +++ b/src/components/profile/addresses/AddressCard.jsx @@ -0,0 +1,83 @@ +import { MapPin, Phone, Home, Briefcase, Star } from "lucide-react"; +import { useDeleteAddressMutation } from "../../../features/auth/authApi"; + + + +const AddressCard = ({ address, onEdit }) => { + const [deleteAddress] = useDeleteAddressMutation(); + + const handleDelete = async () => { + if (confirm("Are you sure you want to delete this address?")) { + await deleteAddress(address.id); + alert("Address deleted!"); + onClose(); + } + }; + + return ( +
+ {/* Header */} +
+
+ +

+ {address.firstName} {address.lastName} +

+
+ + {address.isDefault && ( + + Default + + )} +
+ + {/* Company */} + {address.company && ( +

+ {address.company} +

+ )} + + {/* Address */} +
+

+ + {address.addressLine1}, {address.addressLine2} +

+

+ {address.city}, {address.state} - {address.postalCode} +

+

{address.country}

+

+ {address.phone} +

+
+ + {/* Actions */} +
+ + +
+
+ ); +}; + +export default AddressCard; diff --git a/src/components/profile/addresses/AddressSection.jsx b/src/components/profile/addresses/AddressSection.jsx new file mode 100644 index 0000000..3e67c8d --- /dev/null +++ b/src/components/profile/addresses/AddressSection.jsx @@ -0,0 +1,41 @@ +import { useState } from "react"; +import AddAddressForm from "./AddAddressForm"; +import Addresses from "./Addresses"; + +const AddressSection = () => { + const [showForm, setShowForm] = useState(false); + + return ( +
+
+

My Addresses

+ + {/* Add Address Button */} + +
+ +
+ {/* LEFT SIDE — Address List */} +
+ +
+ + {/* RIGHT SIDE — Form */} + {showForm && ( +
+
+ +
+
+ )} +
+
+ ); +}; + +export default AddressSection; diff --git a/src/components/profile/addresses/Addresses.jsx b/src/components/profile/addresses/Addresses.jsx new file mode 100644 index 0000000..67087d0 --- /dev/null +++ b/src/components/profile/addresses/Addresses.jsx @@ -0,0 +1,46 @@ +import { useState } from "react"; +import { useGetAddressesQuery } from "../../../features/auth/authApi"; +import AddressCard from "./AddressCard"; +import UpdateAddressModal from "./UpdateAddressModal"; + +const Addresses = () => { + const { data, isLoading, isError } = useGetAddressesQuery(); + const addresses = data?.data?.addresses || []; + + const [selectedAddress, setSelectedAddress] = useState(null); + + if (isLoading) + return ( +

Loading addresses...

+ ); + + if (isError) + return ( +

Failed to load addresses

+ ); + + // const addresses = data?.data?.addresses || []; + + return ( + <> +
+ {addresses.map((address) => ( + setSelectedAddress(addr)} + /> + ))} +
+ + {selectedAddress && ( + setSelectedAddress(null)} + /> + )} + + ); +}; + +export default Addresses; diff --git a/src/components/profile/addresses/UpdateAddressModal.jsx b/src/components/profile/addresses/UpdateAddressModal.jsx new file mode 100644 index 0000000..c5a766f --- /dev/null +++ b/src/components/profile/addresses/UpdateAddressModal.jsx @@ -0,0 +1,141 @@ + +import { useUpdateAddressMutation, } from "../../../features/auth/authApi"; +import { useState } from "react"; + +const UpdateAddressModal = ({ address, onClose }) => { + const [updateAddress] = useUpdateAddressMutation(); + const [formData, setFormData] = useState(address); + + const handleChange = (e) => { + setFormData({ ...formData, [e.target.name]: e.target.value }); + }; + + const handleUpdate = async (e) => { + e.preventDefault(); + + await updateAddress({ + id: address.id, + body: formData, + }); + + alert("Address updated!"); + onClose(); + }; + + return ( +
+
+

Update Address

+ + {/* 2-column grid for inputs */} +
+ + + + + + + + + + +
+ + {/* Checkbox */} + + + {/* Buttons */} +
+ + +
+
+
+ ); +}; + +export default UpdateAddressModal; diff --git a/src/components/recentlyViewed/RecentlyViewed.jsx b/src/components/recentlyViewed/RecentlyViewed.jsx new file mode 100644 index 0000000..c4122d9 --- /dev/null +++ b/src/components/recentlyViewed/RecentlyViewed.jsx @@ -0,0 +1,265 @@ +// import React from "react"; +// import { useSelector } from "react-redux"; +// import { Link } from "react-router-dom"; +// import { selectRecentProducts } from "../../features/recentlyViewed/recentlyViewedApi"; + +// const RecentlyViewedCard = ({ product }) => { +// const image = product.image || "/placeholder-product.png"; +// const price = product.price || 0; + +// return ( +// +// {/* Image */} +//
+// {product.name} + +// {/* Soft gradient */} +//
+ +// {/* "Viewed" badge */} +//
+// Viewed +//
+//
+ +// {/* Content */} +//
+//

+// {product.name || "Product Name"} +//

+//
+// ₹{price} +//
+//
+//
+// +// ); +// }; + +// const RecentlyViewed = ({ limit = 6 }) => { +// const allProducts = useSelector(selectRecentProducts); +// const recentProducts = allProducts.slice(0, limit); + +// if (recentProducts.length === 0) return null; + +// return ( +//
+//
+// {/* Heading */} +//
+//

+// Recently Viewed Products +//

+//

+// Continue shopping from where you left off +//

+//
+ +// {/* Product Grid */} +//
+// {recentProducts.map((product) => ( +// +// ))} +//
+//
+//
+// ); +// }; + +// export default RecentlyViewed; + +import React from "react"; +import { useSelector } from "react-redux"; +import { Link } from "react-router-dom"; +import { Heart } from "lucide-react"; +import { selectRecentProducts } from "../../features/recentlyViewed/recentlyViewedApi"; +import { + useAddToWishlistMutation, + useRemoveFromWishlistMutation, + useGetWishlistQuery, +} from "../../features/wishlist/wishlistApi"; + +const RecentlyViewedCard = ({ product, wishlistIds, handleWishlist }) => { + const discount = + product.comparePrice && product.comparePrice > product.price + ? Math.round( + ((product.comparePrice - product.price) / product.comparePrice) * 100, + ) + : 0; + + const offersCount = Math.floor(Math.random() * 3) + 1; // mock offers + + return ( + + {/* Image */} +
+ {product.name} + + {/* Viewed Badge */} +
+ Viewed +
+ + {/* Offers Badge */} + {offersCount > 0 && ( +
+ {offersCount} OFFER{offersCount > 1 ? "S" : ""} FOR YOU +
+ )} + + {/* Wishlist */} +
+ +
+
+ + {/* Product Info */} +
+ {/* Brand/Category */} +

+ {product.brand || "Brand"} +

+ + {/* Product Name */} +

+ {product.name || "Product Name"} +

+ + {/* Price */} +
+ + ₹{product.price?.toLocaleString() || "0"} + + {product.comparePrice && ( + + ₹{product.comparePrice.toLocaleString()} + + )} + {discount > 0 && ( +

+ {discount}% OFF +

+ )} +
+ + {/* Color Variants */} + {product.variants?.length > 0 && ( +
+ {product.variants.slice(0, 5).map((variant, idx) => { + const colorValue = variant.color?.toLowerCase() || "#e5e7eb"; + return ( +
+ ); + })} + {product.variants.length > 5 && ( + + +{product.variants.length - 5} + + )} +
+ )} +
+ + ); +}; + +const RecentlyViewed = ({ limit = 6 }) => { + const allProducts = useSelector(selectRecentProducts); + const recentProducts = allProducts.slice(0, limit); + + const { data: wishlistData } = useGetWishlistQuery(); + const [addToWishlist] = useAddToWishlistMutation(); + const [removeFromWishlist] = useRemoveFromWishlistMutation(); + + const wishlistIds = + wishlistData?.data?.wishlist?.map( + (item) => item.product?._id || item.productId, + ) || + wishlistData?.data?.products?.map((item) => item._id) || + []; + + const handleWishlist = async (e, productId) => { + e.preventDefault(); + e.stopPropagation(); + + try { + if (wishlistIds.includes(productId)) { + await removeFromWishlist(productId).unwrap(); + } else { + await addToWishlist(productId).unwrap(); + } + } catch (error) { + console.error("Wishlist error:", error); + } + }; + + if (recentProducts.length === 0) return null; + + return ( +
+
+ {/* Heading */} +
+

+ Recently Viewed Products +

+

+ Continue shopping from where you left off +

+
+ + {/* Product Grid */} +
+ {recentProducts.map((product) => ( + + ))} +
+
+
+ ); +}; + +export default RecentlyViewed; diff --git a/src/components/sarees/SareeCard.jsx b/src/components/sarees/SareeCard.jsx new file mode 100644 index 0000000..5dcbb57 --- /dev/null +++ b/src/components/sarees/SareeCard.jsx @@ -0,0 +1,71 @@ +import { Heart } from "lucide-react"; +import { Link } from "react-router-dom"; + +const SareeCard = ({ item }) => { + return ( + +
+ {/* Image */} +
+ { + if (item.images && item.images[1]) + e.currentTarget.src = item.images[1]; + }} + onMouseLeave={(e) => { + e.currentTarget.src = item.images ? item.images[0] : item.image; + }} + alt={item.title} + className="h-full w-full object-cover group-hover:scale-110 transition duration-300" + /> + + {/* Wishlist */} + + + {/* Discount Badge */} + {item.discount && ( + + {item.discount}% OFF + + )} +
+ + {/* Content */} +
+

+ {item.title} +

+ + {/* Price */} +
+

₹{item.offerPrice}

+

+ ₹{item.originalPrice} +

+
+ + {/* Colors */} +
+ {item.colors?.slice(0, 4).map((c, idx) => ( + + ))} + {item.colors?.length > 4 && ( + + +{item.colors.length - 4} + + )} +
+
+
+ + ); +}; + +export default SareeCard; diff --git a/src/components/sarees/SareeFilters.jsx b/src/components/sarees/SareeFilters.jsx new file mode 100644 index 0000000..7266a43 --- /dev/null +++ b/src/components/sarees/SareeFilters.jsx @@ -0,0 +1,91 @@ +import { useState } from "react"; +import Dropdown from "../ui/Dropdown"; + +import Checkbox from "../common/Checkbox"; +import FilterPanel from "../Filters/FilterPanel"; +import { SlidersHorizontal } from "lucide-react"; +// import FilterPanel from "../Filters/FilterPanel"; + +const SareeFilters = () => { + const [openFilter, setOpenFilter] = useState(false); + + return ( +
+ {/* FILTER BUTTON */} + + + {/* SIDEBAR OVERLAY */} + {openFilter && ( +
setOpenFilter(false)} + /> + )} + + {/* LEFT SIDE DRAWER */} +
+ {/* HEADER */} +
+

Filters

+ +
+ + {/* FILTER CONTENT */} +
+ +
+
+ + {/* Sort Dropdown */} + + +
+ + + + + + + +
+
+ + {/* Price Filter */} + +
+ + + +
+
+ + {/* Fabric Filter */} + {/* +
+ + + +
+
*/} +
+ ); +}; + +export default SareeFilters; diff --git a/src/components/sarees/SareeGrid.jsx b/src/components/sarees/SareeGrid.jsx new file mode 100644 index 0000000..3b2eb60 --- /dev/null +++ b/src/components/sarees/SareeGrid.jsx @@ -0,0 +1,14 @@ +import SareeCard from "./SareeCard"; + +const SareeGrid = ({ products = [] }) => { + return ( +
+ {products.map((item) => ( + + ))} +
+ ); +}; + + +export default SareeGrid; diff --git a/src/components/sarees/SareesPage.jsx b/src/components/sarees/SareesPage.jsx new file mode 100644 index 0000000..61c6d0f --- /dev/null +++ b/src/components/sarees/SareesPage.jsx @@ -0,0 +1,46 @@ +import React, { useState } from "react"; +import Pagination from "../ui/Pagination"; +import SareeGrid from "../sarees/SareeGrid"; +import { sareesData } from "../../data/sareesData"; +import SareeFilters from "./SareeFilters"; +import Breadcrumb from "../ui/Breadcrumb"; +import CategoryGrid from "../home/Category/CategoryGrid"; +// import FilterPanel from "../Filters/FilterPanel"; + +const SareesPage = () => { + const [currentPage, setCurrentPage] = useState(1); + const itemsPerPage = 8; + + const indexOfLast = currentPage * itemsPerPage; + const indexOfFirst = indexOfLast - itemsPerPage; + const currentProducts = sareesData.slice(indexOfFirst, indexOfLast); + + const totalPages = Math.ceil(sareesData.length / itemsPerPage); + + return ( +
+ + + +

+ Sarees Collection +

+

+ Explore our newest and most loved sarees curated just for you. +

+ + + + {/* FIXED */} + + + +
+ ); +}; + +export default SareesPage; diff --git a/src/components/skeletons/ProductCardSkeleton.jsx b/src/components/skeletons/ProductCardSkeleton.jsx new file mode 100644 index 0000000..456c762 --- /dev/null +++ b/src/components/skeletons/ProductCardSkeleton.jsx @@ -0,0 +1,19 @@ +import React from "react"; + +const ProductCardSkeleton = () => { + return ( +
+ {/* Image */} +
+ + {/* Content */} +
+
+
+
+
+
+ ); +}; + +export default ProductCardSkeleton; diff --git a/src/components/skeletons/ProductGridSkeleton.jsx b/src/components/skeletons/ProductGridSkeleton.jsx new file mode 100644 index 0000000..24a7d22 --- /dev/null +++ b/src/components/skeletons/ProductGridSkeleton.jsx @@ -0,0 +1,14 @@ +import React from "react"; +import ProductCardSkeleton from "./ProductCardSkeleton"; + +const ProductGridSkeleton = ({ count = 8 }) => { + return ( +
+ {[...Array(count)].map((_, i) => ( + + ))} +
+ ); +}; + +export default ProductGridSkeleton; diff --git a/src/components/ui/Accordion.jsx b/src/components/ui/Accordion.jsx new file mode 100644 index 0000000..bccc091 --- /dev/null +++ b/src/components/ui/Accordion.jsx @@ -0,0 +1,81 @@ + + +// import { useState } from "react"; + +// const PlusMinusAccordion = ({ title, children }) => { +// const [open, setOpen] = useState(false); + +// return ( +//
+// {/* Header */} +// + +// {/* Body */} +//
+//

+// {children} +//

+//
+//
+// ); +// }; + +// export default PlusMinusAccordion; + + + +import { useState } from "react"; + +const PlusMinusAccordion = ({ title, children, defaultOpen = false }) => { + const [open, setOpen] = useState(defaultOpen); + + return ( +
+ {/* Header */} + + + {/* Body */} +
+
{children}
+
+
+ ); +}; + +export default PlusMinusAccordion; diff --git a/src/components/ui/Breadcrumb.jsx b/src/components/ui/Breadcrumb.jsx new file mode 100644 index 0000000..4db1384 --- /dev/null +++ b/src/components/ui/Breadcrumb.jsx @@ -0,0 +1,38 @@ +import { useLocation, Link } from "react-router-dom"; +import React from "react"; + +const Breadcrumb = () => { + const location = useLocation(); + + // Split the path into segments and remove empty strings + const pathnames = location.pathname.split("/").filter(Boolean); + + return ( + + ); +}; + +export default Breadcrumb; diff --git a/src/components/ui/Drawer.jsx b/src/components/ui/Drawer.jsx new file mode 100644 index 0000000..714ff24 --- /dev/null +++ b/src/components/ui/Drawer.jsx @@ -0,0 +1,39 @@ +import { motion, AnimatePresence } from "framer-motion"; + +const Drawer = ({ open, onClose, children, position = "right" }) => { + const positions = { + right: "right-0 top-0 h-full w-[350px]", + left: "left-0 top-0 h-full w-[350px]", + bottom: "left-0 bottom-0 w-full h-[400px]", + }; + + return ( + + {open && ( + <> + {/* Overlay */} + + + {/* Drawer Content */} + + {children} + + + )} + + ); +}; + +export default Drawer; diff --git a/src/components/ui/Dropdown.jsx b/src/components/ui/Dropdown.jsx new file mode 100644 index 0000000..40047d6 --- /dev/null +++ b/src/components/ui/Dropdown.jsx @@ -0,0 +1,64 @@ +import { useEffect, useRef, useState } from "react"; +import { motion, AnimatePresence } from "framer-motion"; +import { ChevronDown } from "lucide-react"; + +const Dropdown = ({ label, children, className = "" }) => { + const [open, setOpen] = useState(false); + const ref = useRef(); + + // Close dropdown on outside click + useEffect(() => { + function handleClick(e) { + if (ref.current && !ref.current.contains(e.target)) setOpen(false); + } + document.addEventListener("mousedown", handleClick); + return () => document.removeEventListener("mousedown", handleClick); + }, []); + + return ( +
+ {/* Trigger Button */} + + + {/* Dropdown Box */} + + {open && ( + +
{children}
+
+ )} +
+
+ ); +}; + +export default Dropdown; diff --git a/src/components/ui/Pagination.jsx b/src/components/ui/Pagination.jsx new file mode 100644 index 0000000..0283361 --- /dev/null +++ b/src/components/ui/Pagination.jsx @@ -0,0 +1,108 @@ +// import React from "react"; + +// const Pagination = ({ currentPage, totalPages, onPageChange }) => { +// if (totalPages <= 1) return null; + +// // Create an array of page numbers +// const pages = Array.from({ length: totalPages }, (_, i) => i + 1); + +// return ( +//
+// {/* Previous Button */} +// + +// {/* Page Numbers */} +// {pages.map((page) => ( +// +// ))} + +// {/* Next Button */} +// +//
+// ); +// }; + +// export default Pagination; + + + +import React from "react"; + +const Pagination = ({ currentPage, totalPages, onPageChange }) => { + if (totalPages <= 1) return null; + + const pages = Array.from({ length: totalPages }, (_, i) => i + 1); + + return ( +
+ {/* Previous Button */} + + + {/* Page Numbers */} + {pages.map((page) => ( + + ))} + + {/* Next Button */} + +
+ ); +}; + +export default Pagination; diff --git a/src/components/ui/ScrollToTop.jsx b/src/components/ui/ScrollToTop.jsx new file mode 100644 index 0000000..5f170ca --- /dev/null +++ b/src/components/ui/ScrollToTop.jsx @@ -0,0 +1,42 @@ +import { useState, useEffect } from "react"; +import { ArrowUp } from "lucide-react"; // optional icon + +const ScrollToTop = () => { + const [show, setShow] = useState(false); + + useEffect(() => { + const handleScroll = () => { + // Show button when user scrolls 300px down from top + if (window.scrollY > 300) { + setShow(true); + } else { + setShow(false); + } + }; + + window.addEventListener("scroll", handleScroll); + return () => window.removeEventListener("scroll", handleScroll); + }, []); + + const handleClick = () => { + window.scrollTo({ + top: 0, + behavior: "smooth", // smooth scrolling + }); + }; + + return ( + <> + {show && ( + + )} + + ); +}; + +export default ScrollToTop; diff --git a/src/constants/statusColor.js b/src/constants/statusColor.js new file mode 100644 index 0000000..f4503a6 --- /dev/null +++ b/src/constants/statusColor.js @@ -0,0 +1,8 @@ +export const statusColor = { + PENDING: "bg-yellow-100 text-yellow-800", + PAID: "bg-blue-100 text-blue-800", + SHIPPED: "bg-purple-100 text-purple-800", + DELIVERED: "bg-green-100 text-green-800", + CANCELLED: "bg-red-100 text-red-800", + RETURNED: "bg-gray-100 text-gray-800", +}; diff --git a/src/data/categoriesData.js b/src/data/categoriesData.js new file mode 100644 index 0000000..61d62f1 --- /dev/null +++ b/src/data/categoriesData.js @@ -0,0 +1,47 @@ +export const categories = [ + { + name: "Saree", + image: "/hero1.jpg", + link: "/products/saree", + }, + { + name: "Bandhani", + image: "/hero2.jpg", + link: "/products/bandhani", + }, + { + name: "Patola", + image: "/hero3.jpg", + link: "/products/patola", + }, + { + name: "Lehengas", + image: "/hero4.jpg", + link: "/products/lehengas", + }, + { + name: "Saree", + image: "/hero1.jpg", + link: "/products/saree", + }, + { + name: "Bandhani", + image: "/hero2.jpg", + link: "/products/bandhani", + }, + { + name: "Patola", + image: "/hero3.jpg", + link: "/products/patola", + }, + { + name: "Lehengas", + image: "/hero4.jpg", + link: "/products/lehengas", + }, + { + name: "Patola", + image: "/hero3.jpg", + link: "/products/patola", + }, +]; diff --git a/src/data/mostLovedData.js b/src/data/mostLovedData.js new file mode 100644 index 0000000..457bc67 --- /dev/null +++ b/src/data/mostLovedData.js @@ -0,0 +1,50 @@ +export const mostLoved = [ + { + name: "Silk Saree", + image: "/hero2.jpg", + discount: "30% OFF", + link: "/product/1", + }, + { + name: "Lehenga Set", + image: "/hero2.jpg", + discount: "30% OFF", + link: "/product/2", + }, + { + name: "Badhni Saree", + image: "/hero2.jpg", + discount: "30% OFF", + link: "/product/3", + }, + { + name: "Patola Saree", + image: "/hero2.jpg", + discount: "30% OFF", + link: "/product/4", + }, + { + name: "Designer Kurta", + image: "/hero2.jpg", + discount: "30% OFF", + link: "/product/5", + }, + { + name: "Bridal Lehenga", + image: "/hero2.jpg", + discount: "30% OFF", + link: "/product/6", + }, + { + name: "Banarasi Saree", + image: "/hero4.jpg", + discount: "30% OFF", + link: "/product/7", + }, + { + name: "Silk Kurta", + image: "/hero3.jpg", + discount: "30% OFF", + link: "/product/8", + }, +]; diff --git a/src/data/newArrivalsData.js b/src/data/newArrivalsData.js new file mode 100644 index 0000000..185f05b --- /dev/null +++ b/src/data/newArrivalsData.js @@ -0,0 +1,26 @@ +export const newArrivals = [ + { + name: "Embroidered Kurta", + image: "/hero1.jpg", + price: "₹2,499", + link: "/product/kurta-1", + }, + { + name: "Designer Lehenga", + image: "/hero2.jpg", + price: "₹6,999", + link: "/product/lehenga-2", + }, + { + name: "Silk Saree", + image: "/hero3.jpg", + price: "₹4,299", + link: "/product/saree-3", + }, + { + name: "Men's Sherwani", + image: "/hero4.jpg", + price: "₹8,999", + link: "/product/sherwani-4", + }, +]; diff --git a/src/data/offeringsData.js b/src/data/offeringsData.js new file mode 100644 index 0000000..c7cf744 --- /dev/null +++ b/src/data/offeringsData.js @@ -0,0 +1,28 @@ +// components/Offerings/offeringsData.js + +export const offeringsData = [ + { + id: 1, + title: "Easy Exchange & Returns", + // description: "Hassle-free returns and quick exchanges on all orders.", + icon: "↩️", + }, + { + id: 2, + title: "Express Delivery", + // description: "Fast & secure delivery at your doorstep.", + icon: "⚡", + }, + { + id: 3, + title: "Personalized Engraving", + // description: "Make it special with custom engraving options.", + icon: "✨", + }, + { + id: 4, + title: "Click & Collect", + // description: "Order online and pick up from your nearest store.", + icon: "🛍️", + }, +]; diff --git a/src/data/sareesData.js b/src/data/sareesData.js new file mode 100644 index 0000000..e96a491 --- /dev/null +++ b/src/data/sareesData.js @@ -0,0 +1,315 @@ +export const sareesData = [ + { + _id: 1, + title: "Pure Silk Banarasi Saree with Rich Pallu", + // image: "/saree-1.webp", + images: [ + "/lehangaa-1.avif", + "/lehangaa-2.avif", + "/lehangaa-3.avif", + "/lehangaa-5.avif", + ], + + originalPrice: 4999, + offerPrice: 3499, + discount: 30, + colors: ["#E88E8E", "#C02F2F", "#FFD700"], + }, + { + _id: 2, + title: "Traditional Bandhani Saree - Handmade", + // image: "/saree-2.webp", + images: [ + "/saree-1.avif", + "/saree-2.avif", + "/saree-3.avif", + "/saree-4.avif", + "/saree-5.avif", + ], + originalPrice: 2999, + offerPrice: 2099, + discount: 26, + colors: ["#112D4E", "#DBE2EF", "#F9F7F7"], + }, + { + _id: 3, + title: "Premium Patola Saree | Limited Edition", + // image: "/saree-3.webp", + images: [ + "/pink-1.avif", + "/pink-2.avif", + "/pink-3.avif", + "/pink-4.avif", + "/pink-5.avif", + ], + originalPrice: 6999, + offerPrice: 5299, + discount: 24, + colors: ["#FF5733", "#900C3F", "#FFC300"], + }, + { + _id: 4, + title: "Elegant Chiffon Saree with Gold Embroidery", + // image: "/saree-4.webp", + images: [ + "/yellow-1.avif", + "/yellow-2.avif", + "/yellow-3.avif", + "/yellow-4.avif", + ], + originalPrice: 3999, + offerPrice: 3199, + discount: 20, + colors: ["#FFD700", "#FF4500", "#8B0000"], + }, + { + _id: 5, + title: "Designer Georgette Saree with Sequins", + // image: "/saree-5.webp", + images: [ + "/chinon-1.avif", + "/chinon-2.avif", + "/chinon-3.avif", + "/chinon-4.avif", + ], + originalPrice: 5999, + offerPrice: 4499, + discount: 25, + colors: ["#8A2BE2", "#4B0082", "#9370DB"], + }, + { + _id: 6, + title: "Cotton Handloom Saree | Daily Wear", + // image: "/saree-6.webp", + images: [ + "/lehangaa-1.avif", + "/lehangaa-2.avif", + "/lehangaa-3.avif", + "/lehangaa-4.avif", + "/lehangaa-5.avif", + ], + originalPrice: 1999, + offerPrice: 1499, + discount: 25, + colors: ["#228B22", "#ADFF2F", "#006400"], + }, + { + _id: 7, + title: "Silk Linen Saree with Contrast Border", + // image: "/saree-7.webp", + images: [ + "/chinon-1.avif", + "/chinon-2.avif", + "/chinon-3.avif", + "/chinon-4.avif", + ], + originalPrice: 5499, + offerPrice: 4299, + discount: 22, + colors: ["#FF6347", "#FFA07A", "#FF4500"], + }, + { + _id: 8, + title: "Net Saree with Floral Embroidery", + // image: "/saree-8.webp", + images: [ + "/elegance-1.avif", + "/elegance-2.avif", + "/elegance-3.avif", + "/elegance-4.avif", + ], + originalPrice: 4999, + offerPrice: 3999, + discount: 20, + colors: ["#FFC0CB", "#FF69B4", "#DB7093"], + }, + { + _id: 9, + title: "Handwoven Maheshwari Silk Saree", + // image: "/saree-9.webp", + images: [ + "/lehangaa-1.avif", + "/lehangaa-2.avif", + "/lehangaa-3.avif", + "/lehangaa-4.avif", + "/lehangaa-5.avif", + ], + originalPrice: 7999, + offerPrice: 6399, + discount: 20, + colors: ["#800000", "#B22222", "#DC143C"], + }, + { + _id: 10, + title: "Traditional Kanjivaram Silk Saree", + // image: "/saree-10.webp", + images: [ + "/elegance-1.avif", + "/elegance-2.avif", + "/elegance-3.avif", + "/elegance-4.avif", + ], + originalPrice: 12999, + offerPrice: 9999, + discount: 23, + colors: ["#FFD700", "#FF8C00", "#8B4513"], + }, + { + _id: 11, + title: "Casual Cotton Printed Saree", + // image: "/saree-11.webp", + images: [ + "/saree-1.avif", + "/saree-2.avif", + "/saree-3.avif", + "/saree-4.avif", + "/saree-5.avif", + ], + originalPrice: 2499, + offerPrice: 1999, + discount: 20, + colors: ["#00CED1", "#20B2AA", "#48D1CC"], + }, + { + _id: 12, + title: "Designer Satin Saree with Zari Work", + // image: "/saree-12.webp", + images: [ + "/lehangaa-1.avif", + "/lehangaa-2.avif", + "/lehangaa-3.avif", + "/lehangaa-4.avif", + "/lehangaa-5.avif", + ], + originalPrice: 6999, + offerPrice: 5499, + discount: 22, + colors: ["#8B008B", "#DA70D6", "#C71585"], + }, + { + _id: 13, + title: "Elegant Organza Saree with Sequin Border", + // image: "/saree-13.webp", + images: [ + "/saree-1.avif", + "/saree-2.avif", + "/saree-3.avif", + "/saree-4.avif", + "/saree-5.avif", + ], + originalPrice: 5999, + offerPrice: 4799, + discount: 20, + colors: ["#FF69B4", "#DB7093", "#FF1493"], + }, + { + _id: 14, + title: "Banarasi Silk Saree with Traditional Motifs", + // image: "/saree-14.webp", + images: [ + "/chinon-1.avif", + "/chinon-2.avif", + "/chinon-3.avif", + "/chinon-4.avif", + ], + originalPrice: 8499, + offerPrice: 6799, + discount: 20, + colors: ["#FFD700", "#DAA520", "#B8860B"], + }, + { + _id: 15, + title: "Soft Chiffon Saree for Party Wear", + // image: "/saree-15.webp", + images: [ + "/saree-1.avif", + "/saree-2.avif", + "/saree-3.avif", + "/saree-4.avif", + "/saree-5.avif", + ], + originalPrice: 4999, + offerPrice: 3999, + discount: 20, + colors: ["#FF6347", "#FF7F50", "#FF4500"], + }, + { + _id: 16, + title: "Traditional Patola Saree for Festivals", + // image: "/saree-16.webp", + images: [ + "/lehangaa-1.avif", + "/lehangaa-2.avif", + "/lehangaa-3.avif", + "/lehangaa-4.avif", + "/lehangaa-5.avif", + ], + originalPrice: 7499, + offerPrice: 5999, + discount: 20, + colors: ["#FF0000", "#8B0000", "#B22222"], + }, + { + _id: 17, + title: "Handmade Bandhani Saree", + // image: "/saree-17.webp", + images: [ + "/chinon-1.avif", + "/chinon-2.avif", + "/chinon-3.avif", + "/chinon-4.avif", + ], + originalPrice: 3999, + offerPrice: 3199, + discount: 20, + colors: ["#8B4513", "#A0522D", "#CD853F"], + }, + { + _id: 18, + title: "Designer Georgette Saree with Blouse Piece", + // image: "/saree-18.webp", + images: [ + "/saree-1.avif", + "/saree-2.avif", + "/saree-3.avif", + "/saree-4.avif", + "/saree-5.avif", + ], + originalPrice: 5999, + offerPrice: 4499, + discount: 25, + colors: ["#FF69B4", "#DB7093", "#C71585"], + }, + { + _id: 19, + title: "Silk Cotton Saree for Occasions", + // image: "/saree-19.webp", + images: [ + "/saree-1.avif", + "/saree-2.avif", + "/saree-3.avif", + "/saree-4.avif", + "/saree-5.avif", + ], + originalPrice: 6499, + offerPrice: 4999, + discount: 23, + colors: ["#008080", "#20B2AA", "#40E0D0"], + }, + { + _id: 20, + title: "Luxury Designer Saree with Hand Embroidery", + // image: "/saree-20.webp", + images: [ + "/lehangaa-1.avif", + "/lehangaa-2.avif", + "/lehangaa-3.avif", + "/lehangaa-4.avif", + "/lehangaa-5.avif", + ], + originalPrice: 11999, + offerPrice: 8999, + discount: 25, + colors: ["#4B0082", "#8A2BE2", "#9400D3"], + }, +]; diff --git a/src/features/address/addressApi.js b/src/features/address/addressApi.js new file mode 100644 index 0000000..8e79f42 --- /dev/null +++ b/src/features/address/addressApi.js @@ -0,0 +1,35 @@ +// src/store/api/addressApi.js +import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; + +export const addressApi = createApi({ + reducerPath: "addressApi", + tagTypes: ["Addresses"], + baseQuery: fetchBaseQuery({ + baseUrl: import.meta.env.VITE_API_BASE_URL, + // baseUrl: "http://localhost:3000/api", + prepareHeaders: (headers, { getState }) => { + const token = getState().auth?.token; + console.log("RTK token:", token); + if (token) { + headers.set("Authorization", `Bearer ${token}`); + } + return headers; + }, + }), + endpoints: (builder) => ({ + getAddresses: builder.query({ + query: () => "/users/addresses", + providesTags: ["Addresses"], + }), + addAddress: builder.mutation({ + query: (addressData) => ({ + url: "/users/addresses", + method: "POST", + body: addressData, + }), + invalidatesTags: ["Addresses"], + }), + }), +}); + +export const { useGetAddressesQuery, useAddAddressMutation } = addressApi; diff --git a/src/features/auth/authApi.js b/src/features/auth/authApi.js new file mode 100644 index 0000000..92b266b --- /dev/null +++ b/src/features/auth/authApi.js @@ -0,0 +1,150 @@ +import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; + +export const authApi = createApi({ + reducerPath: "authApi", + baseQuery: fetchBaseQuery({ + baseUrl: import.meta.env.VITE_API_BASE_URL, + prepareHeaders: (headers, { getState }) => { + const token = getState().auth.token; + if (token) { + headers.set("Authorization", `Bearer ${token}`); + } + return headers; + }, + }), + + tagTypes: ["Profile", "Addresses"], + + endpoints: (builder) => ({ + login: builder.mutation({ + query: (credentials) => ({ + url: "/auth/login", + method: "POST", + body: credentials, + }), + }), + + register: builder.mutation({ + query: (body) => ({ + url: "/auth/register", + method: "POST", + body, + }), + }), + + sendOtp: builder.mutation({ + query: (phone) => ({ + url: "/auth/send-otp", + method: "POST", + body: { phone }, + }), + }), + + verifyOtp: builder.mutation({ + query: ({ phone, otp }) => ({ + url: "/auth/verify-otp", + method: "POST", + body: { phone, otp }, + }), + }), + + getProfile: builder.query({ + query: () => ({ + url: "/users/profile", + method: "GET", + }), + }), + + updateProfile: builder.mutation({ + query: (profileData) => ({ + url: "/users/profile", + method: "PUT", + body: profileData, + }), + invalidatesTags: ["Profile"], // automatically refetch getProfile + }), + + // getAddresses: builder.query({ + // query: () => "/users/addresses", + // }), + + getAddresses: builder.query({ + query: () => "/users/addresses", + providesTags: ["Addresses"], + }), + + // addAddress: builder.mutation({ + // query: (addressData) => ({ + // url: "/users/addresses", + // method: "POST", + // body: addressData, + // }), + // }), + + addAddress: builder.mutation({ + query: (addressData) => ({ + url: "/users/addresses", + method: "POST", + body: addressData, + }), + invalidatesTags: ["Addresses"], + }), + + // updateAddress: builder.mutation({ + // query: ({ id, body }) => ({ + // url: `/users/addresses/${id}`, + // method: "PUT", + // body, + // }), + // }), + + updateAddress: builder.mutation({ + query: ({ id, body }) => ({ + url: `/users/addresses/${id}`, + method: "PUT", + body, + }), + invalidatesTags: ["Addresses"], + }), + + // deleteAddress: builder.mutation({ + // query: (id) => ({ + // url: `/users/addresses/${id}`, + // method: "DELETE", + // }), + // invalidatesTags: ["Addresses"], // optional, if you are caching the list + // }), + + deleteAddress: builder.mutation({ + query: (id) => ({ + url: `/users/addresses/${id}`, + method: "DELETE", + }), + invalidatesTags: ["Addresses"], + }), + + changePassword: builder.mutation({ + query: (body) => ({ + url: "/auth/change-password", + method: "PUT", + body, + }), + }), + }), +}); + +export const { + useRegisterMutation, + useLoginMutation, + useSendOtpMutation, + useVerifyOtpMutation, + useGetProfileQuery, + useUpdateProfileMutation, + useGetAddressesQuery, + useAddAddressMutation, + useUpdateAddressMutation, + useDeleteAddressMutation, + useChangePasswordMutation, +} = authApi; + +// export const { useLoginMutation, useGetProfileQuery } = authApi; diff --git a/src/features/auth/authSlice.js b/src/features/auth/authSlice.js new file mode 100644 index 0000000..6622b13 --- /dev/null +++ b/src/features/auth/authSlice.js @@ -0,0 +1,71 @@ +// import { createSlice } from "@reduxjs/toolkit"; + +// const initialState = { +// user: JSON.parse(localStorage.getItem("user")) || null, +// token: localStorage.getItem("token") || null, +// refreshToken: localStorage.getItem("refreshToken") || null, +// }; + +// const authSlice = createSlice({ +// name: "auth", +// initialState, +// reducers: { +// setCredentials: (state, action) => { +// state.user = action.payload.user; +// state.token = action.payload.token; +// state.refreshToken = action.payload.refreshToken; +// }, +// logout: (state) => { +// state.user = null; +// state.token = null; +// state.refreshToken = null; +// }, +// }, +// }); + +// export const { setCredentials, logout } = authSlice.actions; +// export default authSlice.reducer; + + + + +import { createSlice } from "@reduxjs/toolkit"; + +const initialState = { + user: JSON.parse(localStorage.getItem("user")) || null, + token: localStorage.getItem("token") || null, + refreshToken: localStorage.getItem("refreshToken") || null, +}; + +const authSlice = createSlice({ + name: "auth", + initialState, + reducers: { + setCredentials: (state, action) => { + const { user, token, refreshToken } = action.payload; + + state.user = user; + state.token = token; + state.refreshToken = refreshToken; + + // ✅ Save to localStorage + localStorage.setItem("user", JSON.stringify(user)); + localStorage.setItem("token", token); + localStorage.setItem("refreshToken", refreshToken); + }, + + logout: (state) => { + state.user = null; + state.token = null; + state.refreshToken = null; + + // ✅ Clear localStorage + localStorage.removeItem("user"); + localStorage.removeItem("token"); + localStorage.removeItem("refreshToken"); + }, + }, +}); + +export const { setCredentials, logout } = authSlice.actions; +export default authSlice.reducer; \ No newline at end of file diff --git a/src/features/cart/cartAPI.js b/src/features/cart/cartAPI.js new file mode 100644 index 0000000..c8af292 --- /dev/null +++ b/src/features/cart/cartAPI.js @@ -0,0 +1,56 @@ +import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; + +export const cartApi = createApi({ + reducerPath: "cartApi", + baseQuery: fetchBaseQuery({ + // baseUrl: "http://localhost:3000/api/users", + baseUrl: import.meta.env.VITE_API_BASE_URL, + prepareHeaders: (headers, { getState }) => { + const token = getState().auth?.token || localStorage.getItem("token"); + + if (token) { + headers.set("Authorization", `Bearer ${token}`); + } + + return headers; + }, + + // prepareHeaders: (headers, { getState }) => { + // const token = getState().auth?.token; + // if (token) headers.set("Authorization", `Bearer ${token}`); + // return headers; + // }, + }), + tagTypes: ["Cart"], + endpoints: (builder) => ({ + // Add to Cart + addToCart: builder.mutation({ + query: ({ productId, quantity }) => ({ + url: "/users/my-cart/add-item", + method: "POST", + body: { productId, quantity }, + }), + invalidatesTags: ["Cart"], + }), + + // Fetch Cart Items - THIS IS CONFIRMED CORRECT + getCartItems: builder.query({ + query: () => "/users/my-cart", // ✔ matches your backend curl + providesTags: ["Cart"], + }), + // Remove from Cart + removeFromCart: builder.mutation({ + query: (productId) => ({ + url: `/users/my-cart/remove-item/${productId}`, + method: "DELETE", + }), + invalidatesTags: ["Cart"], + }), + }), +}); + +export const { + useAddToCartMutation, + useGetCartItemsQuery, + useRemoveFromCartMutation, +} = cartApi; diff --git a/src/features/cart/cartSlice.js b/src/features/cart/cartSlice.js new file mode 100644 index 0000000..ad9c21c --- /dev/null +++ b/src/features/cart/cartSlice.js @@ -0,0 +1,21 @@ +import { createSlice } from "@reduxjs/toolkit"; + +const initialState = { + items: [], +}; + +const cartSlice = createSlice({ + name: "cart", + initialState, + reducers: { + setCart: (state, action) => { + state.items = action.payload; + }, + clearCart: (state) => { + state.items = []; + }, + }, +}); + +export const { setCart, clearCart } = cartSlice.actions; +export default cartSlice.reducer; diff --git a/src/features/categories/categoryApi.js b/src/features/categories/categoryApi.js new file mode 100644 index 0000000..6c77f6b --- /dev/null +++ b/src/features/categories/categoryApi.js @@ -0,0 +1,26 @@ +import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; + +export const categoriesApi = createApi({ + reducerPath: "categoriesApi", + baseQuery: fetchBaseQuery({ + baseUrl: import.meta.env.VITE_API_BASE_URL, + }), + endpoints: (builder) => ({ + getCategoryTree: builder.query({ + query: () => "/products/tree", + }), + getCategories: builder.query({ + query: () => "/products/categories", + }), + // ✅ NEW: Category Colors (shirts, sarees, lehenga, etc.) + getCategoryColors: builder.query({ + query: (categorySlug) => `/products/category-colors/${categorySlug}`, + }), + }), +}); + +export const { + useGetCategoryTreeQuery, + useGetCategoriesQuery, + useGetCategoryColorsQuery, +} = categoriesApi; diff --git a/src/features/coupons/couponsApi.js b/src/features/coupons/couponsApi.js new file mode 100644 index 0000000..33382c5 --- /dev/null +++ b/src/features/coupons/couponsApi.js @@ -0,0 +1,36 @@ +import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; + +export const couponsApi = createApi({ + reducerPath: "couponsApi", + baseQuery: fetchBaseQuery({ + baseUrl: import.meta.env.VITE_API_BASE_URL, + prepareHeaders: (headers, { getState }) => { + const token = getState().auth?.token || localStorage.getItem("token"); + if (token) headers.set("Authorization", `Bearer ${token}`); + return headers; + }, + }), + tagTypes: ["Coupon"], + endpoints: (builder) => ({ + // Validate a coupon + validateCoupon: builder.mutation({ + query: ({ code, orderAmount }) => ({ + url: "/coupons/validate", + method: "POST", + body: { code, orderAmount }, + }), + invalidatesTags: ["Coupon"], + }), + + // Fetch available coupons + getAvailableCoupons: builder.query({ + query: (orderAmount) => `/coupons/available?orderAmount=${orderAmount}`, + providesTags: ["Coupon"], + }), + }), +}); + +export const { + useValidateCouponMutation, + useGetAvailableCouponsQuery, +} = couponsApi; diff --git a/src/features/orders/ordersApi.js b/src/features/orders/ordersApi.js new file mode 100644 index 0000000..b69ffd5 --- /dev/null +++ b/src/features/orders/ordersApi.js @@ -0,0 +1,72 @@ +import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; + +export const ordersApi = createApi({ + reducerPath: "ordersApi", + baseQuery: fetchBaseQuery({ + baseUrl: import.meta.env.VITE_API_BASE_URL, + prepareHeaders: (headers, { getState }) => { + const token = getState().auth.token; + if (token) { + headers.set("Authorization", `Bearer ${token}`); + } + return headers; + }, + }), + tagTypes: ["Orders", "Cart"], + endpoints: (builder) => ({ + getOrders: builder.query({ + query: ({ page = 1, limit = 10 } = {}) => + `/orders?page=${page}&limit=${limit}`, + transformResponse: (response) => ({ + orders: response?.data?.orders || [], + pagination: response?.data?.pagination || {}, + }), + providesTags: ["Orders"], + }), + createOrder: builder.mutation({ + query: (body) => ({ + url: "/orders", + method: "POST", + body, + }), + invalidatesTags: ["Orders", "Cart"], + }), + getOrderById: builder.query({ + query: (id) => `/orders/${id}`, + providesTags: (result, error, id) => [{ type: "Orders", id }], + }), + + getOrderTracking: builder.query({ + query: ({ orderId, pincode, shippingMethod }) => ({ + url: `/delivery/orders/${orderId}/tracking`, + method: "GET", + params: { + pincode, + shippingMethod, + }, + }), + providesTags: (result, error, { orderId }) => [ + { type: "Orders", id: orderId }, + ], + }), + + // 🔴 Cancel Order + cancelOrder: builder.mutation({ + query: (orderId) => ({ + url: `/orders/${orderId}/cancel`, + method: "PUT", + }), + invalidatesTags: (result, error, orderId) => [ + { type: "Orders", id: orderId }, + ], + }), + }), +}); + +export const { + useGetOrdersQuery, + useCreateOrderMutation, + useGetOrderByIdQuery, + useCancelOrderMutation, + useGetOrderTrackingQuery, +} = ordersApi; diff --git a/src/features/products/productsAPI.js b/src/features/products/productsAPI.js new file mode 100644 index 0000000..6b37083 --- /dev/null +++ b/src/features/products/productsAPI.js @@ -0,0 +1,66 @@ +import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; + +export const productApi = createApi({ + reducerPath: "productApi", + baseQuery: fetchBaseQuery({ + baseUrl: import.meta.env.VITE_API_BASE_URL, + prepareHeaders: (headers, { getState }) => { + const token = getState().auth?.token; + if (token) { + headers.set("Authorization", `Bearer ${token}`); + } + return headers; + }, + }), + endpoints: (builder) => ({ + getFeaturedProducts: builder.query({ + query: (limit = 10) => { + console.log("🔍 Fetching products with limit:", limit); + return `/products/featured?limit=${limit}`; + }, + transformResponse: (response) => { + // console.log("📦 Raw API Response:", response); + // console.log("📦 Products array:", response.data.products); + return response.data.products; + }, + }), + getProductsByCategory: builder.query({ + query: ({ categorySlug, limit = 20, skip = 0 }) => + `/products/category/${categorySlug}?limit=${limit}&skip=${skip}`, + }), + getProductBySlug: builder.query({ + query: (slug) => `/products/${slug}`, + }), + getNewArrivals: builder.query({ + query: (limit = 10) => `/products/new-arrivals?limit=${limit}`, + }), + + // ✅ New Most Loved Products endpoint + getMostLovedProducts: builder.query({ + query: (limit = 8) => `/products/most-loved?limit=${limit}`, + transformResponse: (response) => response.data.products, + }), + // ✅ Add this: Recommendations endpoint + getProductRecommendations: builder.query({ + query: ({ slug, limit }) => + `/products/${slug}/recommendations?limit=${limit}`, + transformResponse: (response) => response.data || [], // <-- this ensures you get the array directly + }), + // Add inside endpoints: + getPersonalizedRecommendations: builder.query({ + query: (limit = 10) => + `/products/recommendations/personalized?limit=${limit}`, + transformResponse: (response) => response.data || [], // directly returns array + }), + }), +}); + +export const { + useGetFeaturedProductsQuery, + useGetProductsByCategoryQuery, + useGetProductBySlugQuery, + useGetNewArrivalsQuery, + useGetMostLovedProductsQuery, + useGetProductRecommendationsQuery, + useGetPersonalizedRecommendationsQuery, +} = productApi; diff --git a/src/features/products/productsSlice.js b/src/features/products/productsSlice.js new file mode 100644 index 0000000..e69de29 diff --git a/src/features/recentlyViewed/recentlyViewedApi.js b/src/features/recentlyViewed/recentlyViewedApi.js new file mode 100644 index 0000000..1bb1da3 --- /dev/null +++ b/src/features/recentlyViewed/recentlyViewedApi.js @@ -0,0 +1,81 @@ +import { createSlice, createSelector } from '@reduxjs/toolkit'; + +const STORAGE_KEY = 'recentlyViewedProducts'; +const MAX_ITEMS = 10; + +// Load from localStorage on init +const loadFromStorage = () => { + try { + const data = localStorage.getItem(STORAGE_KEY); + return data ? JSON.parse(data) : []; + } catch (error) { + console.error('Error loading recently viewed:', error); + return []; + } +}; + +// Save to localStorage +const saveToStorage = (products) => { + try { + localStorage.setItem(STORAGE_KEY, JSON.stringify(products)); + } catch (error) { + console.error('Error saving recently viewed:', error); + } +}; + +const recentlyViewedSlice = createSlice({ + name: 'recentlyViewed', + initialState: { + products: loadFromStorage(), + }, + reducers: { + addRecentProduct: (state, action) => { + // action.payload should contain: id, name, image, price, url + const product = { + ...action.payload, + viewedAt: new Date().toISOString(), + }; + + // Remove if already exists + state.products = state.products.filter(p => p.id !== product.id); + + // Add to beginning + state.products.unshift(product); + + // Keep only MAX_ITEMS + if (state.products.length > MAX_ITEMS) { + state.products = state.products.slice(0, MAX_ITEMS); + } + + // Save to localStorage + saveToStorage(state.products); + }, + + removeRecentProduct: (state, action) => { + state.products = state.products.filter(p => p.id !== action.payload); + saveToStorage(state.products); + }, + + clearRecentProducts: (state) => { + state.products = []; + localStorage.removeItem(STORAGE_KEY); + }, + }, +}); + +export const { + addRecentProduct, + removeRecentProduct, + clearRecentProducts +} = recentlyViewedSlice.actions; + +export default recentlyViewedSlice.reducer; + +// Base selector +export const selectRecentProducts = (state) => state.recentlyViewed.products; + +// ✅ MEMOIZED SELECTOR - Fixes the warning +export const selectRecentProductsLimit = createSelector( + [selectRecentProducts, (state, limit) => limit], + (products, limit) => products.slice(0, limit) +); \ No newline at end of file diff --git a/src/features/user/userSlice.js b/src/features/user/userSlice.js new file mode 100644 index 0000000..de7bf3b --- /dev/null +++ b/src/features/user/userSlice.js @@ -0,0 +1,27 @@ +import { createSlice } from "@reduxjs/toolkit"; + +const initialState = { + userInfo: null, + isLoggedIn: false, +}; + +const userSlice = createSlice({ + name: "user", + initialState, + reducers: { + login: (state, action) => { + state.userInfo = action.payload; + state.isLoggedIn = true; + }, + logout: (state) => { + state.userInfo = null; + state.isLoggedIn = false; + }, + }, +}); + +// Named exports for actions +export const { login, logout } = userSlice.actions; + +// Default export for the reducer +export default userSlice.reducer; diff --git a/src/features/wardrobe/wardrobeApi.js b/src/features/wardrobe/wardrobeApi.js new file mode 100644 index 0000000..c3aa572 --- /dev/null +++ b/src/features/wardrobe/wardrobeApi.js @@ -0,0 +1,61 @@ +import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; + +export const wardrobeApi = createApi({ + reducerPath: "wardrobeApi", + baseQuery: fetchBaseQuery({ + baseUrl: import.meta.env.VITE_API_BASE_URL, + prepareHeaders: (headers, { getState }) => { + const token = localStorage.getItem("token"); // or from your auth slice + if (token) { + headers.set("Authorization", `Bearer ${token}`); + } + return headers; + }, + }), + endpoints: (builder) => ({ + getWardrobe: builder.query({ + query: () => ({ + url: "/wardrobe", + method: "GET", + }), + }), + addWardrobeItem: builder.mutation({ + query: (body) => ({ + url: "/wardrobe/items", + method: "POST", + body, + }), + invalidatesTags: ["Wardrobe"], + }), + getItemsByCategory: builder.query({ + query: (category) => `/wardrobe/items/category/${category}`, + }), + getWardrobeStats: builder.query({ + query: () => "/wardrobe/stats", + }), + searchItems: builder.query({ + query: ({ query, category }) => { + const params = new URLSearchParams(); + if (query) params.append("query", query); + if (category && category !== "All") + params.append("category", category.toLowerCase()); + return `/wardrobe/items/search?${params.toString()}`; + }, + }), + getPublicWardrobes: builder.query({ + query: ({ page = 1, limit = 20 } = {}) => `/wardrobe/public?page=${page}&limit=${limit}`, + }), + getPublicWardrobeById: builder.query({ + query: (id) => `/wardrobe/public/${id}`, + }), + }), +}); + +export const { + useGetWardrobeQuery, + useAddWardrobeItemMutation, + useGetItemsByCategoryQuery, + useGetWardrobeStatsQuery, + useSearchItemsQuery, + useGetPublicWardrobesQuery, useGetPublicWardrobeByIdQuery, +} = wardrobeApi; diff --git a/src/features/wishlist/wishlistApi.js b/src/features/wishlist/wishlistApi.js new file mode 100644 index 0000000..f09a712 --- /dev/null +++ b/src/features/wishlist/wishlistApi.js @@ -0,0 +1,45 @@ +import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; + +export const wishlistApi = createApi({ + reducerPath: "wishlistApi", + baseQuery: fetchBaseQuery({ + baseUrl: import.meta.env.VITE_API_BASE_URL, + prepareHeaders: (headers, { getState }) => { + // FIX — Get token from Redux, not localStorage + const token = getState().auth?.token; + + if (token) { + headers.set("Authorization", `Bearer ${token}`); + } + return headers; + }, + }), + tagTypes: ["Wishlist"], + endpoints: (builder) => ({ + getWishlist: builder.query({ + query: () => "/users/wishlist", + providesTags: ["Wishlist"], + }), + addToWishlist: builder.mutation({ + query: (productId) => ({ + url: "/users/wishlist", + method: "POST", + body: { productId }, + }), + invalidatesTags: ["Wishlist"], + }), + removeFromWishlist: builder.mutation({ + query: (productId) => ({ + url: `/users/wishlist/${productId}`, + method: "DELETE", + }), + invalidatesTags: ["Wishlist"], + }), + }), +}); + +export const { + useGetWishlistQuery, + useAddToWishlistMutation, + useRemoveFromWishlistMutation, +} = wishlistApi; diff --git a/src/features/wishlist/wishlistSlice.js b/src/features/wishlist/wishlistSlice.js new file mode 100644 index 0000000..116d816 --- /dev/null +++ b/src/features/wishlist/wishlistSlice.js @@ -0,0 +1,19 @@ +// wishlistSlice.js +import { createSlice } from "@reduxjs/toolkit"; + +const initialState = { + items: [], +}; + +const wishlistSlice = createSlice({ + name: "wishlist", + initialState, + reducers: { + setWishlist: (state, action) => { + state.items = action.payload; + }, + }, +}); + +export const { setWishlist } = wishlistSlice.actions; +export default wishlistSlice.reducer; diff --git a/src/hooks/useCart.js b/src/hooks/useCart.js new file mode 100644 index 0000000..e69de29 diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..1d657cb --- /dev/null +++ b/src/index.css @@ -0,0 +1,91 @@ +@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap'); + +/* Tailwind base */ +@tailwind base; +@tailwind components; +@tailwind utilities; + +/* Optional: set Poppins as default font */ +body { + @apply font-sans; /* Tailwind default sans */ + font-family: 'Poppins', sans-serif; +} + +.input-field { + @apply w-full border border-gray-300 rounded-lg p-2 focus:outline-none focus:ring-2 focus:ring-primary-default; +} + + +.swiper-button-prev-custom, +.swiper-button-next-custom { + position: absolute; + top: 50%; + z-index: 50; + width: 55px; + height: 55px; + border-radius: 50%; + background: rgba(255, 255, 255, 0.35); + backdrop-filter: blur(10px); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25); + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transform: translateY(-50%); + transition: all 0.3s ease; +} + +.swiper-button-prev-custom { + left: -20px; +} + +.swiper-button-next-custom { + right: -20px; +} + +.swiper-button-prev-custom:hover, +.swiper-button-next-custom:hover { + background: rgba(255, 255, 255, 0.85); + transform: translateY(-50%) scale(1.12); +} + + + +/* Webkit Browsers (Chrome, Safari, Edge) */ +::-webkit-scrollbar { + width: 10px; /* wider for better visibility */ + height: 10px; /* horizontal scrollbar */ +} + +::-webkit-scrollbar-track { + background: rgba(255, 255, 255, 0.1); /* light transparent track */ + border-radius: 10px; + box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.05); /* subtle inner shadow */ +} + +::-webkit-scrollbar-thumb { + background: linear-gradient(180deg, #B07F5D, #FF9F1C); /* golden gradient */ + border-radius: 10px; + border: 2px solid rgba(0,0,0,0.1); /* adds separation from track */ + box-shadow: 0 2px 5px rgba(0,0,0,0.2); /* subtle depth */ +} + +::-webkit-scrollbar-thumb:hover { + background: linear-gradient(180deg, #FFD36A, #FFA500); /* brighter gradient on hover */ + box-shadow: 0 4px 10px rgba(0,0,0,0.3); /* stronger depth */ + transform: scale(1.05); + transition: all 0.2s ease-in-out; +} + +/* Firefox */ +* { + scrollbar-width: thin; + scrollbar-color: #B07F5D rgba(255, 255, 255, 0.1); +} + + +@layer components { + .inp { + @apply w-full px-4 py-2 border rounded-xl focus:ring-2 focus:ring-blue-500 outline-none; + } +} diff --git a/src/main.jsx b/src/main.jsx new file mode 100644 index 0000000..c68b0a8 --- /dev/null +++ b/src/main.jsx @@ -0,0 +1,17 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import { Provider } from "react-redux"; +import { BrowserRouter as Router } from "react-router-dom"; +import App from "./App"; +import "./index.css"; +import { store } from "./app/store"; + +ReactDOM.createRoot(document.getElementById("root")).render( + + + + + + + +); diff --git a/src/pages/Auth/Login.txt b/src/pages/Auth/Login.txt new file mode 100644 index 0000000..ac23e75 --- /dev/null +++ b/src/pages/Auth/Login.txt @@ -0,0 +1,127 @@ +// src/pages/Auth/Login.jsx +import React, { useState } from "react"; + +const Login = () => { + const [mobile, setMobile] = useState(""); + const [otpSent, setOtpSent] = useState(false); + const [otp, setOtp] = useState(""); + + const handleSendOtp = () => { + if (mobile.length === 10) { + setOtpSent(true); + alert(`OTP sent to ${mobile}`); + } else { + alert("Enter valid 10-digit mobile number"); + } + }; + + const handleVerifyOtp = () => { + if (otp.length === 6) { + alert("OTP Verified! Logged in successfully"); + } else { + alert("Enter valid 6-digit OTP"); + } + }; + + return ( +
+ {/* Left Image & Promo */} +
+
+ + EXTRA 15% OFF + +

+ On Your 1st Order +

+

+ Use Code: VCNEWS15 +

+ +

+ Stay stylish with the latest fashion trends! Explore our exclusive + saree collection. +

+
+
+ + {/* Right Form */} +
+
+ {/* Card Badge */} +
+ Login / Signup +
+ +
+ {!otpSent ? ( + <> + setMobile(e.target.value)} + className="w-full px-5 py-3 border border-gray-300 rounded-2xl mb-4 focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary text-gray-700 transition" + /> + + + ) : ( + <> + setOtp(e.target.value)} + className="w-full px-5 py-3 border border-gray-300 rounded-2xl mb-4 focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary text-gray-700 transition" + /> + + + )} + +

+ By continuing, I confirm that I am at least 18 years old, and + agree to{" "} + + Terms & Conditions + {" "} + and{" "} + + Privacy Policy + + . +

+ +

+ New here?{" "} + + Register Now + +

+
+
+
+
+ ); +}; + +export default Login; diff --git a/src/pages/Auth/Login/EmailLoginForm.jsx b/src/pages/Auth/Login/EmailLoginForm.jsx new file mode 100644 index 0000000..8b6304f --- /dev/null +++ b/src/pages/Auth/Login/EmailLoginForm.jsx @@ -0,0 +1,143 @@ +// import React, { useState } from "react"; +// import { useLoginMutation } from "../../../features/auth/authApi"; +// import { useDispatch } from "react-redux"; +// import { setCredentials } from "../../../features/auth/authSlice"; +// import { useNavigate } from "react-router-dom"; + +// const EmailLoginForm = () => { +// const [email, setEmail] = useState(""); +// const [password, setPassword] = useState(""); +// const [login, { isLoading }] = useLoginMutation(); +// const dispatch = useDispatch(); +// const navigate = useNavigate(); + +// const handleLogin = async () => { +// try { +// const res = await login({ email, password }).unwrap(); +// dispatch( +// setCredentials({ +// user: res.data.user, +// token: res.data.token, +// refreshToken: res.data.refreshToken, +// }) +// ); +// localStorage.setItem("token", res.data.token); +// localStorage.setItem("refreshToken", res.data.refreshToken); +// localStorage.setItem("user", JSON.stringify(res.data.user)); +// alert("Login successful!"); +// navigate("/"); +// } catch (err) { +// alert(err?.data?.message || "Login failed"); +// } +// }; + +// return ( +//
+// setEmail(e.target.value)} +// className="w-full px-5 py-3 border border-gray-300 rounded-2xl mb-4 focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary text-gray-700 transition" +// /> +// setPassword(e.target.value)} +// className="w-full px-5 py-3 border border-gray-300 rounded-2xl mb-4 focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary text-gray-700 transition" +// /> +// +//
+// ); +// }; + +// export default EmailLoginForm; + + + +import React, { useState } from "react"; +import { useLoginMutation } from "../../../features/auth/authApi"; +import { useDispatch } from "react-redux"; +import { setCredentials } from "../../../features/auth/authSlice"; +import { useNavigate } from "react-router-dom"; +import { Eye, EyeOff } from "lucide-react"; // optional, looks clean + +const EmailLoginForm = () => { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [showPassword, setShowPassword] = useState(false); + + const [login, { isLoading }] = useLoginMutation(); + const dispatch = useDispatch(); + const navigate = useNavigate(); + + const handleLogin = async () => { + try { + const res = await login({ email, password }).unwrap(); + dispatch( + setCredentials({ + user: res.data.user, + token: res.data.token, + refreshToken: res.data.refreshToken, + }) + ); + localStorage.setItem("token", res.data.token); + localStorage.setItem("refreshToken", res.data.refreshToken); + localStorage.setItem("user", JSON.stringify(res.data.user)); + alert("Login successful!"); + navigate("/"); + } catch (err) { + alert(err?.data?.message || "Login failed"); + } + }; + + return ( +
+ {/* Email */} + setEmail(e.target.value)} + className="w-full px-5 py-3 border border-gray-300 rounded-2xl mb-4 focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary text-gray-700 transition" + /> + + {/* Password with show/hide */} +
+ setPassword(e.target.value)} + className="w-full px-5 py-3 pr-12 border border-gray-300 rounded-2xl focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary text-gray-700 transition" + /> + + +
+ + {/* Login button */} + +
+ ); +}; + +export default EmailLoginForm; diff --git a/src/pages/Auth/Login/Login.jsx b/src/pages/Auth/Login/Login.jsx new file mode 100644 index 0000000..c7d2aee --- /dev/null +++ b/src/pages/Auth/Login/Login.jsx @@ -0,0 +1,16 @@ +import React from "react"; +import PromoSection from "../../Auth/Login/PromoSection"; +import LoginCard from "../../Auth/Login/LoginCard"; + +const Login = () => { + return ( +
+ +
+ +
+
+ ); +}; + +export default Login; diff --git a/src/pages/Auth/Login/LoginCard.jsx b/src/pages/Auth/Login/LoginCard.jsx new file mode 100644 index 0000000..4961505 --- /dev/null +++ b/src/pages/Auth/Login/LoginCard.jsx @@ -0,0 +1,67 @@ +import React, { useState } from "react"; +import OtpLoginForm from "./OtpLoginForm"; +import EmailLoginForm from "./EmailLoginForm"; + +const LoginCard = () => { + const [loginMethod, setLoginMethod] = useState("otp"); // 'otp' or 'email' + + return ( +
+ {/* Card Badge */} +
+ Login / Signup +
+ + {/* Toggle Login Method */} +
+ + +
+ + {/* Form */} + {loginMethod === "otp" ? : } + +

+ By continuing, I confirm that I am at least 18 years old, and agree to{" "} + + Terms & Conditions + {" "} + and{" "} + + Privacy Policy + + . +

+ +

+ New here?{" "} + + Register Now + +

+
+ ); +}; + +export default LoginCard; diff --git a/src/pages/Auth/Login/OtpLoginForm.jsx b/src/pages/Auth/Login/OtpLoginForm.jsx new file mode 100644 index 0000000..c4e2e9c --- /dev/null +++ b/src/pages/Auth/Login/OtpLoginForm.jsx @@ -0,0 +1,102 @@ +import React, { useState } from "react"; +import { + useSendOtpMutation, + useVerifyOtpMutation, +} from "../../../features/auth/authApi"; +import { useDispatch } from "react-redux"; +import { setCredentials } from "../../../features/auth/authSlice"; +// assuming you store token here +import { useNavigate } from "react-router-dom"; + +const OtpLoginForm = ({ onOtpLogin }) => { + const navigate = useNavigate(); + const [mobile, setMobile] = useState(""); + const [otpSent, setOtpSent] = useState(false); + const [otp, setOtp] = useState(""); + + const dispatch = useDispatch(); + + const [sendOtp, { isLoading: sending }] = useSendOtpMutation(); + const [verifyOtp, { isLoading: verifying }] = useVerifyOtpMutation(); + + const handleSendOtp = async () => { + if (mobile.length !== 12) { + return alert("Enter valid 10-digit mobile number"); + } + + try { + const res = await sendOtp(mobile).unwrap(); + alert(res.message); + setOtpSent(true); + } catch (err) { + alert(err?.data?.message || "Failed to send OTP"); + } + }; + + const handleVerifyOtp = async () => { + if (otp.length !== 6) { + return alert("Enter valid 6-digit OTP"); + } + + try { + const res = await verifyOtp({ phone: mobile, otp }).unwrap(); + + // Save token in redux + dispatch( + setCredentials({ + user: res.data.user, + token: res.data.token, + refreshToken: res.data.refreshToken, + }), + ); + + alert("Login successful!"); + navigate("/"); + if (onOtpLogin) onOtpLogin(); + } catch (err) { + alert(err?.data?.message || "Invalid OTP"); + } + }; + + return ( +
+ {!otpSent ? ( + <> + setMobile(e.target.value)} + className="w-full px-5 py-3 border border-gray-300 rounded-2xl mb-4" + /> + + + ) : ( + <> + setOtp(e.target.value)} + className="w-full px-5 py-3 border border-gray-300 rounded-2xl mb-4" + /> + + + )} +
+ ); +}; + +export default OtpLoginForm; diff --git a/src/pages/Auth/Login/PromoSection.jsx b/src/pages/Auth/Login/PromoSection.jsx new file mode 100644 index 0000000..b8c7c56 --- /dev/null +++ b/src/pages/Auth/Login/PromoSection.jsx @@ -0,0 +1,33 @@ +import React from "react"; + +const PromoSection = () => { + return ( +
+
+ + EXTRA 15% OFF + +

+ On Your 1st Order +

+

+ Use Code: VCNEWS15 +

+ +

+ Stay stylish with the latest fashion trends! Explore our exclusive + saree collection. +

+
+
+ ); +}; + +export default PromoSection; diff --git a/src/pages/Auth/Register.txt b/src/pages/Auth/Register.txt new file mode 100644 index 0000000..574a025 --- /dev/null +++ b/src/pages/Auth/Register.txt @@ -0,0 +1,119 @@ +import { useState } from "react"; +import { useRegisterMutation } from "../../features/auth/authApi"; +import { useNavigate } from "react-router-dom"; + +const Register = () => { + const [formData, setFormData] = useState({ + firstName: "", + lastName: "", + username: "", + email: "", + password: "", + phone: "", + }); + + const [register, { isLoading }] = useRegisterMutation(); + const navigate = useNavigate(); + + const handleChange = (e) => { + setFormData({ ...formData, [e.target.name]: e.target.value }); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + try { + const res = await register(formData).unwrap(); + console.log("Registered user:", res); + // Save token to localStorage or Redux + localStorage.setItem("token", res.token); + alert("Registration successful!"); + navigate("/login"); // redirect to login or dashboard + } catch (err) { + console.error(err); + alert(err?.data?.message || "Registration failed"); + } + }; + + return ( +
+

Register

+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+
+ ); +}; + +export default Register; diff --git a/src/pages/Auth/Register/Register.jsx b/src/pages/Auth/Register/Register.jsx new file mode 100644 index 0000000..da9d137 --- /dev/null +++ b/src/pages/Auth/Register/Register.jsx @@ -0,0 +1,19 @@ +import React from "react"; +import PromoSection from "../Login/PromoSection"; // reuse the same promo section +import RegisterCard from "./RegisterCard"; // the form on the right + +const Register = () => { + return ( +
+ {/* Promo Section */} + + + {/* Register Form Card */} +
+ +
+
+ ); +}; + +export default Register; diff --git a/src/pages/Auth/Register/RegisterCard.jsx b/src/pages/Auth/Register/RegisterCard.jsx new file mode 100644 index 0000000..a064d9d --- /dev/null +++ b/src/pages/Auth/Register/RegisterCard.jsx @@ -0,0 +1,145 @@ +import { useState } from "react"; +import { useRegisterMutation } from "../../../features/auth/authApi"; +import { useNavigate } from "react-router-dom"; + +const RegisterCard = () => { + const [formData, setFormData] = useState({ + firstName: "", + lastName: "", + username: "", + email: "", + password: "", + phone: "", + }); + + const [register, { isLoading }] = useRegisterMutation(); + const navigate = useNavigate(); + + const handleChange = (e) => { + setFormData({ ...formData, [e.target.name]: e.target.value }); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + try { + const res = await register(formData).unwrap(); + localStorage.setItem("token", res.token); + alert("Registration successful!"); + navigate("/login"); + } catch (err) { + console.error(err); + alert(err?.data?.message || "Registration failed"); + } + }; + + return ( +
+
+ Create Account +
+ +

+ Register +

+ +
+ {/* First & Last Name */} + + + + {/* Username & Email */} + + + + {/* Password & Phone */} + + + + {/* Submit Button */} + +
+ +

+ By clicking on Register Now, I + agree with the{" "} + + Terms of Use + {" "} + and{" "} + + Privacy Policy + + . +

+ +

+ Already have an account?{" "} + navigate("/login")} + > + Login + +

+
+ ); +}; + +export default RegisterCard; diff --git a/src/pages/Cart/AddToCart.jsx b/src/pages/Cart/AddToCart.jsx new file mode 100644 index 0000000..ba20401 --- /dev/null +++ b/src/pages/Cart/AddToCart.jsx @@ -0,0 +1,69 @@ +import { useState } from "react"; +import QuantityBox from "./QuantityBox"; +import { ShoppingCart } from "lucide-react"; +import Button from "../../components/common/Button"; +import { Link, useNavigate } from "react-router-dom"; +// import { useAddToCartMutation } from "../../features/cart/cartAPI"; +import { useAddToCartMutation } from "../../features/cart/cartAPI"; + +const AddToCart = ({ price = 0, stock = 0, productId }) => { + const navigate = useNavigate(); + const [quantity, setQuantity] = useState(1); + + const [addToCart, { isLoading }] = useAddToCartMutation(); + + const handleAddToCart = async () => { + const token = localStorage.getItem("token"); + + // 🚨 NOT LOGGED IN + if (!token) { + alert("Please login first to add product to cart"); + navigate("/login"); + return; + } + + try { + await addToCart({ productId, quantity }).unwrap(); + navigate("/cart"); + alert("Added to cart!"); + } catch (error) { + console.log("Error adding to cart", error); + } + }; + + if (stock === 0) { + return ( +
+

Out of stock

+ + + +
+ ); + } + + return ( +
+
+

₹{price}

+

In stock

+
+ +
+ Quantity: + +
+ + +
+ ); +}; + +export default AddToCart; diff --git a/src/pages/Cart/Cart.jsx b/src/pages/Cart/Cart.jsx new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/Cart/CartFeatures.jsx b/src/pages/Cart/CartFeatures.jsx new file mode 100644 index 0000000..8dda4c6 --- /dev/null +++ b/src/pages/Cart/CartFeatures.jsx @@ -0,0 +1,61 @@ +import { Shield, CreditCard, RefreshCw } from "lucide-react"; + +const features = [ + { + id: 1, + icon: Shield, + title: "Genuine Products", + desc: "100% authentic items from trusted brands", + gradient: "from-green-500 to-emerald-400", + }, + { + id: 2, + icon: CreditCard, + title: "Secure Payments", + desc: "SSL encrypted & trusted payment gateways", + gradient: "from-blue-500 to-indigo-500", + }, + { + id: 3, + icon: RefreshCw, + title: "Easy Returns", + desc: "Simple & quick returns within 7 days", + gradient: "from-orange-500 to-amber-400", + }, +]; + +const CartFeatures = () => { + return ( +
+
+
+ {features.map((feature) => ( +
+ {/* Icon */} +
+ +
+ + {/* Text */} +
+

+ {feature.title} +

+

+ {feature.desc} +

+
+
+ ))} +
+
+
+ ); +}; + +export default CartFeatures; diff --git a/src/pages/Cart/CartPage.jsx b/src/pages/Cart/CartPage.jsx new file mode 100644 index 0000000..17558ed --- /dev/null +++ b/src/pages/Cart/CartPage.jsx @@ -0,0 +1,372 @@ +import React, { useState, useEffect } from "react"; +import { + useGetCartItemsQuery, + useRemoveFromCartMutation, +} from "../../features/cart/cartAPI"; +// import { useState } from "react"; +import { Trash2, Plus, Heart, Minus } from "lucide-react"; +import EmptyCart from "./EmptyCart"; +import ProgressStepper from "./ProgressStepper"; +import { useNavigate } from "react-router-dom"; +import CartFeatures from "./CartFeatures"; + +const CartPage = () => { + const navigate = useNavigate(); + + const { data, isLoading, error } = useGetCartItemsQuery(); + const cartItems = data?.data || []; + const [quantities, setQuantities] = useState({}); + + useEffect(() => { + if (cartItems.length > 0) { + setQuantities( + cartItems.reduce((acc, item) => { + acc[item.id] = item.quantity; + return acc; + }, {}) + ); + } + }, [cartItems]); + + // const [quantities, setQuantities] = useState( + // cartItems.reduce((acc, item) => ({ ...acc, [item.id]: item.quantity }), {}) + // ); + + const [removeFromCart] = useRemoveFromCartMutation(); + + const [modalItem, setModalItem] = useState(null); + + const handleIncrement = (id) => + setQuantities({ ...quantities, [id]: quantities[id] + 1 }); + + const handleDecrement = (id) => + quantities[id] > 1 && + setQuantities({ ...quantities, [id]: quantities[id] - 1 }); + + // ✅ 1. Calculate subtotal FIRST + const subtotal = cartItems.reduce( + (acc, item) => + acc + (item.product?.basePrice || 0) * (quantities[item.id] || 0), + 0 + ); + + // ✅ 2. Derived calculations + const bagTotal = subtotal; + + const mrpDiscount = bagTotal * 0.1; // 10% MRP discount + const subTotal = bagTotal - mrpDiscount; + + const additionalDiscount = subTotal > 500 ? 50 : 0; + const convenienceFee = subTotal > 500 ? 0 : 49; + + const payableAmount = subTotal - additionalDiscount + convenienceFee; + + const handleRemoveClick = (item) => setModalItem(item); + + const handleConfirmRemove = async () => { + try { + await removeFromCart(modalItem.productId).unwrap(); + setModalItem(null); + } catch (err) { + console.log("Error removing item:", err); + } + }; + + const handleCheckout = () => { + if (!cartItems.length) return alert("Your cart is empty"); + + const subtotalCalc = cartItems.reduce( + (acc, item) => + acc + (item.product?.basePrice || 0) * (quantities[item.id] || 0), + 0 + ); + + const bagTotalCalc = subtotalCalc; + const mrpDiscountCalc = bagTotalCalc * 0.1; + const subTotalCalc = bagTotalCalc - mrpDiscountCalc; + const additionalDiscountCalc = subTotalCalc > 500 ? 50 : 0; + const convenienceFeeCalc = subTotalCalc > 500 ? 0 : 49; + const payableAmountCalc = + subTotalCalc - additionalDiscountCalc + convenienceFeeCalc; + + navigate("/checkout", { + state: { + cartItems, + quantities, + subtotal: subTotalCalc, + bagTotal: bagTotalCalc, + mrpDiscount: mrpDiscountCalc, + additionalDiscount: additionalDiscountCalc, + convenienceFee: convenienceFeeCalc, + payableAmount: payableAmountCalc, + }, + }); + }; + + const handleMoveToWishlist = () => { + // TODO: call API to move item to wishlist + alert(`${modalItem.product?.name} moved to wishlist!`); + setModalItem(null); + }; + + if (isLoading) + return

Loading cart...

; + // if (error) + // return

Error loading cart

; + if (!cartItems.length) return ; + + return ( +
+ {/* Header with cart item count */} +

+ Your Shopping Cart | {cartItems.length}{" "} + {cartItems.length === 1 ? "item" : "items"} +

+ + + +
+ {/* Products List */} +
+ {cartItems.map((item) => ( +
+ {/* Product Image */} +
+ {item.product?.name} +
+ + {/* Product Details */} +
+

+ {item.product?.name} +

+

+ ₹{item.product?.basePrice} +

+ + {/* Quantity Controls */} +
+ + + {quantities[item.id]} + + +
+
+ + {/* Price & Actions */} +
+ +

+ ₹{(item.product?.basePrice || 0) * quantities[item.id]} +

+
+
+ ))} +
+ + {/* Summary Sidebar */} +
+ {/* Header */} +

+ Order Summary +

+ + {/* Price Details */} +
+ {/* Bag Total */} +
+ Bag Total + + ₹{bagTotal.toFixed(2)} + +
+ + {/* Discount on MRP */} +
+ Discount on MRP + + -₹{mrpDiscount.toFixed(2)} + +
+ + {/* Sub Total */} +
+ Sub Total + + ₹{subTotal.toFixed(2)} + +
+ + {/* Additional Discount */} +
+ + Additional Discount + (Offer) + + + -₹{additionalDiscount.toFixed(2)} + +
+ + {/* Convenience Fee */} +
+ Convenience Charges + + ₹{convenienceFee.toFixed(2)} + +
+
+ + {/* Divider */} +
+ + {/* You Pay */} +
+ You Pay + + ₹{payableAmount.toFixed(2)} + +
+ + {/* Checkout Button */} + {/* */} + + + + + {/* Savings Info */} +

+ 🎉 You saved ₹{(mrpDiscount + additionalDiscount).toFixed(2)} on + this order +

+
+ + {/* Remove Item Modal */} + {modalItem && ( +
+
+ {/* Close Button */} + + +
+ {/* LEFT: Product Image */} + {modalItem.product?.name} + + {/* RIGHT SIDE */} +
+
+

+ Remove this item? +

+ +

+ You are about to remove{" "} + + {modalItem.product?.name} + {" "} + from your cart. +

+ + {/* Product Price */} +

+ Price: ₹{modalItem.product?.basePrice} +

+
+ + {/* BUTTONS */} +
+ {/* Move to Wishlist */} + + + {/* Remove Button */} + +
+ + {/* Cancel Button */} + {/* */} +
+
+
+
+ )} +
+ +
+ +
+
+ ); +}; + +export default CartPage; diff --git a/src/pages/Cart/EmptyCart.jsx b/src/pages/Cart/EmptyCart.jsx new file mode 100644 index 0000000..88f0ec9 --- /dev/null +++ b/src/pages/Cart/EmptyCart.jsx @@ -0,0 +1,26 @@ +import React from "react"; +import { Link } from "react-router-dom"; +import Button from "../../components/common/Button"; + +const EmptyCart = () => { + return ( +
+ Empty Cart +

+ Your Cart is Empty +

+

+ Add items to your cart to proceed with your shopping. +

+ + + +
+ ); +}; + +export default EmptyCart; diff --git a/src/pages/Cart/ProgressStepper.jsx b/src/pages/Cart/ProgressStepper.jsx new file mode 100644 index 0000000..16f30cf --- /dev/null +++ b/src/pages/Cart/ProgressStepper.jsx @@ -0,0 +1,182 @@ +// import React from "react"; +// import { ShoppingCart, MapPin, CreditCard, Check } from "lucide-react"; + +// const ProgressStepper = () => { +// const currentStep = 1; + +// const steps = [ +// { number: 1, label: "Cart", icon: ShoppingCart }, +// { number: 2, label: "Address & Payment", icon: MapPin }, +// { number: 3, label: "Payment", icon: CreditCard }, +// ]; + +// const progressPercentage = ((currentStep - 1) / (steps.length - 1)) * 100; + +// return ( +//
+//
+// {/* Background line - centered through circles */} +//
+ +// {/* Progress line - centered through circles */} +//
+ +// {steps.map((step, index) => { +// const isCompleted = currentStep > step.number; +// const isActive = currentStep === step.number; +// const Icon = step.icon; + +// return ( +//
+// {/* Circle with icon */} +//
+// {isCompleted ? ( +// +// ) : ( +// +// )} +//
+ +// {/* Label */} +//

+// {step.label} +//

+//
+// ); +// })} +//
+//
+// ); +// }; + +// export default ProgressStepper; + + + + +import React from "react"; +import { ShoppingCart, MapPin, CreditCard, Check } from "lucide-react"; + +const ProgressStepper = () => { + const currentStep = 1; + + const steps = [ + { number: 1, label: "Cart", icon: ShoppingCart }, + { number: 2, label: "Address & Payment", icon: MapPin }, + { number: 3, label: "Payment", icon: CreditCard }, + ]; + + const progressPercentage = ((currentStep - 1) / (steps.length - 1)) * 100; + + return ( +
+
+ {/* Background line */} +
+ + {/* Progress line */} +
+ + {steps.map((step) => { + const isCompleted = currentStep > step.number; + const isActive = currentStep === step.number; + const Icon = step.icon; + + return ( +
+ {/* Step circle */} +
+ {isCompleted ? ( + + ) : ( + + )} +
+ + {/* Label */} +

+ {step.label} +

+
+ ); + })} +
+
+ ); +}; + +export default ProgressStepper; diff --git a/src/pages/Cart/QuantityBox.jsx b/src/pages/Cart/QuantityBox.jsx new file mode 100644 index 0000000..062fbdc --- /dev/null +++ b/src/pages/Cart/QuantityBox.jsx @@ -0,0 +1,25 @@ +import { Minus, Plus } from "lucide-react"; + +const QuantityBox = ({ quantity, setQuantity }) => { + const decrease = () => { + if (quantity > 1) setQuantity(quantity - 1); + }; + + const increase = () => setQuantity(quantity + 1); + + return ( +
+ + + {quantity} + + +
+ ); +}; + +export default QuantityBox; diff --git a/src/pages/Checkout/Checkout.jsx b/src/pages/Checkout/Checkout.jsx new file mode 100644 index 0000000..4479749 --- /dev/null +++ b/src/pages/Checkout/Checkout.jsx @@ -0,0 +1,327 @@ +// import React, { useEffect, useState } from "react"; +// import { useLocation, useNavigate } from "react-router-dom"; +// import CheckoutGuard from "./components/CheckoutGuard"; +// import AddressSection from "./components/AddressSection"; +// import PaymentSection from "./components/PaymentSection"; +// import OrderSummary from "./components/OrderSummary"; +// import { useGetAddressesQuery } from "../../features/address/addressApi"; +// import { useCreateOrderMutation } from "../../features/orders/ordersApi"; +// import { useDispatch } from "react-redux"; +// import { cartApi } from "../../features/cart/cartAPI"; + +// const Checkout = () => { +// const { state } = useLocation(); +// const dispatch = useDispatch(); + +// const navigate = useNavigate(); +// const [addingNewAddress, setAddingNewAddress] = useState(false); +// const [createOrder, { isLoading: placingOrder }] = useCreateOrderMutation(); + +// // ✅ Add coupon state +// const [appliedCoupon, setAppliedCoupon] = useState(null); +// const [finalAmount, setFinalAmount] = useState(0); + +// const { +// cartItems = [], +// quantities = {}, +// bagTotal = 0, +// mrpDiscount = 0, +// additionalDiscount = 0, +// convenienceFee = 0, +// payableAmount = 0, +// } = state || {}; + +// const { data, isLoading } = useGetAddressesQuery(); +// const addresses = data?.data?.addresses || []; + +// useEffect(() => { +// console.log("Address API response:", data); +// console.log("Parsed addresses:", addresses); +// }, [data]); + +// const [selectedAddress, setSelectedAddress] = useState(null); +// const [paymentMethod, setPaymentMethod] = useState("card"); + +// // Auto select default address +// useEffect(() => { +// if (addresses.length && !selectedAddress) { +// const defaultAddr = addresses.find((a) => a.isDefault); +// setSelectedAddress(defaultAddr?.id || addresses[0].id); +// } +// }, [addresses, selectedAddress]); + +// const handlePlaceOrder = async () => { +// const validItems = cartItems +// .filter((item) => item.productId && item.product && item.quantity > 0) +// .map((item) => { +// const variant = item.product.variants?.[0]; +// const price = variant?.price || item.product.basePrice || 0; +// const sku = variant?.sku || "NA"; + +// return { +// productId: item.productId, +// quantity: item.quantity, +// price, +// sku, +// }; +// }) +// .filter((item) => item.price > 0); + +// if (validItems.length === 0) { +// return alert("Your cart has invalid products. Please check your cart."); +// } + +// const orderPayload = { +// items: validItems, +// shippingAddressId: selectedAddress, +// paymentMethod: paymentMethod, +// couponCode: null, +// }; + +// try { +// // const response = await createOrder(orderPayload); +// const response = await createOrder(orderPayload).unwrap(); +// // if (response?.data?.statusCode === 201) { +// // navigate("/order-success", { state: { order: response.data.order } }); +// // ✅ THIS LINE FIXES YOUR ISSUE +// dispatch(cartApi.util.resetApiState()); + +// navigate("/order-success", { +// state: { order: response.order || response }, +// }); +// } catch (error) { +// console.error("Order failed", error); +// alert("Failed to place order. Please try again."); +// } +// }; + +// return ( +// +//
+//

+// Checkout +//

+ +//
+//
+// + +// {!addingNewAddress && ( +// +// )} +//
+ +// +//
+//
+//
+// ); +// }; + +// export default Checkout; + +// pages/Checkout.jsx - UPDATED with Coupon Integration + +import React, { useEffect, useState } from "react"; +import { useLocation, useNavigate } from "react-router-dom"; +import CheckoutGuard from "./components/CheckoutGuard"; +import AddressSection from "./components/AddressSection"; +import PaymentSection from "./components/PaymentSection"; +import OrderSummary from "./components/OrderSummary"; +import CouponApply from "./components/CouponApply"; +import { useGetAddressesQuery } from "../../features/address/addressApi"; +import { useCreateOrderMutation } from "../../features/orders/ordersApi"; +import { useDispatch } from "react-redux"; +import { cartApi } from "../../features/cart/cartAPI"; + +const Checkout = () => { + const { state } = useLocation(); + const dispatch = useDispatch(); + const navigate = useNavigate(); + + const [addingNewAddress, setAddingNewAddress] = useState(false); + const [createOrder, { isLoading: placingOrder }] = useCreateOrderMutation(); + + // ✅ Add coupon state + const [appliedCoupon, setAppliedCoupon] = useState(null); + const [finalAmount, setFinalAmount] = useState(0); + + const { + cartItems = [], + quantities = {}, + bagTotal = 0, + mrpDiscount = 0, + additionalDiscount = 0, + convenienceFee = 0, + payableAmount = 0, + } = state || {}; + + const { data, isLoading } = useGetAddressesQuery(); + const addresses = data?.data?.addresses || []; + + const [selectedAddress, setSelectedAddress] = useState(null); + const [paymentMethod, setPaymentMethod] = useState("card"); + + // Auto select default address + useEffect(() => { + if (addresses.length && !selectedAddress) { + const defaultAddr = addresses.find((a) => a.isDefault); + setSelectedAddress(defaultAddr?.id || addresses[0].id); + } + }, [addresses, selectedAddress]); + + // ✅ Calculate final amount + useEffect(() => { + const discountAmount = appliedCoupon?.discountAmount || 0; + setFinalAmount(payableAmount - discountAmount); + }, [payableAmount, appliedCoupon]); + + // ✅ Handle coupon applied + const handleCouponApplied = (couponData) => { + console.log("✅ Coupon applied:", couponData); + setAppliedCoupon(couponData); + }; + + // ✅ Handle coupon removed + const handleCouponRemoved = () => { + console.log("❌ Coupon removed"); + setAppliedCoupon(null); + }; + + const handlePlaceOrder = async () => { + const validItems = cartItems + .filter((item) => item.productId && item.product && item.quantity > 0) + .map((item) => { + const variant = item.product.variants?.[0]; + const price = variant?.price || item.product.basePrice || 0; + const sku = variant?.sku || "NA"; + + return { + productId: item.productId, + quantity: item.quantity, + price, + sku, + }; + }) + .filter((item) => item.price > 0); + + if (validItems.length === 0) { + return alert("Your cart has invalid products. Please check your cart."); + } + + if (!selectedAddress) { + return alert("Please select a shipping address."); + } + + // ✅ Include coupon code in order payload + const orderPayload = { + items: validItems, + shippingAddressId: selectedAddress, + paymentMethod: paymentMethod, + couponCode: appliedCoupon?.couponCode || null, // ✅ Send coupon code + }; + + + console.log("================================="); + console.log("🎟️ FRONTEND SENDING:"); + console.log("Coupon code:", orderPayload.couponCode); + console.log("Applied coupon:", appliedCoupon); + console.log("================================="); + + console.log("📦 Creating order with payload:", orderPayload); + + try { + const response = await createOrder(orderPayload).unwrap(); + + console.log("✅ Order created:", response); + + // Clear cart state + dispatch(cartApi.util.resetApiState()); + + // Navigate to success page + navigate("/order-success", { + state: { + order: response.order || response, + appliedCoupon: response.appliedCoupon, // Pass coupon info to success page + }, + }); + } catch (error) { + console.error("Order failed:", error); + alert(error?.data?.message || "Failed to place order. Please try again."); + } + }; + + return ( + +
+

+ Checkout +

+ +
+
+ + + {!addingNewAddress && ( + + )} + + {/* ✅ Add Coupon Section */} + {/* {!addingNewAddress && ( + + )} */} +
+ + +
+
+
+ ); +}; + +export default Checkout; diff --git a/src/pages/Checkout/components/AddressSection.jsx b/src/pages/Checkout/components/AddressSection.jsx new file mode 100644 index 0000000..8920c0d --- /dev/null +++ b/src/pages/Checkout/components/AddressSection.jsx @@ -0,0 +1,231 @@ +import { MapPin } from "lucide-react"; +import { useState } from "react"; +import { useAddAddressMutation } from "../../../features/address/addressApi"; + +const AddressSection = ({ + addresses, + selectedAddress, + setSelectedAddress, + showAddForm, + setShowAddForm, +}) => { + const [addAddress, { isLoading }] = useAddAddressMutation(); + // const [showAddForm, setShowAddForm] = useState(false); + const [newAddress, setNewAddress] = useState({ + firstName: "", + lastName: "", + phone: "", + addressLine1: "", + addressLine2: "", + city: "", + state: "", + postalCode: "", + country: "", + }); + + const handleChange = (e) => { + setNewAddress({ ...newAddress, [e.target.name]: e.target.value }); + }; + + // const handleAddAddress = () => { + // console.log("New Address:", newAddress); + // // Here call your API to save address + + // // Reset form + // setNewAddress({ + // firstName: "", + // lastName: "", + // phone: "", + // addressLine1: "", + // addressLine2: "", + // city: "", + // state: "", + // postalCode: "", + // country: "", + // }); + + // setShowAddForm(false); + // }; + + const handleAddAddress = async () => { + try { + await addAddress(newAddress).unwrap(); + setShowAddForm(false); + } catch (err) { + console.error("Failed to add address", err); + alert("Failed to save address"); + } + }; + + if (showAddForm) { + // SHOW ONLY Add New Address FORM + return ( +
+

+ Add New Address +

+ +
+ + + + + + + + + +
+ +
+ {/* */} + + + +
+
+ ); + } + + // NORMAL ADDRESS LIST VIEW + // For empty addresses + if (!addresses.length) { + return ( +
+

+ Delivery Address +

+

No saved addresses found

+ +
+ ); + } + + return ( +
+

+ Delivery Address +

+ +
+ {addresses.map((addr) => ( + + ))} +
+ + +
+ ); +}; + +export default AddressSection; diff --git a/src/pages/Checkout/components/CheckoutGuard.jsx b/src/pages/Checkout/components/CheckoutGuard.jsx new file mode 100644 index 0000000..26745d4 --- /dev/null +++ b/src/pages/Checkout/components/CheckoutGuard.jsx @@ -0,0 +1,18 @@ +import { useEffect } from "react"; +import { useNavigate } from "react-router-dom"; + +const CheckoutGuard = ({ cartItems, children }) => { + const navigate = useNavigate(); + + useEffect(() => { + if (!cartItems?.length) { + navigate("/cart"); + } + }, [cartItems, navigate]); + + if (!cartItems?.length) return null; + + return children; +}; + +export default CheckoutGuard; diff --git a/src/pages/Checkout/components/CouponApply.jsx b/src/pages/Checkout/components/CouponApply.jsx new file mode 100644 index 0000000..fc38166 --- /dev/null +++ b/src/pages/Checkout/components/CouponApply.jsx @@ -0,0 +1,119 @@ +import { useState } from "react"; +import { Tag, X, Check, AlertCircle } from "lucide-react"; +import { + useValidateCouponMutation, + useGetAvailableCouponsQuery, +} from "../../../features/coupons/couponsApi"; + +const CouponApply = ({ orderAmount, onCouponApplied, onCouponRemoved }) => { + const [couponCode, setCouponCode] = useState(""); + const [appliedCoupon, setAppliedCoupon] = useState(null); + const [showAvailable, setShowAvailable] = useState(false); + + const [validateCoupon, { isLoading }] = useValidateCouponMutation(); + const { data: availableCouponsData } = useGetAvailableCouponsQuery(orderAmount); + + const handleApplyCoupon = async () => { + if (!couponCode.trim()) return; + + try { + const response = await validateCoupon({ + code: couponCode.toUpperCase(), + orderAmount, + }).unwrap(); + + if (response.status) { + setAppliedCoupon(response.data); + setCouponCode(""); + onCouponApplied && onCouponApplied(response.data); + } + } catch (err) { + alert(err?.data?.message || "Invalid coupon"); + setAppliedCoupon(null); + } + }; + + const handleRemoveCoupon = () => { + setAppliedCoupon(null); + setCouponCode(""); + onCouponRemoved && onCouponRemoved(); + }; + + return ( +
+ {appliedCoupon ? ( +
+
+

+ Coupon Applied: {appliedCoupon.couponCode} +

+

+ You saved ₹{appliedCoupon.discountAmount.toFixed(2)} +

+
+ +
+ ) : ( + <> +
+ setCouponCode(e.target.value.toUpperCase())} + placeholder="Enter coupon code" + className="flex-1 px-3 py-2 border rounded-lg focus:ring-2 focus:ring-green-500" + /> + +
+ + + + {showAvailable && ( +
+ {availableCouponsData?.data?.length ? ( + availableCouponsData.data.map((c) => ( +
setCouponCode(c.code)} + > +
+

{c.code}

+

{c.description}

+
+ +
+ )) + ) : ( +

No coupons available

+ )} +
+ )} + + )} +
+ ); +}; + +export default CouponApply; diff --git a/src/pages/Checkout/components/OrderSummary.jsx b/src/pages/Checkout/components/OrderSummary.jsx new file mode 100644 index 0000000..02ba035 --- /dev/null +++ b/src/pages/Checkout/components/OrderSummary.jsx @@ -0,0 +1,257 @@ +// import { CheckCircle, ShieldCheck } from "lucide-react"; +// import CouponApply from "./CouponApply"; + +// const OrderSummary = ({ +// cartItems = [], +// quantities = {}, +// bagTotal = 0, +// mrpDiscount = 0, +// additionalDiscount = 0, +// convenienceFee = 0, +// payableAmount = 0, +// onPlaceOrder, +// }) => { +// return ( +//
+// {/* Header */} +//
+//

Order Summary

+//

+// Review your items & price details +//

+//
+ +// {/* Items */} +//
+// {cartItems.map((item) => { +// const qty = quantities[item.id] || 1; +// const price = item.product?.basePrice || 0; + +// return ( +//
+//
+//

+// {item.product?.name} +//

+//

Quantity: {qty}

+//
+// +// ₹{(price * qty).toFixed(2)} +// +//
+// ); +// })} +//
+ +// {/* Divider */} +//
+ +// {/* Price Summary */} +//
+//
+// Bag Total +// ₹{bagTotal.toFixed(2)} +//
+ +//
+// MRP Discount +// -₹{mrpDiscount.toFixed(2)} +//
+ +//
+// Additional Discount +// -₹{additionalDiscount.toFixed(2)} +//
+ +//
+// Convenience Fee +// ₹{convenienceFee.toFixed(2)} +//
+//
+ +// {/* Divider */} +//
+ +// {/* Total */} +//
+// Total Payable +// +// ₹{payableAmount.toFixed(2)} +// +//
+ +// {/* Trust Badge */} +//
+// +// 100% Secure Payments · Easy Returns +//
+ +// {/* CTA */} +// {/* */} + +// +//
+// ); +// }; + +// export default OrderSummary; + + + +import { useState, useMemo } from "react"; +import { CheckCircle, ShieldCheck } from "lucide-react"; +import CouponApply from "./CouponApply"; + +const OrderSummary = ({ + cartItems = [], + quantities = {}, + bagTotal = 0, + mrpDiscount = 0, + additionalDiscount = 0, + convenienceFee = 0, + payableAmount = 0, + onPlaceOrder, +}) => { + // State for applied coupon + const [appliedCoupon, setAppliedCoupon] = useState(null); + + // Compute total after coupon + const totalPayable = useMemo(() => { + if (appliedCoupon?.discountAmount) { + return Math.max(payableAmount - appliedCoupon.discountAmount, 0); + } + return payableAmount; + }, [payableAmount, appliedCoupon]); + + return ( +
+ {/* Header */} +
+

Order Summary

+

+ Review your items & price details +

+
+ + {/* Items */} +
+ {cartItems.map((item) => { + const qty = quantities[item.id] || 1; + const price = item.product?.basePrice || 0; + + return ( +
+
+

+ {item.product?.name} +

+

Quantity: {qty}

+
+ + ₹{(price * qty).toFixed(2)} + +
+ ); + })} +
+ + {/* Divider */} +
+ + {/* Price Summary */} +
+
+ Bag Total + ₹{bagTotal.toFixed(2)} +
+ +
+ MRP Discount + -₹{mrpDiscount.toFixed(2)} +
+ +
+ Additional Discount + -₹{additionalDiscount.toFixed(2)} +
+ +
+ Convenience Fee + ₹{convenienceFee.toFixed(2)} +
+
+ + {/* Divider */} +
+ + {/* Coupon Apply Section */} + setAppliedCoupon(couponData)} + onCouponRemoved={() => setAppliedCoupon(null)} + /> + + {/* Total */} +
+ Total Payable + + ₹{totalPayable.toFixed(2)} + +
+ + {/* Trust Badge */} +
+ + 100% Secure Payments · Easy Returns +
+ + {/* Place Order Button */} + +
+ ); +}; + +export default OrderSummary; diff --git a/src/pages/Checkout/components/PaymentSection.jsx b/src/pages/Checkout/components/PaymentSection.jsx new file mode 100644 index 0000000..3c3f2ce --- /dev/null +++ b/src/pages/Checkout/components/PaymentSection.jsx @@ -0,0 +1,39 @@ +import { CreditCard } from "lucide-react"; + +const PaymentSection = ({ paymentMethod, setPaymentMethod }) => { + const methods = [ + { id: "card", label: "Credit / Debit Card" }, + { id: "upi", label: "UPI (Google Pay, PhonePe)" }, + { id: "cod", label: "Cash on Delivery" }, + ]; + + return ( +
+

+ Payment Method +

+ +
+ {methods.map((method) => ( + + ))} +
+
+ ); +}; + +export default PaymentSection; diff --git a/src/pages/NotFound.jsx b/src/pages/NotFound.jsx new file mode 100644 index 0000000..039681d --- /dev/null +++ b/src/pages/NotFound.jsx @@ -0,0 +1,50 @@ +import React from "react"; +import { Link } from "react-router-dom"; +import { ShoppingCart, ChevronLeft } from "lucide-react"; + +function NotFound() { + return ( +
+
+ {/* 404 Illustration */} + {/* Page Not Found */} + + {/* 404 Text */} +

404

+

+ Oops! Page Not Found +

+

+ The page you are looking for might have been removed, had its name + changed, or is temporarily unavailable. Don't worry, your shopping + spree doesn’t have to stop! +

+ + {/* Button to redirect back */} + + + Back to Shop + + + {/* Optional cart illustration or promo */} +
+
+ +

+ Check out our latest products instead! +

+
+
+
+
+ ); +} + +export default NotFound; diff --git a/src/pages/OrderSuccess.jsx b/src/pages/OrderSuccess.jsx new file mode 100644 index 0000000..756fe83 --- /dev/null +++ b/src/pages/OrderSuccess.jsx @@ -0,0 +1,59 @@ +import { useEffect } from "react"; +import { useLocation, useNavigate } from "react-router-dom"; + +const OrderSuccess = () => { + const { state } = useLocation(); + const navigate = useNavigate(); + + useEffect(() => { + if (!state?.order) { + navigate("/", { replace: true }); + } + }, [state, navigate]); + + if (!state?.order) return null; + + const { order, appliedCoupon } = state; + + const orderId = order.id; + const paymentMethod = order.paymentMethod; + const subtotal = order.subtotal; + + return ( +
+
+ Order Success + +

+ Order Placed Successfully 🎉 +

+ +

+ Order ID: {orderId} +

+ +
+ + + +
+
+
+ ); +}; + +export default OrderSuccess; diff --git a/src/pages/ProductDetails/ProductDetails.txt b/src/pages/ProductDetails/ProductDetails.txt new file mode 100644 index 0000000..20acf5e --- /dev/null +++ b/src/pages/ProductDetails/ProductDetails.txt @@ -0,0 +1,137 @@ +import { useParams } from "react-router-dom"; +import { sareesData } from "../../data/sareesData"; +import { Heart } from "lucide-react"; +import { useState } from "react"; +import Breadcrumb from "../../components/ui/Breadcrumb"; + +const ProductDetails = () => { + const { id } = useParams(); + const product = sareesData.find((item) => item._id === Number(id)); + const [quantity, setQuantity] = useState(1); + + // Main image state + const [mainImg, setMainImg] = useState( + product?.images ? product.images[0] : product?.image + ); + + if (!product) return

Product not found!

; + + const handleQuantity = (type) => { + if (type === "inc") setQuantity((prev) => prev + 1); + else if (type === "dec" && quantity > 1) setQuantity((prev) => prev - 1); + }; + + return ( +
+ + +
+ {/* Left Thumbnails */} +
+ {product.images?.map((img, idx) => ( + setMainImg(img)} + className="w-20 h-20 md:h-24 md:w-24 object-cover rounded-lg border cursor-pointer hover:scale-105 transition-all" + /> + ))} +
+ + {/* Main Image */} +
+ {product.title} + + {/* Discount Badge */} + {product.discount && ( + + {product.discount}% OFF + + )} + + {/* Wishlist */} + +
+ + {/* Product Info */} +
+

+ {product.title} +

+

+ {product.description || "No description available."} +

+ + {/* Price */} +
+

+ ₹{product.offerPrice} +

+

+ ₹{product.originalPrice} +

+
+ + {/* Colors */} + {product.colors?.length > 0 && ( +
+ Colors: + {product.colors.map((c, idx) => ( + + ))} +
+ )} + + {/* Quantity Selector */} +
+ Quantity: +
+ + + {quantity} + + +
+
+ + {/* Add to Cart */} +
+ + +
+ + {/* Additional Info */} +

+ Free shipping on orders above ₹2000. Easy returns within 7 days. +

+
+
+
+ ); +}; + +export default ProductDetails; diff --git a/src/pages/Products/Products.jsx b/src/pages/Products/Products.jsx new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/RatingPage/CommentCard.jsx b/src/pages/RatingPage/CommentCard.jsx new file mode 100644 index 0000000..5062853 --- /dev/null +++ b/src/pages/RatingPage/CommentCard.jsx @@ -0,0 +1,30 @@ +import React from "react"; +import Stars from "./Stars"; + +const CommentCard = ({ name, rating, comment, date, avatar }) => { + return ( +
+ {/* Avatar */} + {name} + + {/* Content */} +
+
+

{name}

+ {date} +
+
+ + {rating.toFixed(1)} +
+

{comment}

+
+
+ ); +}; + +export default CommentCard; diff --git a/src/pages/RatingPage/RatingPage.jsx b/src/pages/RatingPage/RatingPage.jsx new file mode 100644 index 0000000..d9425f7 --- /dev/null +++ b/src/pages/RatingPage/RatingPage.jsx @@ -0,0 +1,103 @@ +import React, { useState } from "react"; +import CommentCard from "./CommentCard"; +import Stars from "./Stars"; + +const dummyComments = [ + { + id: 1, + name: "Vaibhav Tupe", + rating: 5, + comment: "Great product, fast delivery!", + date: "2025-12-22", + }, + { + id: 2, + name: "Priya Sharma", + rating: 4.8, + comment: "Excellent quality, highly recommended.", + date: "2025-12-21", + }, + { + id: 3, + name: "Rahul Patil", + rating: 4.7, + comment: "Good but packaging could be better.", + date: "2025-12-20", + }, + { + id: 4, + name: "Sneha Joshi", + rating: 4.9, + comment: "Loved it, will buy again!", + date: "2025-12-19", + }, +]; + +const RatingPage = () => { + const [comments, setComments] = useState(dummyComments); + + // Average rating + const averageRating = + comments.reduce((acc, c) => acc + c.rating, 0) / comments.length; + + return ( +
+ {/* Average Rating */} +
+
+ {/* Left image wrapper */} +
+ Left +
+ + {/* Center Rating */} +
+
+ {averageRating.toFixed(2)} +
+ + {/* */} + + {/*

+ {comments.length} Reviews +

*/} +
+ + {/* Right image wrapper */} +
+ Right +
+
+ + {/*
+ +
*/} + {/*
{comments.length} Reviews
*/} +
+ + {/* Comments List (2 by 2 grid on desktop) */} +
+ {comments.map((c) => ( + + ))} +
+
+ ); +}; + +export default RatingPage; diff --git a/src/pages/RatingPage/Stars.jsx b/src/pages/RatingPage/Stars.jsx new file mode 100644 index 0000000..06dff15 --- /dev/null +++ b/src/pages/RatingPage/Stars.jsx @@ -0,0 +1,24 @@ +import React from "react"; +import { Star, StarHalf, Star as StarOutline } from "lucide-react"; + +const Stars = ({ rating = 0 }) => { + const stars = []; + + for (let i = 1; i <= 5; i++) { + if (rating >= i) { + stars.push(); + } else if (rating >= i - 0.5) { + stars.push( + + ); + } else { + stars.push( + + ); + } + } + + return
{stars}
; +}; + +export default Stars; diff --git a/src/pages/RatingPage/index.js b/src/pages/RatingPage/index.js new file mode 100644 index 0000000..b80a80a --- /dev/null +++ b/src/pages/RatingPage/index.js @@ -0,0 +1,3 @@ +export { default as RatingPage } from "./RatingPage"; +export { default as CommentCard } from "./CommentCard"; +export { default as Stars } from "./Stars"; diff --git a/src/pages/TrackOrder.jsx b/src/pages/TrackOrder.jsx new file mode 100644 index 0000000..ae0e48d --- /dev/null +++ b/src/pages/TrackOrder.jsx @@ -0,0 +1,39 @@ +import { useParams } from "react-router-dom"; +import { useGetOrderTrackingQuery } from "../features/orders/ordersApi"; +import OrderTrackingTimeline from "../components/order/OrderTrackingTimeline"; + +const TrackOrder = () => { + const { orderId } = useParams(); + + const { data, isLoading, isError } = + useGetOrderTrackingQuery( + { + orderId, + pincode: "400607", + shippingMethod: "STANDARD", + }, + { + skip: !orderId, // VERY IMPORTANT + } + ); +console.log("Tracking API response:", data); + + if (!orderId) return
Invalid Order ID
; + if (isLoading) return
Loading tracking...
; + if (isError) return
Failed to load tracking
; + + const tracking = data?.data?.tracking; +console.log("Timeline:", tracking?.timeline); + return ( +
+

Track Order

+

+ Order ID: {orderId} +

+ + +
+ ); +}; + +export default TrackOrder; diff --git a/src/pages/Trending/MiniBanner.jsx b/src/pages/Trending/MiniBanner.jsx new file mode 100644 index 0000000..ac28df5 --- /dev/null +++ b/src/pages/Trending/MiniBanner.jsx @@ -0,0 +1,26 @@ +import { motion } from "framer-motion"; +import { Link } from "react-router-dom"; + +const MiniBanner = ({ title, img, href }) => { + return ( + + {title} + +
+

+ {title} +

+
+ + +
+ ); +}; + +export default MiniBanner; diff --git a/src/pages/Trending/Trending.jsx b/src/pages/Trending/Trending.jsx new file mode 100644 index 0000000..bdc9420 --- /dev/null +++ b/src/pages/Trending/Trending.jsx @@ -0,0 +1,25 @@ +import React from "react"; +import MiniBanner from "./MiniBanner"; +import TrendingChips from "./TrendingChips"; +import TrendingCircleRow from "./TrendingCircleRow"; +import TrendingSmallGrid from "./TrendingSmallGrid"; + +const Trending = () => { + return ( +
+ {/* Trending Chips */} + + + {/* Mini Banner */} + + + {/* Trending Circle Row */} + + + {/* Trending Small Grid */} + +
+ ); +}; + +export default Trending; diff --git a/src/pages/Trending/TrendingCard.jsx b/src/pages/Trending/TrendingCard.jsx new file mode 100644 index 0000000..191f825 --- /dev/null +++ b/src/pages/Trending/TrendingCard.jsx @@ -0,0 +1,62 @@ +// import { motion } from "framer-motion"; +// import { Link } from "react-router-dom"; + +// const TrendingCard = ({ title, img, href }) => { +// return ( +// +// +// {title} +//
+//

+// {title} +//

+//
+// +//
+// ); +// }; + +// export default TrendingCard; + + + + +import { motion } from "framer-motion"; +import { Link } from "react-router-dom"; + +const TrendingCard = ({ title, img, href }) => { + return ( + + + {/* Image */} + {title} + + {/* Gradient Overlay */} +
+ + {/* Title */} +
+

+ {title} +

+
+ +
+ ); +}; + +export default TrendingCard; diff --git a/src/pages/Trending/TrendingChips.jsx b/src/pages/Trending/TrendingChips.jsx new file mode 100644 index 0000000..d4b5899 --- /dev/null +++ b/src/pages/Trending/TrendingChips.jsx @@ -0,0 +1,19 @@ +const TrendingChips = () => { + const chips = ["Sarees", "Lehengas", "Kurtas", "Wedding Wear", "Ethnic Sets"]; + + return ( +
+ {chips.map((c, i) => ( + + {c} + + ))} +
+ ); +}; + +export default TrendingChips; diff --git a/src/pages/Trending/TrendingCircle.jsx b/src/pages/Trending/TrendingCircle.jsx new file mode 100644 index 0000000..54f8765 --- /dev/null +++ b/src/pages/Trending/TrendingCircle.jsx @@ -0,0 +1,20 @@ +import { motion } from "framer-motion"; +import { Link } from "react-router-dom"; + +const TrendingCircle = ({ img, title, href }) => { + return ( +
+ + + {title} + + + {title} +
+ ); +}; + +export default TrendingCircle; diff --git a/src/pages/Trending/TrendingCircleRow.jsx b/src/pages/Trending/TrendingCircleRow.jsx new file mode 100644 index 0000000..860cbe1 --- /dev/null +++ b/src/pages/Trending/TrendingCircleRow.jsx @@ -0,0 +1,26 @@ +import TrendingCircle from "./TrendingCircle"; + +const data = [ + { title: "Sarees", img: "/hero1.jpg", href: "/sarees" }, + { title: "Lehengas", img: "/hero2.jpg", href: "/lehenga" }, + { title: "Kurtas", img: "/hero3.jpg", href: "/kurtas" }, + { title: "Patola", img: "/hero1.jpg", href: "/patola" }, +]; + +const TrendingCircleRow = () => { + return ( +
+

Trending Now

+ +
+ {data.map((item, i) => ( +
+ +
+ ))} +
+
+ ); +}; + +export default TrendingCircleRow; diff --git a/src/pages/Trending/TrendingSmallGrid.jsx b/src/pages/Trending/TrendingSmallGrid.jsx new file mode 100644 index 0000000..c08e443 --- /dev/null +++ b/src/pages/Trending/TrendingSmallGrid.jsx @@ -0,0 +1,54 @@ +// import TrendingCard from "./TrendingCard"; + +// const smallTrendingData = [ +// { title: "Sarees", img: "/hero1.jpg", href: "/sarees" }, +// { title: "Lehengas", img: "/hero2.jpg", href: "/lehenga" }, +// { title: "Kurtas", img: "/hero3.jpg", href: "/kurtas" }, +// { title: "Patola", img: "/hero1.jpg", href: "/patola" }, +// ]; + +// const TrendingSmallGrid = () => { +// return ( +//
+//

+// Trending Categories +//

+ +//
+// {smallTrendingData.map((item, i) => ( +// +// ))} +//
+//
+// ); +// }; + +// export default TrendingSmallGrid; + + + + +import TrendingCard from "./TrendingCard"; + +const smallTrendingData = [ + { title: "Sarees", img: "/hero1.jpg", href: "/sarees" }, + { title: "Lehengas", img: "/hero2.jpg", href: "/lehenga" }, + { title: "Kurtas", img: "/hero3.jpg", href: "/kurtas" }, + { title: "Patola", img: "/hero1.jpg", href: "/patola" }, +]; + +const TrendingSmallGrid = () => { + return ( +
+

Trending Categories

+ +
+ {smallTrendingData.map((item, i) => ( + + ))} +
+
+ ); +}; + +export default TrendingSmallGrid; diff --git a/src/pages/Wardrobe/AddWardrobeItemModal.jsx b/src/pages/Wardrobe/AddWardrobeItemModal.jsx new file mode 100644 index 0000000..e331977 --- /dev/null +++ b/src/pages/Wardrobe/AddWardrobeItemModal.jsx @@ -0,0 +1,331 @@ +// import { useState } from "react"; +// import { X, Image as ImageIcon } from "lucide-react"; +// import { motion, AnimatePresence } from "framer-motion"; +// import { useAddWardrobeItemMutation } from "../../features/wardrobe/wardrobeApi"; + +// const AddWardrobeItemModal = ({ onClose }) => { +// const [addItem, { isLoading }] = useAddWardrobeItemMutation(); + +// const [form, setForm] = useState({ +// name: "", +// category: "dresses", +// description: "", +// image: "", +// size: "M", +// }); + +// const handleChange = (e) => +// setForm({ ...form, [e.target.name]: e.target.value }); + +// const handleSubmit = async (e) => { +// e.preventDefault(); + +// await addItem({ +// name: form.name, +// category: form.category, +// description: form.description, +// images: [{ url: form.image, isPrimary: true }], +// size: form.size, +// }); + +// onClose(); +// }; + +// return ( +// +// +// +// {/* Close */} +// + +// {/* Header */} +//
+//

+// Add Wardrobe Item +//

+//

+// Keep your fashion organized ✨ +//

+//
+ +// {/* Image Preview */} +//
+// {form.image ? ( +// Preview +// ) : ( +//
+// +// Image preview +//
+// )} +//
+ +// {/* Form */} +//
+// + +//
+// + +// +//
+ +// + +//