From 125b1f9d26e37d44f3945599f04d425c4bdffd7c Mon Sep 17 00:00:00 2001 From: vickytechkey Date: Fri, 21 Aug 2026 10:43:19 +0530 Subject: [PATCH] version 2 initial commit --- index.html | 9 +- package-lock.json | 389 +- package.json | 3 +- src/App.tsx | 3235 +---------------- src/components/dashboard/DashboardLayout.tsx | 42 + .../dashboard/tabs/AddProductTab.tsx | 113 + src/components/dashboard/tabs/OrdersTab.tsx | 56 + src/components/dashboard/tabs/OverviewTab.tsx | 116 + src/components/dashboard/tabs/PaymentsTab.tsx | 51 + src/components/dashboard/tabs/ProductsTab.tsx | 51 + src/components/dashboard/tabs/SettingsTab.tsx | 91 + src/components/layout/DashboardHeader.tsx | 51 + src/components/layout/Footer.tsx | 38 + src/components/layout/Header.tsx | 88 + src/components/layout/Sidebar.tsx | 38 + src/components/pages/AboutPage.tsx | 98 + src/components/pages/AuthForms.tsx | 86 + src/components/pages/ContactPage.tsx | 164 + src/components/pages/LandingPage.tsx | 113 + src/components/pages/OnboardingWizard.tsx | 255 ++ src/config.ts | 94 - src/context/SellerContext.tsx | 822 +++++ src/index.css | 2006 +--------- vite.config.ts | 8 + 24 files changed, 2715 insertions(+), 5302 deletions(-) create mode 100644 src/components/dashboard/DashboardLayout.tsx create mode 100644 src/components/dashboard/tabs/AddProductTab.tsx create mode 100644 src/components/dashboard/tabs/OrdersTab.tsx create mode 100644 src/components/dashboard/tabs/OverviewTab.tsx create mode 100644 src/components/dashboard/tabs/PaymentsTab.tsx create mode 100644 src/components/dashboard/tabs/ProductsTab.tsx create mode 100644 src/components/dashboard/tabs/SettingsTab.tsx create mode 100644 src/components/layout/DashboardHeader.tsx create mode 100644 src/components/layout/Footer.tsx create mode 100644 src/components/layout/Header.tsx create mode 100644 src/components/layout/Sidebar.tsx create mode 100644 src/components/pages/AboutPage.tsx create mode 100644 src/components/pages/AuthForms.tsx create mode 100644 src/components/pages/ContactPage.tsx create mode 100644 src/components/pages/LandingPage.tsx create mode 100644 src/components/pages/OnboardingWizard.tsx create mode 100644 src/context/SellerContext.tsx diff --git a/index.html b/index.html index 4809195..9066c61 100644 --- a/index.html +++ b/index.html @@ -1,11 +1,16 @@ - + sellercentral - + + + + + +
diff --git a/package-lock.json b/package-lock.json index 8fa4bbe..ba14b94 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,8 @@ "dependencies": { "bulma": "^1.0.4", "react": "^19.2.8", - "react-dom": "^19.2.8" + "react-dom": "^19.2.8", + "recharts": "^3.10.1" }, "devDependencies": { "@playwright/test": "^1.49.0", @@ -779,6 +780,32 @@ "dev": true, "license": "MIT" }, + "node_modules/@reduxjs/toolkit": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.12.0.tgz", + "integrity": "sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^11.0.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@rolldown/binding-android-arm64": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.3.tgz", @@ -1028,7 +1055,12 @@ "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/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", "license": "MIT" }, "node_modules/@tailwindcss/node": { @@ -1659,6 +1691,69 @@ "assertion-error": "^2.0.1" } }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, "node_modules/@types/deep-eql": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", @@ -1687,7 +1782,7 @@ "version": "19.2.18", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -1724,6 +1819,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, "node_modules/@vitejs/plugin-react": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.5.tgz", @@ -2291,6 +2392,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2416,7 +2526,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/cypress": { @@ -2473,6 +2583,127 @@ "node": "^20.1.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -2547,6 +2778,12 @@ "dev": true, "license": "MIT" }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -2723,6 +2960,18 @@ "node": ">= 0.4" } }, + "node_modules/es-toolkit": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.51.0.tgz", + "integrity": "sha512-zC2lQGkM7QX+Gm6iM3+WIdZJzthsEd14LvRNJneSO2hzyz/zNBENR8+YXWo1cKxgPBtV6ksPYHELbcwBRzmdCw==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks", + "tests/types", + "tests/browser-compat" + ] + }, "node_modules/estree-walker": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", @@ -2744,7 +2993,6 @@ "version": "5.0.4", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", - "dev": true, "license": "MIT" }, "node_modules/execa": { @@ -3153,6 +3401,16 @@ ], "license": "BSD-3-Clause" }, + "node_modules/immer": { + "version": "11.1.18", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.18.tgz", + "integrity": "sha512-EQyQtLiYW029lyoczMl/Hh4Xu7cDecSc58JRYpHyL4tIAu3eqd1yJzQX04d2BZHDkzFFvm6qJEJWOtfDSWAXbQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", @@ -3173,6 +3431,15 @@ "node": ">=10" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/is-fullwidth-code-point": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", @@ -4274,9 +4541,61 @@ "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/react-redux": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz", + "integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/recharts": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.10.1.tgz", + "integrity": "sha512-QXFrvt6IVcw7eeZCoyXTwkIJAX3Dv1nyVhMicXJ47GsGDDpcN8z6o644DibE9XjpBTThtsomLKnTV6lc+cVFUA==", + "license": "MIT", + "workspaces": [ + "www" + ], + "dependencies": { + "@reduxjs/toolkit": "^1.9.0 || 2.x.x", + "clsx": "^2.1.1", + "decimal.js-light": "^2.5.1", + "es-toolkit": "^1.39.3", + "eventemitter3": "^5.0.1", + "immer": "^11.1.8", + "react-redux": "8.x.x || 9.x.x", + "reselect": "5.2.0", + "tiny-invariant": "^1.3.3", + "use-sync-external-store": "^1.2.2", + "victory-vendor": "^37.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -4291,6 +4610,21 @@ "node": ">=8" } }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, "node_modules/request-progress": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", @@ -4311,6 +4645,12 @@ "node": ">=0.10.0" } }, + "node_modules/reselect": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.2.0.tgz", + "integrity": "sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==", + "license": "MIT" + }, "node_modules/restore-cursor": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", @@ -4823,6 +5163,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -5024,6 +5370,15 @@ "node": ">=8" } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", @@ -5039,6 +5394,28 @@ "extsprintf": "^1.2.0" } }, + "node_modules/victory-vendor": { + "version": "37.3.6", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", + "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "node_modules/vite": { "version": "8.2.1", "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.1.tgz", diff --git a/package.json b/package.json index 13bbc56..470cff9 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "dependencies": { "bulma": "^1.0.4", "react": "^19.2.8", - "react-dom": "^19.2.8" + "react-dom": "^19.2.8", + "recharts": "^3.10.1" }, "devDependencies": { "@playwright/test": "^1.49.0", diff --git a/src/App.tsx b/src/App.tsx index 06ac11e..673cee1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,3212 +1,41 @@ -import { useState, useEffect } from 'react' -import { CONFIG, apiFetch } from './config' -type Page = 'home' | 'about' | 'contact' | 'login' | 'signup' | 'profile-completion' | 'confirmation' | 'dashboard' | 'forgot-password' | 'login-otp' | 'welcome-tour' -type DashboardTab = 'overview' | 'products' | 'orders' | 'returns' | 'wallet' | 'settings' | 'barcode-generator' | 'analytics' -type DateFilter = 'year' | 'week' | 'day' | 'custom' +import React from 'react'; +import { SellerProvider, useSeller } from './context/SellerContext'; +import Header from './components/layout/Header'; +import Footer from './components/layout/Footer'; +import LandingPage from './components/pages/LandingPage'; +import AboutPage from './components/pages/AboutPage'; +import ContactPage from './components/pages/ContactPage'; +import AuthForms from './components/pages/AuthForms'; +import OnboardingWizard from './components/pages/OnboardingWizard'; +import DashboardLayout from './components/dashboard/DashboardLayout'; -interface Product { - id: string - title: string - category: string - price: number - stock: number - sku: string - image: string -} +function AppRouter() { + const { currentPage } = useSeller(); -interface Order { - id: string - date: string - item: string - quantity: number - customer: string - total: number - status: 'Ready to Ship test' | 'Ready to Ship' | 'Shipped' | 'Delivered' | 'Cancelled' | 'Pending Acceptance' | 'Rejected' - carrier: string - tracking: string - eta: string -} + if (currentPage === 'dashboard') { + return ; + } -interface ReturnRequest { - id: string - orderId: string - customer: string - item: string - reason: string - status: 'Pending Approval' | 'Approved' | 'Rejected' | 'In Transit' - image: string - returningTracking: string + return ( +
+
+
+ {currentPage === 'profile-completion' && } + {currentPage === 'home' && } + {currentPage === 'about' && } + {currentPage === 'contact' && } + {(currentPage === 'login' || currentPage === 'signup') && } +
+
+
+ ); } export default function App() { - const [currentPage, setCurrentPage] = useState('home') - const [isSidebarOpen, setIsSidebarOpen] = useState(false) - const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false) - const [activeTab, setActiveTab] = useState<'login' | 'register'>('login') - - // Onboarding Status - const [isProfileComplete, setIsProfileComplete] = useState(false) - const [profileStep, setProfileStep] = useState(1) - - // Registration / Onboarding Form States - const [email, setEmail] = useState('') - const [phone, setPhone] = useState('') - const [password, setPassword] = useState('') - - // Step 1: Verification - const [phoneVerified, setPhoneVerified] = useState(false) - const [emailVerified, setEmailVerified] = useState(false) - const [phoneOtpSent, setPhoneOtpSent] = useState(false) - const [emailOtpSent, setEmailOtpSent] = useState(false) - const [enteredPhoneOtp, setEnteredPhoneOtp] = useState('') - const [enteredEmailOtp, setEnteredEmailOtp] = useState('') - - // Step 2: Business details (moved GSTIN here) - const [gstin, setGstin] = useState('29AAAAA1111A1Z1') - const [isGstinVerified, setIsGstinVerified] = useState(false) - const [aadharFile, setAadharFile] = useState(null) - const [panFile, setPanFile] = useState(null) - const [aadharS3Key, setAadharS3Key] = useState(null) - const [panS3Key, setPanS3Key] = useState(null) - - // New onboarding customization states - const [businessType, setBusinessType] = useState<'registered_company' | 'self_help_group' | 'individual_maker'>('registered_company') - const [storeSlug, setStoreSlug] = useState('') - const [supportEmail, setSupportEmail] = useState('') - const [supportPhone, setSupportPhone] = useState('') - const [selectedCategories, setSelectedCategories] = useState([]) - - // Step 3: Store and Location details - const [storeName, setStoreName] = useState('My Artisan Handloom') - const [storeLogo, setStoreLogo] = useState(null) - const [logoS3Key, setLogoS3Key] = 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' - }) - const [mapCoordinates, setMapCoordinates] = useState({ lat: 12.9716, lng: 77.5946 }) - - // --- Password visibility, confirm password and policy states --- - const [showLoginPass, setShowLoginPass] = useState(false) - const [showSignupPass, setShowSignupPass] = useState(false) - const [showSignupConfirmPass, setShowSignupConfirmPass] = useState(false) - const [confirmPassword, setConfirmPassword] = useState('') - const [policyAccepted, setPolicyAccepted] = useState(false) - - // --- Password Reset Page States --- - const [resetEmail, setResetEmail] = useState('') - const [resetOtpSent, setResetOtpSent] = useState(false) - const [resetOtp, setResetOtp] = useState('') - const [resetPassword, setResetPassword] = useState('') - - // --- OTP Login States --- - const [otpLoginPhone, setOtpLoginPhone] = useState('') - const [otpLoginSent, setOtpLoginSent] = useState(false) - const [otpLoginCode, setOtpLoginCode] = useState('') - - // --- Barcode Generator States --- - const [barcodeProductSku, setBarcodeProductSku] = useState('') - const [barcodeGenerated, setBarcodeGenerated] = useState(false) - - // --- 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([]) - const [orders, setOrders] = useState([]) - const [returns, setReturns] = useState([]) - - // 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) - - // New Heritage UI wizard states - const [productWizardStep, setProductWizardStep] = useState(0) // 0: Catalog list, 1-5: Product upload wizard steps - const [productFormDetails, setProductFormDetails] = useState({ - id: '', - name: '', - category: 'Textiles & Apparel', - description: '', - isGiTagged: false, - primaryImage: 'https://images.unsplash.com/photo-1544022613-e87ca75a784a?auto=format&fit=crop&q=80&w=300', - additionalViews: [] as string[], - videoUrl: '', - view360Url: '', - basePrice: '', - compareAtPrice: '', - trackInventory: true, - sku: '', - initialStock: '', - shippingProfile: 'Standard Fragile', - processingDays: 3, - packageWeight: 1.0 - }) - const [selectedOrderDetail, setSelectedOrderDetail] = useState(null) - const [orderNotes, setOrderNotes] = useState('') - - // 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('') - - // Load data from backend on mount or when profile is complete / logged in - useEffect(() => { - const token = localStorage.getItem('access_token'); - if (token) { - apiFetch(`${CONFIG.apiBaseUrl}/api/profile/`) - .then(res => { - if (res.ok) return res.json(); - throw new Error('Session expired'); - }) - .then(user => { - if (user && user.profile) { - const profile = user.profile; - setPhone(profile.phone || ''); - setPhoneVerified(profile.phone_verified || false); - setEmail(user.email || ''); - setEmailVerified(profile.email_verified || false); - setGstin(profile.gstin || '29AAAAA1111A1Z1'); - setIsGstinVerified(profile.is_gstin_verified || false); - setAadharFile(profile.aadhar_file || null); - setPanFile(profile.pan_file || null); - setAadharS3Key(profile.aadhar_s3_key || null); - setPanS3Key(profile.pan_s3_key || null); - - setBusinessType(profile.business_type || 'registered_company'); - setStoreSlug(profile.store_slug || ''); - setSupportEmail(profile.support_email || ''); - setSupportPhone(profile.support_phone || ''); - setSelectedCategories(profile.categories || []); - - setStoreName(profile.store_name || 'My Artisan Handloom'); - setStoreLogo(profile.store_logo || null); - setLogoS3Key(profile.logo_s3_key || null); - setBusinessBio(profile.business_bio || 'Traditional weaving and local sustainable designs.'); - setAddress({ - street: profile.street || '123 Handloom Lane', - city: profile.city || 'Textile Town', - state: profile.state || 'Karnataka', - pincode: profile.pincode || '560001' - }); - if (profile.latitude && profile.longitude) { - setMapCoordinates({ lat: Number(profile.latitude), lng: Number(profile.longitude) }); - } - - const step = profile.onboarding_step; - if (step >= 7) { - setIsProfileComplete(true); - setCurrentPage('dashboard'); - } else { - setProfileStep(step); - setIsProfileComplete(false); - setCurrentPage('profile-completion'); - } - } - }) - .catch(err => { - console.error('Session restore failed:', err); - localStorage.removeItem('access_token'); - }); - } - }, []); - - useEffect(() => { - if (isProfileComplete || currentPage === 'dashboard') { - // Fetch Products - apiFetch(`${CONFIG.apiBaseUrl}/api/products/`) - .then(res => res.json()) - .then(data => { - if (Array.isArray(data)) setProducts(data); - else if (data && Array.isArray(data.results)) setProducts(data.results); - }) - .catch(err => console.error('Error fetching products:', err)); - - // Fetch Orders - apiFetch(`${CONFIG.apiBaseUrl}/api/orders/`) - .then(res => res.json()) - .then(data => { - if (Array.isArray(data)) setOrders(data); - else if (data && Array.isArray(data.results)) setOrders(data.results); - }) - .catch(err => console.error('Error fetching orders:', err)); - - // Fetch Returns - apiFetch(`${CONFIG.apiBaseUrl}/api/returns/`) - .then(res => res.json()) - .then(data => { - if (Array.isArray(data)) setReturns(data); - else if (data && Array.isArray(data.results)) setReturns(data.results); - }) - .catch(err => console.error('Error fetching returns:', err)); - - // Fetch Wallet - apiFetch(`${CONFIG.apiBaseUrl}/api/wallet/`) - .then(res => res.json()) - .then(data => { - if (data) setWallet(data); - }) - .catch(err => console.error('Error fetching wallet:', err)); - } - }, [isProfileComplete, currentPage]); - - const uploadDocument = async (file: File, fileType: string) => { - try { - const response = await apiFetch(`${CONFIG.apiBaseUrl}/api/profile/presigned-url/`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ file_type: fileType, content_type: file.type }) - }); - if (!response.ok) throw new Error('Failed to get presigned URL'); - const data = await response.json(); - - // Simulate file upload PUT to mock presigned_url - await fetch(data.presigned_url, { - method: 'PUT', - body: file, - headers: { 'Content-Type': file.type } - }).catch(err => console.log('Mock S3 upload:', err)); - - return data.s3_key; - } catch (err) { - console.error(err); - return `suppliers/default/${fileType}.jpg`; - } - }; - - const handleAcceptOrder = (id: string) => { - apiFetch(`${CONFIG.apiBaseUrl}/api/orders/${id}/accept/`, { - method: 'POST' - }) - .then(res => res.json()) - .then(updatedOrder => { - setOrders(orders.map(o => o.id === updatedOrder.id ? updatedOrder : o)) - alert(`Order accepted successfully!`) - }) - .catch(err => console.error(err)) - } - - const handleRejectOrder = (id: string) => { - apiFetch(`${CONFIG.apiBaseUrl}/api/orders/${id}/reject/`, { - method: 'POST' - }) - .then(res => res.json()) - .then(updatedOrder => { - setOrders(orders.map(o => o.id === updatedOrder.id ? updatedOrder : o)) - alert(`Order rejected.`) - }) - .catch(err => console.error(err)) - } - - // Bulk Upload - const [bulkLog, setBulkLog] = useState([]) - const [isParsingBulk, setIsParsingBulk] = useState(false) - const [bulkCsvFile, setBulkCsvFile] = useState(null) - const [bulkZipFile, setBulkZipFile] = useState(null) - - // GSTIN verification simulator - const handleVerifyGstin = () => { - if (!gstin.trim() || gstin.length !== 15) { - alert('Invalid GSTIN length. Must be 15 chars.'); - return; - } - apiFetch(`${CONFIG.apiBaseUrl}/api/profile/submit-gstin/`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ gstin }) - }) - .then(res => { - if (!res.ok) throw new Error('Failed to verify GSTIN.'); - return res.json(); - }) - .then(data => { - if (data.verified) { - setIsGstinVerified(true); - } - }) - .catch(err => { - alert(err.message); - }); - } - - // Handle Logo Upload simulation - const handleLogoChange = async (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 s3Key = await uploadDocument(file, 'logo'); - setLogoS3Key(s3Key); - } - } - - // Route/Navigation Guard: If profile is not complete, redirect to profile-completion - const navigateTo = (page: Page, forceComplete: boolean = false) => { - const publicPages: Page[] = ['home', 'about', 'contact', 'login', 'signup', 'forgot-password', 'login-otp'] - const complete = isProfileComplete || forceComplete - - if (!complete && !publicPages.includes(page) && page !== 'profile-completion') { - alert('Access Denied: Please complete your supplier profile first!') - setCurrentPage('profile-completion') - } else { - setCurrentPage(page) - } - window.scrollTo({ top: 0, behavior: 'smooth' }) - } - - const handleLogout = () => { - apiFetch(`${CONFIG.apiBaseUrl}/api/auth/logout/`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' } - }) - .catch(err => console.error('Error logging out:', err)) - .finally(() => { - localStorage.removeItem('access_token') - // Reset all onboarding & profile states to their defaults - setIsProfileComplete(false) - setProfileStep(1) - setEmail('') - setPhone('') - setPassword('') - setConfirmPassword('') - setPolicyAccepted(false) - - setPhoneVerified(false) - setEmailVerified(false) - setPhoneOtpSent(false) - setEmailOtpSent(false) - setEnteredPhoneOtp('') - setEnteredEmailOtp('') - - setGstin('29AAAAA1111A1Z1') - setIsGstinVerified(false) - setAadharFile(null) - setPanFile(null) - setAadharS3Key(null) - setPanS3Key(null) - - setStoreName('My Artisan Handloom') - setStoreLogo(null) - setLogoS3Key(null) - setBusinessBio('Traditional weaving and local sustainable designs.') - setAddress({ - street: '123 Handloom Lane', - city: 'Textile Town', - state: 'Karnataka', - pincode: '560001' - }) - setMapCoordinates({ lat: 12.9716, lng: 77.5946 }) - - navigateTo('home', true) - }) - } - - - const handleRegisterSubmit = (e: React.FormEvent) => { - e.preventDefault() - - // Email Validation - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - if (!emailRegex.test(email)) { - alert('Please enter a valid email address.'); - return; - } - - // Phone Validation - const phoneRegex = /^[6-9]\d{9}$/; - if (!phoneRegex.test(phone)) { - alert('Please enter a valid 10-digit mobile number starting with 6, 7, 8, or 9.'); - return; - } - - // Password Validation - if (password.length < 8) { - alert('Password must be at least 8 characters long.'); - return; - } - if (password !== confirmPassword) { - alert('Passwords do not match.'); - return; - } - - // Policy Validation - if (!policyAccepted) { - alert('You must accept the Terms of Service and Privacy Policy.'); - return; - } - - apiFetch(`${CONFIG.apiBaseUrl}/api/auth/register/`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - username: email, - email: email, - phone: phone, - password: password, - confirm_password: confirmPassword, - policy_accepted: policyAccepted - }) - }) - .then(res => { - if (!res.ok) throw new Error('Registration failed. Username/email might already be taken.'); - return res.json(); - }) - .then(() => { - // Auto login after signup - apiFetch(`${CONFIG.apiBaseUrl}/api/auth/login/`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ username: email, password: password }) - }) - .then(res => { - if (!res.ok) throw new Error('Auto-login failed.'); - return res.json(); - }) - .then(data => { - localStorage.setItem('access_token', data.access_token); - navigateTo('profile-completion') - }) - }) - .catch(err => { - alert(err.message); - }); - } - - const handleLoginSubmit = (e: React.FormEvent) => { - e.preventDefault() - - if (!email.trim()) { - alert('Please enter your email or phone number.'); - return; - } - - apiFetch(`${CONFIG.apiBaseUrl}/api/auth/login/`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ username: email, password: password }) - }) - .then(res => { - if (!res.ok) throw new Error('Invalid credentials.'); - return res.json(); - }) - .then(data => { - localStorage.setItem('access_token', data.access_token); - if (data.user && data.user.profile) { - const profile = data.user.profile; - setPhone(profile.phone || ''); - setPhoneVerified(profile.phone_verified || false); - setEmail(data.user.email || ''); - setEmailVerified(profile.email_verified || false); - setGstin(profile.gstin || '29AAAAA1111A1Z1'); - setIsGstinVerified(profile.is_gstin_verified || false); - setAadharFile(profile.aadhar_file || null); - setPanFile(profile.pan_file || null); - setAadharS3Key(profile.aadhar_s3_key || null); - setPanS3Key(profile.pan_s3_key || null); - setStoreName(profile.store_name || 'My Artisan Handloom'); - setStoreLogo(profile.store_logo || null); - setLogoS3Key(profile.logo_s3_key || null); - setBusinessBio(profile.business_bio || 'Traditional weaving and local sustainable designs.'); - setAddress({ - street: profile.street || '123 Handloom Lane', - city: profile.city || 'Textile Town', - state: profile.state || 'Karnataka', - pincode: profile.pincode || '560001' - }); - if (profile.latitude && profile.longitude) { - setMapCoordinates({ lat: Number(profile.latitude), lng: Number(profile.longitude) }); - } - - const step = profile.onboarding_step; - if (step >= 4) { - setIsProfileComplete(true); - navigateTo('dashboard', true); - } else { - setProfileStep(step); - setIsProfileComplete(false); - navigateTo('profile-completion'); - } - } else { - setIsProfileComplete(false); - navigateTo('profile-completion'); - } - }) - .catch(err => { - alert(err.message); - }); - } - - const saveProfileBackend = (isComplete: boolean) => { - return apiFetch(`${CONFIG.apiBaseUrl}/api/profile/`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - phone: phone, - phone_verified: phoneVerified, - email_verified: emailVerified, - gstin: gstin, - is_gstin_verified: isGstinVerified, - business_type: businessType, - store_slug: storeSlug, - support_email: supportEmail, - support_phone: supportPhone, - categories: selectedCategories, - aadhar_file: aadharFile, - pan_file: panFile, - aadhar_s3_key: aadharS3Key || 'suppliers/default/aadhar.pdf', - pan_s3_key: panS3Key || 'suppliers/default/pan.pdf', - store_name: storeName, - store_logo: storeLogo || 'https://images.unsplash.com/photo-1513519245088-0e12902e5a38?auto=format&fit=crop&q=80&w=800', - logo_s3_key: logoS3Key || 'suppliers/default/logo.jpg', - business_bio: businessBio, - street: address.street, - city: address.city, - state: address.state, - pincode: address.pincode, - latitude: mapCoordinates.lat, - longitude: mapCoordinates.lng, - is_profile_complete: isComplete - }) - }) - .then(res => { - if (!res.ok) throw new Error('Failed to update profile.'); - return res.json(); - }); - }; - - const handleProfileSubmit = (e: React.FormEvent) => { - e.preventDefault() - if (profileStep === 2) { - setIsGstinVerified(true) - } - const isLastStep = profileStep === 6; - saveProfileBackend(isLastStep) - .then(() => { - if (!isLastStep) { - setProfileStep(prev => prev + 1) - } else { - setProfileStep(7); - } - }) - .catch(err => { - alert(err.message); - }); - } - - // --- Dashboard Logic Actions --- - - // Product creation/modification - const handleSaveProduct = (e: React.FormEvent) => { - e.preventDefault() - const url = isEditingProduct - ? `${CONFIG.apiBaseUrl}/api/products/${productForm.id}/` - : `${CONFIG.apiBaseUrl}/api/products/` - const method = isEditingProduct ? 'PUT' : 'POST' - - apiFetch(url, { - method: method, - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - title: productForm.title, - category: productForm.category, - price: String(productForm.price), - stock: Number(productForm.stock), - sku: productForm.sku || `PROD-${Date.now().toString().slice(-6)}`, - image: productForm.image - }) - }) - .then(res => res.json()) - .then(() => { - // Refresh products from backend - apiFetch(`${CONFIG.apiBaseUrl}/api/products/`) - .then(r => r.json()) - .then(prods => { - if (Array.isArray(prods)) setProducts(prods); - else if (prods && Array.isArray(prods.results)) setProducts(prods.results); - }) - setIsEditingProduct(false) - alert(isEditingProduct ? 'Product modified successfully!' : 'Product uploaded successfully!') - }) - .catch(err => console.error(err)) - - // 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?')) { - apiFetch(`${CONFIG.apiBaseUrl}/api/products/${id}/`, { - method: 'DELETE' - }) - .then(() => { - setProducts(products.filter(p => p.id !== id)) - }) - .catch(err => console.error(err)) - } - } - - const handleBulkUploadSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - if (!bulkCsvFile) { - alert("Please select a CSV file to upload."); - return; - } - setIsParsingBulk(true); - setBulkLog(['Uploading files to server...', 'Parsing CSV data and extracting ZIP images...']); - - const formData = new FormData(); - formData.append('csv_file', bulkCsvFile); - if (bulkZipFile) { - formData.append('zip_file', bulkZipFile); - } - - try { - const token = localStorage.getItem('access_token'); - const response = await fetch(`${CONFIG.apiBaseUrl}/api/products/bulk-upload/`, { - method: 'POST', - headers: { - ...(token ? { 'Authorization': `Bearer ${token}` } : {}) - }, - body: formData - }); - - const data = await response.json(); - if (!response.ok) { - throw new Error(data.error || 'Failed to complete bulk upload.'); - } - - setBulkLog([ - 'CSV file parsed successfully.', - `Extracted and matched images for SKUs from ZIP file.`, - `Successfully added ${data.products?.length || 0} product listings!`, - ]); - - // Refresh product list - const prodRes = await apiFetch(`${CONFIG.apiBaseUrl}/api/products/`); - const prods = await prodRes.json(); - if (Array.isArray(prods)) { - setProducts(prods); - } else if (prods && Array.isArray(prods.results)) { - setProducts(prods.results); - } - setBulkCsvFile(null); - setBulkZipFile(null); - } catch (err: any) { - setBulkLog(prev => [...prev, `Error: ${err.message}`]); - } finally { - setIsParsingBulk(false); - } - }; - - // 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 - } - - apiFetch(`${CONFIG.apiBaseUrl}/api/wallet/withdraw/`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ amount: String(amount) }) - }) - .then(res => { - if (!res.ok) throw new Error('Insufficient funds or invalid request'); - return res.json(); - }) - .then(updatedWallet => { - setWallet(updatedWallet) - setWithdrawAmount('') - alert(`Payout of $${amount} successfully transferred!`) - }) - .catch(err => { - alert(err.message) - }) - } - - // Returns actions - const handleReturnAction = (id: string, action: 'Approved' | 'Rejected') => { - apiFetch(`${CONFIG.apiBaseUrl}/api/returns/${id}/action/`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ action: action }) - }) - .then(res => res.json()) - .then(updatedReturn => { - setReturns(returns.map(ret => ret.id === id ? updatedReturn : ret)) - alert(`Return request ${action.toLowerCase()}!`) - }) - .catch(err => console.error(err)) - } - - // Compute realtime metrics based on state loaded from backend - const computeRealtimeMetrics = () => { - const totalSales = orders - .filter(o => o.status !== 'Cancelled' && o.status !== 'Rejected') - .reduce((sum, o) => sum + Number(o.total), 0) - - const totalEarned = Number(wallet.outstanding) + Number(wallet.withdrawn) - const totalStock = products.reduce((sum, p) => sum + Number(p.stock), 0) - const totalReturns = returns.length - - const isLoggedIn = currentPage === 'dashboard' || currentPage === 'profile-completion' || currentPage === 'welcome-tour' || isProfileComplete - - if (isLoggedIn) { - return { - totalSales, - totalEarned, - stockDetails: totalStock, - returnedItems: totalReturns, - chartValues: orders.length > 0 - ? orders.map(o => Number(o.total)) - : [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - } - } - - return { - totalSales: 45280.00, - totalEarned: 38488.00, - stockDetails: 342, - returnedItems: 12, - chartValues: CONFIG.dashboardData.filters[dateFilter]?.chartValues || [30, 45, 35, 60, 50, 75, 65, 80, 70, 95, 90, 110] - } - } - - const selectedMetrics = computeRealtimeMetrics() - return ( - <> - {/* Dynamic Header */} - {currentPage !== 'dashboard' && ( - - )} - {/* Main Content Area */} - {currentPage !== 'dashboard' ? ( -
- - {/* HOMEPAGE VIEW */} - {currentPage === 'home' && ( -
- {/* Hero Section */} -
-
- -
-
-
-

Sell Globally. Celebrate Craft.

-

Discover authentic Indian craftsmanship, preserved for generations and handcrafted for you.

-
- - -
-
-
- - {/* Discover Heritage Cards */} -
-

Discover Heritage

-
- {/* Category 1 */} -
- -
-
-

Hand-Thrown Pottery

-

Explore Collection

-
-
- {/* Category 2 */} -
- -
-
-

Traditional Textiles

-

Explore Collection

-
-
- {/* Category 3 */} -
- -
-
-

Metallic Arts

-

Explore Collection

-
-
- {/* Category 4 */} -
- -
-
-

Wooden Heritage

-

Explore Collection

-
-
-
-
- - {/* The Tradhox Promise */} -
-
-
-
- verified -

Expand Your Reach

-

Every piece is verified for its origin and traditional crafting methods, ensuring you receive true heritage art.

-
-
- handshake -

Easy Inventory Tools

-

We empower creators directly, bypassing intermediaries to foster sustainable livelihoods for artisanal communities.

-
-
- eco -

Secure Payments

-

Committed to eco-friendly materials and ethical production processes that respect both people and the planet.

-
-
-
-
-
- )} - {/* 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 - style={{ paddingRight: '45px' }} - /> - -
-
-
- -
- -
- -
-
- ) : ( -
-

Create Your Supplier Account

-
- - setEmail(e.target.value)} - required - /> -
-
- - setPhone(e.target.value)} - required - /> -
-
- -
- setPassword(e.target.value)} - required - style={{ paddingRight: '45px' }} - /> - -
-
- -
- -
- setConfirmPassword(e.target.value)} - required - style={{ paddingRight: '45px' }} - /> - -
-
- -
- setPolicyAccepted(e.target.checked)} - required - /> - -
- - -
- )} -
- )} - - {/* FORGOT PASSWORD PAGE */} - {currentPage === 'forgot-password' && ( -
-

Reset Your Password

-

- Enter your registered business email or mobile number to receive a secure password reset link / OTP. -

- - {!resetOtpSent ? ( -
{ e.preventDefault(); if (resetEmail.trim()) { setResetOtpSent(true); alert('Simulated Reset Code (123456) sent successfully!'); } }}> -
- - setResetEmail(e.target.value)} - required - /> -
- -
- -
-
- ) : ( -
{ e.preventDefault(); if (resetOtp === '123456') { setPassword(resetPassword); alert('Password updated successfully! Please login with your new password.'); setResetOtpSent(false); setResetEmail(''); setResetOtp(''); setResetPassword(''); navigateTo('login'); } else { alert('Invalid OTP. Please enter 123456'); } }}> -
- - setResetOtp(e.target.value)} - required - /> -
-
- - setResetPassword(e.target.value)} - required - /> -
- -
- )} -
- )} - - {/* LOGIN WITH OTP PAGE */} - {currentPage === 'login-otp' && ( -
-

Login with OTP

-

- Access your seller portal using a temporary verification code sent to your mobile. -

- - {!otpLoginSent ? ( -
{ e.preventDefault(); if (otpLoginPhone.trim()) { setOtpLoginSent(true); alert('Simulated Login Code (123456) sent to mobile!'); } }}> -
- - setOtpLoginPhone(e.target.value)} - required - /> -
- -
- -
-
- ) : ( -
{ e.preventDefault(); if (otpLoginCode === '123456') { setOtpLoginSent(false); setOtpLoginPhone(''); setOtpLoginCode(''); if (!isProfileComplete) { navigateTo('profile-completion'); } else { navigateTo('dashboard'); } } else { alert('Invalid OTP. Please enter 123456'); } }}> -
- - setOtpLoginCode(e.target.value)} - required - /> -
- -
- )} -
- )} - {currentPage === 'profile-completion' && ( -
-
- Step {profileStep} of 7: { - profileStep === 1 ? 'Welcome' : - profileStep === 2 ? 'Business Details' : - profileStep === 3 ? 'Identity Verification' : - profileStep === 4 ? 'Contact Verification' : - profileStep === 5 ? 'Choose Categories' : - profileStep === 6 ? 'Store Setup' : - 'Verification' - } -
-
-
-
- -

Complete Your Supplier Profile

- -
- {profileStep === 1 && ( -
-
👋
-

Welcome to Tradhox Onboarding

-

- We bridge the gap between traditional Indian artistry and a global marketplace. - Let's set up your supplier profile in a few simple steps. -

- -
- )} - - {profileStep === 2 && ( -
-

Step 2: Business Details

-
- -
- - - -
-
-
- -
- setGstin(e.target.value)} - required - /> - -
- {isGstinVerified && ( -

- ✓ GSTIN verification will be completed within the next 24 hrs -

- )} -
-
- - -
-
- )} - - {profileStep === 3 && ( -
-

Step 3: Identity Verification

- - {/* Aadhaar Upload */} -
- -
- { - if (e.target.files?.[0]) { - const file = e.target.files[0]; - setAadharFile(file.name); - alert(`Aadhaar card selected: ${file.name}. Uploading...`); - const s3Key = await uploadDocument(file, 'aadhar'); - setAadharS3Key(s3Key); - alert(`Aadhaar card uploaded successfully!`); - } - }} - style={{ display: 'none' }} - /> - - - {aadharFile ? `Selected: ${aadharFile}` : 'No file uploaded yet'} - -
-
- - {/* PAN Upload */} -
- -
- { - if (e.target.files?.[0]) { - const file = e.target.files[0]; - setPanFile(file.name); - alert(`PAN card selected: ${file.name}. Uploading...`); - const s3Key = await uploadDocument(file, 'pan'); - setPanS3Key(s3Key); - alert(`PAN card uploaded successfully!`); - } - }} - style={{ display: 'none' }} - /> - - - {panFile ? `Selected: ${panFile}` : 'No file uploaded yet'} - -
-
- -
- - -
-
- )} - - {profileStep === 4 && ( -
-

Step 4: Verify Contacts

- - {/* Phone/WhatsApp Verification */} -
- -
- setPhone(e.target.value)} - disabled={phoneVerified} - /> - -
- - {phoneOtpSent && !phoneVerified && ( -
- setEnteredPhoneOtp(e.target.value)} - style={{ maxWidth: '150px' }} - /> - -
- )} - - {phoneVerified && ( -
- ✓ Mobile & WhatsApp Verified -
- )} -
- - {/* Email Verification */} -
- -
- setEmail(e.target.value)} - disabled={emailVerified} - /> - -
- - {emailOtpSent && !emailVerified && ( -
- setEnteredEmailOtp(e.target.value)} - style={{ maxWidth: '150px' }} - /> - -
- )} - - {emailVerified && ( -
- ✓ Email Verified -
- )} -
- -
- - -
-
- )} - - {profileStep === 5 && ( -
-

Step 5: Choose Product Categories

-

- Select the categories that apply to your artisanal goods. -

-
- {['Sustainable Products', 'Home Decor', 'Eco-Friendly', 'OPOD Products', 'GI Tagged', 'Textiles & Apparel'].map(cat => { - const isSelected = selectedCategories.includes(cat); - return ( -
{ - if (isSelected) { - setSelectedCategories(selectedCategories.filter(c => c !== cat)); - } else { - setSelectedCategories([...selectedCategories, cat]); - } - }} - style={{ - padding: '1rem', - border: isSelected ? '2px solid var(--primary)' : '1px solid var(--border)', - backgroundColor: isSelected ? 'rgba(107, 26, 44, 0.05)' : 'transparent', - borderRadius: '8px', - cursor: 'pointer', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - fontWeight: isSelected ? '600' : 'normal', - transition: 'all 0.2s ease', - textAlign: 'center' - }} - > - {cat} -
- ); - })} -
- -
- - -
-
- )} - - {profileStep === 6 && ( -
-

Step 6: Store Details & Pickup Location

- -
-
- - setStoreName(e.target.value)} - required - /> -
-
- - setStoreSlug(e.target.value.toLowerCase().replace(/[^a-z0-9\-]/g, ''))} - required - /> -
-
- -
-
- - setSupportEmail(e.target.value)} - required - /> -
-
- - setSupportPhone(e.target.value)} - required - /> -
-
- -
- - -
- -
- -
{ - const rect = e.currentTarget.getBoundingClientRect(); - const x = Math.round(e.clientX - rect.left); - const y = Math.round(e.clientY - rect.top); - setMapCoordinates({ lat: Number((12.9 + y * 0.001).toFixed(4)), lng: Number((77.5 + x * 0.001).toFixed(4)) }); - setAddress(prev => ({ - ...prev, - street: `Plot ${x}, Sector ${Math.round(y/10)}, Handicraft Park` - })); - }} - > -
- 📍 -
- Click map to pin location -
-
- -
- - 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 - /> -
-
- -
- - -
-
- )} - - {profileStep === 7 && ( -
-
🎉
-

Setup Complete!

-

- Your supplier profile has been successfully submitted and is under review. You can now access your preview dashboard. -

- -
- )} -
-
- )} - - {/* WELCOME PAGE & SETUP TOUR FOR NEW SELLERS */} - {currentPage === 'welcome-tour' && ( - navigateTo('dashboard')} /> - )} - -
- ) : ( - /* --- FULL SERVICE SUPPLIER ACTIVE DASHBOARD PAGE --- */ -
- - {/* Sidebar */} - <> {isSidebarOpen &&
setIsSidebarOpen(false)}>
} - - {/* Main Workspace Frame */} -
- - {/* Top Workspace Header */} -
- -
- search - -
-
- - -
- {CONFIG.logoLetter} -
-
-
- - {/* Inner Dashboard Tabs Area */} -
- - {/* OVERVIEW PANEL / HOME TAB */} - {dashTab === 'overview' && !selectedOrderDetail && ( -
-
-

Welcome back, Artisan!

-

Your store is looking great. Here is what's happening today.

-
- -
- {/* Left Column (Metrics + Orders) */} -
- - {/* Metric Cards */} -
- {/* Card 1 */} -
-
- Total Sales (₹) -

₹ 42,500

-
- - - -
- - {/* Card 2 */} -
-
- Active Orders -

14

-
- - - -
- - {/* Card 3 */} -
-
- Store Views -

1,204

-
- - - -
-
- - {/* Recent Orders Queue Card */} -
-
-

Recent Orders

- -
-
- - - - - - - - - - - {orders.slice(0, 5).map(o => ( - - - - - - - ))} - -
Order IDDateProduct NameStatus
- - {o.date}{o.item} - - {o.status === 'Pending Acceptance' ? 'Pending' : o.status} - -
-
-
- -
- - {/* Right Column (Live Preview) */} -
-
-
-
-

Live Preview

- -
- - {/* Store Preview Mockup */} -
-
- -
-

{storeName || 'Tradhox Artisan Co.'}

-
-
-
-
-
- -
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-

Your store is currently live and looking great.

- -
-
-
-
-
- )} - {/* PRODUCTS TAB */} - {dashTab === 'products' && ( -
- {productWizardStep === 0 ? ( -
-
-
-

Products

-

Manage your listings, catalog collections, and stock values.

-
- -
- - {/* Filters */} -
- - -
- - {/* Products Cards Grid */} -
- {products.map(p => { - const isOut = p.stock === 0; - const isLow = p.stock > 0 && p.stock <= 5; - return ( -
-
-
- {p.image ? ( - - ) : ( -
🖼️ No Image
- )} - - {isOut ? 'Out of Stock' : isLow ? `Low Stock (${p.stock})` : `In Stock (${p.stock})`} - -
-
-
- {p.category} - {(p as any).is_gi_tagged && ( - GI Tagged - )} -
-

{p.title}

-

₹ {Number(p.price).toFixed(2)}

-
-
-
- - -
-
- ); - })} -
-
- ) : ( - /* Multi-Step Wizard */ -
-
-
-

{productFormDetails.id ? 'Edit Product Details' : 'Add New Product'}

-

Fill out the heritage marketplace listing form.

-
- -
- - {/* Step lines progress */} -
- {['General', 'Media', 'Pricing', 'Shipping', 'Review'].map((tabLabel, idx) => { - const sNum = idx + 1; - const active = productWizardStep === sNum; - const done = productWizardStep > sNum; - return ( -
-
- {done ? '✓' : sNum} -
- {tabLabel} -
- ); - })} -
- -
{ - e.preventDefault(); - if (productWizardStep < 5) { - setProductWizardStep(prev => prev + 1); - } else { - const url = productFormDetails.id - ? `${CONFIG.apiBaseUrl}/api/products/${productFormDetails.id}/` - : `${CONFIG.apiBaseUrl}/api/products/`; - const method = productFormDetails.id ? 'PUT' : 'POST'; - apiFetch(url, { - method, - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - title: productFormDetails.name, - category: productFormDetails.category, - price: String(productFormDetails.basePrice), - stock: Number(productFormDetails.initialStock), - sku: productFormDetails.sku || `PROD-${Date.now().toString().slice(-6)}`, - image: productFormDetails.primaryImage || '/kanchipuram_silk.jpg', - description: productFormDetails.description, - is_gi_tagged: productFormDetails.isGiTagged, - additional_images: productFormDetails.additionalViews, - video_url: productFormDetails.videoUrl, - view_360_url: productFormDetails.view360Url, - compare_at_price: productFormDetails.compareAtPrice ? String(productFormDetails.compareAtPrice) : null, - track_inventory: productFormDetails.trackInventory, - shipping_profile: productFormDetails.shippingProfile, - processing_days: Number(productFormDetails.processingDays), - package_weight: Number(productFormDetails.packageWeight) - }) - }) - .then(res => res.json()) - .then(() => { - apiFetch(`${CONFIG.apiBaseUrl}/api/products/`) - .then(r => r.json()) - .then(prods => { - if (Array.isArray(prods)) setProducts(prods); - else if (prods && Array.isArray(prods.results)) setProducts(prods.results); - }); - setProductWizardStep(0); - alert('Product details successfully processed!'); - }) - .catch(err => alert(err.message)); - } - }} className="space-y-6"> - - {/* Step 1 */} - {productWizardStep === 1 && ( -
-
- - setProductFormDetails({ ...productFormDetails, name: e.target.value })} - required - /> -
-
- - -
-
- - +
+
+ +
+
+
+ +
+ + + attach_money + +
+
+
+
+
+ +
+ + + inventory_2 + +
+
+
+
+
+ + {/* Right Column: Imagery & Category */} +
+
+ +
+
+ +
+
+
+ +
+ +
+
+ +
+
+
+
+ +
+ +
+ + +
+ +
+
+ ); +} diff --git a/src/components/dashboard/tabs/OrdersTab.tsx b/src/components/dashboard/tabs/OrdersTab.tsx new file mode 100644 index 0000000..7655860 --- /dev/null +++ b/src/components/dashboard/tabs/OrdersTab.tsx @@ -0,0 +1,56 @@ +import React from 'react'; + +export default function OrdersTab() { + return ( +
+
+

Orders

+ +
+ +
+ + + + + + + + + + + + + + {[ + { id: '#1045', date: 'Oct 24, 2023', customer: 'Jane Doe', status: 'Processing', type: 'info', total: '$120.00' }, + { id: '#1044', date: 'Oct 22, 2023', customer: 'Alex Johnson', status: 'Shipped', type: 'warning', total: '$45.50' }, + { id: '#1043', date: 'Oct 20, 2023', customer: 'Sarah Williams', status: 'Completed', type: 'success', total: '$210.00' } + ].map(order => ( + + + + + + + + + ))} + +
Order IDDateCustomerStatusTotalAction
{order.id}{order.date}{order.customer}{order.status}{order.total} + +
+
+
+ ); +} diff --git a/src/components/dashboard/tabs/OverviewTab.tsx b/src/components/dashboard/tabs/OverviewTab.tsx new file mode 100644 index 0000000..7436f03 --- /dev/null +++ b/src/components/dashboard/tabs/OverviewTab.tsx @@ -0,0 +1,116 @@ +import React from 'react'; +import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; + +const data = [ + { name: 'Jan', sales: 4000 }, + { name: 'Feb', sales: 3000 }, + { name: 'Mar', sales: 5000 }, + { name: 'Apr', sales: 2780 }, + { name: 'May', sales: 8890 }, + { name: 'Jun', sales: 14500 }, +]; + +export default function OverviewTab() { + const handleDownloadCsv = () => { + // Generate CSV string + const header = "Month,Sales\n"; + const rows = data.map(row => `${row.name},${row.sales}`).join("\n"); + const csvContent = header + rows; + + // Create Blob and trigger download + const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + link.setAttribute("href", url); + link.setAttribute("download", "sales_report.csv"); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }; + + return ( +
+
+

Overview

+ +
+ +
+
+
+

Total Sales

+

$14,500

+
+
+
+
+

Active Orders

+

23

+
+
+
+
+

Page Views

+

1,204

+
+
+
+ + {/* Sales Chart */} +
+

Sales Performance (YTD)

+
+ + + + + + + + + + + `$${value}`} /> + [`$${value}`, 'Sales']} /> + + + +
+
+ +
+

