From 72ba3feb90297089b53a2f0a6f60858d6f4c6ae2 Mon Sep 17 00:00:00 2001 From: vickytechkey Date: Sat, 8 Aug 2026 13:16:17 +0530 Subject: [PATCH] feat: implement new branding and layout system with full test suite scaffolding --- guide.md | 86 +++ package-lock.json | 1198 ++++++++++++++++++++++++++++++++++++- package.json | 9 +- src/App.test.tsx | 133 +++++ src/App.tsx | 1450 +++++++++++++++++++++++++++++++++++++++++---- src/config.ts | 116 ++++ src/index.css | 1114 +++++++++++++++++++++++++++++++--- src/setupTests.ts | 1 + vitest.config.ts | 13 + 9 files changed, 3916 insertions(+), 204 deletions(-) create mode 100644 guide.md create mode 100644 src/App.test.tsx create mode 100644 src/config.ts create mode 100644 src/setupTests.ts create mode 100644 vitest.config.ts diff --git a/guide.md b/guide.md new file mode 100644 index 0000000..e73212d --- /dev/null +++ b/guide.md @@ -0,0 +1,86 @@ +# Supplier Portal - View & Navigation Guide + +This guide explains how to start the development server, navigate the screens, and view the onboarding flows and simulated dashboard preview. + +## 1. Running the Portal Locally + +To start the Vite development server, run the following command in the project root: + +```bash +npm run dev +``` + +By default, the server runs at **[http://localhost:5173/](http://localhost:5173/)** (or the port specified in your console). + +--- + +## 2. Page Routing & State Mapping + +The portal uses a client-side state router mapping component views to logical routes: + +| Route State | Component/View Description | Navigable Via | +| :--- | :--- | :--- | +| `home` | **Landing Page**: Brand value proposition, hero banner, primary action CTAs. | Logo click, "Platform" header link. | +| `about` | **About Us**: Organization mission details and team profiles. | "Success Stories" header link. | +| `contact` | **Contact Support**: "Get in Touch" inquiry form and help desk coordinates. | "Support" header link. | +| `login` | **Supplier Login**: Account login via password credentials or OTP mock. | "Login" header button. | +| `signup` | **Supplier Registration**: Fields for business email, password, and GSTIN. | "Get Started" header button. | +| `profile-completion` | **Profile Wizard (Step 2)**: Logo picker, bio, and interactive GSTIN validation. | Auto-redirect on Signup form submit. | +| `confirmation` | **Dashboard Preview (Step 3)**: Payout status notifications and mock graphs. | Auto-redirect on Profile Completion save. | +| `dashboard` | **Seller Dashboard & Tools**: Overview performance metrics, single/bulk product uploads, orders tracking log, returns approvals, wallet payouts, and settings configuration. | Click "Go to Dashboard" button in confirmation, or submit Login form. | + +--- + +## 3. Navigating the Phase 1 Onboarding Flow + +You can step through the interactive onboarding flow sequentially to experience the full walkthrough: + +### Step A: The Landing Page +- Open the application. You will see the **Global Artisans Hub** landing page with the primary theme (Deep Navy Blue headers/footers, Amber Gold accents, and Warm Cream backgrounds). +- Click **Platform**, **Success Stories** (About Us), or **Support** (Contact Us) in the header navigation to view the respective information layouts. + +### Step B: Account Registration +1. Click **Get Started** in the header. +2. The view will switch to the **Register** tab. +3. Fill in the required fields: + - **Business Email** + - **Mobile Number** + - **Create Password** + - **GSTIN / Tax ID** +4. Click **Register Business**. + +### Step C: Profile Completion (Step 2 of 3) +1. You will be taken to the **Complete Your Supplier Profile** wizard. +2. Enter a **Store Display Name**. +3. (Optional) Choose an image file to upload. It will immediately show an avatar logo preview. +4. Enter an **About Your Craft / Business** description. +5. Fill in the **Business Address for Pickup** fields (Street Address, City, State, Pincode). +6. Enter a GSTIN, and click **Verify GSTIN**. The button will show a loading spinner, then update to **Verified ✓** status. +7. Click **Save & Continue**. + +### Step D: Onboarding Confirmation (Step 3 of 3) +- You will arrive at the **Welcome to Global Artisans Hub!** confirmation view. +- Click **Go to Dashboard (Preview Only)** to go to the live seller portal dashboard page. +- Alternatively, you can directly access the dashboard page by going to **Login** -> enter credentials -> click **Secure Login**. + +### Step E: Navigating the Seller Dashboard & Tools +Once inside the active dashboard (`/dashboard`), use the left sidebar menu to navigate the tools: +1. **Overview & Analytics**: View Sales, net margins, stock, and returns. Toggle the date filters (Year, Week, Day, Custom Date) to see the metrics update dynamically. +2. **Manage Products**: + - View your active inventory listing. + - Fill out the **Upload Product** form on the right to add a product or click **Edit** on a row to modify it. + - Click the bulk upload zone to simulate importing products via CSV files. +3. **Orders & Transit**: Track active orders, status (Ready to Ship, Shipped, Delivered), carriers, and tracking coordinates. +4. **Returns Management**: View buyer return requests. Click **Approve** or **Reject** to update their logistics status in the warehouse transit log. +5. **Wallet & Payouts**: Enter an amount to process a withdrawal from your outstanding ready balance. +6. **Store Settings**: Update your store name, logo file, bio, and pickup coordinates. + +--- + +## 4. Run Automated Component Tests + +To run the Vitest unit tests verifying these navigation flows and tools, execute: + +```bash +npm run test +``` diff --git a/package-lock.json b/package-lock.json index 8b92991..d4809a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,15 +12,273 @@ "react-dom": "^19.2.8" }, "devDependencies": { + "@testing-library/dom": "^10.4.1", + "@testing-library/jest-dom": "^7.0.0", + "@testing-library/react": "^16.3.2", "@types/node": "^24.13.3", "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^6.0.4", + "jsdom": "^30.0.1", "oxlint": "^1.75.0", "typescript": "~6.0.2", - "vite": "^8.2.0" + "vite": "^8.2.0", + "vitest": "^4.1.10" } }, + "node_modules/@adobe/css-tools": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.5.0.tgz", + "integrity": "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@asamuzakjp/css-color": { + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-6.0.7.tgz", + "integrity": "sha512-vC/bk1Lz7Tn/EfU9/apOTBk80/8dyGyWMowPoV1tJ52muDGsDqt2HPT2klrFUiY60MQmQv9q8yIht15JnBgDGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^3.3.0", + "@csstools/css-color-parser": "^4.1.10", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.5.2" + }, + "engines": { + "node": "^22.13.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-8.3.2.tgz", + "integrity": "sha512-93Z1N+BQNXysodoicpOIyNh2drHfz/CTf9nnT0FEx72GJcIiwgydD7tGAr78j41LsYn3hlRn+LdGPuBLn1Bl8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.5.2" + }, + "engines": { + "node": "^22.13.0 || >=24.0.0" + } + }, + "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/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/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.0.tgz", + "integrity": "sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.3.0.tgz", + "integrity": "sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.10.tgz", + "integrity": "sha512-UZhQLIUyJaaMepqehrCODwCg2KW25vFvLWBmqYFaPclYvvxzj/sG8LBOhBFCp11i9uE7t1EyS+RAoV9tztPFyw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.1.0", + "@csstools/css-calc": "^3.3.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.7.tgz", + "integrity": "sha512-fQ+05118eQS1cofO3aJpB5efgpBZMvIzwr/sbC8kDLVA5XLG8q1kJV5yzrUAI1f7lvhPnm8fgIjzFB8/O/5Dig==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@exodus/bytes": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", + "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, "node_modules/@oxc-project/types": { "version": "0.143.0", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.143.0.tgz", @@ -599,6 +857,123 @@ "dev": true, "license": "MIT" }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-7.0.0.tgz", + "integrity": "sha512-HKAH9C6mBo5yBG6yRO5i43L2iisencAo5z+o5P/saHUoY+miC5ivXRxHBJcFyB5ypPNxHJdK3BoF/3O4DIptMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=22", + "npm": ">=6", + "yarn": ">=1" + }, + "peerDependencies": { + "@testing-library/dom": ">=10 <11" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "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/node": { "version": "24.13.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", @@ -655,6 +1030,210 @@ } } }, + "node_modules/@vitest/expect": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", + "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", + "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.10", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", + "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", + "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.10", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", + "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.10", + "@vitest/utils": "4.1.10", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", + "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", + "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.10", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "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/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -662,6 +1241,52 @@ "dev": true, "license": "MIT" }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -672,6 +1297,53 @@ "node": ">=8" } }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-module-lexer": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", + "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -705,6 +1377,84 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsdom": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.0.1.tgz", + "integrity": "sha512-52v7mUVUfNQVYYqE1lcdaymWL0njO7lTLUog6ZvW2U5KsbiLk/GnZlVJ+qx0xfNJZ6Gn+KSpPNE52vurbxZwrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^6.0.5", + "@asamuzakjp/dom-selector": "^8.3.0", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.7", + "@exodus/bytes": "^1.15.1", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.5.2", + "parse5": "^8.0.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.2", + "undici": "^8.9.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^17.1.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + }, + "peerDependencies": { + "canvas": "^3.2.3" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, "node_modules/lightningcss": { "version": "1.33.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", @@ -966,6 +1716,53 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/nanoid": { "version": "3.3.17", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.17.tgz", @@ -985,6 +1782,20 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/obug": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/oxlint": { "version": "1.77.0", "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.77.0.tgz", @@ -1034,6 +1845,26 @@ } } }, + "node_modules/parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -1083,6 +1914,31 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/react": { "version": "19.2.8", "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", @@ -1104,6 +1960,37 @@ "react": "^19.2.8" } }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT" + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/rolldown": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.3.tgz", @@ -1137,12 +2024,32 @@ "@rolldown/binding-win32-x64-msvc": "1.2.3" } }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -1153,6 +2060,57 @@ "node": ">=0.10.0" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { "version": "0.2.17", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", @@ -1170,6 +2128,62 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tinyrainbow": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", + "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.4.10", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.10.tgz", + "integrity": "sha512-GgouD1B+sWwvkaEq8vXC15DjQitxbvs12oIXELpconwm+Tg3zfcEv4jgzq3vtKverDXsg3VI8aRgNL2Nra0Iog==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.4.10" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.4.10", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.10.tgz", + "integrity": "sha512-KnQjp53ZekKgm/r3l+u8kJGGzYgrWdP8+Mql7a4vijh2WE0IrZWspQj/TpTxDho/YxO+AnOZnIjQcCD+q6iJsw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", + "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/typescript": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", @@ -1184,6 +2198,16 @@ "node": ">=14.17" } }, + "node_modules/undici": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.10.0.tgz", + "integrity": "sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.19.0" + } + }, "node_modules/undici-types": { "version": "7.18.2", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", @@ -1268,6 +2292,178 @@ "optional": true } } + }, + "node_modules/vitest": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", + "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.10", + "@vitest/mocker": "4.1.10", + "@vitest/pretty-format": "4.1.10", + "@vitest/runner": "4.1.10", + "@vitest/snapshot": "4.1.10", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.10", + "@vitest/browser-preview": "4.1.10", + "@vitest/browser-webdriverio": "4.1.10", + "@vitest/coverage-istanbul": "4.1.10", + "@vitest/coverage-v8": "4.1.10", + "@vitest/ui": "4.1.10", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-url": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-17.1.0.tgz", + "integrity": "sha512-3GeworPmc2ZfEEHP7lEbUfBX/L75wdEsi0rLNhXcXxnoN5jyq0SL5gCy06SGW2cyTIZdTvWIDQNQoza++vKeaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.15.1", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^22.14.0 || >=24.0.0" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" } } } diff --git a/package.json b/package.json index 380e4ae..881a7f2 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "dev": "vite", "build": "tsc -b && vite build", "lint": "oxlint", - "test": "echo 'No tests specified' && exit 0", + "test": "vitest run", "preview": "vite preview" }, "dependencies": { @@ -15,12 +15,17 @@ "react-dom": "^19.2.8" }, "devDependencies": { + "@testing-library/dom": "^10.4.1", + "@testing-library/jest-dom": "^7.0.0", + "@testing-library/react": "^16.3.2", "@types/node": "^24.13.3", "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^6.0.4", + "jsdom": "^30.0.1", "oxlint": "^1.75.0", "typescript": "~6.0.2", - "vite": "^8.2.0" + "vite": "^8.2.0", + "vitest": "^4.1.10" } } diff --git a/src/App.test.tsx b/src/App.test.tsx new file mode 100644 index 0000000..de6b1c0 --- /dev/null +++ b/src/App.test.tsx @@ -0,0 +1,133 @@ +import { render, screen, fireEvent } from '@testing-library/react' +import { describe, it, expect, vi, beforeAll } from 'vitest' +import App from './App' + +// Mock window.scrollTo since jsdom does not implement it +beforeAll(() => { + window.scrollTo = vi.fn() +}) + +describe('Supplier Portal Onboarding & Active Dashboard Tests', () => { + it('renders landing page with correct primary titles', () => { + render() + + expect(screen.getByText(/Sell Globally\./i)).toBeInTheDocument() + expect(screen.getByText(/Celebrate Craft\./i)).toBeInTheDocument() + + expect(screen.getByRole('heading', { name: /^Expand Your Reach$/i })).toBeInTheDocument() + }) + + it('navigates to Support page and handles contact form submission', () => { + render() + + const supportBtn = screen.getByText('Support') + fireEvent.click(supportBtn) + + const nameInput = screen.getByLabelText(/Full Name \*/i) + const emailInput = screen.getByLabelText(/Email Address \*/i) + const msgInput = screen.getByLabelText(/Message \*/i) + + fireEvent.change(nameInput, { target: { value: 'John Doe' } }) + fireEvent.change(emailInput, { target: { value: 'john@example.com' } }) + fireEvent.change(msgInput, { target: { value: 'Hello support!' } }) + + const alertMock = vi.spyOn(window, 'alert').mockImplementation(() => {}) + + const submitBtn = screen.getByText('Submit Inquiry') + fireEvent.click(submitBtn) + + expect(alertMock).toHaveBeenCalled() + alertMock.mockRestore() + }) + + it('signs up successfully and completes profile to active dashboard', async () => { + render() + + const getStartedBtn = screen.getByRole('button', { name: /^Get Started$/i }) + fireEvent.click(getStartedBtn) + + const emailInput = screen.getByLabelText(/Business Email \*/i) + const phoneInput = screen.getByLabelText(/Mobile Number \*/i) + const passInput = screen.getByLabelText(/Create Password \*/i) + const taxInput = screen.getByLabelText(/GSTIN \/ Tax ID \*/i) + + fireEvent.change(emailInput, { target: { value: 'seller@example.com' } }) + fireEvent.change(phoneInput, { target: { value: '9876543210' } }) + fireEvent.change(passInput, { target: { value: 'password123' } }) + fireEvent.change(taxInput, { target: { value: '29AAAAA1111A1Z1' } }) + + const registerBtn = screen.getByRole('button', { name: /Register Business/i }) + fireEvent.click(registerBtn) + + expect(screen.getByText('Step 2 of 3: Business Details')).toBeInTheDocument() + + const storeNameInput = screen.getByLabelText(/Store Display Name \*/i) + fireEvent.change(storeNameInput, { target: { value: 'Crafty Store' } }) + + const bioText = screen.getByLabelText(/About Your Craft \/ Business \*/i) + fireEvent.change(bioText, { target: { value: 'Handmade pottery.' } }) + + const streetInput = screen.getByPlaceholderText('Street Address') + const cityInput = screen.getByPlaceholderText('City') + const stateInput = screen.getByPlaceholderText('State') + const pincodeInput = screen.getByPlaceholderText('Pincode') + + fireEvent.change(streetInput, { target: { value: '123 Loom Lane' } }) + fireEvent.change(cityInput, { target: { value: 'Handloom City' } }) + fireEvent.change(stateInput, { target: { value: 'Karnataka' } }) + fireEvent.change(pincodeInput, { target: { value: '560001' } }) + + const saveBtn = screen.getByRole('button', { name: /Save & Continue/i }) + fireEvent.click(saveBtn) + + // Redirects directly to Dashboard Page + expect(screen.getByText('Performance Analytics')).toBeInTheDocument() + expect(screen.getByText(/Total Sales Volume/i)).toBeInTheDocument() + }) + + it('logs in successfully and navigates dashboard tools', () => { + render() + + const loginBtn = screen.getByRole('button', { name: /^Login$/i }) + fireEvent.click(loginBtn) + + const emailInput = screen.getByLabelText(/Business Email/i) + const passInput = screen.getByLabelText(/Password/i) + + fireEvent.change(emailInput, { target: { value: 'seller@example.com' } }) + fireEvent.change(passInput, { target: { value: 'password123' } }) + + const submitBtn = screen.getByRole('button', { name: /Secure Login/i }) + fireEvent.click(submitBtn) + + // Check dashboard rendering + expect(screen.getByText('Performance Analytics')).toBeInTheDocument() + + // Check Sidebar Tab click: Manage Products + const productsTabBtn = screen.getByText(/Manage Products/i) + fireEvent.click(productsTabBtn) + + expect(screen.getByText(/Active Inventory/i)).toBeInTheDocument() + expect(screen.getByText(/Upload New Product/i)).toBeInTheDocument() + + // Check Sidebar Tab click: Orders & Transit + const ordersTabBtn = screen.getByText(/Orders & Transit/i) + fireEvent.click(ordersTabBtn) + + expect(screen.getByText(/Active Orders & Payout status/i)).toBeInTheDocument() + + // Check Sidebar Tab click: Returns Management + const returnsTabBtn = screen.getByText(/Returns Management/i) + fireEvent.click(returnsTabBtn) + + expect(screen.getByText(/Returns & Quality Assurance Center/i)).toBeInTheDocument() + expect(screen.getByText(/Items Requested for Return/i)).toBeInTheDocument() + + // Check Sidebar Tab click: Wallet & Payouts + const walletTabBtn = screen.getByText(/Wallet & Payouts/i) + fireEvent.click(walletTabBtn) + + expect(screen.getByText(/Wallet & Payout Portal/i)).toBeInTheDocument() + expect(screen.getByText(/Outstanding Ready Balance/i)).toBeInTheDocument() + }) +}) diff --git a/src/App.tsx b/src/App.tsx index 838a8d4..0693478 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,122 +1,1354 @@ import { useState } from 'react' -import reactLogo from './assets/react.svg' -import viteLogo from './assets/vite.svg' -import heroImg from './assets/hero.png' -import './App.css' +import { CONFIG } from './config' -function App() { - const [count, setCount] = useState(0) +type Page = 'home' | 'about' | 'contact' | 'login' | 'signup' | 'profile-completion' | 'confirmation' | 'dashboard' +type DashboardTab = 'overview' | 'products' | 'orders' | 'returns' | 'wallet' | 'settings' +type DateFilter = 'year' | 'week' | 'day' | 'custom' + +interface Product { + id: string + title: string + category: string + price: number + stock: number + sku: string + image: string +} + +interface Order { + id: string + date: string + item: string + quantity: number + customer: string + total: number + status: 'Ready to Ship' | 'Shipped' | 'Delivered' | 'Cancelled' + carrier: string + tracking: string + eta: string +} + +interface ReturnRequest { + id: string + orderId: string + customer: string + item: string + reason: string + status: 'Pending Approval' | 'Approved' | 'Rejected' | 'In Transit' + image: string + returningTracking: string +} + +export default function App() { + const [currentPage, setCurrentPage] = useState('home') + const [activeTab, setActiveTab] = useState<'login' | 'register'>('login') + + // Registration / Onboarding Form States + const [email, setEmail] = useState('') + const [phone, setPhone] = useState('') + const [password, setPassword] = useState('') + const [gstin, setGstin] = useState('29AAAAA1111A1Z1') + const [isGstinVerified, setIsGstinVerified] = useState(true) + const [isVerifyingGstin, setIsVerifyingGstin] = useState(false) + + // Profile Completion States + const [storeName, setStoreName] = useState('My Artisan Handloom') + const [storeLogo, setStoreLogo] = useState(null) + const [businessBio, setBusinessBio] = useState('Traditional weaving and local sustainable designs.') + const [address, setAddress] = useState({ + street: '123 Handloom Lane', + city: 'Textile Town', + state: 'Karnataka', + pincode: '560001' + }) + + // --- Active Dashboard States --- + const [dashTab, setDashTab] = useState('overview') + const [dateFilter, setDateFilter] = useState('year') + const [customDates, setCustomDates] = useState({ start: '2026-06-01', end: '2026-06-15' }) + + // Lists + const [products, setProducts] = useState(CONFIG.dashboardData.initialProducts) + const [orders] = useState(CONFIG.dashboardData.initialOrders as Order[]) + const [returns, setReturns] = useState(CONFIG.dashboardData.initialReturns as ReturnRequest[]) + + // Forms & Editing + const [productForm, setProductForm] = useState({ + id: '', + title: '', + category: 'Apparel', + price: 0, + stock: 0, + sku: '', + image: 'https://images.unsplash.com/photo-1544022613-e87ca75a784a?auto=format&fit=crop&q=80&w=100' + }) + const [isEditingProduct, setIsEditingProduct] = useState(false) + + // Payout outstanding states + const [wallet, setWallet] = useState({ + outstanding: 850.00, + withdrawn: 1250.00, + history: [ + { id: 'TX-9031', date: '2026-08-01', amount: 500.00, status: 'Transferred' }, + { id: 'TX-9022', date: '2026-07-15', amount: 750.00, status: 'Transferred' } + ] + }) + const [withdrawAmount, setWithdrawAmount] = useState('') + + // Bulk Upload simulations + const [bulkLog, setBulkLog] = useState([]) + const [isParsingBulk, setIsParsingBulk] = useState(false) + + // GSTIN verification simulator + const handleVerifyGstin = () => { + if (!gstin.trim()) return + setIsVerifyingGstin(true) + setTimeout(() => { + setIsVerifyingGstin(false) + setIsGstinVerified(true) + }, 1200) + } + + // Handle Logo Upload simulation + const handleLogoChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0] + if (file) { + const reader = new FileReader() + reader.onloadend = () => { + setStoreLogo(reader.result as string) + } + reader.readAsDataURL(file) + } + } + + const navigateTo = (page: Page) => { + setCurrentPage(page) + window.scrollTo({ top: 0, behavior: 'smooth' }) + } + + const handleRegisterSubmit = (e: React.FormEvent) => { + e.preventDefault() + navigateTo('profile-completion') + } + + const handleLoginSubmit = (e: React.FormEvent) => { + e.preventDefault() + navigateTo('dashboard') + } + + const handleProfileSubmit = (e: React.FormEvent) => { + e.preventDefault() + navigateTo('dashboard') + } + + // --- Dashboard Logic Actions --- + + // Product creation/modification + const handleSaveProduct = (e: React.FormEvent) => { + e.preventDefault() + if (isEditingProduct) { + setProducts(products.map(p => p.id === productForm.id ? { ...productForm, price: Number(productForm.price), stock: Number(productForm.stock) } : p)) + setIsEditingProduct(false) + alert('Product modified successfully!') + } else { + const newProd: Product = { + ...productForm, + id: String(products.length + 1), + price: Number(productForm.price), + stock: Number(productForm.stock), + sku: productForm.sku || `PROD-${Date.now().toString().slice(-6)}` + } + setProducts([...products, newProd]) + alert('Product uploaded successfully!') + } + // reset form + setProductForm({ id: '', title: '', category: 'Apparel', price: 0, stock: 0, sku: '', image: 'https://images.unsplash.com/photo-1544022613-e87ca75a784a?auto=format&fit=crop&q=80&w=100' }) + } + + const handleEditClick = (p: Product) => { + setProductForm(p) + setIsEditingProduct(true) + } + + const handleDeleteProduct = (id: string) => { + if (confirm('Are you sure you want to delete this listing?')) { + setProducts(products.filter(p => p.id !== id)) + } + } + + // Bulk parser simulation + const handleBulkUploadSimulate = (e: React.ChangeEvent) => { + if (!e.target.files?.length) return + setIsParsingBulk(true) + setBulkLog(['Parsing CSV file template...', 'Validating rows against inventory Schema...']) + + setTimeout(() => { + const parsedProducts: Product[] = [ + { id: String(products.length + 1), title: 'Hand-Carved Walnut Bowl', category: 'Kitchenware', price: 65.00, stock: 15, sku: 'BOWL-WAL-12', image: 'https://images.unsplash.com/photo-1610701596007-11502861dcfa?auto=format&fit=crop&q=80&w=100' }, + { id: String(products.length + 2), title: 'Organic Cotton Tablecloth', category: 'Linens', price: 48.00, stock: 30, sku: 'LINE-COT-15', image: 'https://images.unsplash.com/photo-1603006905003-be475563bc59?auto=format&fit=crop&q=80&w=100' } + ] + setProducts(prev => [...prev, ...parsedProducts]) + setBulkLog(prev => [ + ...prev, + 'Row 1: Verified SKU BOWL-WAL-12 - Added', + 'Row 2: Verified SKU LINE-COT-15 - Added', + 'Successfully added 2 new listings bulk!' + ]) + setIsParsingBulk(false) + }, 1500) + } + + // Wallet outstanding requests + const handleWithdrawRequest = (e: React.FormEvent) => { + e.preventDefault() + const amount = Number(withdrawAmount) + if (isNaN(amount) || amount <= 0) { + alert('Please enter a valid amount') + return + } + if (amount > wallet.outstanding) { + alert('Amount exceeds outstanding ready payout balance') + return + } + + const txId = `TX-${Math.floor(1000 + Math.random() * 9000)}` + const newTx = { + id: txId, + date: new Date().toISOString().split('T')[0], + amount: amount, + status: 'Transferred' + } + + setWallet({ + outstanding: wallet.outstanding - amount, + withdrawn: wallet.withdrawn + amount, + history: [newTx, ...wallet.history] + }) + setWithdrawAmount('') + alert(`Payout of $${amount} successfully transferred!`) + } + + // Returns actions + const handleReturnAction = (id: string, action: 'Approved' | 'Rejected') => { + setReturns(returns.map(ret => { + if (ret.id === id) { + return { + ...ret, + status: action === 'Approved' ? 'In Transit' : 'Rejected' + } + } + return ret + })) + alert(`Return request ${action.toLowerCase()}!`) + } + + const selectedMetrics = CONFIG.dashboardData.filters[dateFilter] return ( <> -
-
- - React logo - Vite logo + {/* Dynamic Header */} +
+
navigateTo('home')}> +
{CONFIG.logoLetter}
+ {CONFIG.companyName}
-
-

