diff --git a/index.html b/index.html index 95db7228..bc123b6d 100644 --- a/index.html +++ b/index.html @@ -105,6 +105,8 @@ + + diff --git a/package-lock.json b/package-lock.json index 703df311..82c8224f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,8 @@ "dotenv": "^16.4.7", "dotenv-webpack": "^8.1.0", "eventemitter3": "^5.0.1", + "html2canvas": "^1.4.1", + "jspdf": "^3.0.3", "jwt-decode": "^4.0.0", "localforage": "^1.10.0", "match-sorter": "^6.3.1", @@ -259,12 +261,10 @@ } }, "node_modules/@babel/runtime": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", - "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -1561,6 +1561,12 @@ "undici-types": "~7.12.0" } }, + "node_modules/@types/pako": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz", + "integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==", + "license": "MIT" + }, "node_modules/@types/prop-types": { "version": "15.7.14", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", @@ -1576,6 +1582,13 @@ "parchment": "^1.1.2" } }, + "node_modules/@types/raf": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", + "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==", + "license": "MIT", + "optional": true + }, "node_modules/@types/react": { "version": "18.3.16", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.16.tgz", @@ -1595,6 +1608,13 @@ "@types/react": "^18.0.0" } }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, "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", @@ -2156,6 +2176,15 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2276,6 +2305,26 @@ } ] }, + "node_modules/canvg": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz", + "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@types/raf": "^3.4.0", + "core-js": "^3.8.3", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.7", + "rgbcolor": "^1.0.1", + "stackblur-canvas": "^2.0.0", + "svg-pathdata": "^6.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/cfb": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", @@ -2388,6 +2437,18 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, + "node_modules/core-js": { + "version": "3.46.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.46.0.tgz", + "integrity": "sha512-vDMm9B0xnqqZ8uSBpZ8sNtRtOdmfShrvT6h2TuQGLs0Is+cR0DYbj/KWP6ALVNbWPpqA/qPLoOuppJN07humpA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/crc-32": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", @@ -2414,6 +2475,15 @@ "node": ">= 8" } }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -2575,6 +2645,16 @@ "node": ">=6.0.0" } }, + "node_modules/dompurify": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.0.tgz", + "integrity": "sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optional": true, + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/dotenv": { "version": "16.4.7", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", @@ -3151,6 +3231,23 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-png": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.4.0.tgz", + "integrity": "sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==", + "license": "MIT", + "dependencies": { + "@types/pako": "^2.0.3", + "iobuffer": "^5.3.2", + "pako": "^2.1.0" + } + }, + "node_modules/fast-png/node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "license": "(MIT AND Zlib)" + }, "node_modules/fast-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", @@ -3187,6 +3284,12 @@ "tough-cookie": "^4.0.0" } }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -3558,6 +3661,19 @@ "node": ">= 0.4" } }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3637,6 +3753,12 @@ "node": ">= 0.4" } }, + "node_modules/iobuffer": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz", + "integrity": "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==", + "license": "MIT" + }, "node_modules/is-arguments": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", @@ -4131,6 +4253,23 @@ "node": ">=6" } }, + "node_modules/jspdf": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.3.tgz", + "integrity": "sha512-eURjAyz5iX1H8BOYAfzvdPfIKK53V7mCpBTe7Kb16PaM8JSXEcUQNBQaiWMI8wY5RvNOPj4GccMjTlfwRBd+oQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.9", + "fast-png": "^6.2.0", + "fflate": "^0.8.1" + }, + "optionalDependencies": { + "canvg": "^3.0.11", + "core-js": "^3.6.0", + "dompurify": "^3.2.4", + "html2canvas": "^1.0.0-rc.5" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -4612,6 +4751,13 @@ "resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-1.5.6.tgz", "integrity": "sha512-rixgxw3SxyJbCaSpo1n35A/fwI1r2rdwMKOTCg/AcG+xOEyZcE8UHVjpZMFCVImzsFoCZeJTT+M/rdEIQYO2nw==" }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT", + "optional": true + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -4757,6 +4903,16 @@ "integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==", "license": "MIT" }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "license": "MIT", + "optional": true, + "dependencies": { + "performance-now": "^2.1.0" + } + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -4947,9 +5103,11 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT", + "optional": true }, "node_modules/regexp.prototype.flags": { "version": "1.5.3", @@ -5030,6 +5188,16 @@ "node": ">=0.10.0" } }, + "node_modules/rgbcolor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", + "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", + "license": "MIT OR SEE LICENSE IN FEEL-FREE.md", + "optional": true, + "engines": { + "node": ">= 0.8.15" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -5429,6 +5597,16 @@ "node": ">=0.8" } }, + "node_modules/stackblur-canvas": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz", + "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.14" + } + }, "node_modules/string.prototype.matchall": { "version": "4.0.11", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", @@ -5562,6 +5740,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svg-pathdata": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", + "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/swiper": { "version": "11.2.10", "resolved": "https://registry.npmjs.org/swiper/-/swiper-11.2.10.tgz", @@ -5648,6 +5836,15 @@ } } }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -5866,6 +6063,15 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/vite": { "version": "5.4.11", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", diff --git a/package.json b/package.json index 02c59115..45aacec0 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,8 @@ "dotenv": "^16.4.7", "dotenv-webpack": "^8.1.0", "eventemitter3": "^5.0.1", + "html2canvas": "^1.4.1", + "jspdf": "^3.0.3", "jwt-decode": "^4.0.0", "localforage": "^1.10.0", "match-sorter": "^6.3.1", diff --git a/public/assets/css/core-extend.css b/public/assets/css/core-extend.css index 32ebbf32..bd512d58 100644 --- a/public/assets/css/core-extend.css +++ b/public/assets/css/core-extend.css @@ -67,34 +67,75 @@ font-weight: 600; z-index: 2; position: relative; - padding: 3px; + padding: 3px; + transition: all 0.3s ease; } +/* ✅ Success */ .timeline-point.completed { background-color: var(--bs-success); - color: white; + color: #fff; + box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); } +/* ❌ Failed */ +.timeline-point.failed { + background-color: var(--bs-danger); + color: #fff; + box-shadow: 0 0 5px rgba(220, 53, 69, 0.5); +} + +/* ⏳ Pending (Active) */ .timeline-point.active { background-color: var(--bs-info); - color: white; - transform: scale(1.1); - padding: 4px; + color: #fff; + transform: scale(1.15); + box-shadow: 0 0 6px rgba(13, 202, 240, 0.5); } +/* Connecting line */ .timeline-line-horizontal { content: ""; position: absolute; - top: 10px; + top: 10px; left: 50%; - width:100% ; + width: 100%; height: 2px; background-color: #dee2e6; z-index: 1; + transition: background-color 0.3s ease; } +/* Make line green for completed sections */ .timeline-item.completed ~ .timeline-line-horizontal { - background-color: #28a745; + background-color: var(--bs-success); +} + +/* Optional: subtle pulse for active step */ +.timeline-point.active::after { + content: ""; + position: absolute; + width: 25px; + height: 25px; + border-radius: 50%; + border: 2px solid var(--bs-info); + animation: pulse 1.5s infinite; + opacity: 0.6; +} + +@keyframes pulse { + 0% { + transform: scale(1); + opacity: 0.6; + } + 70% { + transform: scale(1.5); + opacity: 0; + } + 100% { + transform: scale(1); + opacity: 0; + } } @@ -175,3 +216,38 @@ .h-screen{ height: 100vh; } .h-min { height: min-content; } .h-max { height: max-content; } + + + + +/* ------------------------Text------------------------- */ +@media (min-width: 576px) { + .fs-sm-1 { font-size: calc(1.3rem + 1.6vw) !important; } + .fs-sm-2 { font-size: calc(1.2rem + 1.2vw) !important; } + .fs-sm-3 { font-size: calc(1.1rem + 0.8vw) !important; } + .fs-sm-4 { font-size: calc(1rem + 0.5vw) !important; } + .fs-sm-5 { font-size: 1.05rem !important; } + .fs-sm-6 { font-size: 0.9rem !important; } + + .fs-sm-tiny { font-size: 72% !important; } + .fs-sm-big { font-size: 115% !important; } + .fs-sm-large { font-size: 155% !important; } + .fs-sm-xlarge { font-size: 175% !important; } + .fs-sm-xxlarge { font-size: calc(1.6rem + 3.5vw) !important; } +} + +/* 💻 Medium devices (≥768px) */ +@media (min-width: 768px) { + .fs-md-1 { font-size: calc(1.4125rem + 1.95vw) !important; } + .fs-md-2 { font-size: calc(1.3625rem + 1.35vw) !important; } + .fs-md-3 { font-size: calc(1.3rem + 0.6vw) !important; } + .fs-md-4 { font-size: calc(1.275rem + 0.3vw) !important; } + .fs-md-5 { font-size: 1.125rem !important; } + .fs-md-6 { font-size: 0.9375rem !important; } + + .fs-md-tiny { font-size: 70% !important; } + .fs-md-big { font-size: 112% !important; } + .fs-md-large { font-size: 150% !important; } + .fs-md-xlarge { font-size: 170% !important; } + .fs-md-xxlarge { font-size: calc(1.725rem + 5.7vw) !important; } +} \ No newline at end of file diff --git a/src/components/Tenant/SubScription.jsx b/src/components/Tenant/SubScription.jsx index adb97c70..cd4ebb33 100644 --- a/src/components/Tenant/SubScription.jsx +++ b/src/components/Tenant/SubScription.jsx @@ -105,8 +105,8 @@ const SubScription = ({ onSubmitSubScription, onNext }) => { return (
handlePlanSelection(plan)} > diff --git a/src/components/UserSubscription/Invoice.jsx b/src/components/UserSubscription/Invoice.jsx new file mode 100644 index 00000000..2583a451 --- /dev/null +++ b/src/components/UserSubscription/Invoice.jsx @@ -0,0 +1,306 @@ +import React, { useRef, useState } from "react"; +import html2canvas from "html2canvas"; +import jsPDF from "jspdf"; +import { formatFigure } from "../../utils/appUtils"; + +const Invoice = ({ invoiceData, currencySymbol }) => { + const [isGenerating, setIsGenerating] = useState(false); + const invoiceRef = useRef(null); + + const data = invoiceData || { + razorpayPaymentDetails: { + amount: 19999, + bankDetails: null, + captured: true, + cardDetails: { + cardId: null, + cardType: null, + emi: false, + international: false, + issuer: null, + last4Digits: null, + network: null, + subType: null, + }, + contact: "+919145445127", + createdAt: "2025-10-25T06:46:30", + currency: "INR", + description: "", + email: "avn18042001@gmail.com", + errorCode: "", + errorDescription: "", + fee: 707.97, + internationalPayment: true, + method: "card", + orderId: "order_RXbzfh8d1X1SSg", + paymentId: "pay_RXc08bJHVpjytP", + status: "captured", + tax: 108, + upiDetails: null, + walletDetails: null, + }, + razorpayOrderDetails: { + amount: 19999, + amountDue: 0, + amountPaid: 19999, + attempts: 1, + createdAt: "2025-10-25T06:46:02", + currency: "INR", + orderId: "order_RXbzfh8d1X1SSg", + receipt: "rec_aae16ec3-9571-4c96-bd77-59950fdce236", + status: "paid", + }, + }; + + const formatAmount = (amount) => { + return currencySymbol + amount.toFixed(2); + }; + + const formatDate = (dateString) => { + const date = new Date(dateString); + return date.toLocaleDateString("en-IN", { + year: "numeric", + month: "short", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + }); + }; + + const downloadPDF = async () => { + setIsGenerating(true); + + try { + const invoice = invoiceRef.current; + const canvas = await html2canvas(invoice, { + scale: 2, + useCORS: true, + logging: false, + backgroundColor: "#ffffff", + }); + + const imgData = canvas.toDataURL("image/png"); + const pdf = new jsPDF({ + orientation: "portrait", + unit: "mm", + format: "a4", + }); + + const imgWidth = 210; + const pageHeight = 297; + const imgHeight = (canvas.height * imgWidth) / canvas.width; + + pdf.addImage(imgData, "PNG", 0, 0, imgWidth, imgHeight); + + const paymentId = data.razorpayPaymentDetails.paymentId; + pdf.save(`Invoice_${paymentId}.pdf`); + } catch (error) { + console.error("Error generating PDF:", error); + alert("Failed to generate PDF. Please try again."); + } finally { + setIsGenerating(false); + } + }; + + const payment = data.razorpayPaymentDetails; + const order = data.razorpayOrderDetails; + const subtotal = payment.amount - payment.fee - payment.tax; + + return ( +
+ + +
+
+ {/* Header */} +
+
+

INVOICE

+ Payment Receipt +
+
+
+ INV-{payment.paymentId.slice(-8).toUpperCase()} +
+
+
+ Date: {formatDate(payment.createdAt)} +
+
+ Payment ID: {payment.paymentId} +
+
+ + {order.status.toUpperCase()} + +
+
+ +
+ + {/* Billing Details */} +
+
+
Bill To
+

{payment.customerName || "N/A"}

+

{payment.email || "N/A"}

+

{payment.contact || "N/A"}

+
+
+
+ Payment Information +
+

+ Order ID: {order.orderId} +

+

+ Receipt: {order.receipt} +

+

+ Method:{" "} + {payment.method.charAt(0).toUpperCase() + + payment.method.slice(1)} +

+
+
+ +
+ + {/* Transaction Details */} +
+
+ Transaction Details +
+
+
+
+ Payment Status + + {payment.status.charAt(0).toUpperCase() + + payment.status.slice(1)} + +
+
+
+
+ Currency + {payment.currency} +
+
+
+
+ Card Type + + {payment.cardDetails?.cardType || + payment.method.toUpperCase()} + +
+
+
+
+ Last 4 Digits + + {payment.cardDetails?.last4Digits || "N/A"} + +
+
+
+
+ International + + {payment.internationalPayment ? "Yes" : "No"} + +
+
+
+
+ Captured + + {payment.captured ? "Yes" : "No"} + +
+
+
+
+ +
+ + {/* Amount Breakdown */} +
+
+ Payment Summary +
+
+
+ + + + + + + + + + + + + + + + + + + +
Subtotal{formatFigure(subtotal.toFixed(2),{ + type: "currency", + currency: payment.currency, + })}
Processing Fee{formatFigure(payment.fee.toFixed(2),{ + type: "currency", + currency: payment.currency, + })}
Tax{formatFigure(payment.tax.toFixed(2),{ + type: "currency", + currency: payment.currency, + })}
Total Paid + {formatFigure(payment.amount, { + type: "currency", + currency: payment.currency, + })} +
+
+
+
+ +
+ + {/* Footer */} +
+

+ Thank you for your payment! +

+ + This is a computer-generated invoice and does not require a + signature. + + + Generated on:{" "} + {new Date().toLocaleDateString("en-IN", { + year: "numeric", + month: "long", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + })} + +
+
+
+
+ ); +}; + +export default Invoice; diff --git a/src/components/UserSubscription/ProcessedPayment.jsx b/src/components/UserSubscription/ProcessedPayment.jsx index aabaae27..fa2963c1 100644 --- a/src/components/UserSubscription/ProcessedPayment.jsx +++ b/src/components/UserSubscription/ProcessedPayment.jsx @@ -1,12 +1,24 @@ -import React, { useState, useMemo } from "react"; +import React, { useState, useMemo, useEffect } from "react"; import { useSubscription } from "../../hooks/useAuth"; import { useParams } from "react-router-dom"; import { useCreateTenant, useIndustries } from "../../hooks/useTenant"; -import { frequencyLabel } from "../../utils/appUtils"; +import { + formatCurrency, + formatFigure, + frequencyLabel, +} from "../../utils/appUtils"; import { formatUTCToLocalTime } from "../../utils/dateUtils"; -const ProcessedPayment = () => { +import { PaymentRepository } from "../../repositories/PaymentRepository"; +import { useMakePayment } from "../../hooks/usePayment"; +import { useDispatch } from "react-redux"; +import { setSelfTenant } from "../../slices/localVariablesSlice"; + +const ProcessedPayment = ({ onNext, resetPaymentStep }) => { const { frequency, planName } = useParams(); + const dispatch = useDispatch(); const [selectedPlan, setSelectedPlan] = useState(planName); + const [currentPlan, setCurrentPlan] = useState(null); + const [failPayment, setFailPayment] = useState(null); const { data: plans, isError: isPlanError, @@ -21,13 +33,51 @@ const ProcessedPayment = () => { const planOrder = ["basic", "pro", "super"]; const currentIndex = planOrder.indexOf(planName?.toLowerCase()); - if (currentIndex === -1) return plans; // fallback: show all + if (currentIndex === -1) return plans; const visibleNames = planOrder.slice(currentIndex); + const selected = plans?.find((p) => p.planName === selectedPlan); + debugger; + dispatch( + setSelfTenant({ + planId: selected?.id, + }) + ); + + setCurrentPlan(selected); return plans.filter((p) => visibleNames.includes(p.planName?.toLowerCase()) ); }, [plans, selectedPlan]); + + const loadScript = (src) => { + return new Promise((resolve) => { + const script = document.createElement("script"); + script.src = src; + script.onload = () => resolve(true); + script.onerror = () => resolve(false); + document.body.appendChild(script); + }); + }; + + const { mutate: MakePayment, isPending } = useMakePayment( + (response) => { + onNext(response); + }, + (fail) => { + setFailPayment(fail); + onNext(fail); + }, + currentPlan + ); + const ProcessToPayment = async (payload) => { + const res = await loadScript( + "https://checkout.razorpay.com/v1/checkout.js" + ); + + MakePayment({ amount: 1 }); + }; + const clients = [ { firstName: "Alice", @@ -43,13 +93,65 @@ const ProcessedPayment = () => { reference: "d2e3f4a5-6789-0123-4567-89abcdef0123", }, ]; + + const handleRetry = () => { + setFailPayment(null); + resetPaymentStep(); + }; + + if (failPayment) { + return ( +
+
+
+ +
+

Payment Failed!

+

+ Unfortunately, your payment could not be completed. Please try again + or use a different payment method. +

+ +
+ + + Go Back to Dashboard + +
+ + {failPayment?.error && ( +
+ Error Details: +
+                {JSON.stringify(failPayment.error, null, 2)}
+              
+
+ )} +
+
+ ); + } return ( -
-
-
+
+
+ Make sure your selected details, and process to payment +
+
+
-

+

Choose the Perfect Plan for Your Organization

@@ -58,7 +160,7 @@ const ProcessedPayment = () => {

{visiblePlans?.map((plan) => { - let colSize = "8"; // default 1 card full width + let colSize = "8"; if (visiblePlans.length === 2) colSize = "6"; else if (visiblePlans.length === 3) colSize = "4"; @@ -106,12 +208,13 @@ const ProcessedPayment = () => { })} {selectedPlan && (
-
+
{(() => { const selected = plans?.find( (p) => p.planName === selectedPlan ); if (!selected) return null; + const { planName, description, @@ -123,7 +226,6 @@ const ProcessedPayment = () => { currency, features, } = selected; - return ( <>
@@ -150,42 +252,25 @@ const ProcessedPayment = () => {
Included Features
-
- {Object.entries(features?.modules || {}).map( - ([key, mod]) => ( -
+
+ {features && + Object.entries(features?.modules || {}) + .filter(([key]) => key !== "id") + .map(([key, mod]) => (
-
- - - {mod.name} - - - {mod.enabled ? "Enabled" : "Disabled"} - -
+ + {mod.name}
-
- ) - )} + ))}
@@ -211,6 +296,26 @@ const ProcessedPayment = () => { )} + +
+
+
+
Duration
+
+ {frequencyLabel(frequency, true)} +
+
+ +
+
Total Price
+
+ {formatFigure(price, { + type: "currency", + currency: currency.currencyCode, + })} +
+
+
); })()} @@ -219,8 +324,7 @@ const ProcessedPayment = () => { )}
-
-
Client Info
+
{clients.map((client, idx) => (
@@ -284,8 +388,9 @@ const ProcessedPayment = () => {
diff --git a/src/components/UserSubscription/SubscriptionForm.jsx b/src/components/UserSubscription/SubscriptionForm.jsx index d917f985..e310bc8e 100644 --- a/src/components/UserSubscription/SubscriptionForm.jsx +++ b/src/components/UserSubscription/SubscriptionForm.jsx @@ -9,19 +9,12 @@ import Label from "../common/Label"; import { orgSize, reference } from "../../utils/constants"; import DatePicker from "../common/DatePicker"; import { useCreateTenant, useIndustries } from "../../hooks/useTenant"; +import { useCreateSelfTenant } from "../../hooks/useAuth"; - - -const SubscriptionForm = ({onNext}) => { - - - - +const SubscriptionForm = ({currentStep, + setCurrentStep, + setStepStatus }) => { const { data, isError, isLoading: industryLoading } = useIndustries(); - - - - const { register, handleSubmit, @@ -33,17 +26,22 @@ const SubscriptionForm = ({onNext}) => { defaultValues: OrganizationDefaultValue, }); - const { mutate: CreateTenant, isPending } = useCreateTenant(() => { - // nextstep - if (onNext) onNext(); - }); + const { mutate: CreateTenant, isPending } = useCreateSelfTenant( + (resp) => { + setStepStatus((prev) => ({ ...prev, [currentStep]: "success" })); + setCurrentStep((prev) => prev + 1); + }, + (error) => { + setStepStatus((prev) => ({ ...prev, [currentStep]: "failed" })); + } + ); const onSubmit = (data) => { - CreateTenant(data) + CreateTenant(data); // reset(); }; return ( -
+
@@ -157,24 +155,6 @@ const SubscriptionForm = ({onNext}) => { )}
- {/* Onboarding Date */} -
- - - {errors.onBoardingDate && ( -
- {errors.onBoardingDate.message} -
- )} -
- {/* Organization Size */}