Recent Activity

+ + + + + + + + + + + + + + + + + + + + + + + +
DateCustomerActionStatus
Oct 24, 2023Jane DoePlaced Order #1045Processing
Oct 23, 2023John SmithLeft a 5-star reviewCompleted
+
+
+ ); +} diff --git a/src/components/dashboard/tabs/PaymentsTab.tsx b/src/components/dashboard/tabs/PaymentsTab.tsx new file mode 100644 index 0000000..aaf99d2 --- /dev/null +++ b/src/components/dashboard/tabs/PaymentsTab.tsx @@ -0,0 +1,51 @@ +import React from 'react'; + +export default function PaymentsTab() { + return ( +
+

Payments & Earnings

+ +
+
+
+

Available for Payout

+

$1,240.00

+ +
+
+
+
+

Pending Clearance

+

$320.00

+

Funds will be available in 3-5 business days after order delivery.

+
+
+
+ +
+

Recent Transactions

+ + + + + + + + + + + + + + + + + + + + +
DateDescriptionAmount
Oct 20, 2023Payout to Bank Account (...1234)-$500.00
Oct 19, 2023Order #1042 Revenue+$85.00
+
+
+ ); +} diff --git a/src/components/dashboard/tabs/ProductsTab.tsx b/src/components/dashboard/tabs/ProductsTab.tsx new file mode 100644 index 0000000..d2062b3 --- /dev/null +++ b/src/components/dashboard/tabs/ProductsTab.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { useSeller } from '../../../context/SellerContext'; + +export default function ProductsTab() { + const { setDashTab } = useSeller(); + return ( +
+
+

Products

+
+ + +
+
+ +
+ {[1, 2, 3, 4].map(item => ( +
+
+
+
+
+ image +
+
+
+
+

Handwoven Silk Scarf

+

$85.00

+
+ inventory + 12 in stock +
+
+ +
+
+ ))} +
+
+ ); +} diff --git a/src/components/dashboard/tabs/SettingsTab.tsx b/src/components/dashboard/tabs/SettingsTab.tsx new file mode 100644 index 0000000..141617a --- /dev/null +++ b/src/components/dashboard/tabs/SettingsTab.tsx @@ -0,0 +1,91 @@ +import React from 'react'; +import { useSeller } from '../../../context/SellerContext'; + +export default function SettingsTab() { + const { storeSlug, supportEmail, supportPhone } = useSeller(); + + return ( +
+
+

Settings

+ +
+

Store Profile

+ +
+ +
+ +
+
+ +
+ +
+ +
+

Contact support to change your store URL.

+
+ +
+ +
+ +
+
+
+ +
+

Contact Information

+ +
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+ + +
+

Business Documents

+

Upload any additional documents (like business licenses, identity proof, or craft certifications).

+ +
+
+
+ +
+
+
+
+ +
+ +
+
+
+ ); +} diff --git a/src/components/layout/DashboardHeader.tsx b/src/components/layout/DashboardHeader.tsx new file mode 100644 index 0000000..e7de9ef --- /dev/null +++ b/src/components/layout/DashboardHeader.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { useSeller } from '../../context/SellerContext'; + +export default function DashboardHeader() { + const { handleLogout, storeSlug } = useSeller(); + + return ( + + ); +} diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx new file mode 100644 index 0000000..f20b7c7 --- /dev/null +++ b/src/components/layout/Footer.tsx @@ -0,0 +1,38 @@ +import React from 'react'; + +export default function Footer() { + return ( +
+
+
+ {/* Brand & Copyright */} +
+

Tradhox

+

+ © 2024 Tradhox. Honoring Human Craftsmanship. +

+
+ + {/* Links 1 */} + + + {/* Links 2 */} + +
+
+ +
+ ); +} \ No newline at end of file diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx new file mode 100644 index 0000000..46bd518 --- /dev/null +++ b/src/components/layout/Header.tsx @@ -0,0 +1,88 @@ +import React from 'react'; +import { useSeller } from '../../context/SellerContext'; + +export default function Header() { + const { currentPage, setCurrentPage, isMobileMenuOpen, setIsMobileMenuOpen } = useSeller(); + + return ( + + ); +} diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx new file mode 100644 index 0000000..7bc81fa --- /dev/null +++ b/src/components/layout/Sidebar.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { useSeller } from '../../context/SellerContext'; + +export default function Sidebar() { + const { dashTab, setDashTab } = useSeller(); + + const menuItems = [ + { id: 'overview', icon: 'grid_view', label: 'Overview' }, + { id: 'orders', icon: 'local_shipping', label: 'Orders' }, + { id: 'products', icon: 'inventory_2', label: 'Products' }, + { id: 'payments', icon: 'account_balance_wallet', label: 'Payments & Earnings' }, + { id: 'settings', icon: 'settings', label: 'Settings' } + ]; + + return ( + + ); +} diff --git a/src/components/pages/AboutPage.tsx b/src/components/pages/AboutPage.tsx new file mode 100644 index 0000000..63276e8 --- /dev/null +++ b/src/components/pages/AboutPage.tsx @@ -0,0 +1,98 @@ +import React from 'react'; +import { useSeller } from '../../context/SellerContext'; + +export default function AboutPage() { + const { setCurrentPage } = useSeller(); + + return ( +
+ {/* Hero Section */} +
+
+
+
+ +
+
+
+
+

+ Honoring Human Craftsmanship +

+

+ Preserving Heritage, Inspiring the Modern World. +

+

+ Discover authentic, handcrafted masterworks from generations of skilled artisans. Every piece tells a story of culture, resilience, and unparalleled skill. +

+ +
+
+
+
+
+ + {/* Why Tradhox Section */} +
+
+
+

Why Choose Tradhox

+

+ We bridge the gap between discerning collectors and master artisans, ensuring fairness, authenticity, and enduring quality. +

+
+ +
+ {/* Feature 1 */} +
+
+ + verified + +

Authenticity Guaranteed

+

Direct sourcing from verified artisanal clusters ensures every piece is genuine and ethically procured.

+
+
+ + {/* Feature 2 */} +
+
+ + handshake + +

Fair Trade Promise

+

We empower makers by providing direct market access, ensuring they receive equitable compensation for their craft.

+
+
+ + {/* Feature 3 */} +
+
+ + eco + +

Sustainable Heritage

+

Supporting traditional techniques that inherently respect natural materials and sustainable production cycles.

+
+
+
+
+
+ + +
+ ); +} diff --git a/src/components/pages/AuthForms.tsx b/src/components/pages/AuthForms.tsx new file mode 100644 index 0000000..9e4b1e9 --- /dev/null +++ b/src/components/pages/AuthForms.tsx @@ -0,0 +1,86 @@ +import React from 'react'; +import { useSeller } from '../../context/SellerContext'; + +export default function AuthForms() { + const { activeTab, setActiveTab, email, setEmail, password, setPassword, confirmPassword, setConfirmPassword, phone, setPhone, policyAccepted, setPolicyAccepted, handleLoginSubmit, handleRegisterSubmit } = useSeller(); + + return ( +
+
+
+
+
+

Seller Central Login

+ + + {activeTab === 'login' && ( +
+
+ +
+ setEmail(e.target.value)} /> +
+
+
+ +
+ setPassword(e.target.value)} /> +
+
+ +
+ )} + + {activeTab === 'register' && ( +
+
+ +
+ setEmail(e.target.value)} /> +
+
+
+ +
+ setPhone(e.target.value)} /> +
+
+
+ +
+ setPassword(e.target.value)} /> +
+
+
+ +
+ setConfirmPassword(e.target.value)} /> +
+
+
+
+ +
+
+ +
+ )} +
+
+
+
+
+ ); +} diff --git a/src/components/pages/ContactPage.tsx b/src/components/pages/ContactPage.tsx new file mode 100644 index 0000000..4d59dbd --- /dev/null +++ b/src/components/pages/ContactPage.tsx @@ -0,0 +1,164 @@ +import React from 'react'; + +export default function ContactPage() { + return ( +
+ {/* Header Section */} +
+

Get in Touch

+

+ Whether you have a question about an artisanal piece, a partnership inquiry, or need support with your order, we are here to help. Reach out to the Tradhox team. +

+
+ + {/* Contact Section */} +
+ {/* Left Side: Contact Details */} +
+
+

Contact Information

+ +
+ location_on +
+

Headquarters

+

12 Craft Heritage Lane,
Dashashwamedh Ghat Area,
Varanasi, UP 221001, India

+
+
+ +
+ mail +
+

Email Us

+ namaste@tradhox.com +

We aim to reply within 24 hours.

+
+
+ +
+ call +
+

Call Us

+ +91 800 555 0199 +

Mon-Fri, 9am - 6pm IST

+
+
+
+ + {/* Social Links Card */} +
+

Connect With Us

+ +
+
+ + {/* Right Side: Inquiry Form */} +
+
+

Send an Inquiry

+ +
e.preventDefault()}> +
+
+
+ +
+ +
+
+
+ +
+
+ +
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+
+ + {/* Visit Our Studio Section */} +
+
+
+

Visit Our Studio

+

+ Experience the craftsmanship firsthand. Our Varanasi studio is open to visitors who wish to learn about our sourcing process and see exclusive GI-tagged products before they launch online. +

+ +
+

Studio Hours

+
    +
  • Monday - Friday: 10:00 - 18:00
  • +
  • Saturday: 10:00 - 14:00
  • +
  • Sunday: Closed
  • +
+
+
+ +
+
+ map +

Interactive Map view of Varanasi Studio

+
+ {/* Dot Pattern Overlay */} +
+
+
+
+ + +
+ ); +} diff --git a/src/components/pages/LandingPage.tsx b/src/components/pages/LandingPage.tsx new file mode 100644 index 0000000..5f32ca0 --- /dev/null +++ b/src/components/pages/LandingPage.tsx @@ -0,0 +1,113 @@ +import React from 'react'; +import { useSeller } from '../../context/SellerContext'; + +export default function LandingPage() { + const { setCurrentPage } = useSeller(); + + const handleSellerLogin = () => { + setCurrentPage('login'); + }; + + return ( +
+ {/* Hero Section */} +
+
+
+
+ +
+
+

+ Seasonal Collection +

+

+ The Winter Weaves +

+

+ Discover the warmth of tradition. Hand-loomed pashminas and thick cotton weaves, crafted by generational artisans from the highlands. +

+ +
+
+
+ + {/* Categories Bento Grid */} +
+
+
+

Explore by Craft

+
+
+ +
+ {/* Handloom (Large span - 8 columns) */} + + + {/* Right side items (4 columns) */} +
+ {/* Pottery */} + +
+
+
+

Studio Pottery

+
+
+ + {/* Woodwork */} + +
+
+
+

Carved Woodwork

+
+
+
+
+
+
+ + {/* Add hover zoom style */} + +
+ ); +} diff --git a/src/components/pages/OnboardingWizard.tsx b/src/components/pages/OnboardingWizard.tsx new file mode 100644 index 0000000..ddd49c6 --- /dev/null +++ b/src/components/pages/OnboardingWizard.tsx @@ -0,0 +1,255 @@ +import React from 'react'; +import { useSeller } from '../../context/SellerContext'; + +export default function OnboardingWizard() { + const { + profileStep, setProfileStep, + setIsProfileComplete, setCurrentPage, + businessType, setBusinessType, + gstin, setGstin, + storeSlug, setStoreSlug, + supportEmail, setSupportEmail, + supportPhone, setSupportPhone, + selectedCategories, setSelectedCategories + } = useSeller(); + + const handleNext = () => { + if (profileStep < 3) { + setProfileStep(profileStep + 1); + } else { + setIsProfileComplete(true); + setCurrentPage('dashboard'); + } + }; + + const handleBack = () => { + if (profileStep > 1) { + setProfileStep(profileStep - 1); + } + }; + + const toggleCategory = (cat: string) => { + if (selectedCategories.includes(cat)) { + setSelectedCategories(selectedCategories.filter((c: string) => c !== cat)); + } else { + setSelectedCategories([...selectedCategories, cat]); + } + }; + + const renderStep1 = () => ( +
+ {/* Left Column (Text) */} +
+

+ Welcome to Tradhox — Step 1 of 3 +

+

+ Every Piece You List Keeps a Craft Alive +

+

+ Join a global marketplace dedicated to cultural preservation. Your craftsmanship tells a story. Let's build your store to share it with the world. +

+ +
+
+ + handshake + +
+

Fair Trade Certified

+

Directly connect with buyers seeking authentic, ethically sourced goods.

+
+
+
+ + storefront + +
+

Your Brand, Your Way

+

Customize your storefront to reflect your unique heritage and style.

+
+
+
+ +
+ +
+
+ + {/* Right Column (Image) */} +
+
+
+ Artisan at work + {/* Global Reach Badge */} +
+ public +
+

Global Reach

+

Buyers in 50+ countries

+
+
+
+
+
+
+ ); + + const renderStep2 = () => ( +
+
+
+

+ Step 2 of 3 +

+

+ Business Details +

+ +
+ +
+
+ +
+
+
+ +
+ +
+ setGstin(e.target.value)} /> + receipt_long +
+
+ +
+
+
+ +
+ setSupportEmail(e.target.value)} /> + mail +
+
+
+
+
+ +
+ setSupportPhone(e.target.value)} /> + phone +
+
+
+
+ +
+ +
+

