From c9f429d123eb9da059286f9697e06d1d2081f61d Mon Sep 17 00:00:00 2001 From: Sumanth Date: Wed, 16 Sep 2026 17:36:46 +0530 Subject: [PATCH] Initial commit --- PROJECT_DOCUMENTATION.md | 228 ++ client/.gitignore | 8 + client/index.html | 17 + client/package-lock.json | 3239 +++++++++++++++++ client/package.json | 38 + client/postcss.config.js | 7 + client/src/App.jsx | 120 + client/src/api/axios.js | 30 + client/src/components/CompareDrawer.jsx | 57 + client/src/components/Footer.jsx | 155 + client/src/components/GuitarFinderModal.jsx | 193 + client/src/components/MobileNav.jsx | 82 + client/src/components/Navbar.jsx | 286 ++ client/src/components/ProductCard.jsx | 175 + client/src/components/ProductQuickView.jsx | 110 + client/src/components/ScrollToTop.jsx | 15 + client/src/components/SkeletonLoaders.jsx | 21 + client/src/components/ToastNotification.jsx | 38 + client/src/index.css | 25 + client/src/main.jsx | 23 + client/src/pages/admin/AdminBanners.jsx | 76 + client/src/pages/admin/AdminBrands.jsx | 58 + client/src/pages/admin/AdminCategories.jsx | 143 + client/src/pages/admin/AdminCoupons.jsx | 75 + client/src/pages/admin/AdminDashboard.jsx | 157 + .../src/pages/admin/AdminEmailTemplates.jsx | 35 + client/src/pages/admin/AdminInventory.jsx | 161 + client/src/pages/admin/AdminLayout.jsx | 129 + client/src/pages/admin/AdminLoginPage.jsx | 88 + client/src/pages/admin/AdminMessages.jsx | 51 + client/src/pages/admin/AdminOrders.jsx | 165 + client/src/pages/admin/AdminProducts.jsx | 165 + client/src/pages/admin/AdminSettings.jsx | 152 + client/src/pages/admin/ProductFormModal.jsx | 529 +++ client/src/pages/customer/CMSPages.jsx | 24 + client/src/pages/customer/CartPage.jsx | 194 + client/src/pages/customer/CheckoutPage.jsx | 306 ++ client/src/pages/customer/ComparePage.jsx | 116 + client/src/pages/customer/ContactPage.jsx | 140 + client/src/pages/customer/HomePage.jsx | 250 ++ client/src/pages/customer/LoginPage.jsx | 127 + .../pages/customer/OrderConfirmationPage.jsx | 77 + .../src/pages/customer/OrderTrackingPage.jsx | 95 + .../src/pages/customer/ProductDetailPage.jsx | 402 ++ client/src/pages/customer/ShopPage.jsx | 323 ++ .../src/pages/customer/UserDashboardPage.jsx | 252 ++ client/src/pages/customer/WishlistPage.jsx | 41 + client/src/store/index.js | 17 + client/src/store/slices/authSlice.js | 73 + client/src/store/slices/cartSlice.js | 85 + client/src/store/slices/compareSlice.js | 33 + client/src/store/slices/uiSlice.js | 30 + client/src/store/slices/wishlistSlice.js | 41 + client/src/utils/formatCurrency.js | 15 + client/tailwind.config.js | 25 + client/vite.config.js | 20 + server/.env.example | 13 + server/.gitignore | 8 + server/app.js | 77 + server/config/db.js | 15 + server/controllers/authController.js | 128 + server/controllers/bannerController.js | 44 + server/controllers/brandController.js | 44 + server/controllers/cartController.js | 150 + server/controllers/categoryController.js | 44 + server/controllers/contactController.js | 34 + server/controllers/couponController.js | 34 + server/controllers/emailTemplateController.js | 24 + server/controllers/inventoryController.js | 85 + server/controllers/orderController.js | 212 ++ server/controllers/productController.js | 235 ++ server/controllers/reviewController.js | 50 + server/controllers/settingsController.js | 32 + server/controllers/socialMediaController.js | 33 + server/controllers/wishlistController.js | 41 + server/middleware/authMiddleware.js | 43 + server/middleware/errorHandler.js | 31 + server/middleware/upload.js | 40 + server/models/Banner.js | 16 + server/models/Brand.js | 15 + server/models/Cart.js | 20 + server/models/Category.js | 16 + server/models/ContactMessage.js | 13 + server/models/Coupon.js | 17 + server/models/EmailTemplate.js | 11 + server/models/InventoryHistory.js | 19 + server/models/Order.js | 85 + server/models/Product.js | 105 + server/models/Review.js | 17 + server/models/SocialMedia.js | 10 + server/models/User.js | 47 + server/models/WebsiteSettings.js | 34 + server/models/Wishlist.js | 9 + server/package-lock.json | 1821 +++++++++ server/package.json | 31 + server/routes/authRoutes.js | 12 + server/routes/bannerRoutes.js | 12 + server/routes/brandRoutes.js | 12 + server/routes/cartRoutes.js | 12 + server/routes/categoryRoutes.js | 12 + server/routes/contactRoutes.js | 11 + server/routes/couponRoutes.js | 11 + server/routes/emailTemplateRoutes.js | 10 + server/routes/inventoryRoutes.js | 11 + server/routes/orderRoutes.js | 19 + server/routes/productRoutes.js | 24 + server/routes/reviewRoutes.js | 10 + server/routes/settingsRoutes.js | 10 + server/routes/socialMediaRoutes.js | 10 + server/routes/uploadRoutes.js | 25 + server/routes/wishlistRoutes.js | 10 + server/seed/seedData.js | 152 + server/seed/seeder.js | 430 +++ server/server.js | 24 + server/utils/apiResponse.js | 24 + server/utils/emailSender.js | 52 + server/utils/jwt.js | 12 + 117 files changed, 14145 insertions(+) create mode 100644 PROJECT_DOCUMENTATION.md create mode 100644 client/.gitignore create mode 100644 client/index.html create mode 100644 client/package-lock.json create mode 100644 client/package.json create mode 100644 client/postcss.config.js create mode 100644 client/src/App.jsx create mode 100644 client/src/api/axios.js create mode 100644 client/src/components/CompareDrawer.jsx create mode 100644 client/src/components/Footer.jsx create mode 100644 client/src/components/GuitarFinderModal.jsx create mode 100644 client/src/components/MobileNav.jsx create mode 100644 client/src/components/Navbar.jsx create mode 100644 client/src/components/ProductCard.jsx create mode 100644 client/src/components/ProductQuickView.jsx create mode 100644 client/src/components/ScrollToTop.jsx create mode 100644 client/src/components/SkeletonLoaders.jsx create mode 100644 client/src/components/ToastNotification.jsx create mode 100644 client/src/index.css create mode 100644 client/src/main.jsx create mode 100644 client/src/pages/admin/AdminBanners.jsx create mode 100644 client/src/pages/admin/AdminBrands.jsx create mode 100644 client/src/pages/admin/AdminCategories.jsx create mode 100644 client/src/pages/admin/AdminCoupons.jsx create mode 100644 client/src/pages/admin/AdminDashboard.jsx create mode 100644 client/src/pages/admin/AdminEmailTemplates.jsx create mode 100644 client/src/pages/admin/AdminInventory.jsx create mode 100644 client/src/pages/admin/AdminLayout.jsx create mode 100644 client/src/pages/admin/AdminLoginPage.jsx create mode 100644 client/src/pages/admin/AdminMessages.jsx create mode 100644 client/src/pages/admin/AdminOrders.jsx create mode 100644 client/src/pages/admin/AdminProducts.jsx create mode 100644 client/src/pages/admin/AdminSettings.jsx create mode 100644 client/src/pages/admin/ProductFormModal.jsx create mode 100644 client/src/pages/customer/CMSPages.jsx create mode 100644 client/src/pages/customer/CartPage.jsx create mode 100644 client/src/pages/customer/CheckoutPage.jsx create mode 100644 client/src/pages/customer/ComparePage.jsx create mode 100644 client/src/pages/customer/ContactPage.jsx create mode 100644 client/src/pages/customer/HomePage.jsx create mode 100644 client/src/pages/customer/LoginPage.jsx create mode 100644 client/src/pages/customer/OrderConfirmationPage.jsx create mode 100644 client/src/pages/customer/OrderTrackingPage.jsx create mode 100644 client/src/pages/customer/ProductDetailPage.jsx create mode 100644 client/src/pages/customer/ShopPage.jsx create mode 100644 client/src/pages/customer/UserDashboardPage.jsx create mode 100644 client/src/pages/customer/WishlistPage.jsx create mode 100644 client/src/store/index.js create mode 100644 client/src/store/slices/authSlice.js create mode 100644 client/src/store/slices/cartSlice.js create mode 100644 client/src/store/slices/compareSlice.js create mode 100644 client/src/store/slices/uiSlice.js create mode 100644 client/src/store/slices/wishlistSlice.js create mode 100644 client/src/utils/formatCurrency.js create mode 100644 client/tailwind.config.js create mode 100644 client/vite.config.js create mode 100644 server/.env.example create mode 100644 server/.gitignore create mode 100644 server/app.js create mode 100644 server/config/db.js create mode 100644 server/controllers/authController.js create mode 100644 server/controllers/bannerController.js create mode 100644 server/controllers/brandController.js create mode 100644 server/controllers/cartController.js create mode 100644 server/controllers/categoryController.js create mode 100644 server/controllers/contactController.js create mode 100644 server/controllers/couponController.js create mode 100644 server/controllers/emailTemplateController.js create mode 100644 server/controllers/inventoryController.js create mode 100644 server/controllers/orderController.js create mode 100644 server/controllers/productController.js create mode 100644 server/controllers/reviewController.js create mode 100644 server/controllers/settingsController.js create mode 100644 server/controllers/socialMediaController.js create mode 100644 server/controllers/wishlistController.js create mode 100644 server/middleware/authMiddleware.js create mode 100644 server/middleware/errorHandler.js create mode 100644 server/middleware/upload.js create mode 100644 server/models/Banner.js create mode 100644 server/models/Brand.js create mode 100644 server/models/Cart.js create mode 100644 server/models/Category.js create mode 100644 server/models/ContactMessage.js create mode 100644 server/models/Coupon.js create mode 100644 server/models/EmailTemplate.js create mode 100644 server/models/InventoryHistory.js create mode 100644 server/models/Order.js create mode 100644 server/models/Product.js create mode 100644 server/models/Review.js create mode 100644 server/models/SocialMedia.js create mode 100644 server/models/User.js create mode 100644 server/models/WebsiteSettings.js create mode 100644 server/models/Wishlist.js create mode 100644 server/package-lock.json create mode 100644 server/package.json create mode 100644 server/routes/authRoutes.js create mode 100644 server/routes/bannerRoutes.js create mode 100644 server/routes/brandRoutes.js create mode 100644 server/routes/cartRoutes.js create mode 100644 server/routes/categoryRoutes.js create mode 100644 server/routes/contactRoutes.js create mode 100644 server/routes/couponRoutes.js create mode 100644 server/routes/emailTemplateRoutes.js create mode 100644 server/routes/inventoryRoutes.js create mode 100644 server/routes/orderRoutes.js create mode 100644 server/routes/productRoutes.js create mode 100644 server/routes/reviewRoutes.js create mode 100644 server/routes/settingsRoutes.js create mode 100644 server/routes/socialMediaRoutes.js create mode 100644 server/routes/uploadRoutes.js create mode 100644 server/routes/wishlistRoutes.js create mode 100644 server/seed/seedData.js create mode 100644 server/seed/seeder.js create mode 100644 server/server.js create mode 100644 server/utils/apiResponse.js create mode 100644 server/utils/emailSender.js create mode 100644 server/utils/jwt.js diff --git a/PROJECT_DOCUMENTATION.md b/PROJECT_DOCUMENTATION.md new file mode 100644 index 0000000..d350345 --- /dev/null +++ b/PROJECT_DOCUMENTATION.md @@ -0,0 +1,228 @@ +# 🎸 MERN Stack Modern Guitar Ecommerce Application + +## Complete Architecture, Workflow & Technical Documentation + +This document provides a comprehensive technical guide on how the Modern Guitar Ecommerce Application works under the hood, detailing the MERN stack architecture, backend controllers, MongoDB database models, frontend state management, customer and admin workflows, and API endpoints. + +--- + +## 📑 Table of Contents + +1. [Architecture Overview](#1-architecture-overview) +2. [Database Schema & Models (MongoDB)](#2-database-schema--models-mongodb) +3. [Backend API Architecture (Node & Express)](#3-backend-api-architecture-node--express) +4. [Frontend Architecture (React, Vite & Redux Toolkit)](#4-frontend-architecture-react-vite--redux-toolkit) +5. [End-to-End Application Workflows](#5-end-to-end-application-workflows) + - [Customer Shopping Workflow](#customer-shopping-workflow) + - [Interactive Guitar Finder Tool](#interactive-guitar-finder-tool) + - [Product Comparison Matrix](#product-comparison-matrix) + - [Admin Stock Audit & Inventory System](#admin-stock-audit--inventory-system) + - [Order Fulfillment & Status Timeline](#order-fulfillment--status-timeline) +6. [API Endpoints Reference](#6-api-endpoints-reference) +7. [Installation & Execution Guide](#7-installation--execution-guide) + +--- + +## 1. Architecture Overview + +The application follows a **Decoupled Architecture** built on the **MERN Stack**: + +``` + ┌─────────────────────────────────────────────────────────┐ + │ React 18 Customer & Admin Frontend │ + │ (Vite + Tailwind CSS + Framer Motion + Redux) │ + └────────────────────────────┬────────────────────────────┘ + │ REST API Calls (Axios / JWT) + ▼ + ┌─────────────────────────────────────────────────────────┐ + │ Node.js & Express.js Backend │ + │ Routes ──► Controllers ──► Services ──► Models │ + └────────────────────────────┬────────────────────────────┘ + │ Mongoose ODM + ▼ + ┌─────────────────────────────────────────────────────────┐ + │ MongoDB Database │ + │ (16 Collections with Structured Schemas) │ + └────────────────────────────┴────────────────────────────┘ +``` + +### Key Technologies Used: + +- **Database**: MongoDB (Mongoose ODM with indexed text search & references) +- **Backend**: Node.js, Express.js, JWT (`jsonwebtoken`), Password Hashing (`bcryptjs`), Multer (Media upload), Nodemailer (Email engine), Helmet (Security), CORS, Rate Limiter. +- **Frontend**: React 18, Vite, Tailwind CSS, Framer Motion (Animations), Redux Toolkit (Global Store), TanStack Query (Data Fetching), Lucide Icons, React Hook Form. + +--- + +## 2. Database Schema & Models (MongoDB) + +The backend features **16 MongoDB collections** located in `server/models/`: + +| Collection / Model | Description | Key Fields | +| :----------------- | :---------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `User` | Stores customer and admin accounts | `name`, `email`, `password` (hashed), `role` (`Customer`, `Admin`), `phone`, `addresses[]` | +| `Product` | Core guitar catalog items with rich specs | `name`, `slug`, `sku`, `brand`, `category`, `price`, `salePrice`, `stock`, `specs` (body, neck, pickups, frets, bridge), `variants[]`, `images[]`, `rating` | +| `Category` | Hierarchy for guitar types | `name`, `slug`, `description`, `image`, `displayOrder` | +| `Brand` | Guitar manufacturers | `name`, `slug`, `logo`, `description`, `website` | +| `Cart` | Shopping carts for users & guests | `user`, `sessionId`, `items[]` (`product`, `quantity`, `variantColor`, `price`), `couponCode` | +| `Wishlist` | User saved instruments | `user`, `products[]` | +| `Order` | Customer purchase orders | `orderNumber`, `user`, `orderItems[]`, `shippingAddress`, `paymentMethod`, `orderStatus`, `statusHistory[]`, `trackingNumber` | +| `InventoryHistory` | Audit trail for stock changes | `product`, `sku`, `changeType` (`Order Placed`, `Restock`, `Manual`), `quantityChanged`, `previousStock`, `newStock`, `reason` | +| `Review` | Customer product reviews | `user`, `product`, `rating`, `title`, `comment`, `isApproved` | +| `Coupon` | Promo discount codes | `code`, `discountType`, `discountValue`, `minPurchaseAmount`, `endDate` | +| `Banner` | Homepage hero sliders | `title`, `subtitle`, `desktopImage`, `mobileImage`, `buttonText`, `buttonLink` | +| `WebsiteSettings` | Global store branding | `siteName`, `tagline`, `contactEmail`, `phone`, `address`, `currencySymbol`, `logos` | +| `SocialMedia` | Dynamic social links | `platform`, `url`, `isEnabled` | +| `EmailTemplate` | Dynamic HTML emails | `name`, `subject`, `bodyHtml`, `placeholders[]` | +| `ContactMessage` | Customer contact submissions | `name`, `email`, `phone`, `subject`, `message`, `status` | + +--- + +## 3. Backend API Architecture (Node & Express) + +The backend follows the clean **Routes → Controllers → Services → Models** pattern: + +1. **Routes (`server/routes/`)**: Map incoming HTTP endpoints (e.g. `POST /api/products`, `PUT /api/orders/:id/status`) to controller functions, applying authentication (`protect`) and authorization (`authorize('Admin')`) middlewares. +2. **Controllers (`server/controllers/`)**: Handle request data validation, invoke database queries through Mongoose models, format responses using standardized helper `apiResponse.js`, and trigger email notifications. +3. **Middlewares (`server/middleware/`)**: + - `authMiddleware.js`: Verifies JWT tokens in `Authorization: Bearer ` headers and enforces role restrictions. + - `errorHandler.js`: Intercepts Mongoose validation errors, duplicate keys, and invalid ObjectIds, returning clean JSON error payloads. + - `upload.js`: Configures Multer for local disk storage of product images and banner media in `server/uploads/`. +4. **Email Sender (`server/utils/emailSender.js`)**: Loads dynamic `EmailTemplate` documents from MongoDB and performs placeholder replacement (e.g. `{{customerName}}`, `{{orderId}}`) before sending via Nodemailer. + +--- + +## 4. Frontend Architecture (React, Vite & Redux Toolkit) + +The frontend is structured in `client/src/`: + +``` +client/src/ +├── api/ # Axios HTTP client with JWT interceptor +├── components/ # Reusable UI components (Navbar, Footer, ProductCard, Modals, Drawers) +├── pages/ # Customer & Admin pages +│ ├── customer/ # HomePage, ShopPage, ProductDetailPage, CartPage, CheckoutPage, etc. +│ └── admin/ # AdminDashboard, AdminProducts, AdminInventory, AdminOrders, etc. +├── store/ # Redux Toolkit Slices (auth, cart, wishlist, compare, ui) +└── index.css # Tailwind CSS & custom scrollbars +``` + +### Global Redux State Slices: + +- `auth`: Manages logged-in user profile, JWT token, and authentication state (`login`, `register`, `logout`). +- `cart`: Manages shopping cart items, quantities, and coupon applications with persistent backend syncing. +- `wishlist`: Synchronizes user saved guitars. +- `compare`: Stores up to 4 instruments for side-by-side spec comparison. +- `ui`: Controls floating modals (`GuitarFinderModal`, `ProductQuickView`), drawers, and toast notifications. + +--- + +## 5. End-to-End Application Workflows + +### Customer Shopping Workflow + +1. **Browse & Search**: Customer lands on `HomePage` -> views Hero Banners -> filters products on `ShopPage` by Category, Brand, Guitar Type (Electric, Acoustic, Bass), Handedness, and Price range. +2. **Product Details & Spec Inspection**: Clicking an item navigates to `ProductDetailPage` -> views high-res thumbnail gallery -> inspects luthier specs table (Body wood, Pickups, Scale length, Frets) -> selects Color/Finish variant. +3. **Cart & Coupons**: Customer adds product to cart -> navigates to `CartPage` -> views Free Shipping Progress Bar -> enters promo code `WELCOME10` for a 10% discount. +4. **Checkout & Order Placement**: Proceeds to `CheckoutPage` -> enters shipping address -> selects payment method (`Stripe Encrypted` or `Cash on Delivery`) -> clicks **Authorize & Place Order**. +5. **Stock Deduction & Confirmation**: Backend creates order document -> deducts inventory stock -> creates `InventoryHistory` audit entry -> sends Order Confirmation Email -> clears user cart -> redirects customer to `OrderConfirmationPage`. + +### Interactive Guitar Finder Tool + +- Triggered from Navbar or Home banner (`GuitarFinderModal.jsx`). +- Asks 3 interactive questions: + 1. Instrument type (Electric, Acoustic, Bass, Classical) + 2. Target budget range ($800, $1800, $4000+) + 3. Hand orientation (Right-Handed, Left-Handed) +- Calls `POST /api/products/guitar-finder` -> backend runs rule-based filtering on specs and ratings -> returns matching recommendations directly in the modal for instant cart addition. + +### Product Comparison Matrix + +- Customers click the compare button on up to 4 products -> floating `CompareDrawer.jsx` appears. +- Navigating to `/compare` renders `ComparePage.jsx` showing a side-by-side spec table comparing body woods, pickups, fingerboards, scale lengths, and prices. + +### Admin Stock Audit & Inventory System + +- Admins access `/admin/inventory` -> view low-stock and out-of-stock alerts. +- Admin selects a guitar, inputs new stock number, selects change type (`Restock`, `Manual Adjustment`, `Order Cancelled`, `Returned`), and adds a luthier note. +- Backend updates product stock level AND logs an immutable audit document in `InventoryHistory`. + +### Order Fulfillment & Status Timeline + +- Admin views `/admin/orders` -> selects order -> updates status to `Shipped` -> enters courier tracking code (e.g. `FDX-990842-US`). +- Backend saves status history timestamp -> triggers "Order Shipped" email to customer. +- Customer views `/track-order/:id` -> sees visual horizontal progress timeline highlighting exact current fulfillment status. + +--- + +## 6. API Endpoints Reference + +### Authentication (`/api/auth`) + +- `POST /api/auth/register` - Register customer account +- `POST /api/auth/login` - Login user & receive JWT token +- `GET /api/auth/profile` - Fetch profile (Protected) +- `PUT /api/auth/profile` - Update profile & address book (Protected) + +### Products (`/api/products`) + +- `GET /api/products` - List products (filters: category, brand, guitarType, minPrice, maxPrice, search, sort, page) +- `POST /api/products/guitar-finder` - Interactive recommendation engine +- `POST /api/products/compare` - Compare multiple product IDs +- `GET /api/products/:slug` - Detailed product specs by slug +- `POST /api/products` - Create guitar (Admin) +- `PUT /api/products/:id` - Update guitar specs & variants (Admin) +- `DELETE /api/products/:id` - Delete guitar (Admin) + +### Cart & Wishlist (`/api/cart`, `/api/wishlist`) + +- `GET /api/cart` - Retrieve active session/user cart +- `POST /api/cart/add` - Add item to cart (with stock validation) +- `PUT /api/cart/update` - Change item quantity +- `DELETE /api/cart/item/:itemId` - Remove item +- `POST /api/cart/coupon` - Validate & apply coupon code +- `GET /api/wishlist` & `POST /api/wishlist/toggle` - Manage saved items + +### Orders & Inventory (`/api/orders`, `/api/inventory`) + +- `POST /api/orders` - Place order & deduct stock +- `GET /api/orders/my-orders` - List customer order history +- `GET /api/orders/:id` - Get order receipt & status history +- `GET /api/orders/admin/all` - List all store orders (Admin) +- `PUT /api/orders/:id/status` - Update status & tracking number (Admin) +- `GET /api/inventory/overview` - Stock overview & alerts (Admin) +- `POST /api/inventory/adjust` - Adjust stock & log audit history (Admin) +- `GET /api/inventory/history` - View stock audit trail log (Admin) + +--- + +## 7. Installation & Execution Guide + +### 1. Install All Dependencies + +```bash +npm run install:all +``` + +### 2. Seed Database + +Runs `server/seed/seeder.js` to clear old collections and populate demo data: + +```bash +npm run seed +``` + +### 3. Run Development Servers + +```bash +# Terminal 1: Backend Server (Port 5000) +npm run server + +# Terminal 2: Frontend Client (Port 5173) +npm run client +``` + +### 4. Admin Credentials + +- **Admin**: `admin@guitars.com` | Password: `Admin@123` +- **Customer**: `john@example.com` | Password: `Customer@123` diff --git a/client/.gitignore b/client/.gitignore new file mode 100644 index 0000000..42e3aaa --- /dev/null +++ b/client/.gitignore @@ -0,0 +1,8 @@ +node_modules/ +.next/ +dist/ +build/ +.env +.env.local +.env.*.local +*.log \ No newline at end of file diff --git a/client/index.html b/client/index.html new file mode 100644 index 0000000..cd6e60e --- /dev/null +++ b/client/index.html @@ -0,0 +1,17 @@ + + + + + + + Antigravity Guitars India | Crafted Sound. Pure Perfection. + + + + + + +
+ + + diff --git a/client/package-lock.json b/client/package-lock.json new file mode 100644 index 0000000..99c3459 --- /dev/null +++ b/client/package-lock.json @@ -0,0 +1,3239 @@ +{ + "name": "guitar-ecommerce-client", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "guitar-ecommerce-client", + "version": "1.0.0", + "dependencies": { + "@hookform/resolvers": "^3.3.4", + "@reduxjs/toolkit": "^2.2.3", + "@tanstack/react-query": "^5.29.2", + "axios": "^1.6.8", + "clsx": "^2.1.0", + "framer-motion": "^11.1.7", + "lucide-react": "^0.368.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-hook-form": "^7.51.3", + "react-redux": "^9.1.1", + "react-router-dom": "^6.22.3", + "tailwind-merge": "^2.2.2", + "zod": "^3.22.4" + }, + "devDependencies": { + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@vitejs/plugin-react": "^4.2.1", + "autoprefixer": "^10.4.19", + "postcss": "^8.4.38", + "tailwindcss": "^3.4.3", + "vite": "^5.2.8" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.3.0.tgz", + "integrity": "sha512-U4+70Pc5ZS9osnCBCE5Jha/ciHM+Yp+CNMNC/7HvYbNRk1Ldd+f7qO65W5qfhu/TCv+/ozljlXXe9Nj8419DMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@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.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@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.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "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.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@hookform/resolvers": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz", + "integrity": "sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==", + "license": "MIT", + "peerDependencies": { + "react-hook-form": "^7.0.0" + } + }, + "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.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "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/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "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.12.0", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.12.0.tgz", + "integrity": "sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==", + "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/@remix-run/router": { + "version": "1.23.4", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.4.tgz", + "integrity": "sha512-q7j5geK7xs3UJSdm9/iytUNclBnLmYx1EnSeCFXHPeutdqgIMeFeHtUZgS3EhlKxdBEAu8OwtJCwmLrEzpSs7Q==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.1.tgz", + "integrity": "sha512-UZ8sUxPTiHWYX9QNdJedb1kDZSpS1t/VPWBWGSgqHNi9w3Cu6IXvu2mzbhiTiPvtrqgTQJ+zqiAq2iPIPilpaQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.1.tgz", + "integrity": "sha512-cQ4nFQABN5cDvDpbvJ7bMStCpnaVxynZrRMfUJYgxcIk9Sh54FIO1vtfkg0B69REjER77ioZ/ov+eAApx/KmLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.1.tgz", + "integrity": "sha512-FQNqd1lRy/0QhDk3xeRIkSBiCpXCiDnZO3YLVdcDKN1UBiKToNftCzcXYNLshmPDUMlu2TdeS8tGcsU6f3YF1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.1.tgz", + "integrity": "sha512-pvD16V939D3CloK0+qikpGaxiPrDUXTe7Y5cWOMkMSy7m1cawa8EGy/kXYi/G/cKAC4HDAbSnzCIk1WmsoOKXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.1.tgz", + "integrity": "sha512-pcFGeL2345VwdTnJhA6zLbew+YgWB0qBG2+dMtXjCicf6+rm6kO6cOoh5VnTe0ZMrMRgRyuHmCJxZWrIdzYuOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.1.tgz", + "integrity": "sha512-mRJlqSRulVzcKq/LKA6ICSIc3K/l4fzlVn/gePn2nXIHy8seRi5z/eeRE0d/XMBxcMldiXtQTSpRj0tkkC3g8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.1.tgz", + "integrity": "sha512-YDUNvVM85TI3g/1OpnqKP1h4NeW/j64DfWMf+G3M809xNk1bJSnpFp4sh83NpmVE5DXnkh8ULor4LTVZKoYLHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.1.tgz", + "integrity": "sha512-7Mcn71p9ZuQFAj+h+dhQXy/yeLePRS2yKRnmW1DijA9thKO5qap0GNOIQK4yQ6iP3SU0Mrb/yWo8h8vgRba8lw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.1.tgz", + "integrity": "sha512-4YiLQTX6U4CSl0L9cluep9A9W6UmTfqBDc2/CH6wlu54pl4E7Jn3cOD8oxzvBDEGk/JMKgJ47C8g+radF7mwvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.1.tgz", + "integrity": "sha512-2ra8F7w8OquwZN9z2/fKFnli69wa8PLwaVzRMIPGb13ByMJwC28Fbp8YcVGoUhlYMTt7j5j9bNgpysrN2UM+vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.1.tgz", + "integrity": "sha512-Sy20ncyhjmBP0Ml+UvQbimjlk6VFgjW5uNP+qqwHB00mTE8Bl2C1TuHTlRwK2YoXeZbee5lP2XevBWVkAQAtSQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.1.tgz", + "integrity": "sha512-noITLp8oNjYliPnGWmLyelIHwULGqbHloQHGw1rtxbWhTuWooRpnZarZQJ1y9EUC4szuCusCc+HEpUtxpIwYvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.1.tgz", + "integrity": "sha512-hlxxXd+F1mWiAcaFR7Sv9ZQT6m6UfI8+Vy/kFJzztq2pDMU/0wZ9sish0iszNZvsQDo8Gc0i5yuFEOz5dDf6fA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.1.tgz", + "integrity": "sha512-EF7OpqQTQ/BvGqLzUi4rEHuagCV9MugAUXSHemwPW5vxZ75RR+jxO/2j95Ph2dalMpFHSVECjRoioHZgA9zOYA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.1.tgz", + "integrity": "sha512-wQO3JesW9PRkwlabQ27y7sPfVOOTLRG73I4F2UYHG5PXun3J9U3y+b7ezVKSYbsvSKGQ1k1cq8Qlun4C9kLt3w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.1.tgz", + "integrity": "sha512-ouAGwhO6wHRXdnOVCOsB0tRFkA7nhNB2Nwax6oECXN0YiN8EYUTBAOudADOB1PI+yDL61TeNx/u7MVCzksNbkQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.1.tgz", + "integrity": "sha512-q2R38Sn+1J8RxhfJ+T54wSWmyKXWec+9jgDfqO2AtArEqHO5R2aeayp5H5OYLr5UYDVGsVaZPEFUooMhYCdz5A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.1.tgz", + "integrity": "sha512-gfI5T24WLLuFfSKw7Go/zDXjAAV0fny0swTaDv+WjK7vqcw4cRhFfdsyKL1n+ukI+ooBxn3bVQnyrn06WpI50w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.1.tgz", + "integrity": "sha512-4h6XqthmB4Hspji84wvgk+ElodTsGj+dbZqHJHHtKxj4mYq0ANSEEPX9ys3moJueqsRjwpaJYH7874Itwnj2ow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.1.tgz", + "integrity": "sha512-dlfCOa87o1VAYegLQ9EKilx2JCeRofiyPGhTCmqnuXZ6bMPiycO1rq1+sKoulAp7pGLIsTIw+1x5R+zgh5LhhA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.1.tgz", + "integrity": "sha512-cjkLbOlfcm3QGhMM1J5zaZjsw1GggbN6rw9UTSSRrPrR1KkcXnN7Uq9rPw34xImQ9VOY9GN+6u2Zj80B9ptkcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.1.tgz", + "integrity": "sha512-Li1KdUnWGE4N3e1F/B4RTB1ms+nG4WBgjByO46pkeBVX/2UBsY53xf5vK9WygVmnH3RwncIST7lkSdLSY6P9lg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.1.tgz", + "integrity": "sha512-t4ZYOSoLTgwhuFMrmTMLx/+i1DQVK7HYqMc6kY46EApwi8X0nIVphzdNoThU3xt6n+N5urG1/gxBdCaKDLavfg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.1.tgz", + "integrity": "sha512-RgroPfMmKlD1RzSDxvwgcPiy2HNQKoYV7OmwIXDsk73uKW5t6B/V8KIy27SMv/FNXFo/oSBtWc9J0X7t91ezZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.1.tgz", + "integrity": "sha512-at8QVep6S3h5Y6gSbdGU06bRY5WJkf6WUduM9YtvYMbYhB1MOFfUgc6kehitQXzOtMSaT70q7f9ydPhpqu821w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "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/@tanstack/query-core": { + "version": "5.102.8", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.102.8.tgz", + "integrity": "sha512-ZNjkJ33CqvPNec/6lZBnHqLc3EVGPZ9ySLhYahU9TcuRFdmwXewuj0c4hwSWcGHqEUwcSrKeZ+oGcvPBqXcQcg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.102.8", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.102.8.tgz", + "integrity": "sha512-TYBea4OuXWD7MhaSHq069TWbFe7rcwWN6kzT7JF0OKi1K6c1gTv2IzD6A6ExJsCMozdkqBWeuIUZmu4KQg0O5A==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.102.8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "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.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.31", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz", + "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.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": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "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/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/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.5.4", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.4.tgz", + "integrity": "sha512-MaU0U/za7N3r6brxD4YB/l4NSrFzLPlANv6wEuQVaIPlD3L4W9rFcQPbL/EilY9BHhHvhfcz3gInDLrEtWT4EA==", + "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.28.6", + "caniuse-lite": "^1.0.30001806", + "fraction.js": "^5.3.4", + "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/axios": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.20.0.tgz", + "integrity": "sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.6", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.21", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.21.tgz", + "integrity": "sha512-uh8vpY/1/YyFkunIDFH/12p7/7VdPKA1hejMVEbdkEaWnUz0Hesvx5EbiU6XxjyHZIOju+ZMbQJkRh+es3/spQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "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/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.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", + "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.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", + "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/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/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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/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/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==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "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/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.422", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.422.tgz", + "integrity": "sha512-UvA/32XqrLDdZSn7Jllo1AYNcWji/G0d5M0GTViE7KoGBiMunw3a34Sb2KO4ZZyrSEhqsxFoVhWWJshdyfKqJA==", + "dev": true, + "license": "ISC" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "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/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/fastq": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz", + "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "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/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "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": "11.18.2", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.18.2.tgz", + "integrity": "sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==", + "license": "MIT", + "dependencies": { + "motion-dom": "^11.18.1", + "motion-utils": "^11.18.1", + "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==", + "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/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/immer": { + "version": "11.1.18", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.18.tgz", + "integrity": "sha512-EQyQtLiYW029lyoczMl/Hh4Xu7cDecSc58JRYpHyL4tIAu3eqd1yJzQX04d2BZHDkzFFvm6qJEJWOtfDSWAXbQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "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.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "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/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==", + "license": "MIT" + }, + "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/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/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/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "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.368.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.368.0.tgz", + "integrity": "sha512-soryVrCjheZs8rbXKdINw9B8iPi5OajBJZMJ1HORig89ljcOcEokKKAgGbg3QWxSXel7JwHOfDFUdDHAKyUAMw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "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/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/motion-dom": { + "version": "11.18.1", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz", + "integrity": "sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==", + "license": "MIT", + "dependencies": { + "motion-utils": "^11.18.1" + } + }, + "node_modules/motion-utils": { + "version": "11.18.1", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.18.1.tgz", + "integrity": "sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==", + "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==", + "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.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "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/node-releases": { + "version": "2.0.54", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.54.tgz", + "integrity": "sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "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/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/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": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "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.18", + "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.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", + "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", + "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/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "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": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-hook-form": { + "version": "7.87.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.87.0.tgz", + "integrity": "sha512-zhFzWvLxNHH+8839OnZcUxgMZw88ah2jZWDWvKWgF3Tpbnd0vKL+dlcuU3nZVWESZQjd81EW8K+wU+cYfYAc0w==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, + "node_modules/react-redux": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz", + "integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/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.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.30.6", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.6.tgz", + "integrity": "sha512-5HfK7k5im7LTOB0EqCQmfvy4C13G92Ssj1VTmouTK3AJvyjKTnFuCV0vcMAD/JS+JC4DvDIBRrlAeJIFjh5VWg==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.6", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.6.tgz", + "integrity": "sha512-0RHKZz7wwffvkU+2MFVT2NnjK44ssLEV+m0CAJaS2Ksmorrwj7WxH00jO0SOCW26/tINUnJHToXblDs33I38YQ==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.4", + "react-router": "6.30.6" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/read-cache": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.2.tgz", + "integrity": "sha512-/peqiBB/n07gQGLsWaHho3WfvUyRscw0gYTsEFMhrIe/nWLkYaf5SbKYjGYqtRV3aPwykJgF2VEMo1ac4bnsGA==", + "dev": true, + "license": "MIT" + }, + "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/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.3.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.3.0.tgz", + "integrity": "sha512-XGoLeRAVzUTcJ1qkxPQhDJyIZ5d6zzZD9nT7AEZOaaU9UbWclhycElmhO+VD5bFeLuzhPBaOV2oXC8uG35ZSpg==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "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/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.63.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.1.tgz", + "integrity": "sha512-3Df9jsstwhccuEfmAMi9l8XUh/GOkVObmFTU7CCVBysEbcOZLl84jCtaAZMcPiMz2EGKsATzQcU+Xr3n/wU6cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.1", + "@rollup/rollup-android-arm64": "4.63.1", + "@rollup/rollup-darwin-arm64": "4.63.1", + "@rollup/rollup-darwin-x64": "4.63.1", + "@rollup/rollup-freebsd-arm64": "4.63.1", + "@rollup/rollup-freebsd-x64": "4.63.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.1", + "@rollup/rollup-linux-arm-musleabihf": "4.63.1", + "@rollup/rollup-linux-arm64-gnu": "4.63.1", + "@rollup/rollup-linux-arm64-musl": "4.63.1", + "@rollup/rollup-linux-loong64-gnu": "4.63.1", + "@rollup/rollup-linux-loong64-musl": "4.63.1", + "@rollup/rollup-linux-ppc64-gnu": "4.63.1", + "@rollup/rollup-linux-ppc64-musl": "4.63.1", + "@rollup/rollup-linux-riscv64-gnu": "4.63.1", + "@rollup/rollup-linux-riscv64-musl": "4.63.1", + "@rollup/rollup-linux-s390x-gnu": "4.63.1", + "@rollup/rollup-linux-x64-gnu": "4.63.1", + "@rollup/rollup-linux-x64-musl": "4.63.1", + "@rollup/rollup-openbsd-x64": "4.63.1", + "@rollup/rollup-openharmony-arm64": "4.63.1", + "@rollup/rollup-win32-arm64-msvc": "4.63.1", + "@rollup/rollup-win32-ia32-msvc": "4.63.1", + "@rollup/rollup-win32-x64-gnu": "4.63.1", + "@rollup/rollup-win32-x64-msvc": "4.63.1", + "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.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "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/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/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-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/tailwind-merge": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.1.tgz", + "integrity": "sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "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.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/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/tinyglobby/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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/update-browserslist-db": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz", + "integrity": "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==", + "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/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": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "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/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/client/package.json b/client/package.json new file mode 100644 index 0000000..316f514 --- /dev/null +++ b/client/package.json @@ -0,0 +1,38 @@ +{ + "name": "guitar-ecommerce-client", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "@hookform/resolvers": "^3.3.4", + "@reduxjs/toolkit": "^2.2.3", + "@tanstack/react-query": "^5.29.2", + "axios": "^1.6.8", + "clsx": "^2.1.0", + "framer-motion": "^11.1.7", + "lucide-react": "^0.368.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-hook-form": "^7.51.3", + "react-redux": "^9.1.1", + "react-router-dom": "^6.22.3", + "tailwind-merge": "^2.2.2", + "zod": "^3.22.4" + }, + "devDependencies": { + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@vitejs/plugin-react": "^4.2.1", + "autoprefixer": "^10.4.19", + "postcss": "^8.4.38", + "tailwindcss": "^3.4.3", + "vite": "^5.2.8" + } +} + diff --git a/client/postcss.config.js b/client/postcss.config.js new file mode 100644 index 0000000..b4a6220 --- /dev/null +++ b/client/postcss.config.js @@ -0,0 +1,7 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} + diff --git a/client/src/App.jsx b/client/src/App.jsx new file mode 100644 index 0000000..7269fe2 --- /dev/null +++ b/client/src/App.jsx @@ -0,0 +1,120 @@ +import React, { useState } from 'react'; +import { Routes, Route, Navigate } from 'react-router-dom'; +import { useSelector } from 'react-redux'; + +// Scroll to top on page change +import ScrollToTop from './components/ScrollToTop'; + +// Layouts & Global Components +import Navbar from './components/Navbar'; +import MobileNav from './components/MobileNav'; +import Footer from './components/Footer'; +import ProductQuickView from './components/ProductQuickView'; +import GuitarFinderModal from './components/GuitarFinderModal'; +import CompareDrawer from './components/CompareDrawer'; +import ToastNotification from './components/ToastNotification'; + +// Customer Pages +import HomePage from './pages/customer/HomePage'; +import ShopPage from './pages/customer/ShopPage'; +import ProductDetailPage from './pages/customer/ProductDetailPage'; +import ComparePage from './pages/customer/ComparePage'; +import WishlistPage from './pages/customer/WishlistPage'; +import CartPage from './pages/customer/CartPage'; +import CheckoutPage from './pages/customer/CheckoutPage'; +import OrderConfirmationPage from './pages/customer/OrderConfirmationPage'; +import OrderTrackingPage from './pages/customer/OrderTrackingPage'; +import UserDashboardPage from './pages/customer/UserDashboardPage'; +import ContactPage from './pages/customer/ContactPage'; +import { AboutUsPage, PolicyPage } from './pages/customer/CMSPages'; +import LoginPage from './pages/customer/LoginPage'; + +// Admin Pages & Dedicated Admin Login +import AdminLoginPage from './pages/admin/AdminLoginPage'; +import AdminLayout from './pages/admin/AdminLayout'; +import AdminDashboard from './pages/admin/AdminDashboard'; +import AdminProducts from './pages/admin/AdminProducts'; +import AdminInventory from './pages/admin/AdminInventory'; +import AdminOrders from './pages/admin/AdminOrders'; +import AdminCategories from './pages/admin/AdminCategories'; +import AdminBrands from './pages/admin/AdminBrands'; +import AdminCoupons from './pages/admin/AdminCoupons'; +import AdminBanners from './pages/admin/AdminBanners'; +import AdminSettings from './pages/admin/AdminSettings'; +import AdminMessages from './pages/admin/AdminMessages'; +import AdminEmailTemplates from './pages/admin/AdminEmailTemplates'; + +// Protected Admin Route guard (redirects to /admin/login if not admin) +const ProtectedAdminRoute = ({ children }) => { + const { userInfo } = useSelector((state) => state.auth); + if (!userInfo || userInfo.role !== 'Admin') { + return ; + } + return children; +}; + +const CustomerLayoutWrapper = ({ children }) => { + const [mobileMenuOpen, setMobileMenuOpen] = useState(false); + + return ( +
+ setMobileMenuOpen(true)} /> + setMobileMenuOpen(false)} /> +
{children}
+
+ + + + +
+ ); +}; + +const App = () => { + return ( + <> + + + {/* Customer Website Routes */} + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + {/* Dedicated Admin Login Route (Requirement 3) */} + } /> + + {/* Protected Admin Portal Routes */} + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + {/* Fallback */} + } /> + + + ); +}; + +export default App; diff --git a/client/src/api/axios.js b/client/src/api/axios.js new file mode 100644 index 0000000..09f863b --- /dev/null +++ b/client/src/api/axios.js @@ -0,0 +1,30 @@ +import axios from 'axios'; + +const api = axios.create({ + baseURL: '/api', + headers: { + 'Content-Type': 'application/json' + } +}); + +// Request interceptor to attach JWT token +api.interceptors.request.use( + (config) => { + const user = JSON.parse(localStorage.getItem('userInfo')); + if (user && user.token) { + config.headers.Authorization = `Bearer ${user.token}`; + } + // Guest Session ID for cart + let sessionId = localStorage.getItem('guestSessionId'); + if (!sessionId) { + sessionId = 'session_' + Math.random().toString(36).substring(2, 9); + localStorage.setItem('guestSessionId', sessionId); + } + config.headers['x-session-id'] = sessionId; + return config; + }, + (error) => Promise.reject(error) +); + +export default api; + diff --git a/client/src/components/CompareDrawer.jsx b/client/src/components/CompareDrawer.jsx new file mode 100644 index 0000000..08d63dc --- /dev/null +++ b/client/src/components/CompareDrawer.jsx @@ -0,0 +1,57 @@ +import React from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import { Link } from 'react-router-dom'; +import { X, Sliders, ArrowRight } from 'lucide-react'; +import { removeFromCompare, clearCompare } from '../store/slices/compareSlice'; + +const CompareDrawer = () => { + const dispatch = useDispatch(); + const { items } = useSelector((state) => state.compare); + + if (items.length === 0) return null; + + return ( +
+
+
+ +
+
+
Compare Guitars ({items.length}/4)
+
Side-by-side woods, pickups & price comparison
+
+
+ +
+ {items.map((item) => ( +
+ + +
+ ))} +
+ +
+ + + Compare Now + +
+
+ ); +}; + +export default CompareDrawer; diff --git a/client/src/components/Footer.jsx b/client/src/components/Footer.jsx new file mode 100644 index 0000000..8c514ef --- /dev/null +++ b/client/src/components/Footer.jsx @@ -0,0 +1,155 @@ +import React, { useState, useEffect } from 'react'; +import { Link } from 'react-router-dom'; +import { Instagram, Facebook, Youtube, Twitter, Mail, Phone, MapPin, ShieldCheck, Truck, RotateCcw, Headphones } from 'lucide-react'; +import api from '../api/axios'; + +const Footer = () => { + const [settings, setSettings] = useState({ + siteName: 'Antigravity Guitars India', + tagline: 'Crafted Sound. Pure Perfection.', + businessName: 'Antigravity Music Private Limited', + gstin: '29ABCDE1234F1Z5', + addressLine1: '100 Brigade Road, Ashok Nagar', + addressLine2: 'Suite 400, Music Square', + city: 'Bengaluru', + state: 'Karnataka', + pincode: '560025', + country: 'India', + phone: '+91 98765 43210', + contactEmail: 'support@antigravityguitars.in' + }); + const [socials, setSocials] = useState([]); + + useEffect(() => { + api.get('/settings').then(res => setSettings(res.data.data)).catch(() => {}); + api.get('/social-media').then(res => setSocials(res.data.data)).catch(() => {}); + }, []); + + return ( +
+ {/* Features Bar */} +
+
+
+
+ +
+
+
Free Courier Delivery
+
On all India orders over ₹5,000
+
+
+
+
+ +
+
+
100% Authentic Guitars
+
Official Indian brand warranty
+
+
+
+
+ +
+
+
10-Day Hassle Returns
+
Safe studio trial period
+
+
+
+
+ +
+
+
Luthier Support India
+
Expert setup & advice
+
+
+
+
+ + {/* Main Footer Links */} +
+ + {/* Brand Info */} +
+
+
AG
+ {settings.siteName} +
+

+ {settings.tagline}. India's premier music destination providing hand-crafted acoustic, electric, and custom shop guitars. +

+
+ GSTIN: {settings.gstin || '29ABCDE1234F1Z5'} +
+
+ + {/* Quick Links */} +
+

Explore Instruments

+
    +
  • All Guitars Catalog
  • +
  • Electric Guitars
  • +
  • Acoustic Guitars
  • +
  • Bass Guitars
  • +
  • Amplifiers & Pedals
  • +
+
+ + {/* Customer Care */} +
+

Customer Care

+
    +
  • Contact Us
  • +
  • About Antigravity India
  • +
  • Track Order Status
  • +
  • Privacy Policy
  • +
  • Terms of Service
  • +
+
+ + {/* Requirement 17: Indian Business Address & Contact Info (Replaces VIP Tone Club) */} +
+

Indian Headquarters & Address

+
+
{settings.businessName || settings.siteName}
+
+ + + {settings.addressLine1 || '100 Brigade Road'}, {settings.addressLine2 || 'Ashok Nagar'},
+ {settings.city || 'Bengaluru'}, {settings.state || 'Karnataka'} – {settings.pincode || '560025'}, {settings.country || 'India'} +
+
+
+ + {settings.phone || '+91 98765 43210'} +
+
+ + {settings.contactEmail || 'support@antigravityguitars.in'} +
+
+ +
+
Connect With Us
+
+ + + + +
+
+
+ +
+ +
+ © {new Date().getFullYear()} {settings.businessName || settings.siteName}. All Rights Reserved. Built with MERN Stack Architecture. +
+
+ ); +}; + +export default Footer; diff --git a/client/src/components/GuitarFinderModal.jsx b/client/src/components/GuitarFinderModal.jsx new file mode 100644 index 0000000..2d49dd2 --- /dev/null +++ b/client/src/components/GuitarFinderModal.jsx @@ -0,0 +1,193 @@ +import React, { useState } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import { X, Sparkles, ChevronRight, RotateCcw, ShoppingBag } from 'lucide-react'; +import { closeGuitarFinder, showToast } from '../store/slices/uiSlice'; +import { addToCartAsync } from '../store/slices/cartSlice'; +import { formatINR } from '../utils/formatCurrency'; +import api from '../api/axios'; + +const GuitarFinderModal = () => { + const dispatch = useDispatch(); + const { guitarFinderOpen } = useSelector((state) => state.ui); + + const [step, setStep] = useState(1); + const [answers, setAnswers] = useState({ + skillLevel: 'Intermediate', + acousticElectric: 'Electric', + maxBudget: 100000, // ₹1,000,00 + handOrientation: 'Right-Handed' + }); + const [recommendations, setRecommendations] = useState([]); + const [loading, setLoading] = useState(false); + + if (!guitarFinderOpen) return null; + + const handleNext = async () => { + if (step < 3) { + setStep(step + 1); + } else { + setLoading(true); + try { + const res = await api.post('/products/guitar-finder', answers); + setRecommendations(res.data.data || []); + setStep(4); + } catch (err) { + dispatch(showToast({ message: 'Failed to generate recommendations', type: 'error' })); + } finally { + setLoading(false); + } + } + }; + + const handleReset = () => { + setStep(1); + setRecommendations([]); + }; + + return ( +
+
dispatch(closeGuitarFinder())} /> + +
+ + +
+
+ +
+
+

Tone Matcher & Guitar Finder India

+

Answer 3 quick questions to discover your ideal instrument

+
+
+ + {/* Step 1: Type */} + {step === 1 && ( +
+
1. What type of instrument are you looking for?
+
+ {['Electric', 'Acoustic', 'Bass', 'Classical'].map((type) => ( + + ))} +
+
+ )} + + {/* Step 2: Budget (₹ INR) */} + {step === 2 && ( +
+
2. What is your target budget range?
+
+ {[ + { label: 'Under ₹30,000 (Player Standard)', value: 30000 }, + { label: '₹30,000 - ₹1,00,000 (Custom Deluxe)', value: 100000 }, + { label: '₹1,00,000+ (Master Custom Shop)', value: 300000 } + ].map((b) => ( + + ))} +
+
+ )} + + {/* Step 3: Handedness */} + {step === 3 && ( +
+
3. Select your playing hand orientation
+
+ {['Right-Handed', 'Left-Handed'].map((orient) => ( + + ))} +
+
+ )} + + {/* Step 4: Results */} + {step === 4 && ( +
+
+ Found {recommendations.length} matching instruments + +
+ + {recommendations.map((p) => ( +
+ +
+
{p.brand?.name}
+

{p.name}

+
{formatINR(p.salePrice || p.price)}
+
+ +
+ ))} +
+ )} + + {/* Footer controls */} + {step < 4 && ( +
+
Step {step} of 3
+ +
+ )} + +
+
+ ); +}; + +export default GuitarFinderModal; diff --git a/client/src/components/MobileNav.jsx b/client/src/components/MobileNav.jsx new file mode 100644 index 0000000..c7370d1 --- /dev/null +++ b/client/src/components/MobileNav.jsx @@ -0,0 +1,82 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import { X, Home, ShoppingBag, Heart, Compass, Sliders, User, Shield } from 'lucide-react'; +import { useSelector, useDispatch } from 'react-redux'; +import { openGuitarFinder } from '../store/slices/uiSlice'; + +const MobileNav = ({ isOpen, onClose }) => { + const dispatch = useDispatch(); + const { userInfo } = useSelector((state) => state.auth); + + if (!isOpen) return null; + + return ( +
+ {/* Overlay */} +
+ + {/* Drawer */} +
+
+
+
+
AG
+ ANTIGRAVITY +
+ +
+ +
+ + Home + + + Shop All Guitars + + + + Product Comparison + + + Saved Wishlist + + {userInfo && userInfo.role === 'Admin' && ( + + Admin Dashboard + + )} +
+
+ +
+ {userInfo ? ( + + +
+
{userInfo.name}
+
{userInfo.email}
+
+ + ) : ( + + Sign In / Register + + )} +
+
+
+ ); +}; + +export default MobileNav; diff --git a/client/src/components/Navbar.jsx b/client/src/components/Navbar.jsx new file mode 100644 index 0000000..9d59c24 --- /dev/null +++ b/client/src/components/Navbar.jsx @@ -0,0 +1,286 @@ +import React, { useState, useEffect, useRef } from 'react'; +import { Link, useNavigate, useLocation } from 'react-router-dom'; +import { useSelector, useDispatch } from 'react-redux'; +import { Search, ShoppingBag, Heart, Sliders, User, Menu, Shield, LogOut, Compass, ChevronDown, Sparkles } from 'lucide-react'; +import { logout } from '../store/slices/authSlice'; +import { openGuitarFinder } from '../store/slices/uiSlice'; +import api from '../api/axios'; + +const Navbar = ({ onOpenMobileMenu }) => { + const navigate = useNavigate(); + const location = useLocation(); + const dispatch = useDispatch(); + + const { userInfo } = useSelector((state) => state.auth); + const { items: cartItems } = useSelector((state) => state.cart); + const { products: wishlistProducts } = useSelector((state) => state.wishlist); + const { items: compareItems } = useSelector((state) => state.compare); + + const [searchQuery, setSearchQuery] = useState(''); + const [suggestions, setSuggestions] = useState([]); + const [isSearchOpen, setIsSearchOpen] = useState(false); + const [categories, setCategories] = useState([]); + const [profileDropdownOpen, setProfileDropdownOpen] = useState(false); + const dropdownRef = useRef(null); + + useEffect(() => { + api.get('/categories').then((res) => setCategories(res.data.data)).catch(() => {}); + }, []); + + // Click outside listener for profile dropdown + useEffect(() => { + const handleClickOutside = (event) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { + setProfileDropdownOpen(false); + } + }; + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, []); + + const handleSearchChange = async (e) => { + const query = e.target.value; + setSearchQuery(query); + if (query.trim().length > 1) { + try { + const res = await api.get(`/products?search=${encodeURIComponent(query)}&limit=5`); + setSuggestions(res.data.data.products || []); + setIsSearchOpen(true); + } catch (err) { + setSuggestions([]); + } + } else { + setSuggestions([]); + setIsSearchOpen(false); + } + }; + + const handleSearchSubmit = (e) => { + e.preventDefault(); + if (searchQuery.trim()) { + setIsSearchOpen(false); + navigate(`/shop?search=${encodeURIComponent(searchQuery.trim())}`); + } + }; + + const totalCartCount = cartItems.reduce((acc, item) => acc + item.quantity, 0); + + const isActiveRoute = (path) => location.pathname === path; + + return ( +
+ {/* Top Shipping Announcement Bar */} +
+ 🇮🇳 FREE EXPRESS COURIER DELIVERY ACROSS INDIA ON ORDERS OVER ₹5,000 + | CODE: WELCOME10 +
+ +
+
+ + {/* Logo & Mobile Menu Toggle */} +
+ + + +
+ AG +
+
+ + ANTIGRAVITY + + + GUITARS INDIA + +
+ +
+ + {/* Main Navigation Links */} + + + {/* Actions: Search, Wishlist, Compare, Cart, Account */} +
+ + {/* Search Input */} +
+
+ + + + + {/* Suggestions Dropdown */} + {isSearchOpen && suggestions.length > 0 && ( +
+ {suggestions.map((p) => ( + setIsSearchOpen(false)} + className="flex items-center gap-3 p-3 hover:bg-slate-50 transition-colors border-b border-slate-100 last:border-0" + > + {p.name} +
+
{p.name}
+
₹{(p.salePrice || p.price).toLocaleString('en-IN')}
+
+ + ))} +
+ )} +
+ + {/* Compare Icon */} + + + {compareItems.length > 0 && ( + + {compareItems.length} + + )} + + + {/* Wishlist Icon */} + + + {wishlistProducts.length > 0 && ( + + {wishlistProducts.length} + + )} + + + {/* Cart Icon */} + + + {totalCartCount > 0 && ( + + {totalCartCount} + + )} + + + {/* Fixed User Profile Dropdown (Requirement 18) */} + {userInfo ? ( +
+ + + {profileDropdownOpen && ( +
+
+
{userInfo.name}
+
{userInfo.email}
+
+ {userInfo.role === 'Admin' && ( + setProfileDropdownOpen(false)} + className="flex items-center gap-2 px-4 py-2 text-xs font-bold text-amber-600 hover:bg-slate-50" + > + Admin Dashboard + + )} + setProfileDropdownOpen(false)} + className="flex items-center gap-2 px-4 py-2 text-xs font-medium text-slate-700 hover:bg-slate-50" + > + My Account & Orders + + +
+ )} +
+ ) : ( + + Sign In + + )} + +
+ +
+
+
+ ); +}; + +export default Navbar; diff --git a/client/src/components/ProductCard.jsx b/client/src/components/ProductCard.jsx new file mode 100644 index 0000000..40c633b --- /dev/null +++ b/client/src/components/ProductCard.jsx @@ -0,0 +1,175 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import { useDispatch, useSelector } from 'react-redux'; +import { Heart, Eye, Sliders, ShoppingBag, Star } from 'lucide-react'; +import { addToCartAsync } from '../store/slices/cartSlice'; +import { toggleWishlistAsync } from '../store/slices/wishlistSlice'; +import { addToCompare, removeFromCompare } from '../store/slices/compareSlice'; +import { openQuickView, showToast } from '../store/slices/uiSlice'; +import { formatINR } from '../utils/formatCurrency'; + +const ProductCard = ({ product }) => { + const dispatch = useDispatch(); + const { products: wishlistProducts } = useSelector((state) => state.wishlist); + const { items: compareItems } = useSelector((state) => state.compare); + const { userInfo } = useSelector((state) => state.auth); + + const isWishlisted = wishlistProducts.some((p) => (p._id || p) === product._id); + const isCompared = compareItems.some((p) => p._id === product._id); + + const handleWishlist = (e) => { + e.preventDefault(); + if (!userInfo) { + dispatch(showToast({ message: 'Please login to save to wishlist', type: 'error' })); + return; + } + dispatch(toggleWishlistAsync(product._id)); + dispatch(showToast({ message: isWishlisted ? 'Removed from wishlist' : 'Added to wishlist', type: 'success' })); + }; + + const handleCompare = (e) => { + e.preventDefault(); + if (isCompared) { + dispatch(removeFromCompare(product._id)); + dispatch(showToast({ message: 'Removed from compare list', type: 'info' })); + } else { + if (compareItems.length >= 4) { + dispatch(showToast({ message: 'Max 4 items allowed for comparison', type: 'error' })); + return; + } + dispatch(addToCompare(product)); + dispatch(showToast({ message: 'Added to compare list', type: 'success' })); + } + }; + + const handleAddToCart = (e) => { + e.preventDefault(); + if (product.stock <= 0) return; + dispatch(addToCartAsync({ productId: product._id, quantity: 1 })); + dispatch(showToast({ message: `${product.name} added to cart!`, type: 'success' })); + }; + + const displayPrice = product.salePrice > 0 ? product.salePrice : product.price; + const hasDiscount = product.salePrice > 0 && product.salePrice < product.price; + const discountPercent = hasDiscount + ? Math.round(((product.price - product.salePrice) / product.price) * 100) + : 0; + + return ( +
+ {/* Product Image & Hover Overlay */} +
+ + {product.name} + {product.images?.[1] && ( + + )} + + + {/* Badges (Requirement 10) */} +
+ {hasDiscount && ( + + -{discountPercent}% OFF + + )} + {product.isNewArrival && ( + + NEW + + )} + {product.isBestSeller && ( + + BEST SELLER + + )} +
+ + {/* Quick Action Floating Buttons */} +
+ + + +
+
+ + {/* Content */} +
+
+
+ {product.brand?.name || 'Custom Shop'} +
+ + {product.rating || '4.9'} +
+
+ + +

+ {product.name} +

+ +
+ +
+ {/* Requirement 19: ₹ INR Currency Format & Requirement 16: Hidden SKU */} +
+ {formatINR(displayPrice)} + {hasDiscount && ( + {formatINR(product.price)} + )} +
+ + +
+
+
+ ); +}; + +export default ProductCard; diff --git a/client/src/components/ProductQuickView.jsx b/client/src/components/ProductQuickView.jsx new file mode 100644 index 0000000..5f8f859 --- /dev/null +++ b/client/src/components/ProductQuickView.jsx @@ -0,0 +1,110 @@ +import React, { useState } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import { X, Star, ShoppingBag, Check } from 'lucide-react'; +import { closeQuickView, showToast } from '../store/slices/uiSlice'; +import { addToCartAsync } from '../store/slices/cartSlice'; +import { formatINR } from '../utils/formatCurrency'; + +const ProductQuickView = () => { + const dispatch = useDispatch(); + const { quickViewProduct } = useSelector((state) => state.ui); + const [selectedImage, setSelectedImage] = useState(0); + + if (!quickViewProduct) return null; + + const handleAddToCart = () => { + dispatch(addToCartAsync({ productId: quickViewProduct._id, quantity: 1 })); + dispatch(showToast({ message: `${quickViewProduct.name} added to cart`, type: 'success' })); + dispatch(closeQuickView()); + }; + + return ( +
+
dispatch(closeQuickView())} /> +
+ + + {/* Gallery */} +
+
+ {quickViewProduct.name} +
+ {quickViewProduct.images?.length > 1 && ( +
+ {quickViewProduct.images.map((img, idx) => ( + + ))} +
+ )} +
+ + {/* Info */} +
+
+
+ {quickViewProduct.brand?.name} +
+

{quickViewProduct.name}

+
+
+ {[...Array(5)].map((_, i) => ( + + ))} +
+ ({quickViewProduct.numReviews} Reviews) +
+ +
+ {formatINR(quickViewProduct.salePrice || quickViewProduct.price)} +
+ +

+ {quickViewProduct.description} +

+ + {/* Quick Specs */} + {quickViewProduct.specs && ( +
+
Body Wood: {quickViewProduct.specs.bodyMaterial}
+
Neck: {quickViewProduct.specs.neckMaterial}
+
Pickups: {quickViewProduct.specs.pickupConfiguration}
+
+ )} +
+ +
+ +
+ Ready to dispatch in 24 Hours +
+
+
+ +
+
+ ); +}; + +export default ProductQuickView; diff --git a/client/src/components/ScrollToTop.jsx b/client/src/components/ScrollToTop.jsx new file mode 100644 index 0000000..948c1a6 --- /dev/null +++ b/client/src/components/ScrollToTop.jsx @@ -0,0 +1,15 @@ +import { useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; + +const ScrollToTop = () => { + const { pathname } = useLocation(); + + useEffect(() => { + window.scrollTo(0, 0); + }, [pathname]); + + return null; +}; + +export default ScrollToTop; + diff --git a/client/src/components/SkeletonLoaders.jsx b/client/src/components/SkeletonLoaders.jsx new file mode 100644 index 0000000..fa55ea8 --- /dev/null +++ b/client/src/components/SkeletonLoaders.jsx @@ -0,0 +1,21 @@ +import React from 'react'; + +export const ProductSkeleton = () => ( +
+
+
+
+
+
+
+
+
+); + +export const ProductGridSkeleton = ({ count = 4 }) => ( +
+ {[...Array(count)].map((_, i) => ( + + ))} +
+); diff --git a/client/src/components/ToastNotification.jsx b/client/src/components/ToastNotification.jsx new file mode 100644 index 0000000..1af9aeb --- /dev/null +++ b/client/src/components/ToastNotification.jsx @@ -0,0 +1,38 @@ +import React, { useEffect } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import { CheckCircle2, AlertCircle, Info, X } from 'lucide-react'; +import { hideToast } from '../store/slices/uiSlice'; + +const ToastNotification = () => { + const dispatch = useDispatch(); + const { toast } = useSelector((state) => state.ui); + + useEffect(() => { + if (toast) { + const timer = setTimeout(() => { + dispatch(hideToast()); + }, 4000); + return () => clearTimeout(timer); + } + }, [toast, dispatch]); + + if (!toast) return null; + + const icons = { + success: , + error: , + info: + }; + + return ( +
+ {icons[toast.type || 'info']} + {toast.message} + +
+ ); +}; + +export default ToastNotification; diff --git a/client/src/index.css b/client/src/index.css new file mode 100644 index 0000000..8a746a5 --- /dev/null +++ b/client/src/index.css @@ -0,0 +1,25 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + body { + @apply bg-slate-50 text-slate-900 overflow-x-hidden antialiased; + } +} + +/* Light Mode Custom Scrollbar */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} +::-webkit-scrollbar-track { + background: #f1f5f9; +} +::-webkit-scrollbar-thumb { + background: #cbd5e1; + border-radius: 4px; +} +::-webkit-scrollbar-thumb:hover { + background: #d97706; +} diff --git a/client/src/main.jsx b/client/src/main.jsx new file mode 100644 index 0000000..d44e555 --- /dev/null +++ b/client/src/main.jsx @@ -0,0 +1,23 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; +import { Provider } from 'react-redux'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { store } from './store'; +import App from './App'; +import './index.css'; + +const queryClient = new QueryClient(); + +ReactDOM.createRoot(document.getElementById('root')).render( + + + + + + + + + +); + diff --git a/client/src/pages/admin/AdminBanners.jsx b/client/src/pages/admin/AdminBanners.jsx new file mode 100644 index 0000000..e01cfb3 --- /dev/null +++ b/client/src/pages/admin/AdminBanners.jsx @@ -0,0 +1,76 @@ +import React, { useState, useEffect } from 'react'; +import { Image, Trash2 } from 'lucide-react'; +import api from '../../api/axios'; + +const AdminBanners = () => { + const [banners, setBanners] = useState([]); + const [title, setTitle] = useState(''); + const [subtitle, setSubtitle] = useState(''); + const [desktopImage, setDesktopImage] = useState('https://images.unsplash.com/photo-1511671782779-c97d3d27a1d4?w=1600'); + + const fetchBanners = () => api.get('/banners').then(res => setBanners(res.data.data)); + + useEffect(() => { fetchBanners(); }, []); + + const handleAdd = async (e) => { + e.preventDefault(); + await api.post('/banners', { title, subtitle, desktopImage, buttonText: 'Shop Guitars', buttonLink: '/shop' }); + setTitle(''); setSubtitle(''); + fetchBanners(); + }; + + const handleDelete = async (id) => { + await api.delete(`/banners/${id}`); + fetchBanners(); + }; + + return ( +
+

Hero Slider Banners

+ +
+

Add Hero Banner

+ setTitle(e.target.value)} + className="w-full bg-slate-50 border border-slate-300 rounded-xl p-2.5 text-slate-900 focus:outline-none focus:border-amber-500" + /> + setSubtitle(e.target.value)} + className="w-full bg-slate-50 border border-slate-300 rounded-xl p-2.5 text-slate-900 focus:outline-none focus:border-amber-500" + /> + setDesktopImage(e.target.value)} + className="w-full bg-slate-50 border border-slate-300 rounded-xl p-2.5 text-slate-900 focus:outline-none focus:border-amber-500" + /> + +
+ +
+ {banners.map((b) => ( +
+ +

{b.title}

+

{b.subtitle}

+ +
+ ))} +
+
+ ); +}; + +export default AdminBanners; diff --git a/client/src/pages/admin/AdminBrands.jsx b/client/src/pages/admin/AdminBrands.jsx new file mode 100644 index 0000000..c80385b --- /dev/null +++ b/client/src/pages/admin/AdminBrands.jsx @@ -0,0 +1,58 @@ +import React, { useState, useEffect } from 'react'; +import { Plus, Trash2 } from 'lucide-react'; +import api from '../../api/axios'; + +const AdminBrands = () => { + const [brands, setBrands] = useState([]); + const [name, setName] = useState(''); + + const fetchBrands = () => api.get('/brands').then(res => setBrands(res.data.data)); + + useEffect(() => { fetchBrands(); }, []); + + const handleAdd = async (e) => { + e.preventDefault(); + await api.post('/brands', { name, slug: name.toLowerCase().replace(/\s+/g, '-') }); + setName(''); + fetchBrands(); + }; + + const handleDelete = async (id) => { + await api.delete(`/brands/${id}`); + fetchBrands(); + }; + + return ( +
+

Guitar Brands Management

+ +
+

Add New Manufacturer

+ setName(e.target.value)} + className="w-full bg-slate-50 border border-slate-300 rounded-xl p-2.5 text-slate-900 focus:outline-none focus:border-amber-500" + /> + +
+ +
+ {brands.map((b) => ( +
+ {b.name} + +
+ ))} +
+
+ ); +}; + +export default AdminBrands; diff --git a/client/src/pages/admin/AdminCategories.jsx b/client/src/pages/admin/AdminCategories.jsx new file mode 100644 index 0000000..d595e0c --- /dev/null +++ b/client/src/pages/admin/AdminCategories.jsx @@ -0,0 +1,143 @@ +import React, { useState, useEffect } from 'react'; +import { Plus, Trash2 } from 'lucide-react'; +import api from '../../api/axios'; + +const AdminCategories = () => { + const [categories, setCategories] = useState([]); + const [name, setName] = useState(''); + const [description, setDescription] = useState(''); + const [image, setImage] = useState(''); + const [homeImage, setHomeImage] = useState(''); + const [displayOrder, setDisplayOrder] = useState(1); + const [isFeaturedHome, setIsFeaturedHome] = useState(true); + + const fetchCats = () => api.get('/categories').then(res => setCategories(res.data.data)); + + useEffect(() => { fetchCats(); }, []); + + const handleAdd = async (e) => { + e.preventDefault(); + await api.post('/categories', { + name, + slug: name.toLowerCase().replace(/\s+/g, '-'), + description, + image, + homeImage, + displayOrder: Number(displayOrder), + isFeaturedHome + }); + setName(''); setDescription(''); setImage(''); setHomeImage(''); + fetchCats(); + }; + + const handleDelete = async (id) => { + if (window.confirm('Delete category?')) { + await api.delete(`/categories/${id}`); + fetchCats(); + } + }; + + return ( +
+

Category Management (Homepage 4 Categories)

+ +
+

Add New Category

+ +
+
+ + setName(e.target.value)} + className="w-full bg-slate-50 border border-slate-200 rounded-xl p-2.5 text-slate-900" + /> +
+
+ + setDisplayOrder(e.target.value)} + className="w-full bg-slate-50 border border-slate-200 rounded-xl p-2.5 text-slate-900" + /> +
+
+ +
+ + setDescription(e.target.value)} + className="w-full bg-slate-50 border border-slate-200 rounded-xl p-2.5 text-slate-900" + /> +
+ +
+
+ + setImage(e.target.value)} + className="w-full bg-slate-50 border border-slate-200 rounded-xl p-2.5 text-slate-900" + /> +
+
+ + setHomeImage(e.target.value)} + className="w-full bg-slate-50 border border-slate-200 rounded-xl p-2.5 text-slate-900" + /> +
+
+ + + + +
+ +
+ {categories.map((c) => ( +
+ +
+
+ {c.name} + Order: {c.displayOrder} +
+
{c.description}
+
+
+ {c.isFeaturedHome ? 'Featured on Home' : 'Standard'} + +
+
+ ))} +
+
+ ); +}; + +export default AdminCategories; diff --git a/client/src/pages/admin/AdminCoupons.jsx b/client/src/pages/admin/AdminCoupons.jsx new file mode 100644 index 0000000..f3b3196 --- /dev/null +++ b/client/src/pages/admin/AdminCoupons.jsx @@ -0,0 +1,75 @@ +import React, { useState, useEffect } from 'react'; +import { Tag, Trash2 } from 'lucide-react'; +import api from '../../api/axios'; + +const AdminCoupons = () => { + const [coupons, setCoupons] = useState([]); + const [code, setCode] = useState(''); + const [discountValue, setDiscountValue] = useState(10); + + const fetchCoupons = () => api.get('/coupons').then(res => setCoupons(res.data.data)); + + useEffect(() => { fetchCoupons(); }, []); + + const handleAdd = async (e) => { + e.preventDefault(); + await api.post('/coupons', { + code: code.toUpperCase(), + discountType: 'percentage', + discountValue: Number(discountValue), + endDate: new Date('2028-12-31') + }); + setCode(''); + fetchCoupons(); + }; + + const handleDelete = async (id) => { + await api.delete(`/coupons/${id}`); + fetchCoupons(); + }; + + return ( +
+

Coupons & Promo Codes

+ +
+

Create Promo Code

+ setCode(e.target.value)} + className="w-full bg-slate-50 border border-slate-300 rounded-xl p-2.5 text-slate-900 uppercase font-mono focus:outline-none focus:border-amber-500" + /> + setDiscountValue(e.target.value)} + className="w-full bg-slate-50 border border-slate-300 rounded-xl p-2.5 text-slate-900 focus:outline-none focus:border-amber-500" + /> + +
+ +
+ {coupons.map((c) => ( +
+
+
{c.code}
+
{c.discountValue}% OFF Discount
+
+ +
+ ))} +
+
+ ); +}; + +export default AdminCoupons; diff --git a/client/src/pages/admin/AdminDashboard.jsx b/client/src/pages/admin/AdminDashboard.jsx new file mode 100644 index 0000000..8f5d445 --- /dev/null +++ b/client/src/pages/admin/AdminDashboard.jsx @@ -0,0 +1,157 @@ +import React, { useEffect, useState } from 'react'; +import { Link } from 'react-router-dom'; +import { IndianRupee, ShoppingBag, Users, AlertTriangle, ArrowRight } from 'lucide-react'; +import { formatINR } from '../../utils/formatCurrency'; +import api from '../../api/axios'; + +const AdminDashboard = () => { + const [stats, setStats] = useState({ + totalSales: 0, + totalOrders: 0, + totalProducts: 0, + lowStockCount: 0, + recentOrders: [], + lowStockItems: [] + }); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchDashboardStats = async () => { + try { + setLoading(true); + const [ordersRes, overviewRes] = await Promise.all([ + api.get('/orders/admin/all'), + api.get('/inventory/overview') + ]); + + const allOrders = ordersRes.data.data || []; + const overview = overviewRes.data.data || {}; + + const totalRev = allOrders.reduce((acc, ord) => acc + (ord.totalAmount || 0), 0); + + setStats({ + totalSales: totalRev, + totalOrders: allOrders.length, + totalProducts: overview.totalProducts || 0, + lowStockCount: overview.lowStockCount || 0, + recentOrders: allOrders.slice(0, 5), + lowStockItems: overview.lowStock || [] + }); + } catch (err) { + console.error('Failed to fetch admin stats', err); + } finally { + setLoading(false); + } + }; + + fetchDashboardStats(); + }, []); + + if (loading) return
Loading Store Dashboard...
; + + return ( +
+
+

Executive Store Overview (India)

+

Live sales metrics, inventory stock status, and fulfillment updates.

+
+ + {/* Metric Cards */} +
+
+
+ Total Sales Revenue + +
+
{formatINR(stats.totalSales)}
+
+ +
+
+ Total Orders Placed + +
+
{stats.totalOrders}
+
+ +
+
+ Catalog Guitars + +
+
{stats.totalProducts}
+
+ +
+
+ Low Stock Alerts + +
+
{stats.lowStockCount}
+
+
+ +
+ + {/* Recent Orders Table */} +
+
+

Recent Store Orders

+ + View All + +
+ +
+ {stats.recentOrders.map((ord) => ( +
+
+
{ord.orderNumber}
+
{ord.user?.name || 'Customer'}
+
+
+
{formatINR(ord.totalAmount)}
+ {ord.orderStatus} +
+
+ ))} +
+
+ + {/* Low Stock Alerts */} +
+
+

Low Stock Inventory Alerts

+ + Manage Stock + +
+ +
+ {stats.lowStockItems.length === 0 ? ( +
All guitar stock levels are optimal.
+ ) : ( + stats.lowStockItems.map((item) => ( +
+
+ +
+
{item.name}
+
SKU: {item.sku}
+
+
+
+ {item.stock} Units +
+
+ )) + )} +
+
+ +
+
+ ); +}; + +export default AdminDashboard; diff --git a/client/src/pages/admin/AdminEmailTemplates.jsx b/client/src/pages/admin/AdminEmailTemplates.jsx new file mode 100644 index 0000000..f2c53c6 --- /dev/null +++ b/client/src/pages/admin/AdminEmailTemplates.jsx @@ -0,0 +1,35 @@ +import React, { useState, useEffect } from 'react'; +import { Mail } from 'lucide-react'; +import api from '../../api/axios'; + +const AdminEmailTemplates = () => { + const [templates, setTemplates] = useState([]); + + useEffect(() => { + api.get('/email-templates').then(res => setTemplates(res.data.data)); + }, []); + + return ( +
+
+

Transactional Email Templates

+

Preview transactional order confirmation and invoice email formats.

+
+ +
+ {templates.map((tpl) => ( +
+

{tpl.name}

+
Subject: {tpl.subject}
+
Placeholders: {tpl.placeholders?.join(', ')}
+
+ {tpl.bodyHtml} +
+
+ ))} +
+
+ ); +}; + +export default AdminEmailTemplates; diff --git a/client/src/pages/admin/AdminInventory.jsx b/client/src/pages/admin/AdminInventory.jsx new file mode 100644 index 0000000..1e7ece7 --- /dev/null +++ b/client/src/pages/admin/AdminInventory.jsx @@ -0,0 +1,161 @@ +import React, { useState, useEffect } from 'react'; +import { Layers, RotateCcw, AlertTriangle, CheckCircle2 } from 'lucide-react'; +import { useDispatch } from 'react-redux'; +import { showToast } from '../../store/slices/uiSlice'; +import api from '../../api/axios'; + +const AdminInventory = () => { + const dispatch = useDispatch(); + + const [overview, setOverview] = useState({ products: [], lowStock: [], outOfStock: [] }); + const [history, setHistory] = useState([]); + const [selectedProduct, setSelectedProduct] = useState(''); + const [newStock, setNewStock] = useState(10); + const [changeType, setChangeType] = useState('Restock'); + const [reason, setReason] = useState(''); + const [submitting, setSubmitting] = useState(false); + + const loadData = async () => { + try { + const [ovRes, histRes] = await Promise.all([ + api.get('/inventory/overview'), + api.get('/inventory/history') + ]); + setOverview(ovRes.data.data); + setHistory(histRes.data.data); + } catch (err) { + console.error(err); + } + }; + + useEffect(() => { + loadData(); + }, []); + + const handleAdjustStock = async (e) => { + e.preventDefault(); + if (!selectedProduct) return; + try { + setSubmitting(true); + await api.post('/inventory/adjust', { + productId: selectedProduct, + newStock: Number(newStock), + changeType, + reason + }); + dispatch(showToast({ message: 'Stock level updated and audit logged!', type: 'success' })); + setReason(''); + loadData(); + } catch (err) { + dispatch(showToast({ message: 'Failed to update stock', type: 'error' })); + } finally { + setSubmitting(false); + } + }; + + return ( +
+
+

Stock Audit & Inventory History

+

Track all stock changes, perform manual restocks, and inspect change reasons.

+
+ + {/* Stock Adjustment Form */} +
+

Manual Stock Level Adjustment

+ +
+
+ + +
+
+ + setNewStock(e.target.value)} + className="w-full bg-slate-50 border border-slate-300 rounded-xl p-2.5 text-slate-900 focus:outline-none focus:border-amber-500" + /> +
+
+ + +
+
+ +
+ + setReason(e.target.value)} + className="w-full bg-slate-50 border border-slate-300 rounded-xl p-2.5 text-slate-900 focus:outline-none focus:border-amber-500" + /> +
+ + +
+ + {/* History Audit Log */} +
+