Welcome sir

-

- Edit src/App.tsx and save to test HMR -

+ +
+ {currentPage === 'dashboard' ? ( + + ) : ( + <> + + + + )}
- -
+ -
+ {/* Main Content Area */} + {currentPage !== 'dashboard' ? ( +
+ + {/* HOMEPAGE VIEW */} + {currentPage === 'home' && ( +
+
+
+

+ {CONFIG.hero.title.split('. ')[0]}.
+ {CONFIG.hero.title.split('. ')[1]} +

+

+ {CONFIG.hero.subtitle} +

+
+ + +
+
+
+ Artisans Crafting +
+
-
-
- -

Documentation

-

Your questions, answered

- -
-
- -

Connect with us

-

Join the Vite community

- -
-
+
+
+ {CONFIG.features.map((feature, i) => ( +
+
{feature.icon}
+

{feature.title}

+

{feature.description}

+
+ ))} +
+
-
-
+
+
+ Featured Artisan +
+

Featured Artisan

+

+ {CONFIG.featuredArtisan.quote} +

+

- {CONFIG.featuredArtisan.name} ({CONFIG.featuredArtisan.role})

+
+
+
+
+ )} + + {/* ABOUT US VIEW */} + {currentPage === 'about' && ( +
+
+

{CONFIG.about.missionTitle}

+

+ {CONFIG.about.missionDescription} +

+
+ +
+

Meet Our Leaders

+
+ {CONFIG.about.team.map((member, i) => ( +
+ {member.name} +

{member.name}

+

{member.role}

+
+ ))} +
+
+
+ )} + + {/* CONTACT US VIEW */} + {currentPage === 'contact' && ( +
+
+

Get in Touch

+
{ e.preventDefault(); alert('Thank you for contacting us! We will get back to you shortly.'); navigateTo('home'); }}> +
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+ +
+
+

Address

+

') }}>

+
+
+

Support Email

+

{CONFIG.supportEmail}

+
+
+

Partner Hotline

+

{CONFIG.supportPhone}

+
+ +
+ 📍 Interactive Map Preview +
+
+
+ )} + + {/* AUTHENTICATION VIEW (LOGIN & SIGNUP) */} + {(currentPage === 'login' || currentPage === 'signup') && ( +
+
+ + +
+ + {activeTab === 'login' ? ( +
+

Supplier Portal Access

+
+ + setEmail(e.target.value)} + required + /> +
+
+ + setPassword(e.target.value)} + required + /> +
+ + +
+ +
+
+ ) : ( +
+

Create Your Supplier Account

+
+ + setEmail(e.target.value)} + required + /> +
+
+ + setPhone(e.target.value)} + required + /> +
+
+ + setPassword(e.target.value)} + required + /> +
+
+ + setGstin(e.target.value)} + required + /> +
+ +
+ )} +
+ )} + + {/* PROFILE COMPLETION VIEW (Step 2 of 3) */} + {currentPage === 'profile-completion' && ( +
+
+ Step 2 of 3: Business Details +
+
+
+
+ +

Complete Your Supplier Profile

+ +
+
+ + setStoreName(e.target.value)} + required + /> +
+ +
+ +
+
+ {storeLogo ? ( + Logo preview + ) : ( + 🖼️ + )} +
+ +
+
+ +
+ + +
+ +
+ +
+ setAddress({...address, street: e.target.value})} + required + /> + setAddress({...address, city: e.target.value})} + required + /> +
+
+ setAddress({...address, state: e.target.value})} + required + /> + setAddress({...address, pincode: e.target.value})} + required + /> +
+
+ +
+ +
+ setGstin(e.target.value)} + required + /> + +
+ {isGstinVerified && ( +

+ GSTIN successfully verified with government registry. +

+ )} +
+ +
+ + +
+
+
+ )} + + {/* ONBOARDING CONFIRMATION / REVIEW STATUS VIEW (Step 3 of 3) */} + {currentPage === 'confirmation' && ( +
+
+ Step 3 of 3: Verification +
+
+
+
+ +
🎉
+

Welcome to {CONFIG.companyName}!

+ +

+ Your registration is under review. We will verify your GSTIN credentials and activate your dashboard access within 24-48 hours. +

+ +
+
+
+
+
+
+
+
+
Total Sales
+
$0.00
+
+
+
Active Orders
+
0
+
+
+
Balance
+
$0.00
+
+
+
+
+
+
+
+
+
+
+ + +
+ )} + +
+ ) : ( + /* --- FULL SERVICE SUPPLIER ACTIVE DASHBOARD PAGE --- */ +
+ + {/* Sidebar */} + + + {/* Main Dashboard Space */} +
+ + {/* OVERVIEW PANEL */} + {dashTab === 'overview' && ( +
+
+

Performance Analytics

+
+ + + + +
+
+ + {dateFilter === 'custom' && ( +
+
+ + setCustomDates({...customDates, start: e.target.value})} /> +
+
+ + setCustomDates({...customDates, end: e.target.value})} /> +
+
+ )} + +
+
+
Total Sales Volume
+
${selectedMetrics.totalSales.toFixed(2)}
+
+
+
Total Net Earned
+
${selectedMetrics.totalEarned.toFixed(2)}
+
+
+
Available Stock
+
{selectedMetrics.stockDetails} units
+
+
+
Returned Items
+
{selectedMetrics.returnedItems} requests
+
+
+ +
+
+

Revenue Trajectory

+ Filtered by {dateFilter} metrics +
+
+ {selectedMetrics.chartValues.map((val, idx) => ( +
+ ))} +
+
+
+ )} + + {/* PRODUCTS TAB */} + {dashTab === 'products' && ( +
+

Product Listings & Upload

+ +
+ + {/* Products Grid list */} +
+

Active Inventory ({products.length})

+
+ + + + + + + + + + + + + {products.map(p => ( + + + + + + + + + ))} + +
ProductSKUCategoryPriceStockActions
+ {p.title} + {p.title} + {p.sku}{p.category}${p.price.toFixed(2)} + {p.stock} pcs + + + +
+
+ + {/* Bulk tools */} +
+

Bulk Operations

+
document.getElementById('bulk-file-pick')?.click()}> + {isParsingBulk ? 'Processing bulk data...' : '📥 Click to select CSV Template file for bulk upload'} + +
+ + {bulkLog.length > 0 && ( +
+

Import log:

+ {bulkLog.map((log, i) =>
{log}
)} +
+ )} +
+
+ + {/* Add/Edit Form */} +
+

+ {isEditingProduct ? 'Modify Product Listing' : 'Upload New Product'} +

+
+
+ + setProductForm({...productForm, title: e.target.value})} + required + /> +
+
+ + setProductForm({...productForm, sku: e.target.value})} + /> +
+
+
+ + setProductForm({...productForm, price: Number(e.target.value)})} + required + /> +
+
+ + setProductForm({...productForm, stock: Number(e.target.value)})} + required + /> +
+
+
+ + +
+ +
+ {isEditingProduct && ( + + )} + +
+
+
+
+
+ )} + + {/* ORDERS & TRACKING TAB */} + {dashTab === 'orders' && ( +
+

Active Orders & Payout status

+ +
+ + + + + + + + + + + + + + + {orders.map(o => ( + + + + + + + + + + + ))} + +
Order IDOrder DateItem DetailsQtyCustomerTotalStatusLogistics & Carrier Details
{o.id}{o.date}{o.item}{o.quantity}{o.customer}${o.total.toFixed(2)} + + {o.status} + + +
+
Carrier: {o.carrier}
+
Tracking: {o.tracking}
+
ETA / Delivery: {o.eta}
+
+
+
+
+ )} + + {/* RETURNS MANAGEMENT TAB */} + {dashTab === 'returns' && ( +
+

Returns & Quality Assurance Center

+ +

Items Requested for Return

+
+ + + + + + + + + + + + + + {returns.filter(r => r.status === 'Pending Approval').map(r => ( + + + + + + + + + + ))} + +
Return IDOriginal Order IDItem InfoCustomerReason GivenStatusActions
{r.id}{r.orderId} + {r.item} + {r.item} + {r.customer}"{r.reason}" + {r.status} + + + +
+
+ +

In-Transit Return Tracking

+
+ + + + + + + + + + + + {returns.filter(r => r.status !== 'Pending Approval').map(r => ( + + + + + + + + ))} + +
Return IDItemCustomerReturn Tracking NumberProgress Status
{r.id}{r.item}{r.customer}{r.returningTracking} + + {r.status} + +
+
+
+ )} + + {/* WALLET & PAYOUTS TAB */} + {dashTab === 'wallet' && ( +
+

Wallet & Payout Portal

+ +
+
+
Outstanding Ready Balance
+
${wallet.outstanding.toFixed(2)}
+
+
+
Total Payouts Withdrawn
+
${wallet.withdrawn.toFixed(2)}
+
+
+ +
+ {/* Withdrawal form */} +
+

Withdraw Payout Funds

+
+
+ + setWithdrawAmount(e.target.value)} + required + /> +
+ +
+
+ + {/* Payout History */} +
+

Transaction Payout Logs

+
+ + + + + + + + + + + {wallet.history.map(tx => ( + + + + + + + ))} + +
Transaction IDTransfer DateAmountStatus
{tx.id}{tx.date}${tx.amount.toFixed(2)}{tx.status}
+
+
+
+
+ )} + + {/* SETTINGS TAB */} + {dashTab === 'settings' && ( +
+

Store Configurations

+ +
{ e.preventDefault(); alert('Store configurations updated successfully!'); }}> +
+ + setStoreName(e.target.value)} + required + /> +
+ +
+ +
+
+ {storeLogo ? ( + Logo + ) : ( + 🖼️ + )} +
+ +
+
+ +
+ + +
+ +
+ +
+ setAddress({...address, street: e.target.value})} + required + /> + setAddress({...address, city: e.target.value})} + required + /> +
+
+ setAddress({...address, state: e.target.value})} + required + /> + setAddress({...address, pincode: e.target.value})} + required + /> +
+
+ +
+ + setGstin(e.target.value)} + disabled + /> +