+ tradhox.com/store/ +

+

+ setStoreSlug(e.target.value)} /> +

+
+
+ +
+ + +
+
+
+
+ ); + + const renderStep3 = () => { + const availableCategories = ['Textiles & Apparel', 'Home Decor', 'Jewelry', 'Art & Collectibles', 'Beauty & Wellness', 'Food & Beverages']; + + return ( +
+
+
+

+ Step 3 of 3 +

+

+ What do you sell? +

+

Select all categories that apply to your handcrafted products.

+ +
+ {availableCategories.map(cat => ( +
+
toggleCategory(cat)} + style={{ border: selectedCategories.includes(cat) ? '2px solid var(--bulma-primary)' : '2px solid transparent' }} + > + + + {selectedCategories.includes(cat) ? 'check_circle' : 'circle'} + + + + {cat} + +
+
+ ))} +
+ +
+ + +
+
+
+
+ ); + }; + + return ( +
+
+ {profileStep === 1 && renderStep1()} + {profileStep === 2 && renderStep2()} + {profileStep === 3 && renderStep3()} +
+
+ ); +} diff --git a/src/config.ts b/src/config.ts index 318155e..abe0b64 100644 --- a/src/config.ts +++ b/src/config.ts @@ -121,100 +121,6 @@ export const CONFIG = { } export function apiFetch(url: string, options: RequestInit = {}): Promise { - if ((window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') && !navigator.userAgent.includes('jsdom')) { - const path = url.replace(CONFIG.apiBaseUrl, ''); - let status = 200; - let data: any = {}; - - if (path.startsWith('/api/auth/login/')) { - data = { - access_token: 'mock-access-token', - refresh_token: 'mock-refresh-token', - user: { - id: 1, - username: 'tradhox_artisan', - email: 'artisan@tradhox.com', - profile: { - onboarding_step: 4, - phone_verified: true, - email_verified: true, - is_gstin_verified: true, - aadhar_s3_key: 'mock_aadhar', - pan_s3_key: 'mock_pan', - store_name: 'Tradhox Artisan Co.', - logo_s3_key: 'mock_logo', - street: '123 Loom Lane', - status: 'pending_approval', - phone: '9876543210', - gstin: '29AAAAA1111A1Z1', - business_bio: 'Preserving centuries-old handloom techniques, our family has been creating pure silk and cotton textiles for three generations.', - city: 'Textile Town', - state: 'Karnataka', - pincode: '560001', - business_type: 'individual_maker', - store_slug: 'tradhox-artisan', - support_email: 'hello@tradhox.com', - support_phone: '+91 98765 43210', - selected_categories: ['Textiles & Apparel', 'Sustainable Products'] - } - } - }; - } else if (path.startsWith('/api/auth/register/')) { - data = { message: 'Registration successful' }; - } else if (path.startsWith('/api/auth/verify-otp/')) { - data = { verified: true, message: 'OTP verified successfully' }; - } else if (path.startsWith('/api/profile/submit-gstin/')) { - data = { message: 'GSTIN submitted', is_gstin_verified: true, verified: true }; - } else if (path.startsWith('/api/profile/presigned-url/')) { - data = { url: 'https://mock-s3-upload-url.com', key: 'mock-s3-key' }; - } else if (path.startsWith('/api/profile/')) { - data = { - phone: '9876543210', - phone_verified: true, - email_verified: true, - gstin: '29AAAAA1111A1Z1', - is_gstin_verified: true, - store_name: 'Heritage Weaves & Co.', - business_bio: 'Preserving centuries-old handloom techniques, our family has been creating pure silk and cotton textiles for three generations. Every piece is a testament to the dedication of our master weavers in Varanasi.', - street: '123 Handloom Lane', - city: 'Textile Town', - state: 'Karnataka', - pincode: '560001', - store_logo: '', - business_type: 'individual_maker', - store_slug: 'heritage-weaves', - support_email: 'hello@heritageweaves.in', - support_phone: '+91 98765 43210', - selected_categories: ['Textiles & Apparel', 'Sustainable Products'] - }; - } else if (path.startsWith('/api/products/')) { - data = CONFIG.dashboardData.initialProducts; - } else if (path.startsWith('/api/orders/')) { - if (path.includes('/accept/') || path.includes('/reject/')) { - data = { message: 'Order status updated successfully' }; - } else { - data = CONFIG.dashboardData.initialOrders; - } - } else if (path.startsWith('/api/returns/')) { - if (path.includes('/action/')) { - data = { message: 'Return status updated successfully' }; - } else { - data = CONFIG.dashboardData.initialReturns; - } - } else if (path.startsWith('/api/wallet/withdraw/')) { - data = { message: 'Withdrawal processed successfully' }; - } else if (path.startsWith('/api/wallet/')) { - data = { outstanding: 38488.00, withdrawn: 6792.00, transactions: [] }; - } else if (path.startsWith('/api/auth/logout/')) { - data = { message: 'Logout successful' }; - } - - return Promise.resolve(new Response(JSON.stringify(data), { - status, - headers: { 'Content-Type': 'application/json' } - })); - } - const token = localStorage.getItem('access_token'); const headers = { ...options.headers, diff --git a/src/context/SellerContext.tsx b/src/context/SellerContext.tsx new file mode 100644 index 0000000..98f7b3b --- /dev/null +++ b/src/context/SellerContext.tsx @@ -0,0 +1,822 @@ +import React, { createContext, useContext, useState, useEffect } from 'react'; +import { CONFIG, apiFetch } from '../config'; + +type Page = 'home' | 'about' | 'contact' | 'login' | 'signup' | 'profile-completion' | 'confirmation' | 'dashboard' | 'forgot-password' | 'login-otp' | 'welcome-tour'; +type DashboardTab = 'overview' | 'products' | 'orders' | 'returns' | 'wallet' | 'settings' | 'barcode-generator' | 'analytics'; +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 test' | 'Ready to Ship' | 'Shipped' | 'Delivered' | 'Cancelled' | 'Pending Acceptance' | 'Rejected'; + 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 const SellerContext = createContext(null); + +export const useSeller = () => useContext(SellerContext); + +export const SellerProvider: React.FC<{children: React.ReactNode}> = ({ children }) => { + const [currentPage, setCurrentPage] = useState('home') + const [isSidebarOpen, setIsSidebarOpen] = useState(false) + const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false) + const [activeTab, setActiveTab] = useState<'login' | 'register'>('login') + + // Onboarding Status + const [isProfileComplete, setIsProfileComplete] = useState(false) + const [profileStep, setProfileStep] = useState(1) + + // Registration / Onboarding Form States + const [email, setEmail] = useState('') + const [phone, setPhone] = useState('') + const [password, setPassword] = useState('') + + // Step 1: Verification + const [phoneVerified, setPhoneVerified] = useState(false) + const [emailVerified, setEmailVerified] = useState(false) + const [phoneOtpSent, setPhoneOtpSent] = useState(false) + const [emailOtpSent, setEmailOtpSent] = useState(false) + const [enteredPhoneOtp, setEnteredPhoneOtp] = useState('') + const [enteredEmailOtp, setEnteredEmailOtp] = useState('') + + // Step 2: Business details (moved GSTIN here) + const [gstin, setGstin] = useState('29AAAAA1111A1Z1') + const [isGstinVerified, setIsGstinVerified] = useState(false) + const [aadharFile, setAadharFile] = useState(null) + const [panFile, setPanFile] = useState(null) + const [aadharS3Key, setAadharS3Key] = useState(null) + const [panS3Key, setPanS3Key] = useState(null) + + // New onboarding customization states + const [businessType, setBusinessType] = useState<'registered_company' | 'self_help_group' | 'individual_maker'>('registered_company') + const [storeSlug, setStoreSlug] = useState('') + const [supportEmail, setSupportEmail] = useState('') + const [supportPhone, setSupportPhone] = useState('') + const [selectedCategories, setSelectedCategories] = useState([]) + + // Step 3: Store and Location details + const [storeName, setStoreName] = useState('My Artisan Handloom') + const [storeLogo, setStoreLogo] = useState(null) + const [logoS3Key, setLogoS3Key] = 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' + }) + const [mapCoordinates, setMapCoordinates] = useState({ lat: 12.9716, lng: 77.5946 }) + + // --- Password visibility, confirm password and policy states --- + const [showLoginPass, setShowLoginPass] = useState(false) + const [showSignupPass, setShowSignupPass] = useState(false) + const [showSignupConfirmPass, setShowSignupConfirmPass] = useState(false) + const [confirmPassword, setConfirmPassword] = useState('') + const [policyAccepted, setPolicyAccepted] = useState(false) + + // --- Password Reset Page States --- + const [resetEmail, setResetEmail] = useState('') + const [resetOtpSent, setResetOtpSent] = useState(false) + const [resetOtp, setResetOtp] = useState('') + const [resetPassword, setResetPassword] = useState('') + + // --- OTP Login States --- + const [otpLoginPhone, setOtpLoginPhone] = useState('') + const [otpLoginSent, setOtpLoginSent] = useState(false) + const [otpLoginCode, setOtpLoginCode] = useState('') + + // --- Barcode Generator States --- + const [barcodeProductSku, setBarcodeProductSku] = useState('') + const [barcodeGenerated, setBarcodeGenerated] = useState(false) + + // --- 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([]) + const [orders, setOrders] = useState([]) + const [returns, setReturns] = useState([]) + + // 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) + + // New Heritage UI wizard states + const [productWizardStep, setProductWizardStep] = useState(0) // 0: Catalog list, 1-5: Product upload wizard steps + const [productFormDetails, setProductFormDetails] = useState({ + id: '', + name: '', + category: 'Textiles & Apparel', + description: '', + isGiTagged: false, + primaryImage: 'https://images.unsplash.com/photo-1544022613-e87ca75a784a?auto=format&fit=crop&q=80&w=300', + additionalViews: [] as string[], + videoUrl: '', + view360Url: '', + basePrice: '', + compareAtPrice: '', + trackInventory: true, + sku: '', + initialStock: '', + shippingProfile: 'Standard Fragile', + processingDays: 3, + packageWeight: 1.0 + }) + const [selectedOrderDetail, setSelectedOrderDetail] = useState(null) + const [orderNotes, setOrderNotes] = useState('') + + // 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('') + + // Load data from backend on mount or when profile is complete / logged in + useEffect(() => { + const token = localStorage.getItem('access_token'); + if (token) { + apiFetch(`${CONFIG.apiBaseUrl}/api/profile/`) + .then(res => { + if (res.ok) return res.json(); + throw new Error('Session expired'); + }) + .then(user => { + if (user && user.profile) { + const profile = user.profile; + setPhone(profile.phone || ''); + setPhoneVerified(profile.phone_verified || false); + setEmail(user.email || ''); + setEmailVerified(profile.email_verified || false); + setGstin(profile.gstin || '29AAAAA1111A1Z1'); + setIsGstinVerified(profile.is_gstin_verified || false); + setAadharFile(profile.aadhar_file || null); + setPanFile(profile.pan_file || null); + setAadharS3Key(profile.aadhar_s3_key || null); + setPanS3Key(profile.pan_s3_key || null); + + setBusinessType(profile.business_type || 'registered_company'); + setStoreSlug(profile.store_slug || ''); + setSupportEmail(profile.support_email || ''); + setSupportPhone(profile.support_phone || ''); + setSelectedCategories(profile.categories || []); + + setStoreName(profile.store_name || 'My Artisan Handloom'); + setStoreLogo(profile.store_logo || null); + setLogoS3Key(profile.logo_s3_key || null); + setBusinessBio(profile.business_bio || 'Traditional weaving and local sustainable designs.'); + setAddress({ + street: profile.street || '123 Handloom Lane', + city: profile.city || 'Textile Town', + state: profile.state || 'Karnataka', + pincode: profile.pincode || '560001' + }); + if (profile.latitude && profile.longitude) { + setMapCoordinates({ lat: Number(profile.latitude), lng: Number(profile.longitude) }); + } + + const step = profile.onboarding_step; + if (step >= 7) { + setIsProfileComplete(true); + setCurrentPage('dashboard'); + } else { + setProfileStep(step); + setIsProfileComplete(false); + setCurrentPage('profile-completion'); + } + } + }) + .catch(err => { + console.error('Session restore failed:', err); + localStorage.removeItem('access_token'); + }); + } + }, []); + + useEffect(() => { + if (isProfileComplete || currentPage === 'dashboard') { + // Fetch Products + apiFetch(`${CONFIG.apiBaseUrl}/api/products/`) + .then(res => res.json()) + .then(data => { + if (Array.isArray(data)) setProducts(data); + else if (data && Array.isArray(data.results)) setProducts(data.results); + }) + .catch(err => console.error('Error fetching products:', err)); + + // Fetch Orders + apiFetch(`${CONFIG.apiBaseUrl}/api/orders/`) + .then(res => res.json()) + .then(data => { + if (Array.isArray(data)) setOrders(data); + else if (data && Array.isArray(data.results)) setOrders(data.results); + }) + .catch(err => console.error('Error fetching orders:', err)); + + // Fetch Returns + apiFetch(`${CONFIG.apiBaseUrl}/api/returns/`) + .then(res => res.json()) + .then(data => { + if (Array.isArray(data)) setReturns(data); + else if (data && Array.isArray(data.results)) setReturns(data.results); + }) + .catch(err => console.error('Error fetching returns:', err)); + + // Fetch Wallet + apiFetch(`${CONFIG.apiBaseUrl}/api/wallet/`) + .then(res => res.json()) + .then(data => { + if (data) setWallet(data); + }) + .catch(err => console.error('Error fetching wallet:', err)); + } + }, [isProfileComplete, currentPage]); + + const uploadDocument = async (file: File, fileType: string) => { + try { + const response = await apiFetch(`${CONFIG.apiBaseUrl}/api/profile/presigned-url/`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ file_type: fileType, content_type: file.type }) + }); + if (!response.ok) throw new Error('Failed to get presigned URL'); + const data = await response.json(); + + // Simulate file upload PUT to mock presigned_url + await fetch(data.presigned_url, { + method: 'PUT', + body: file, + headers: { 'Content-Type': file.type } + }).catch(err => console.log('Mock S3 upload:', err)); + + return data.s3_key; + } catch (err) { + console.error(err); + return `suppliers/default/${fileType}.jpg`; + } + }; + + const handleAcceptOrder = (id: string) => { + apiFetch(`${CONFIG.apiBaseUrl}/api/orders/${id}/accept/`, { + method: 'POST' + }) + .then(res => res.json()) + .then(updatedOrder => { + setOrders(orders.map(o => o.id === updatedOrder.id ? updatedOrder : o)) + alert(`Order accepted successfully!`) + }) + .catch(err => console.error(err)) + } + + const handleRejectOrder = (id: string) => { + apiFetch(`${CONFIG.apiBaseUrl}/api/orders/${id}/reject/`, { + method: 'POST' + }) + .then(res => res.json()) + .then(updatedOrder => { + setOrders(orders.map(o => o.id === updatedOrder.id ? updatedOrder : o)) + alert(`Order rejected.`) + }) + .catch(err => console.error(err)) + } + + // Bulk Upload + const [bulkLog, setBulkLog] = useState([]) + const [isParsingBulk, setIsParsingBulk] = useState(false) + const [bulkCsvFile, setBulkCsvFile] = useState(null) + const [bulkZipFile, setBulkZipFile] = useState(null) + + // GSTIN verification simulator + const handleVerifyGstin = () => { + if (!gstin.trim() || gstin.length !== 15) { + alert('Invalid GSTIN length. Must be 15 chars.'); + return; + } + apiFetch(`${CONFIG.apiBaseUrl}/api/profile/submit-gstin/`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ gstin }) + }) + .then(res => { + if (!res.ok) throw new Error('Failed to verify GSTIN.'); + return res.json(); + }) + .then(data => { + if (data.verified) { + setIsGstinVerified(true); + } + }) + .catch(err => { + alert(err.message); + }); + } + + // Handle Logo Upload simulation + const handleLogoChange = async (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 s3Key = await uploadDocument(file, 'logo'); + setLogoS3Key(s3Key); + } + } + + // Route/Navigation Guard: If profile is not complete, redirect to profile-completion + const navigateTo = (page: Page, forceComplete: boolean = false) => { + const publicPages: Page[] = ['home', 'about', 'contact', 'login', 'signup', 'forgot-password', 'login-otp'] + const complete = isProfileComplete || forceComplete + + if (!complete && !publicPages.includes(page) && page !== 'profile-completion') { + alert('Access Denied: Please complete your supplier profile first!') + setCurrentPage('profile-completion') + } else { + setCurrentPage(page) + } + window.scrollTo({ top: 0, behavior: 'smooth' }) + } + + const handleLogout = () => { + apiFetch(`${CONFIG.apiBaseUrl}/api/auth/logout/`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' } + }) + .catch(err => console.error('Error logging out:', err)) + .finally(() => { + localStorage.removeItem('access_token') + // Reset all onboarding & profile states to their defaults + setIsProfileComplete(false) + setProfileStep(1) + setEmail('') + setPhone('') + setPassword('') + setConfirmPassword('') + setPolicyAccepted(false) + + setPhoneVerified(false) + setEmailVerified(false) + setPhoneOtpSent(false) + setEmailOtpSent(false) + setEnteredPhoneOtp('') + setEnteredEmailOtp('') + + setGstin('29AAAAA1111A1Z1') + setIsGstinVerified(false) + setAadharFile(null) + setPanFile(null) + setAadharS3Key(null) + setPanS3Key(null) + + setStoreName('My Artisan Handloom') + setStoreLogo(null) + setLogoS3Key(null) + setBusinessBio('Traditional weaving and local sustainable designs.') + setAddress({ + street: '123 Handloom Lane', + city: 'Textile Town', + state: 'Karnataka', + pincode: '560001' + }) + setMapCoordinates({ lat: 12.9716, lng: 77.5946 }) + + navigateTo('home', true) + }) + } + + + const handleRegisterSubmit = (e: React.FormEvent) => { + e.preventDefault() + + // Email Validation + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(email)) { + alert('Please enter a valid email address.'); + return; + } + + // Phone Validation + const phoneRegex = /^[6-9]\d{9}$/; + if (!phoneRegex.test(phone)) { + alert('Please enter a valid 10-digit mobile number starting with 6, 7, 8, or 9.'); + return; + } + + // Password Validation + if (password.length < 8) { + alert('Password must be at least 8 characters long.'); + return; + } + if (password !== confirmPassword) { + alert('Passwords do not match.'); + return; + } + + // Policy Validation + if (!policyAccepted) { + alert('You must accept the Terms of Service and Privacy Policy.'); + return; + } + + apiFetch(`${CONFIG.apiBaseUrl}/api/auth/register/`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + username: email, + email: email, + phone: phone, + password: password, + confirm_password: confirmPassword, + policy_accepted: policyAccepted + }) + }) + .then(res => { + if (!res.ok) throw new Error('Registration failed. Username/email might already be taken.'); + return res.json(); + }) + .then(() => { + // Auto login after signup + apiFetch(`${CONFIG.apiBaseUrl}/api/auth/login/`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username: email, password: password }) + }) + .then(res => { + if (!res.ok) throw new Error('Auto-login failed.'); + return res.json(); + }) + .then(data => { + localStorage.setItem('access_token', data.access_token); + navigateTo('profile-completion') + }) + }) + .catch(err => { + alert(err.message); + }); + } + + const handleLoginSubmit = (e: React.FormEvent) => { + e.preventDefault() + + if (!email.trim()) { + alert('Please enter your email or phone number.'); + return; + } + + apiFetch(`${CONFIG.apiBaseUrl}/api/auth/login/`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username: email, password: password }) + }) + .then(res => { + if (!res.ok) throw new Error('Invalid credentials.'); + return res.json(); + }) + .then(data => { + localStorage.setItem('access_token', data.access_token); + if (data.user && data.user.profile) { + const profile = data.user.profile; + setPhone(profile.phone || ''); + setPhoneVerified(profile.phone_verified || false); + setEmail(data.user.email || ''); + setEmailVerified(profile.email_verified || false); + setGstin(profile.gstin || '29AAAAA1111A1Z1'); + setIsGstinVerified(profile.is_gstin_verified || false); + setAadharFile(profile.aadhar_file || null); + setPanFile(profile.pan_file || null); + setAadharS3Key(profile.aadhar_s3_key || null); + setPanS3Key(profile.pan_s3_key || null); + setStoreName(profile.store_name || 'My Artisan Handloom'); + setStoreLogo(profile.store_logo || null); + setLogoS3Key(profile.logo_s3_key || null); + setBusinessBio(profile.business_bio || 'Traditional weaving and local sustainable designs.'); + setAddress({ + street: profile.street || '123 Handloom Lane', + city: profile.city || 'Textile Town', + state: profile.state || 'Karnataka', + pincode: profile.pincode || '560001' + }); + if (profile.latitude && profile.longitude) { + setMapCoordinates({ lat: Number(profile.latitude), lng: Number(profile.longitude) }); + } + + const step = profile.onboarding_step; + if (step >= 4) { + setIsProfileComplete(true); + navigateTo('dashboard', true); + } else { + setProfileStep(step); + setIsProfileComplete(false); + navigateTo('profile-completion'); + } + } else { + setIsProfileComplete(false); + navigateTo('profile-completion'); + } + }) + .catch(err => { + alert(err.message); + }); + } + + const saveProfileBackend = (isComplete: boolean) => { + return apiFetch(`${CONFIG.apiBaseUrl}/api/profile/`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + phone: phone, + phone_verified: phoneVerified, + email_verified: emailVerified, + gstin: gstin, + is_gstin_verified: isGstinVerified, + business_type: businessType, + store_slug: storeSlug, + support_email: supportEmail, + support_phone: supportPhone, + categories: selectedCategories, + aadhar_file: aadharFile, + pan_file: panFile, + aadhar_s3_key: aadharS3Key || 'suppliers/default/aadhar.pdf', + pan_s3_key: panS3Key || 'suppliers/default/pan.pdf', + store_name: storeName, + store_logo: storeLogo || 'https://images.unsplash.com/photo-1513519245088-0e12902e5a38?auto=format&fit=crop&q=80&w=800', + logo_s3_key: logoS3Key || 'suppliers/default/logo.jpg', + business_bio: businessBio, + street: address.street, + city: address.city, + state: address.state, + pincode: address.pincode, + latitude: mapCoordinates.lat, + longitude: mapCoordinates.lng, + is_profile_complete: isComplete + }) + }) + .then(res => { + if (!res.ok) throw new Error('Failed to update profile.'); + return res.json(); + }); + }; + + const handleProfileSubmit = (e: React.FormEvent) => { + e.preventDefault() + if (profileStep === 2) { + setIsGstinVerified(true) + } + const isLastStep = profileStep === 6; + saveProfileBackend(isLastStep) + .then(() => { + if (!isLastStep) { + setProfileStep(prev => prev + 1) + } else { + setProfileStep(7); + } + }) + .catch(err => { + alert(err.message); + }); + } + + // --- Dashboard Logic Actions --- + + // Product creation/modification + const handleSaveProduct = (e: React.FormEvent) => { + e.preventDefault() + const url = isEditingProduct + ? `${CONFIG.apiBaseUrl}/api/products/${productForm.id}/` + : `${CONFIG.apiBaseUrl}/api/products/` + const method = isEditingProduct ? 'PUT' : 'POST' + + apiFetch(url, { + method: method, + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + title: productForm.title, + category: productForm.category, + price: String(productForm.price), + stock: Number(productForm.stock), + sku: productForm.sku || `PROD-${Date.now().toString().slice(-6)}`, + image: productForm.image + }) + }) + .then(res => res.json()) + .then(() => { + // Refresh products from backend + apiFetch(`${CONFIG.apiBaseUrl}/api/products/`) + .then(r => r.json()) + .then(prods => { + if (Array.isArray(prods)) setProducts(prods); + else if (prods && Array.isArray(prods.results)) setProducts(prods.results); + }) + setIsEditingProduct(false) + alert(isEditingProduct ? 'Product modified successfully!' : 'Product uploaded successfully!') + }) + .catch(err => console.error(err)) + + // 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?')) { + apiFetch(`${CONFIG.apiBaseUrl}/api/products/${id}/`, { + method: 'DELETE' + }) + .then(() => { + setProducts(products.filter(p => p.id !== id)) + }) + .catch(err => console.error(err)) + } + } + + const handleBulkUploadSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!bulkCsvFile) { + alert("Please select a CSV file to upload."); + return; + } + setIsParsingBulk(true); + setBulkLog(['Uploading files to server...', 'Parsing CSV data and extracting ZIP images...']); + + const formData = new FormData(); + formData.append('csv_file', bulkCsvFile); + if (bulkZipFile) { + formData.append('zip_file', bulkZipFile); + } + + try { + const token = localStorage.getItem('access_token'); + const response = await fetch(`${CONFIG.apiBaseUrl}/api/products/bulk-upload/`, { + method: 'POST', + headers: { + ...(token ? { 'Authorization': `Bearer ${token}` } : {}) + }, + body: formData + }); + + const data = await response.json(); + if (!response.ok) { + throw new Error(data.error || 'Failed to complete bulk upload.'); + } + + setBulkLog([ + 'CSV file parsed successfully.', + `Extracted and matched images for SKUs from ZIP file.`, + `Successfully added ${data.products?.length || 0} product listings!`, + ]); + + // Refresh product list + const prodRes = await apiFetch(`${CONFIG.apiBaseUrl}/api/products/`); + const prods = await prodRes.json(); + if (Array.isArray(prods)) { + setProducts(prods); + } else if (prods && Array.isArray(prods.results)) { + setProducts(prods.results); + } + setBulkCsvFile(null); + setBulkZipFile(null); + } catch (err: any) { + setBulkLog(prev => [...prev, `Error: ${err.message}`]); + } finally { + setIsParsingBulk(false); + } + }; + + // 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 + } + + apiFetch(`${CONFIG.apiBaseUrl}/api/wallet/withdraw/`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ amount: String(amount) }) + }) + .then(res => { + if (!res.ok) throw new Error('Insufficient funds or invalid request'); + return res.json(); + }) + .then(updatedWallet => { + setWallet(updatedWallet) + setWithdrawAmount('') + alert(`Payout of $${amount} successfully transferred!`) + }) + .catch(err => { + alert(err.message) + }) + } + + // Returns actions + const handleReturnAction = (id: string, action: 'Approved' | 'Rejected') => { + apiFetch(`${CONFIG.apiBaseUrl}/api/returns/${id}/action/`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ action: action }) + }) + .then(res => res.json()) + .then(updatedReturn => { + setReturns(returns.map(ret => ret.id === id ? updatedReturn : ret)) + alert(`Return request ${action.toLowerCase()}!`) + }) + .catch(err => console.error(err)) + } + + // Compute realtime metrics based on state loaded from backend + const computeRealtimeMetrics = () => { + const totalSales = orders + .filter(o => o.status !== 'Cancelled' && o.status !== 'Rejected') + .reduce((sum, o) => sum + Number(o.total), 0) + + const totalEarned = Number(wallet.outstanding) + Number(wallet.withdrawn) + const totalStock = products.reduce((sum, p) => sum + Number(p.stock), 0) + const totalReturns = returns.length + + const isLoggedIn = currentPage === 'dashboard' || currentPage === 'profile-completion' || currentPage === 'welcome-tour' || isProfileComplete + + if (isLoggedIn) { + return { + totalSales, + totalEarned, + stockDetails: totalStock, + returnedItems: totalReturns, + chartValues: orders.length > 0 + ? orders.map(o => Number(o.total)) + : [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + } + } + + return { + totalSales: 45280.00, + totalEarned: 38488.00, + stockDetails: 342, + returnedItems: 12, + chartValues: CONFIG.dashboardData.filters[dateFilter]?.chartValues || [30, 45, 35, 60, 50, 75, 65, 80, 70, 95, 90, 110] + } + } + + const selectedMetrics = computeRealtimeMetrics() + + + + const contextValue = { + setBusinessBio, email, resetPassword, setLogoS3Key, setStoreName, setShowLoginPass, storeName, otpLoginSent, setOtpLoginSent, aadharS3Key, logoS3Key, customDates, setProductFormDetails, handleBulkUploadSubmit, setMapCoordinates, setAadharFile, gstin, mapCoordinates, setIsGstinVerified, enteredPhoneOtp, address, setEmailVerified, dashTab, selectedOrderDetail, setShowSignupConfirmPass, handleRegisterSubmit, setBulkCsvFile, confirmPassword, navigateTo, setResetPassword, orderNotes, setOrders, handleRejectOrder, barcodeProductSku, setEnteredEmailOtp, setIsEditingProduct, setBulkLog, isMobileMenuOpen, resetOtp, productWizardStep, setCustomDates, setConfirmPassword, isGstinVerified, bulkCsvFile, setAddress, storeLogo, handleProfileSubmit, setPhoneOtpSent, setOrderNotes, handleDeleteProduct, setWallet, policyAccepted, emailOtpSent, setResetOtpSent, setGstin, phoneOtpSent, showLoginPass, showSignupConfirmPass, setPhoneVerified, aadharFile, setDateFilter, activeTab, computeRealtimeMetrics, setIsParsingBulk, handleLogout, panS3Key, setCurrentPage, selectedCategories, setEmailOtpSent, showSignupPass, resetEmail, handleLogoChange, dateFilter, setShowSignupPass, businessBio, setWithdrawAmount, phoneVerified, currentPage, setResetOtp, enteredEmailOtp, setEnteredPhoneOtp, setPanFile, setIsSidebarOpen, setSelectedOrderDetail, handleAcceptOrder, saveProfileBackend, supportPhone, handleEditClick, setStoreLogo, setEmail, password, setOtpLoginPhone, bulkLog, businessType, setPanS3Key, setProducts, setSupportEmail, setOtpLoginCode, withdrawAmount, handleSaveProduct, setStoreSlug, setBarcodeProductSku, setProductForm, setBulkZipFile, productFormDetails, setIsProfileComplete, otpLoginPhone, orders, setActiveTab, bulkZipFile, isSidebarOpen, setBarcodeGenerated, setBusinessType, handleReturnAction, setSupportPhone, barcodeGenerated, products, productForm, setProductWizardStep, resetOtpSent, emailVerified, panFile, isParsingBulk, setDashTab, setReturns, setPassword, handleWithdrawRequest, uploadDocument, setPolicyAccepted, isEditingProduct, setIsMobileMenuOpen, profileStep, isProfileComplete, setSelectedCategories, storeSlug, phone, setPhone, setAadharS3Key, handleVerifyGstin, setProfileStep, returns, handleLoginSubmit, setResetEmail, wallet, otpLoginCode, supportEmail + }; + + return ( + + {children} + + ); +}; diff --git a/src/index.css b/src/index.css index 1f26110..45e1cdc 100644 --- a/src/index.css +++ b/src/index.css @@ -1,2001 +1,15 @@ -@import url('https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200'); -@import url('https://fonts.googleapis.com/css2?family=Libre+Caslon+Text:ital,wght@0,400;0,700;1,400&family=Work+Sans:wght@300;400;500;600;700&display=swap'); -@import 'bulma/css/bulma.min.css'; - - -/* Global Typography Resets for Smoother Font Rendering */ -body, button, input, select, textarea { - font-family: var(--font-family); - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - text-rendering: optimizeLegibility; -} - - +@import 'bulma/css/bulma.css'; :root { - /* Colors - Artisanal Heritage */ - --primary: #4d0218; /* Primary Maroon */ - --primary-container: #6b1a2c; - --accent: #4d0218; - --bg: #fbf9f8; /* Background */ - --card-bg: #ffffff; /* Surface container lowest */ - --text-dark: #1b1c1c; /* On Surface / On Background */ - --text-muted: #5f5e5b; /* Secondary */ - --border: #dac0c2; /* Outline Variant */ - --outline: #887274; /* Outline */ - --input-focus: #4d0218; - --success: #2A9D8F; /* Muted sage green */ - --error: #ba1a1a; - - /* Stitch Specific Colors */ - --surface: #fbf9f8; - --surface-dim: #dbd9d9; - --surface-bright: #fbf9f8; - --surface-container-lowest: #ffffff; - --surface-container-low: #f5f3f3; - --surface-container: #efeded; - --surface-container-high: #eae8e7; - --surface-container-highest: #e4e2e2; - - /* Typography */ - --font-family: 'Work Sans', system-ui, -apple-system, sans-serif; - --heading-font-family: 'Libre Caslon Text', serif; - - /* Layout */ - --max-width: 1120px; -} - -h1, h2, h3, h4, h5, h6, .logo-text, .form-card-title, .hero-content h1 { - font-family: var(--heading-font-family); -} - -* { - box-sizing: border-box; - margin: 0; - padding: 0; -} - -body { - font-family: var(--font-family); - background-color: var(--bg); - color: var(--text-dark); - line-height: 1.6; - min-height: 100vh; -} - -#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: 4px; - 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; - 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; -} - -.nav-link:hover, .nav-link.active { - opacity: 1; - color: var(--accent); -} - -.nav-buttons { - display: flex; - align-items: center; - gap: 1rem; -} - -/* Buttons */ -.btn { - padding: 0.6rem 1.25rem; - border-radius: 4px; - 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; - align-items: center; - justify-content: center; - gap: 0.5rem; -} - -.btn-primary { - background-color: var(--primary); - color: #ffffff; -} - -.btn-primary:hover { - background-color: rgba(77, 2, 24, 0.9); - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(77, 2, 24, 0.25); -} - -.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; - display: flex; - flex-direction: column; - align-items: center; - width: 100%; -} - -/* 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: 10px; - 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: 8px; - 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: 6px; - 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: 10px; - 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: 8px; - 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: 4px; - 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); + --bulma-primary-h: 342deg; + --bulma-primary-s: 95%; + --bulma-primary-l: 15%; + --bulma-primary: #4d0218; + --bulma-link: #4d0218; + --bulma-body-background-color: #fbf9f8; } -/* 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: 4px; - 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 { - width: 100%; - max-width: 1280px; - 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: 6px; - 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: 8px; - 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: 6px; - 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: 4px; - 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: 6px; - 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: 8px; - 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: 4px; - 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: 4px; - border: 1px solid var(--border); - margin-bottom: 2rem; -} - -/* Tables */ -.dashboard-table-wrapper { - background-color: var(--card-bg); - border-radius: 6px; - 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: 10px; - 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: 6px; - 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: 6px; - 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); -} - -/* Mobile Responsiveness Improvements */ -@media (max-width: 768px) { - .app-header { - flex-direction: column; - gap: 1.25rem; - padding: 1.25rem; - align-items: center; - } - - .nav-links { - flex-wrap: wrap; - justify-content: center; - gap: 1.25rem 1.5rem; - } - - .nav-buttons { - width: 100%; - justify-content: center; - gap: 0.75rem; - } - - .dashboard-sidebar { - padding: 1.25rem 1rem; - border-right: none; - border-bottom: 1px solid rgba(255, 255, 255, 0.1); - } - - .sidebar-menu { - flex-direction: row; - flex-wrap: wrap; - justify-content: center; - gap: 0.5rem; - } - - .sidebar-item-btn { - padding: 0.5rem 0.75rem; - font-size: 0.85rem; - white-space: nowrap; - } - - /* Tables horizontal scroll container */ - .dashboard-table-wrapper { - overflow-x: auto; - -webkit-overflow-scrolling: touch; - } - - /* Forms padding & stacked inputs */ - .form-card { - padding: 1.5rem; - margin: 1rem 0; - } -} - -/* Animated Tour Keyframes */ -@keyframes tourCardEntrance { - 0% { - opacity: 0; - transform: translateY(20px) scale(0.98); - } - 100% { - opacity: 1; - transform: translateY(0) scale(1); - } -} - -@keyframes tourIconBounce { - 0%, 100% { - transform: translateY(0) scale(1); - } - 50% { - transform: translateY(-8px) scale(1.05); - } -} - -@keyframes tourDotPulse { - 0%, 100% { - transform: scale(1); - opacity: 0.8; - } - 50% { - transform: scale(1.25); - opacity: 1; - } -} - -.tour-card-animated { - animation: tourCardEntrance 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards; -} - -.tour-icon-animated { - animation: tourIconBounce 2s infinite ease-in-out; - display: inline-block; -} - -.tour-dot-active { - animation: tourDotPulse 1.5s infinite ease-in-out; -} - -/* --------------------------------------------------------------------------- - Stitch Redesign Layout & Styling Utilities - --------------------------------------------------------------------------- */ -.dashboard-container { - display: flex; - min-height: 100vh; - background-color: var(--bg); -} - -.dashboard-sidebar { - height: 100vh; - width: 16rem; /* 256px */ - position: fixed; - left: 0; - top: 0; - background-color: var(--surface); - border-right: 1px solid var(--outline-variant); - z-index: 50; - display: flex; - flex-direction: column; - padding: 2rem 0; -} - -.dashboard-topbar { - position: fixed; - top: 0; - right: 0; - width: calc(100% - 16rem); - z-index: 40; - background-color: rgba(251, 249, 248, 0.8); - backdrop-filter: blur(8px); - border-bottom: 1px solid var(--outline-variant); - display: flex; - justify-content: space-between; - align-items: center; - height: 4rem; /* 64px */ - padding: 0 2rem; -} - -.dashboard-main { - margin-left: 16rem; - padding: 6rem 2rem 4rem; /* pt-24 pb-16 px-8 */ - flex: 1; - min-height: 100vh; - box-sizing: border-box; -} - -/* Sidebar Specifics */ -.sidebar-brand-area { - padding: 0 2rem; - margin-bottom: 2rem; -} - -.sidebar-profile-card { - display: flex; - align-items: center; - gap: 0.75rem; - padding: 0.5rem; - border-radius: 8px; - cursor: pointer; - transition: background-color 0.2s; -} - -.sidebar-profile-card:hover { - background-color: var(--surface-container-high); -} - -.sidebar-menu-list { - list-style: none; - padding: 0; - margin: 0; - display: flex; - flex-direction: column; - flex-grow: 1; -} - -.sidebar-link { - display: flex; - align-items: center; - gap: 0.75rem; - padding: 0.75rem 2rem; - color: var(--text-muted); - text-decoration: none; - font-weight: 500; - transition: all 0.2s; - border-right: 4px solid transparent; - background: none; - border-top: none; - border-left: none; - border-right: none; - width: 100%; - text-align: left; - cursor: pointer; - font-family: inherit; -} - -.sidebar-link:hover { - background-color: rgba(95, 94, 91, 0.05); - color: var(--primary); -} - -.sidebar-link.active { - background-color: rgba(107, 26, 44, 0.05); - color: var(--primary); - font-weight: bold; - border-right-color: var(--primary); -} - -/* Topbar Components */ -.search-container { - position: relative; - width: 16rem; -} - -.search-input { - width: 100%; - background-color: var(--surface-container-low); - border: 1px solid var(--outline-variant); - border-radius: 4px; - padding: 0.375rem 1rem 0.375rem 2.5rem; - font-size: 0.95rem; - color: var(--text-dark); - transition: all 0.2s; -} - -.search-input:focus { - outline: none; - border-color: var(--primary); -} - -.topbar-actions { - display: flex; - align-items: center; - gap: 1rem; -} - -.topbar-btn { - background: none; - border: none; - color: var(--text-muted); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: color 0.2s, transform 0.1s; -} - -.topbar-btn:hover { - color: var(--primary); -} - -.topbar-btn:active { - transform: scale(0.95); -} - -/* Settings Form Specifics */ -.settings-grid-row { - display: grid; - grid-template-columns: 1fr; - gap: 2rem; -} - -@media (min-width: 768px) { - .settings-grid-row { - grid-template-columns: repeat(12, 1fr); - } - .col-span-4 { - grid-column: span 4 / span 4; - } - .col-span-8 { - grid-column: span 8 / span 8; - } -} - -.settings-card { - background-color: var(--surface-container-lowest); - border: 1px solid var(--outline-variant); - border-radius: 8px; - padding: 2rem; - box-shadow: 0 4px 20px rgba(107, 26, 44, 0.02); -} - -.settings-tab-btn { - background: none; - border: none; - padding-bottom: 0.5rem; - font-weight: 600; - cursor: pointer; - border-bottom: 2px solid transparent; - color: var(--text-muted); - transition: color 0.2s; -} - -.settings-tab-btn:hover { - color: var(--primary); -} - -.settings-tab-btn.active { - color: var(--primary); - border-bottom-color: var(--primary); -} - -/* Custom Workspace Container for Sidebar Clearance */ -.workspace-container { - margin-left: 16rem; - flex: 1; - display: flex; - flex-direction: column; - min-height: 100vh; - width: calc(100% - 16rem); - box-sizing: border-box; -} - -/* Bulma & Tradhox Compatibility Overrides */ -.is-relative { position: relative; } -.overflow-hidden { overflow: hidden; } -.gap-8 { gap: 2rem; } -.gap-4 { gap: 1rem; } -.is-clickable { cursor: pointer; } -.border-bottom { border-bottom: 1px solid var(--border); } -.bg-light { background-color: var(--surface-container-low) !important; } -.bg-background { background-color: var(--bg) !important; } - -/* Custom Heritage Theme Styling */ -.button.is-primary { - background-color: var(--primary-container) !important; - color: #ffffff !important; - border: none !important; - font-family: var(--font-family) !important; - font-weight: 600; -} -.button.is-primary:hover { - background-color: var(--primary) !important; - opacity: 0.9; -} -.button.is-secondary { - background-color: transparent !important; - color: #ffffff !important; - border: 1px solid #ffffff !important; -} -.button.is-secondary:hover { - background-color: rgba(255, 255, 255, 0.1) !important; -} -.title.is-primary { - color: var(--primary-container) !important; -} -.has-text-primary { - color: var(--primary-container) !important; -} -.navbar { - background-color: #ffffff !important; - border-bottom: 1px solid var(--border) !important; -} -.navbar-item { - color: var(--text-muted) !important; - font-family: var(--font-family) !important; -} -.navbar-item:hover, .navbar-item.is-active { - color: var(--primary) !important; -} - -/* Card Styling overrides */ -.card.is-clickable { - border: 1px solid var(--border); - border-radius: 4px; - background-color: #ffffff; - transition: border-color 0.2s ease; - overflow: hidden; -} -.card.is-clickable:hover { - border-color: var(--primary-container); -} - - - -/* Dashboard Layout and Elements */ -aside.menu { - width: 16rem; - position: fixed; - left: 0; - top: 0; - height: 100vh; - background-color: #ffffff; - border-right: 1px solid var(--border); - z-index: 50; - padding: 2rem 1.5rem; - display: flex; - flex-direction: column; - justify-content: space-between; - box-sizing: border-box; -} - -.sidebar-avatar { - width: 3rem !important; - height: 3rem !important; - border-radius: 50% !important; - object-fit: cover !important; - display: block; - margin-bottom: 0.5rem; -} - -.metrics-grid { - display: flex; - gap: 1.5rem; - margin-bottom: 2rem; - width: 100%; -} - -.metric-card { - flex: 1; - background: #ffffff; - border: 1px solid var(--border); - border-radius: 6px; - padding: 1.5rem; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.02); -} - -.dashboard-grid { - display: flex; - gap: 2rem; - width: 100%; -} - -.dashboard-left-col { - flex: 2; - display: flex; - flex-direction: column; - gap: 2rem; -} - -.dashboard-right-col { - flex: 1; - display: flex; - flex-direction: column; - gap: 2rem; -} - -/* Sidebar navigation links */ -.sidebar-link-btn { - display: flex; - align-items: center; - gap: 0.75rem; - width: 100%; - padding: 0.75rem 1rem; - color: var(--text-muted); - background: transparent; - border: none; - border-radius: 4px; - cursor: pointer; - font-family: var(--font-family); - font-size: 0.95rem; - font-weight: 500; - text-align: left; - transition: all 0.2s ease; -} - -.sidebar-link-btn:hover { - background-color: var(--surface-container-low); - color: var(--primary-container); -} - -.sidebar-link-btn.active { - background-color: var(--primary-container); - color: #ffffff; - font-weight: 600; -} - -/* Dashboard Top Bar Header */ -.dashboard-header { - height: 4rem; - border-bottom: 1px solid var(--border); - background-color: #ffffff; - display: flex; - align-items: center; - justify-content: space-between; - padding: 0 2rem; - position: sticky; - top: 0; - z-index: 30; - box-sizing: border-box; -} - -.search-wrapper { - display: flex; - align-items: center; - gap: 0.75rem; - background-color: var(--surface-container-low); - padding: 0.5rem 1rem; - border-radius: 9999px; - border: 1px solid var(--border); - width: 24rem; -} - -.search-wrapper input { - border: none; - background: transparent; - outline: none; - width: 100%; - font-size: 0.875rem; -} - -.header-actions { - display: flex; - align-items: center; - gap: 1rem; -} - -.avatar-badge { - width: 2rem; - height: 2rem; - border-radius: 50%; - background-color: var(--primary-container); - color: #ffffff; - display: flex; - align-items: center; - justify-content: center; - font-weight: 600; - font-size: 0.875rem; -} - -.header-actions button { - background: transparent; - border: none; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - padding: 0.25rem; -} - -/* Products Tab Styling */ -.products-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 2rem; -} -.filters-row { - display: flex; - gap: 1rem; - margin-bottom: 2rem; -} -.filters-row select { - background-color: #ffffff; - border: 1px solid var(--border); - border-radius: 4px; - padding: 0.5rem 1rem; - font-size: 0.875rem; - outline: none; -} -.products-grid { - display: flex; - flex-wrap: wrap; - gap: 1.5rem; - width: 100%; -} -.product-card { - width: calc(33.333% - 1rem); - background-color: #ffffff; - border: 1px solid var(--border); - border-radius: 8px; - overflow: hidden; - display: flex; - flex-direction: column; - justify-content: space-between; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.01); - box-sizing: border-box; -} -@media (max-width: 1024px) { - .product-card { - width: calc(50% - 0.75rem); - } -} -@media (max-width: 600px) { - .product-card { - width: 100%; - } -} -.product-image-container { - position: relative; - aspect-ratio: 16/9; - background-color: var(--surface-container-low); - overflow: hidden; - border-bottom: 1px solid rgba(0, 0, 0, 0.05); -} -.product-image-container img { - width: 100%; - height: 100%; - object-fit: cover; -} -.stock-badge { - position: absolute; - top: 0.75rem; - right: 0.75rem; - padding: 0.25rem 0.5rem; - border-radius: 4px; - font-size: 0.75rem; - font-weight: 600; - border: 1px solid transparent; -} -.stock-badge.in-stock { - background-color: #e6f4ea; - color: #137333; - border-color: #ceead6; -} -.stock-badge.low-stock { - background-color: #fef7e0; - color: #b06000; - border-color: #feebc8; -} -.stock-badge.out-of-stock { - background-color: #fce8e6; - color: #c5221f; - border-color: #fad2cf; -} -.product-card-body { - padding: 1.25rem; - display: flex; - flex-direction: column; - gap: 0.5rem; -} -.product-category-badges { - display: flex; - gap: 0.5rem; - align-items: center; -} -.category-tag { - font-size: 0.625rem; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.05em; - background-color: var(--surface-container); - color: var(--text-muted); - padding: 0.25rem 0.5rem; - border-radius: 4px; -} -.gi-tag { - font-size: 0.625rem; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.05em; - background-color: rgba(107, 26, 44, 0.1); - color: var(--primary-container); - padding: 0.25rem 0.5rem; - border-radius: 4px; -} -.product-card-footer { - padding: 1rem 1.25rem; - border-top: 1px solid rgba(0, 0, 0, 0.05); - display: flex; - gap: 0.75rem; -} -.btn-edit { - flex-grow: 1; - border: 1px solid var(--border); - background-color: transparent; - padding: 0.5rem; - border-radius: 4px; - font-size: 0.75rem; - font-weight: 600; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - gap: 0.5rem; - color: var(--text-dark); - transition: background-color 0.2s; -} -.btn-edit:hover { - background-color: var(--surface-container-low); -} -.btn-delete { - border: 1px solid #fad2cf; - background-color: transparent; - color: #c5221f; - padding: 0.5rem; - border-radius: 4px; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: background-color 0.2s; -} -.btn-delete:hover { - background-color: #fce8e6; -} - -/* Products Tab Styling */ -.products-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 2rem; -} -.filters-row { - display: flex; - gap: 1rem; - margin-bottom: 2rem; -} -.filters-row select { - background-color: #ffffff; - border: 1px solid var(--border); - border-radius: 4px; - padding: 0.5rem 1rem; - font-size: 0.875rem; - outline: none; -} -.products-grid { - display: flex; - flex-wrap: wrap; - gap: 1.5rem; - width: 100%; -} -.product-card { - width: calc(33.333% - 1rem); - background-color: #ffffff; - border: 1px solid var(--border); - border-radius: 8px; - overflow: hidden; - display: flex; - flex-direction: column; - justify-content: space-between; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.01); - box-sizing: border-box; -} -@media (max-width: 1024px) { - .product-card { - width: calc(50% - 0.75rem); - } -} -@media (max-width: 600px) { - .product-card { - width: 100%; - } -} -.product-image-container { - position: relative; - aspect-ratio: 16/9; - background-color: var(--surface-container-low); - overflow: hidden; - border-bottom: 1px solid rgba(0, 0, 0, 0.05); -} -.product-image-container img { - width: 100%; - height: 100%; - object-fit: cover; -} -.stock-badge { - position: absolute; - top: 0.75rem; - right: 0.75rem; - padding: 0.25rem 0.5rem; - border-radius: 4px; - font-size: 0.75rem; - font-weight: 600; - border: 1px solid transparent; -} -.stock-badge.in-stock { - background-color: #e6f4ea; - color: #137333; - border-color: #ceead6; -} -.stock-badge.low-stock { - background-color: #fef7e0; - color: #b06000; - border-color: #feebc8; -} -.stock-badge.out-of-stock { - background-color: #fce8e6; - color: #c5221f; - border-color: #fad2cf; -} -.product-card-body { - padding: 1.25rem; - display: flex; - flex-direction: column; - gap: 0.5rem; -} -.product-category-badges { - display: flex; - gap: 0.5rem; - align-items: center; -} -.category-tag { - font-size: 0.625rem; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.05em; - background-color: var(--surface-container); - color: var(--text-muted); - padding: 0.25rem 0.5rem; - border-radius: 4px; -} -.gi-tag { - font-size: 0.625rem; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.05em; - background-color: rgba(107, 26, 44, 0.1); - color: var(--primary-container); - padding: 0.25rem 0.5rem; - border-radius: 4px; -} -.product-card-footer { - padding: 1rem 1.25rem; - border-top: 1px solid rgba(0, 0, 0, 0.05); - display: flex; - gap: 0.75rem; -} -.btn-edit { - flex-grow: 1; - border: 1px solid var(--border); - background-color: transparent; - padding: 0.5rem; - border-radius: 4px; - font-size: 0.75rem; - font-weight: 600; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - gap: 0.5rem; - color: var(--text-dark); - transition: background-color 0.2s; -} -.btn-edit:hover { - background-color: var(--surface-container-low); -} -.btn-delete { - border: 1px solid #fad2cf; - background-color: transparent; - color: #c5221f; - padding: 0.5rem; - border-radius: 4px; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: background-color 0.2s; -} -.btn-delete:hover { - background-color: #fce8e6; -} -/* Responsive Sidebar & Workspace Container */ -.workspace-container { - min-height: 100vh; - box-sizing: border-box; - width: 100%; -} - -aside.menu { - width: 16rem; - position: fixed; - left: 0; - top: 0; - height: 100vh; - background-color: #ffffff; - border-right: 1px solid var(--border); - z-index: 50; - padding: 2rem 1.5rem; - display: flex; - flex-direction: column; - justify-content: space-between; -} - -@media (min-width: 1024px) { - .workspace-container { - margin-left: 16rem; - width: calc(100% - 16rem); - } - .menu-toggle-btn { - display: none !important; - } -} - -@media (max-width: 1023px) { - .workspace-container { - margin-left: 0; - width: 100%; - } - aside.menu { - display: none; - } - aside.menu.is-active { - display: flex !important; - z-index: 100; - box-shadow: 4px 0 15px rgba(0, 0, 0, 0.1); - } - .sidebar-backdrop { - position: fixed; - inset: 0; - background-color: rgba(0, 0, 0, 0.4); - z-index: 90; - } -} - -/* Bulma burger style custom styling */ -.navbar-burger { - display: none; - cursor: pointer; - background: none; - border: none; -} -@media (max-width: 1023px) { - .navbar-burger { - display: block; - } - .navbar-menu.is-flex { - display: none !important; - } - .navbar-menu.is-active { - display: block !important; - position: absolute; - top: 100%; - left: 0; - width: 100%; - background-color: #ffffff; - box-shadow: 0 4px 10px rgba(0,0,0,0.05); - padding: 1rem; - border-bottom: 1px solid var(--border); - } - .navbar-menu.is-active .navbar-item { - display: block; - padding: 0.75rem 1rem; - } +[data-theme='light'], :root { + color-scheme: light; + --bulma-scheme-main: #fbf9f8; } diff --git a/vite.config.ts b/vite.config.ts index d45cae6..f6173ab 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -6,4 +6,12 @@ export default defineConfig({ plugins: [ react(), ], + server: { + proxy: { + '/api': { + target: 'http://127.0.0.1:8000', + changeOrigin: true, + }, + }, + }, })