Inventory Change History Audit Log

+
+ + + + + + + + + + + + + {history.map((log) => ( + + + + + + + + + ))} + +
ProductChange TypeQty ChangedPrev / NewReasonDate
{log.product?.name || log.sku}{log.changeType}{log.quantityChanged > 0 ? `+${log.quantityChanged}` : log.quantityChanged}{log.previousStock} → {log.newStock}{log.reason}{new Date(log.createdAt).toLocaleString()}
+
+
+
+ ); +}; + +export default AdminInventory; diff --git a/client/src/pages/admin/AdminLayout.jsx b/client/src/pages/admin/AdminLayout.jsx new file mode 100644 index 0000000..a89a78d --- /dev/null +++ b/client/src/pages/admin/AdminLayout.jsx @@ -0,0 +1,129 @@ +import React, { useState } from 'react'; +import { Link, Outlet, useLocation, useNavigate } from 'react-router-dom'; +import { useDispatch, useSelector } from 'react-redux'; +import { LayoutDashboard, ShoppingBag, Layers, Tag, Image, Settings, MessageSquare, Mail, Shield, ExternalLink, Menu, X, LogOut } from 'lucide-react'; +import { logout } from '../../store/slices/authSlice'; +import { showToast } from '../../store/slices/uiSlice'; + +const AdminLayout = () => { + const location = useLocation(); + const navigate = useNavigate(); + const dispatch = useDispatch(); + const { userInfo } = useSelector((state) => state.auth); + + const [sidebarOpen, setSidebarOpen] = useState(false); + + const handleAdminLogout = () => { + dispatch(logout()); + dispatch(showToast({ message: 'Admin logged out', type: 'info' })); + navigate('/admin/login'); + }; + + const navLinks = [ + { name: 'Dashboard', path: '/admin', icon: LayoutDashboard }, + { name: 'Products Catalog', path: '/admin/products', icon: ShoppingBag }, + { name: 'Inventory & Audit', path: '/admin/inventory', icon: Layers }, + { name: 'Orders Fulfillment', path: '/admin/orders', icon: Tag }, + { name: 'Categories', path: '/admin/categories', icon: Layers }, + { name: 'Guitar Brands', path: '/admin/brands', icon: Shield }, + { name: 'Coupons & Promos', path: '/admin/coupons', icon: Tag }, + { name: 'Hero Banners', path: '/admin/banners', icon: Image }, + { name: 'Contact Inquiries', path: '/admin/messages', icon: MessageSquare }, + { name: 'Email Templates', path: '/admin/email-templates', icon: Mail }, + { name: 'Website Settings', path: '/admin/settings', icon: Settings } + ]; + + return ( +
+ + {/* Sidebar (Light Theme) */} + + + {/* Main Content Area */} +
+ + {/* Topbar */} +
+ +
Antigravity Store Administration (India)
+
+ {userInfo?.name || 'Admin'} + +
+
+ +
+ +
+
+ +
+ ); +}; + +export default AdminLayout; diff --git a/client/src/pages/admin/AdminLoginPage.jsx b/client/src/pages/admin/AdminLoginPage.jsx new file mode 100644 index 0000000..30f8db2 --- /dev/null +++ b/client/src/pages/admin/AdminLoginPage.jsx @@ -0,0 +1,88 @@ +import React, { useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useNavigate } from 'react-router-dom'; +import { Shield, Lock } from 'lucide-react'; +import { login } from '../../store/slices/authSlice'; +import { showToast } from '../../store/slices/uiSlice'; + +const AdminLoginPage = () => { + const dispatch = useDispatch(); + const navigate = useNavigate(); + const { loading } = useSelector((state) => state.auth); + + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + + const handleSubmit = (e) => { + e.preventDefault(); + dispatch(login({ email, password })) + .unwrap() + .then((user) => { + if (user.role === 'Admin') { + dispatch(showToast({ message: 'Welcome to Admin Portal', type: 'success' })); + navigate('/admin'); + } else { + dispatch(showToast({ message: 'Access Denied: Admin privileges required', type: 'error' })); + } + }) + .catch((err) => dispatch(showToast({ message: err || 'Invalid admin credentials', type: 'error' }))); + }; + + return ( +
+
+
+
+ +
+

Admin Portal Authentication

+

Antigravity Guitars Store Administration

+
+ +
+
+ + setEmail(e.target.value)} + className="w-full bg-slate-50 border border-slate-200 rounded-xl p-3 text-slate-900 focus:border-amber-500" + /> +
+ +
+ + setPassword(e.target.value)} + className="w-full bg-slate-50 border border-slate-200 rounded-xl p-3 text-slate-900 focus:border-amber-500" + /> +
+ + +
+ +
+
Seeded Admin Quick Credentials:
+
Email: admin@guitars.com
+
Password: Admin@123
+
+
+
+ ); +}; + +export default AdminLoginPage; + diff --git a/client/src/pages/admin/AdminMessages.jsx b/client/src/pages/admin/AdminMessages.jsx new file mode 100644 index 0000000..3bca65b --- /dev/null +++ b/client/src/pages/admin/AdminMessages.jsx @@ -0,0 +1,51 @@ +import React, { useState, useEffect } from 'react'; +import { MessageSquare, CheckCircle } from 'lucide-react'; +import api from '../../api/axios'; + +const AdminMessages = () => { + const [messages, setMessages] = useState([]); + + const fetchMsgs = () => api.get('/contact').then(res => setMessages(res.data.data)); + + useEffect(() => { fetchMsgs(); }, []); + + const markResolved = async (id) => { + await api.put(`/contact/${id}/status`, { status: 'Resolved' }); + fetchMsgs(); + }; + + return ( +
+
+

Customer Contact Submissions

+

Review and resolve customer inquiries sent through the store contact form.

+
+ +
+ {messages.length === 0 ? ( +
+ No contact submissions found. +
+ ) : ( + messages.map((m) => ( +
+
+
{m.name} ({m.email})
+
{m.subject}
+

{m.message}

+
+ +
+ )) + )} +
+
+ ); +}; + +export default AdminMessages; diff --git a/client/src/pages/admin/AdminOrders.jsx b/client/src/pages/admin/AdminOrders.jsx new file mode 100644 index 0000000..9dcb101 --- /dev/null +++ b/client/src/pages/admin/AdminOrders.jsx @@ -0,0 +1,165 @@ +import React, { useState, useEffect } from 'react'; +import { Edit3, CheckCircle2, Truck, Package } from 'lucide-react'; +import { useDispatch } from 'react-redux'; +import { showToast } from '../../store/slices/uiSlice'; +import api from '../../api/axios'; +import { formatINR } from '../../utils/formatCurrency'; + +const AdminOrders = () => { + const dispatch = useDispatch(); + + const [orders, setOrders] = useState([]); + const [loading, setLoading] = useState(true); + const [selectedOrder, setSelectedOrder] = useState(null); + const [newStatus, setNewStatus] = useState(''); + const [trackingNumber, setTrackingNumber] = useState(''); + const [courierProvider, setCourierProvider] = useState('BlueDart Express'); + + const fetchOrders = async () => { + try { + setLoading(true); + const res = await api.get('/orders/admin/all'); + setOrders(res.data.data || []); + } catch (err) { + console.error(err); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchOrders(); + }, []); + + const handleUpdateStatus = async (e) => { + e.preventDefault(); + if (!selectedOrder) return; + try { + await api.put(`/orders/${selectedOrder._id}/status`, { + orderStatus: newStatus, + trackingNumber, + courierProvider, + shippingProvider: courierProvider + }); + dispatch(showToast({ message: 'Order status & courier tracking updated!', type: 'success' })); + setSelectedOrder(null); + fetchOrders(); + } catch (err) { + dispatch(showToast({ message: 'Failed to update order', type: 'error' })); + } + }; + + return ( +
+
+

Customer Order Management

+

Update order fulfillment status, assign Indian courier tracking codes, and update tracking info.

+
+ +
+
+ + + + + + + + + + + + + {orders.map((ord) => ( + + + + + + + + + ))} + +
Order IDCustomerTotalPaymentFulfillment StatusAction
{ord.orderNumber}{ord.user?.name || ord.shippingAddress?.fullName || 'Guest'}{formatINR(ord.totalAmount)} + {ord.paymentMethod} + + + {ord.orderStatus} + + + +
+
+
+ + {/* Modal Status Form */} + {selectedOrder && ( +
+
setSelectedOrder(null)} /> +
+

Fulfill Order #{selectedOrder.orderNumber}

+ +
+ + +
+ +
+ + +
+ +
+ + setTrackingNumber(e.target.value)} + className="w-full bg-slate-50 border border-slate-300 rounded-xl p-2.5 text-slate-900 font-mono focus:outline-none focus:border-amber-500" + /> +
+ +
+ + +
+
+
+ )} +
+ ); +}; + +export default AdminOrders; diff --git a/client/src/pages/admin/AdminProducts.jsx b/client/src/pages/admin/AdminProducts.jsx new file mode 100644 index 0000000..ad7928e --- /dev/null +++ b/client/src/pages/admin/AdminProducts.jsx @@ -0,0 +1,165 @@ +import React, { useState, useEffect } from 'react'; +import { Plus, Search, Edit3, Trash2 } from 'lucide-react'; +import ProductFormModal from './ProductFormModal'; +import { showToast } from '../../store/slices/uiSlice'; +import { useDispatch } from 'react-redux'; +import { formatINR } from '../../utils/formatCurrency'; +import api from '../../api/axios'; + +const AdminProducts = () => { + const dispatch = useDispatch(); + const [products, setProducts] = useState([]); + const [loading, setLoading] = useState(true); + const [isModalOpen, setIsModalOpen] = useState(false); + const [editingProduct, setEditingProduct] = useState(null); + const [searchTerm, setSearchTerm] = useState(''); + + const fetchProducts = async () => { + try { + setLoading(true); + const res = await api.get('/products?limit=50'); + setProducts(res.data.data.products || []); + } catch (err) { + console.error(err); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchProducts(); + }, []); + + const handleSaveProduct = async (formData) => { + try { + if (editingProduct) { + await api.put(`/products/${editingProduct._id}`, formData); + dispatch(showToast({ message: 'Product updated successfully', type: 'success' })); + } else { + await api.post('/products', formData); + dispatch(showToast({ message: 'Product created successfully', type: 'success' })); + } + setIsModalOpen(false); + fetchProducts(); + } catch (err) { + dispatch(showToast({ message: err.response?.data?.message || 'Error saving product', type: 'error' })); + } + }; + + const handleDeleteProduct = async (id) => { + if (window.confirm('Are you sure you want to delete this guitar product?')) { + try { + await api.delete(`/products/${id}`); + dispatch(showToast({ message: 'Product deleted', type: 'info' })); + fetchProducts(); + } catch (err) { + dispatch(showToast({ message: 'Delete failed', type: 'error' })); + } + } + }; + + const filteredProducts = products.filter(p => p.name.toLowerCase().includes(searchTerm.toLowerCase())); + + return ( +
+
+
+

Guitar & Gear Catalog Management

+

Add, update, or edit guitar specifications, pricing, and inventory stock.

+
+ + +
+ + {/* Search Filter */} +
+ setSearchTerm(e.target.value)} + className="w-full bg-white border border-slate-200 rounded-xl py-2.5 pl-10 pr-4 text-xs text-slate-900 shadow-sm" + /> + +
+ + {/* Table */} +
+
+ + + + + + + + + + + + + {filteredProducts.map((p) => ( + + + + + + + + + ))} + +
InstrumentBrandCategoryRegular / Sale PriceStockActions
+
+ +
+
{p.name}
+
SKU: {p.sku}
+
+
+
{p.brand?.name || 'N/A'}{p.category?.name || 'N/A'} + {formatINR(p.salePrice || p.price)} + {p.salePrice > 0 && p.salePrice < p.price && ( + {formatINR(p.price)} + )} + + 3 ? 'bg-emerald-50 text-emerald-700 border border-emerald-200' : 'bg-rose-50 text-rose-700 border border-rose-200' + }`}> + {p.stock} units + + +
+ + +
+
+
+
+ + setIsModalOpen(false)} + onSave={handleSaveProduct} + /> +
+ ); +}; + +export default AdminProducts; diff --git a/client/src/pages/admin/AdminSettings.jsx b/client/src/pages/admin/AdminSettings.jsx new file mode 100644 index 0000000..d0fc4e7 --- /dev/null +++ b/client/src/pages/admin/AdminSettings.jsx @@ -0,0 +1,152 @@ +import React, { useState, useEffect } from 'react'; +import { Settings } from 'lucide-react'; +import { useDispatch } from 'react-redux'; +import { showToast } from '../../store/slices/uiSlice'; +import api from '../../api/axios'; + +const AdminSettings = () => { + const dispatch = useDispatch(); + + const [settings, setSettings] = useState({ + businessName: 'Antigravity Guitars India', + siteName: 'Antigravity Guitars', + tagline: 'Premium Electric, Acoustic & Custom Guitars in India', + contactEmail: 'support@antigravityguitars.in', + phone: '+91 98765 43210', + address: '#102 MG Road, Indiranagar, Bengaluru, Karnataka 560038, India', + gstin: '29ABCDE1234F1Z5', + currencySymbol: '₹', + codEnabled: true, + maxCodAmount: 50000, + freeShippingThreshold: 5000 + }); + + useEffect(() => { + api.get('/settings').then(res => setSettings(prev => ({ ...prev, ...res.data.data }))); + }, []); + + const handleSave = async (e) => { + e.preventDefault(); + await api.put('/settings', settings); + dispatch(showToast({ message: 'Store settings updated successfully!', type: 'success' })); + }; + + return ( +
+
+

Global Storefront & Indian Business Settings

+

Configure Indian GST, business address, currency, COD limits, and store contact info.

+
+ +
+
+
+ + setSettings({ ...settings, businessName: e.target.value })} + className="w-full bg-slate-50 border border-slate-300 rounded-xl p-2.5 text-slate-900 focus:outline-none focus:border-amber-500" + /> +
+
+ + setSettings({ ...settings, gstin: e.target.value })} + className="w-full bg-slate-50 border border-slate-300 rounded-xl p-2.5 text-slate-900 font-mono uppercase focus:outline-none focus:border-amber-500" + /> +
+
+ +
+ + setSettings({ ...settings, siteName: e.target.value })} + className="w-full bg-slate-50 border border-slate-300 rounded-xl p-2.5 text-slate-900 focus:outline-none focus:border-amber-500" + /> +
+ +
+ + setSettings({ ...settings, tagline: e.target.value })} + className="w-full bg-slate-50 border border-slate-300 rounded-xl p-2.5 text-slate-900 focus:outline-none focus:border-amber-500" + /> +
+ +
+
+ + setSettings({ ...settings, contactEmail: e.target.value })} + className="w-full bg-slate-50 border border-slate-300 rounded-xl p-2.5 text-slate-900 focus:outline-none focus:border-amber-500" + /> +
+
+ + setSettings({ ...settings, phone: e.target.value })} + className="w-full bg-slate-50 border border-slate-300 rounded-xl p-2.5 text-slate-900 focus:outline-none focus:border-amber-500" + /> +
+
+ +
+ + setSettings({ ...settings, address: e.target.value })} + className="w-full bg-slate-50 border border-slate-300 rounded-xl p-2.5 text-slate-900 focus:outline-none focus:border-amber-500" + /> +
+ +
+
+ + setSettings({ ...settings, currencySymbol: e.target.value })} + className="w-full bg-slate-50 border border-slate-300 rounded-xl p-2.5 text-slate-900 font-bold focus:outline-none focus:border-amber-500" + /> +
+
+ + setSettings({ ...settings, freeShippingThreshold: Number(e.target.value) })} + className="w-full bg-slate-50 border border-slate-300 rounded-xl p-2.5 text-slate-900 focus:outline-none focus:border-amber-500" + /> +
+
+ + setSettings({ ...settings, maxCodAmount: Number(e.target.value) })} + className="w-full bg-slate-50 border border-slate-300 rounded-xl p-2.5 text-slate-900 focus:outline-none focus:border-amber-500" + /> +
+
+ + +
+
+ ); +}; + +export default AdminSettings; diff --git a/client/src/pages/admin/ProductFormModal.jsx b/client/src/pages/admin/ProductFormModal.jsx new file mode 100644 index 0000000..70e9802 --- /dev/null +++ b/client/src/pages/admin/ProductFormModal.jsx @@ -0,0 +1,529 @@ +import React, { useState, useEffect } from 'react'; +import { X, Plus, Trash2, Upload, Check, Star } from 'lucide-react'; +import api from '../../api/axios'; + +const defaultSpecs = { + guitarType: 'Electric', + bodyShape: 'Stratocaster', + bodyMaterial: 'Alder', + topMaterial: '', + backMaterial: '', + sideMaterial: '', + neckMaterial: 'Maple', + fingerboardMaterial: 'Rosewood', + scaleLength: '25.5"', + numberOfStrings: 6, + frets: 22, + pickupConfiguration: 'SSS', + pickupType: 'Alnico Single-Coil', + bridgeType: '2-Point Tremolo', + tuningMachines: 'Standard Cast/Sealed', + handOrientation: 'Right-Handed', + color: '3-Color Sunburst', + finish: 'Gloss Polyurethane', + includesCase: false +}; + +const ProductFormModal = ({ product, isOpen, onClose, onSave }) => { + const [categories, setCategories] = useState([]); + const [brands, setBrands] = useState([]); + const [uploading, setUploading] = useState(false); + + // Requirement 13: Reset state cleanly when adding new product + const getInitialState = () => { + const randomSKU = `GTR-2026-${Math.floor(10000 + Math.random() * 90000)}`; + if (product) { + return { + ...product, + brand: product.brand?._id || product.brand || '', + category: product.category?._id || product.category || '', + price: product.price || 0, + salePrice: product.salePrice || 0, + discountType: product.discountType || 'percentage', + discountValue: product.discountValue || 0, + stock: product.stock !== undefined ? product.stock : 10, + images: product.images || [], + primaryImage: product.primaryImage || (product.images?.[0] || ''), + specs: { ...defaultSpecs, ...(product.specs || {}) }, + variants: product.variants || [], + isFeatured: Boolean(product.isFeatured), + isNewArrival: Boolean(product.isNewArrival), + isBestSeller: Boolean(product.isBestSeller) + }; + } + return { + name: '', + slug: '', + sku: randomSKU, // Requirement 15: Auto-generated SKU + brand: '', + category: '', + price: 0, + salePrice: 0, + discountType: 'percentage', + discountValue: 0, + stock: 10, + description: '', + images: [], + primaryImage: '', + videoUrl: '', + specs: { ...defaultSpecs }, + variants: [], + isFeatured: false, + isNewArrival: false, + isBestSeller: false + }; + }; + + const [formData, setFormData] = useState(getInitialState); + + useEffect(() => { + api.get('/categories').then(res => setCategories(res.data.data)).catch(() => {}); + api.get('/brands').then(res => setBrands(res.data.data)).catch(() => {}); + }, []); + + useEffect(() => { + setFormData(getInitialState()); + }, [product, isOpen]); + + if (!isOpen) return null; + + // Requirement 11: Auto calculate sale price from discount value & type + const handleDiscountChange = (type, val) => { + const regPrice = Number(formData.price) || 0; + let computedSalePrice = regPrice; + + if (type === 'percentage') { + computedSalePrice = regPrice - (regPrice * (Number(val) || 0)) / 100; + } else if (type === 'fixed') { + computedSalePrice = regPrice - (Number(val) || 0); + } + computedSalePrice = Math.max(0, computedSalePrice); + + setFormData({ + ...formData, + discountType: type, + discountValue: Number(val), + salePrice: Math.round(computedSalePrice) + }); + }; + + const handlePriceChange = (regPrice) => { + const p = Number(regPrice) || 0; + let computedSalePrice = p; + + if (formData.discountType === 'percentage') { + computedSalePrice = p - (p * (formData.discountValue || 0)) / 100; + } else { + computedSalePrice = p - (formData.discountValue || 0); + } + + setFormData({ + ...formData, + price: p, + salePrice: Math.max(0, Math.round(computedSalePrice)) + }); + }; + + // Requirement 4: Complete Image Upload Handling + const handleImageUpload = async (e) => { + const files = e.target.files; + if (!files || files.length === 0) return; + + const data = new FormData(); + for (let i = 0; i < files.length; i++) { + data.append('images', files[i]); + } + + try { + setUploading(true); + const res = await api.post('/upload/multiple', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }); + + const uploadedUrls = res.data.data.urls || []; + const updatedImages = [...formData.images, ...uploadedUrls]; + setFormData({ + ...formData, + images: updatedImages, + primaryImage: formData.primaryImage || updatedImages[0] + }); + } catch (err) { + alert(err.response?.data?.message || 'Failed to upload images'); + } finally { + setUploading(false); + } + }; + + const removeImage = (index) => { + const newImgs = [...formData.images]; + const removed = newImgs.splice(index, 1)[0]; + let newPrimary = formData.primaryImage; + if (newPrimary === removed) { + newPrimary = newImgs[0] || ''; + } + setFormData({ ...formData, images: newImgs, primaryImage: newPrimary }); + }; + + const handleSubmit = (e) => { + e.preventDefault(); + if (formData.price < 0 || formData.salePrice > formData.price) { + alert('Invalid price configuration. Sale price cannot exceed regular price.'); + return; + } + onSave(formData); + }; + + return ( +
+
+ + + +

+ {product ? 'Edit Guitar Specifications' : 'Add New Instrument To Catalog'} +

+ +
+ + {/* Main Info */} +
+
+ + setFormData({ ...formData, name: e.target.value, slug: e.target.value.toLowerCase().replace(/\s+/g, '-') })} + className="w-full bg-slate-50 border border-slate-200 rounded-xl px-4 py-2.5 text-slate-900" + /> +
+ {/* Requirement 15: Auto-generated SKU */} +
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ + setFormData({ ...formData, stock: Number(e.target.value) })} + className="w-full bg-slate-50 border border-slate-200 rounded-xl px-4 py-2.5 text-slate-900" + /> +
+
+ + setFormData({ ...formData, videoUrl: e.target.value })} + className="w-full bg-slate-50 border border-slate-200 rounded-xl px-3 py-2.5 text-slate-900" + /> +
+
+ + {/* Requirement 11: Pricing & Discount Section */} +
+
Pricing & Discount Configuration (₹ INR)
+
+
+ + handlePriceChange(e.target.value)} + className="w-full bg-white border border-slate-200 rounded-xl p-2.5 text-slate-900 font-bold" + /> +
+
+ + +
+
+ + handleDiscountChange(formData.discountType, e.target.value)} + className="w-full bg-white border border-slate-200 rounded-xl p-2.5 text-slate-900" + /> +
+
+ + +
+
+
+ + {/* Requirement 4: Product Image Upload Section */} +
+
+ + +
+ + {formData.images.length > 0 ? ( +
+ {formData.images.map((img, idx) => ( +
+ + + {formData.primaryImage === img && ( + Primary + )} +
+ ))} +
+ ) : ( +
+ No images uploaded yet. Upload images above. +
+ )} +
+ + {/* Requirement 10: Badges & Flags */} +
+ +
+ + + +
+
+ + {/* Requirement 9: Full Technical Specifications Fields */} +
+
All Guitar Technical Specifications
+ +
+
+ + setFormData({ ...formData, specs: { ...formData.specs, guitarType: e.target.value } })} + className="w-full bg-white border border-slate-200 rounded-lg p-2 text-slate-900" + /> +
+
+ + setFormData({ ...formData, specs: { ...formData.specs, bodyShape: e.target.value } })} + className="w-full bg-white border border-slate-200 rounded-lg p-2 text-slate-900" + /> +
+
+ + setFormData({ ...formData, specs: { ...formData.specs, bodyMaterial: e.target.value } })} + className="w-full bg-white border border-slate-200 rounded-lg p-2 text-slate-900" + /> +
+
+ + setFormData({ ...formData, specs: { ...formData.specs, topMaterial: e.target.value } })} + className="w-full bg-white border border-slate-200 rounded-lg p-2 text-slate-900" + /> +
+
+ + setFormData({ ...formData, specs: { ...formData.specs, neckMaterial: e.target.value } })} + className="w-full bg-white border border-slate-200 rounded-lg p-2 text-slate-900" + /> +
+
+ + setFormData({ ...formData, specs: { ...formData.specs, fingerboardMaterial: e.target.value } })} + className="w-full bg-white border border-slate-200 rounded-lg p-2 text-slate-900" + /> +
+
+ + setFormData({ ...formData, specs: { ...formData.specs, scaleLength: e.target.value } })} + className="w-full bg-white border border-slate-200 rounded-lg p-2 text-slate-900" + /> +
+
+ + setFormData({ ...formData, specs: { ...formData.specs, frets: Number(e.target.value) } })} + className="w-full bg-white border border-slate-200 rounded-lg p-2 text-slate-900" + /> +
+
+ + setFormData({ ...formData, specs: { ...formData.specs, pickupConfiguration: e.target.value } })} + className="w-full bg-white border border-slate-200 rounded-lg p-2 text-slate-900" + /> +
+
+ + setFormData({ ...formData, specs: { ...formData.specs, bridgeType: e.target.value } })} + className="w-full bg-white border border-slate-200 rounded-lg p-2 text-slate-900" + /> +
+
+ + +
+
+ + setFormData({ ...formData, specs: { ...formData.specs, finish: e.target.value } })} + className="w-full bg-white border border-slate-200 rounded-lg p-2 text-slate-900" + /> +
+
+
+ +
+ +