GSTIN cannot be modified after verification.

+
+ + +
+
+ )} + +
+
+ )} + + {/* Footer Section */} + ) } - -export default App diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..187fd9e --- /dev/null +++ b/src/config.ts @@ -0,0 +1,116 @@ +export const CONFIG = { + companyName: 'Global Artisans Hub', + logoLetter: 'A', + supportEmail: 'support@globalartisanshub.com', + supportPhone: '+1 (800) 555-0199', + address: '1775 Artisan Ridge Rd, Craftsville, CA 90210', + + hero: { + title: 'Sell Globally. Celebrate Craft.', + subtitle: 'Join the premier marketplace for artisans and reach millions of conscious customers worldwide.', + image: 'https://images.unsplash.com/photo-1513519245088-0e12902e5a38?auto=format&fit=crop&q=80&w=800' + }, + + features: [ + { + icon: '🌍', + title: 'Expand Your Reach', + description: 'Globalize your marketplace for artisans and reach millions of conscious customers.' + }, + { + icon: '🛠️', + title: 'Easy Inventory Tools', + description: 'Dashboard marketplace to manage and update listings with ease.' + }, + { + icon: '🛡️', + title: 'Secure Payments', + description: 'Secure payments and reliable payout processing directly to your bank account.' + } + ], + + featuredArtisan: { + name: 'Naomi K.', + role: 'Master Weaver', + quote: '"Joining the Global Artisans Hub has transformed our community. We went from selling at local weekend fairs to serving buyers worldwide, preserving our traditional craft."', + avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?auto=format&fit=crop&q=80&w=300' + }, + + about: { + missionTitle: 'Our Mission', + missionDescription: 'We aim to empower artisan communities worldwide, providing a premium, transparent, and direct channel to conscious consumers globally.', + team: [ + { + name: 'Sunder R.', + role: 'Founder & CEO', + avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&q=80&w=150' + }, + { + name: 'Sarayu N.', + role: 'Head of Artisan Partnerships', + avatar: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&q=80&w=150' + }, + { + name: 'John C.', + role: 'Operations Lead', + avatar: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=crop&q=80&w=150' + }, + { + name: 'Elena P.', + role: 'Tech Lead', + avatar: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&q=80&w=150' + } + ] + }, + + // --- Mock Dashboard Data --- + dashboardData: { + filters: { + year: { + totalSales: 45280.00, + totalEarned: 38488.00, + stockDetails: 342, + returnedItems: 12, + chartValues: [30, 45, 35, 60, 75, 55, 80, 65, 90, 70, 85, 95] // Monthly values + }, + week: { + totalSales: 1240.00, + totalEarned: 1054.00, + stockDetails: 342, + returnedItems: 1, + chartValues: [40, 60, 50, 70, 45, 80, 90] // Daily values + }, + day: { + totalSales: 210.00, + totalEarned: 178.50, + stockDetails: 342, + returnedItems: 0, + chartValues: [10, 15, 20, 25, 35, 45, 30, 20, 10] // Hourly intervals + }, + custom: { + totalSales: 5820.00, + totalEarned: 4947.00, + stockDetails: 342, + returnedItems: 3, + chartValues: [50, 40, 65, 55, 75, 85, 60, 70] // Range intervals + } + }, + + initialProducts: [ + { id: '1', title: 'Handwoven Indigo Shawl', category: 'Apparel', price: 85.00, stock: 45, sku: 'SHAWL-IND-01', image: 'https://images.unsplash.com/photo-1544022613-e87ca75a784a?auto=format&fit=crop&q=80&w=100' }, + { id: '2', title: 'Terracotta Clay Pot Set', category: 'Home Decor', price: 42.00, stock: 24, sku: 'POT-TER-02', image: 'https://images.unsplash.com/photo-1612196808214-b8e1d6145a8c?auto=format&fit=crop&q=80&w=100' }, + { id: '3', title: 'Brass Elephant Figurine', category: 'Sculptures', price: 120.00, stock: 12, sku: 'FIG-ELE-03', image: 'https://images.unsplash.com/photo-1590736969955-71cc94801759?auto=format&fit=crop&q=80&w=100' } + ], + + initialOrders: [ + { id: 'ORD-8492', date: '2026-08-07', item: 'Handwoven Indigo Shawl', quantity: 1, customer: 'Alice Vance', total: 85.00, status: 'Ready to Ship', carrier: 'DHL Express', tracking: 'DHL-9284102', eta: '2026-08-10' }, + { id: 'ORD-8488', date: '2026-08-06', item: 'Terracotta Clay Pot Set', quantity: 2, customer: 'David Miller', total: 84.00, status: 'Shipped', carrier: 'FedEx Ground', tracking: 'FDX-5829104', eta: '2026-08-09' }, + { id: 'ORD-8471', date: '2026-08-05', item: 'Brass Elephant Figurine', quantity: 1, customer: 'Sarah Connor', total: 120.00, status: 'Delivered', carrier: 'Local Express', tracking: 'LOC-1294801', eta: '2026-08-07' } + ], + + initialReturns: [ + { id: 'RET-019', orderId: 'ORD-8411', customer: 'Lillian G.', item: 'Handwoven Indigo Shawl', reason: 'Slightly different shade than pictured', status: 'Pending Approval', image: 'https://images.unsplash.com/photo-1544022613-e87ca75a784a?auto=format&fit=crop&q=80&w=100', returningTracking: 'DHL-RET-9031' }, + { id: 'RET-018', orderId: 'ORD-8390', customer: 'Robert H.', item: 'Terracotta Clay Pot Set', reason: 'Damaged in transit', status: 'In Transit', image: 'https://images.unsplash.com/photo-1612196808214-b8e1d6145a8c?auto=format&fit=crop&q=80&w=100', returningTracking: 'FDX-RET-2940' } + ] + } +} diff --git a/src/index.css b/src/index.css index 5fb3313..47b343d 100644 --- a/src/index.css +++ b/src/index.css @@ -1,111 +1,1041 @@ +@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&display=swap'); + :root { - --text: #6b6375; - --text-h: #08060d; - --bg: #fff; - --border: #e5e4e7; - --code-bg: #f4f3ec; - --accent: #aa3bff; - --accent-bg: rgba(170, 59, 255, 0.1); - --accent-border: rgba(170, 59, 255, 0.5); - --social-bg: rgba(244, 243, 236, 0.5); - --shadow: - rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px; + /* Colors */ + --primary: #1A2B45; /* Deep Navy Blue */ + --accent: #E9C46A; /* Amber Gold */ + --bg: #F5F0EA; /* Warm Cream */ + --card-bg: #FFFFFF; /* Clean White */ + --text-dark: #1A2B45; + --text-muted: #5A6B82; + --border: #D8D1C5; + --input-focus: #1A2B45; + --success: #2A9D8F; + --error: #E76F51; - --sans: system-ui, 'Segoe UI', Roboto, sans-serif; - --heading: system-ui, 'Segoe UI', Roboto, sans-serif; - --mono: ui-monospace, Consolas, monospace; - - font: 18px/145% var(--sans); - letter-spacing: 0.18px; - color-scheme: light dark; - color: var(--text); - background: var(--bg); - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - - @media (max-width: 1024px) { - font-size: 16px; - } + /* Typography */ + --font-family: 'Outfit', system-ui, -apple-system, sans-serif; + + /* Layout */ + --max-width: 1200px; } -@media (prefers-color-scheme: dark) { - :root { - --text: #9ca3af; - --text-h: #f3f4f6; - --bg: #16171d; - --border: #2e303a; - --code-bg: #1f2028; - --accent: #c084fc; - --accent-bg: rgba(192, 132, 252, 0.15); - --accent-border: rgba(192, 132, 252, 0.5); - --social-bg: rgba(47, 48, 58, 0.5); - --shadow: - rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px; - } - - #social .button-icon { - filter: invert(1) brightness(2); - } -} - -#root { - width: 1126px; - max-width: 100%; - margin: 0 auto; - text-align: center; - border-inline: 1px solid var(--border); - min-height: 100svh; - display: flex; - flex-direction: column; +* { box-sizing: border-box; + margin: 0; + padding: 0; } body { - margin: 0; + font-family: var(--font-family); + background-color: var(--bg); + color: var(--text-dark); + line-height: 1.6; + min-height: 100vh; } -h1, -h2 { - font-family: var(--heading); +#root { + display: flex; + flex-direction: column; + min-height: 100vh; +} + +/* Header Styling */ +.app-header { + background-color: var(--primary); + color: var(--card-bg); + padding: 1rem 2rem; + display: flex; + justify-content: space-between; + align-items: center; + position: sticky; + top: 0; + z-index: 1000; + box-shadow: 0 4px 20px rgba(26, 43, 69, 0.15); +} + +.logo-container { + display: flex; + align-items: center; + gap: 0.75rem; + cursor: pointer; +} + +.logo-icon { + background-color: var(--accent); + color: var(--primary); + width: 36px; + height: 36px; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + font-weight: 800; + font-size: 1.25rem; +} + +.logo-text { + font-size: 1.35rem; + font-weight: 700; + letter-spacing: -0.5px; +} + +.nav-links { + display: flex; + align-items: center; + gap: 2rem; +} + +.nav-link { + color: var(--card-bg); + text-decoration: none; font-weight: 500; - color: var(--text-h); + font-size: 0.95rem; + opacity: 0.85; + transition: opacity 0.2s ease, color 0.2s ease; + cursor: pointer; + background: none; + border: none; + font-family: inherit; } -h1 { - font-size: 56px; - letter-spacing: -1.68px; - margin: 32px 0; - @media (max-width: 1024px) { - font-size: 36px; - margin: 20px 0; - } -} -h2 { - font-size: 24px; - line-height: 118%; - letter-spacing: -0.24px; - margin: 0 0 8px; - @media (max-width: 1024px) { - font-size: 20px; - } -} -p { - margin: 0; +.nav-link:hover, .nav-link.active { + opacity: 1; + color: var(--accent); } -code, -.counter { - font-family: var(--mono); +.nav-buttons { + display: flex; + align-items: center; + gap: 1rem; +} + +/* Buttons */ +.btn { + padding: 0.6rem 1.25rem; + border-radius: 8px; + font-weight: 600; + font-size: 0.95rem; + cursor: pointer; + transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); + border: none; + font-family: inherit; display: inline-flex; - border-radius: 4px; - color: var(--text-h); + align-items: center; + justify-content: center; + gap: 0.5rem; } -code { - font-size: 15px; - line-height: 135%; - padding: 4px 8px; - background: var(--code-bg); +.btn-primary { + background-color: var(--accent); + color: var(--primary); +} + +.btn-primary:hover { + background-color: #dfba5c; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(233, 196, 106, 0.3); +} + +.btn-secondary { + background-color: transparent; + color: var(--card-bg); + border: 1.5px solid rgba(255, 255, 255, 0.4); +} + +.btn-secondary:hover { + background-color: rgba(255, 255, 255, 0.1); + border-color: var(--card-bg); +} + +.btn-outline-dark { + background-color: transparent; + color: var(--primary); + border: 1.5px solid var(--primary); +} + +.btn-outline-dark:hover { + background-color: rgba(26, 43, 69, 0.05); +} + +.btn-dark { + background-color: var(--primary); + color: var(--card-bg); +} + +.btn-dark:hover { + background-color: #121f32; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(26, 43, 69, 0.25); +} + +/* Page Layouts */ +.main-content { + flex: 1; + width: 100%; + max-width: var(--max-width); + margin: 0 auto; + padding: 2rem 1.5rem; +} + +.main-content.full-width { + max-width: 100%; + padding: 0; +} + +/* Hero Section */ +.hero-section { + display: grid; + grid-template-columns: 1fr 1fr; + align-items: center; + gap: 4rem; + padding: 5rem 4rem; + background-color: var(--bg); +} + +@media (max-width: 900px) { + .hero-section { + grid-template-columns: 1fr; + gap: 2rem; + padding: 3rem 1.5rem; + text-align: center; + } +} + +.hero-content h1 { + font-size: 3.5rem; + line-height: 1.15; + color: var(--primary); + margin-bottom: 1.5rem; + font-weight: 800; + letter-spacing: -1px; +} + +.hero-content p { + font-size: 1.15rem; + color: var(--text-muted); + margin-bottom: 2.5rem; +} + +.hero-image-wrapper { + position: relative; + border-radius: 20px; + overflow: hidden; + box-shadow: 0 20px 40px rgba(26, 43, 69, 0.1); + aspect-ratio: 1.5; +} + +.hero-img { + width: 100%; + height: 100%; + object-fit: cover; +} + +/* Features Grid */ +.features-section { + padding: 5rem 4rem; + background-color: var(--card-bg); +} + +@media (max-width: 768px) { + .features-section { + padding: 3rem 1.5rem; + } +} + +.features-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 2.5rem; + max-width: var(--max-width); + margin: 0 auto; +} + +.feature-card { + background-color: var(--bg); + padding: 2.5rem; + border-radius: 16px; + transition: transform 0.3s ease, box-shadow 0.3s ease; + border: 1px solid var(--border); +} + +.feature-card:hover { + transform: translateY(-5px); + box-shadow: 0 12px 30px rgba(26, 43, 69, 0.08); +} + +.feature-icon-wrapper { + width: 50px; + height: 50px; + border-radius: 12px; + background-color: var(--primary); + color: var(--accent); + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 1.5rem; + font-size: 1.5rem; +} + +.feature-card h3 { + font-size: 1.35rem; + margin-bottom: 0.75rem; + color: var(--primary); +} + +.feature-card p { + color: var(--text-muted); + font-size: 0.95rem; +} + +/* Featured Artisan Card */ +.featured-artisan-section { + padding: 4rem; + max-width: var(--max-width); + margin: 0 auto; +} + +@media (max-width: 768px) { + .featured-artisan-section { + padding: 2rem 1.5rem; + } +} + +.artisan-card { + background-color: var(--card-bg); + border-radius: 20px; + padding: 3rem; + display: flex; + gap: 3rem; + align-items: center; + box-shadow: 0 10px 30px rgba(26, 43, 69, 0.05); + border: 1px solid var(--border); +} + +@media (max-width: 768px) { + .artisan-card { + flex-direction: column; + text-align: center; + padding: 2rem 1.5rem; + } +} + +.artisan-avatar { + width: 120px; + height: 120px; + border-radius: 50%; + object-fit: cover; + border: 4px solid var(--accent); +} + +.artisan-info h4 { + font-size: 1.5rem; + color: var(--primary); + margin-bottom: 0.5rem; +} + +.artisan-quote { + font-style: italic; + font-size: 1.1rem; + color: var(--text-muted); + margin-bottom: 1.5rem; +} + +/* Forms */ +.form-card { + background-color: var(--card-bg); + border-radius: 16px; + padding: 3rem; + max-width: 540px; + margin: 3rem auto; + box-shadow: 0 10px 35px rgba(26, 43, 69, 0.08); + border: 1px solid var(--border); +} + +@media (max-width: 580px) { + .form-card { + padding: 2rem 1.5rem; + } +} + +.form-card-title { + text-align: center; + color: var(--primary); + font-size: 1.85rem; + font-weight: 700; + margin-bottom: 2rem; +} + +.form-group { + margin-bottom: 1.25rem; +} + +.form-group label { + display: block; + font-weight: 600; + font-size: 0.9rem; + margin-bottom: 0.5rem; + color: var(--primary); +} + +.form-control { + width: 100%; + padding: 0.75rem 1rem; + border-radius: 8px; + border: 1px solid var(--border); + font-family: inherit; + font-size: 0.95rem; + transition: all 0.2s ease; + background-color: #FAFAFA; +} + +.form-control:focus { + outline: none; + border-color: var(--primary); + background-color: var(--card-bg); + box-shadow: 0 0 0 3px rgba(26, 43, 69, 0.1); +} + +/* Tabs */ +.tab-container { + display: flex; + border-bottom: 2px solid var(--border); + margin-bottom: 2rem; +} + +.tab-btn { + flex: 1; + padding: 0.75rem; + background: none; + border: none; + font-family: inherit; + font-size: 1rem; + font-weight: 600; + color: var(--text-muted); + cursor: pointer; + position: relative; + transition: color 0.2s ease; +} + +.tab-btn.active { + color: var(--primary); +} + +.tab-btn.active::after { + content: ''; + position: absolute; + bottom: -2px; + left: 0; + right: 0; + height: 2px; + background-color: var(--accent); +} + +/* Multi-step progress */ +.step-progress-wrapper { + text-align: center; + margin-bottom: 2.5rem; +} + +.step-label { + font-size: 0.85rem; + font-weight: 700; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 1px; + margin-bottom: 0.75rem; + display: block; +} + +.step-bar-container { + display: flex; + height: 4px; + background-color: rgba(26, 43, 69, 0.1); + border-radius: 2px; + max-width: 320px; + margin: 0 auto; + overflow: hidden; +} + +.step-bar-fill { + height: 100%; + background-color: var(--accent); + transition: width 0.4s ease; +} + +/* Uploader element */ +.upload-btn-wrapper { + display: flex; + align-items: center; + gap: 1.5rem; + margin-top: 0.5rem; +} + +.upload-preview-box { + width: 64px; + height: 64px; + border-radius: 8px; + border: 1px solid var(--border); + background-color: #FAFAFA; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; +} + +.upload-preview-box img { + width: 100%; + height: 100%; + object-fit: cover; +} + +/* Footer Styling */ +.app-footer { + background-color: var(--primary); + color: rgba(255, 255, 255, 0.8); + padding: 3rem 2rem 2rem; + margin-top: auto; + border-top: 1px solid rgba(255, 255, 255, 0.1); +} + +.footer-content { + max-width: var(--max-width); + margin: 0 auto; + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 2rem; + padding-bottom: 2rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.footer-logo-text { + font-size: 1.25rem; + font-weight: 700; + color: var(--card-bg); +} + +.footer-links { + display: flex; + gap: 2rem; +} + +.footer-link { + color: rgba(255, 255, 255, 0.7); + text-decoration: none; + font-size: 0.9rem; + transition: color 0.2s ease; +} + +.footer-link:hover { + color: var(--accent); +} + +.footer-copyright { + text-align: center; + padding-top: 1.5rem; + font-size: 0.85rem; + color: rgba(255, 255, 255, 0.5); +} + +/* About Us and Contact Details */ +.about-mission { + max-width: 800px; + margin: 0 auto 4rem; + text-align: center; +} + +.about-mission h2 { + font-size: 2.25rem; + color: var(--primary); + margin-bottom: 1.5rem; +} + +.about-mission p { + font-size: 1.15rem; + color: var(--text-muted); +} + +.team-section { + margin-top: 4rem; +} + +.team-section h3 { + font-size: 1.75rem; + color: var(--primary); + margin-bottom: 2.5rem; + text-align: center; +} + +.team-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 2.5rem; +} + +.team-card { + background-color: var(--card-bg); + border-radius: 12px; + padding: 2rem; + text-align: center; + border: 1px solid var(--border); + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.02); +} + +.team-avatar { + width: 90px; + height: 90px; + border-radius: 50%; + object-fit: cover; + margin-bottom: 1.25rem; +} + +.team-card h4 { + font-size: 1.1rem; + color: var(--primary); + margin-bottom: 0.25rem; +} + +.team-card p { + color: var(--text-muted); + font-size: 0.85rem; +} + +.contact-layout { + display: grid; + grid-template-columns: 1.2fr 0.8fr; + gap: 4rem; + margin-top: 2rem; +} + +@media (max-width: 800px) { + .contact-layout { + grid-template-columns: 1fr; + gap: 3rem; + } +} + +.contact-info-panel { + background-color: var(--card-bg); + border-radius: 16px; + padding: 3rem; + border: 1px solid var(--border); + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.03); +} + +.info-item { + margin-bottom: 2rem; +} + +.info-item h4 { + font-size: 1.1rem; + color: var(--primary); + margin-bottom: 0.5rem; +} + +.info-item p { + color: var(--text-muted); + font-size: 0.95rem; +} + +.map-placeholder { + height: 180px; + background-color: #e2e8f0; + border-radius: 12px; + margin-top: 1.5rem; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-muted); + font-size: 0.9rem; + border: 1px dashed var(--border); +} + +/* --- ACTIVE SUPPLIER DASHBOARD LAYOUT --- */ +.dashboard-container { + display: grid; + grid-template-columns: 240px 1fr; + min-height: calc(100vh - 70px); +} + +@media (max-width: 768px) { + .dashboard-container { + grid-template-columns: 1fr; + } +} + +.dashboard-sidebar { + background-color: var(--primary); + color: var(--card-bg); + padding: 2rem 1rem; + border-right: 1px solid rgba(255, 255, 255, 0.1); +} + +.sidebar-menu { + list-style: none; + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.sidebar-item-btn { + width: 100%; + text-align: left; + padding: 0.75rem 1rem; + background: none; + border: none; + color: rgba(255, 255, 255, 0.75); + font-family: inherit; + font-size: 0.95rem; + font-weight: 500; + cursor: pointer; + border-radius: 8px; + transition: all 0.2s ease; + display: flex; + align-items: center; + gap: 0.75rem; +} + +.sidebar-item-btn:hover { + background-color: rgba(255, 255, 255, 0.05); + color: var(--card-bg); +} + +.sidebar-item-btn.active { + background-color: var(--accent); + color: var(--primary); + font-weight: 600; +} + +.dashboard-main { + background-color: var(--bg); + padding: 2rem 3rem; +} + +@media (max-width: 768px) { + .dashboard-main { + padding: 1.5rem; + } +} + +.dashboard-header-bar { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 2rem; + flex-wrap: wrap; + gap: 1rem; +} + +.dashboard-title { + font-size: 2rem; + color: var(--primary); + font-weight: 800; +} + +/* Metric Cards */ +.metrics-row { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 1.5rem; + margin-bottom: 2.5rem; +} + +.metric-data-card { + background-color: var(--card-bg); + border-radius: 12px; + padding: 1.5rem; + border: 1px solid var(--border); + box-shadow: 0 4px 15px rgba(26, 43, 69, 0.03); +} + +.metric-title { + font-size: 0.85rem; + font-weight: 600; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.5px; + margin-bottom: 0.5rem; +} + +.metric-value { + font-size: 1.85rem; + font-weight: 800; + color: var(--primary); +} + +/* Dynamic Charts & Filter Layout */ +.chart-card { + background-color: var(--card-bg); + border-radius: 16px; + padding: 2rem; + border: 1px solid var(--border); + margin-bottom: 2.5rem; +} + +.chart-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.5rem; + flex-wrap: wrap; + gap: 1rem; +} + +.chart-title { + font-size: 1.2rem; + font-weight: 700; + color: var(--primary); +} + +.time-filter-bar { + display: flex; + background-color: var(--bg); + padding: 0.25rem; + border-radius: 8px; + border: 1px solid var(--border); + gap: 0.25rem; +} + +.time-filter-btn { + background: none; + border: none; + padding: 0.4rem 1rem; + font-family: inherit; + font-size: 0.85rem; + font-weight: 600; + color: var(--text-muted); + cursor: pointer; + border-radius: 6px; + transition: all 0.2s ease; +} + +.time-filter-btn.active { + background-color: var(--card-bg); + color: var(--primary); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); +} + +.main-bar-chart { + height: 220px; + display: flex; + align-items: flex-end; + justify-content: space-around; + padding-top: 1rem; + border-bottom: 2px solid var(--border); + margin-bottom: 0.5rem; +} + +.chart-bar-col { + width: 6%; + background-color: rgba(26, 43, 69, 0.15); + border-radius: 6px 6px 0 0; + transition: height 0.4s ease; + position: relative; + cursor: pointer; +} + +.chart-bar-col:hover { + background-color: var(--primary); +} + +.chart-bar-col.highlighted { + background-color: var(--accent); +} + +/* Custom Date Selector Panel */ +.custom-date-panel { + display: flex; + gap: 1rem; + align-items: center; + background-color: var(--card-bg); + padding: 1rem; + border-radius: 8px; + border: 1px solid var(--border); + margin-bottom: 2rem; +} + +/* Tables */ +.dashboard-table-wrapper { + background-color: var(--card-bg); + border-radius: 12px; + border: 1px solid var(--border); + overflow: hidden; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.02); + margin-bottom: 2rem; +} + +.dashboard-table { + width: 100%; + border-collapse: collapse; + text-align: left; +} + +.dashboard-table th { + background-color: #FAFAFA; + padding: 1rem; + font-weight: 600; + font-size: 0.85rem; + color: var(--text-muted); + border-bottom: 1px solid var(--border); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.dashboard-table td { + padding: 1rem; + border-bottom: 1px solid var(--border); + font-size: 0.95rem; + vertical-align: middle; +} + +.dashboard-table tr:last-child td { + border-bottom: none; +} + +.product-thumb { + width: 40px; + height: 40px; + border-radius: 6px; + object-fit: cover; + border: 1px solid var(--border); +} + +/* Badges */ +.badge { + padding: 0.25rem 0.6rem; + border-radius: 20px; + font-size: 0.75rem; + font-weight: 700; + display: inline-block; + text-transform: uppercase; +} + +.badge-success { + background-color: rgba(42, 157, 143, 0.15); + color: var(--success); +} + +.badge-warning { + background-color: rgba(233, 196, 106, 0.2); + color: #c99c27; +} + +.badge-info { + background-color: rgba(26, 43, 69, 0.1); + color: var(--primary); +} + +.badge-danger { + background-color: rgba(231, 111, 81, 0.15); + color: var(--error); +} + +/* Bulk uploader zone */ +.bulk-drop-zone { + border: 2px dashed var(--border); + background-color: #FAFAFA; + border-radius: 12px; + padding: 3rem; + text-align: center; + cursor: pointer; + transition: background-color 0.2s ease; + margin-bottom: 1.5rem; +} + +.bulk-drop-zone:hover { + background-color: #F0EDE8; +} + +/* Layout split (Product lists + forms) */ +.tool-split-layout { + display: grid; + grid-template-columns: 1.1fr 0.9fr; + gap: 3rem; + align-items: start; +} + +@media (max-width: 900px) { + .tool-split-layout { + grid-template-columns: 1fr; + gap: 2rem; + } +} + +/* Conf preview styles override */ +.conf-dashboard-preview { + margin-top: 2rem; + border-radius: 12px; + overflow: hidden; + border: 1px solid var(--border); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.05); +} + +.mock-dash-header { + background-color: var(--primary); + height: 40px; + display: flex; + align-items: center; + padding-left: 1rem; + gap: 0.5rem; +} + +.mock-dash-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background-color: rgba(255, 255, 255, 0.2); +} + +.mock-dash-body { + background-color: #f8fafc; + padding: 1.5rem; + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 1rem; +} + +.mock-dash-card { + background-color: var(--card-bg); + padding: 1rem; + border-radius: 6px; + border: 1px solid #edf2f7; + height: 100px; +} + +.mock-dash-card-title { + font-size: 0.75rem; + font-weight: 600; + color: var(--text-muted); + margin-bottom: 0.5rem; +} + +.mock-dash-card-val { + font-size: 1.25rem; + font-weight: 700; + color: var(--primary); +} + +.mock-dash-chart { + grid-column: span 3; + background-color: var(--card-bg); + border-radius: 6px; + border: 1px solid #edf2f7; + height: 120px; + display: flex; + align-items: flex-end; + justify-content: space-around; + padding: 1rem; +} + +.mock-chart-bar { + width: 24px; + background-color: rgba(26, 43, 69, 0.1); + border-radius: 4px 4px 0 0; + transition: height 0.3s ease; +} + +.mock-chart-bar.active { + background-color: var(--accent); } diff --git a/src/setupTests.ts b/src/setupTests.ts new file mode 100644 index 0000000..c44951a --- /dev/null +++ b/src/setupTests.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom' diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..4ef3af4 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,13 @@ +import { defineConfig, mergeConfig } from 'vitest/config' +import viteConfig from './vite.config' + +export default mergeConfig( + viteConfig, + defineConfig({ + test: { + globals: true, + environment: 'jsdom', + setupFiles: './src/setupTests.ts', + }, + }) +)