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.
+
+
+
+
+ {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 */}