diff --git a/.env b/.env new file mode 100644 index 000000000..a90afdb33 --- /dev/null +++ b/.env @@ -0,0 +1,5 @@ +DB_URL="mongodb://localhost:27017/TrustBridge" +JWT_SECRET="NetDpVFFq8H8tYtWT3bKjKzgTmsXPZ7mylWw/Z4t2PKC09bhJuL9aud0u4GPuGBojCDZ/XHrpHDBmPokRoTA==" +FRONTEND_URL=http://localhost:5173 +REDIS_URL=redis://localhost:6379 +VITE_BACKEND_URL=http://localhost:5000 diff --git a/package-lock.json b/package-lock.json index bf00121ba..018331589 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,17 +8,21 @@ "name": "business-nexus", "version": "0.1.0", "dependencies": { + "agora-rtc-sdk-ng": "^4.24.0", "axios": "^1.6.7", "date-fns": "^3.3.1", + "jwt-decode": "^4.0.0", "lucide-react": "^0.344.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-dropzone": "^14.2.3", - "react-hot-toast": "^2.4.1", - "react-router-dom": "^6.22.1" + "react-hot-toast": "^2.6.0", + "react-router-dom": "^6.22.1", + "socket.io-client": "^4.8.1" }, "devDependencies": { "@eslint/js": "^9.9.1", + "@types/node": "^24.3.1", "@types/react": "^18.3.5", "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.3.1", @@ -31,7 +35,40 @@ "tailwindcss": "^3.4.1", "typescript": "^5.5.3", "typescript-eslint": "^8.3.0", - "vite": "^5.4.2" + "vite": "^5.4.20" + } + }, + "node_modules/@agora-js/media": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@agora-js/media/-/media-4.24.0.tgz", + "integrity": "sha512-foii2klr5+qonLznxN0ZZFejoxLt/W8do79wmIsADPZLw2uZjRP35m0lqUGiLXBKeQ8u3i4UygPzEdFaY26hrw==", + "license": "MIT", + "dependencies": { + "@agora-js/report": "4.24.0", + "@agora-js/shared": "4.24.0", + "agora-rte-extension": "^1.2.4", + "axios": "^1.8.3", + "webrtc-adapter": "8.2.0" + } + }, + "node_modules/@agora-js/report": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@agora-js/report/-/report-4.24.0.tgz", + "integrity": "sha512-MYbtkdY1Ls0KW0iagUzrPzyvqMWlyCWSC5odEb1SQaraAl7DJeDUkf91a3wxKzrjVah+LCxFxsS4lCFDxvKgNA==", + "license": "MIT", + "dependencies": { + "@agora-js/shared": "4.24.0", + "axios": "^1.8.3" + } + }, + "node_modules/@agora-js/shared": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@agora-js/shared/-/shared-4.24.0.tgz", + "integrity": "sha512-Vj67ZcTHZI+1ctWusrEPSSGLM3l6CFiAze/Bi8r7YHRMLivzhZR79nV6GiKvHS3muLAON2YAExznvjPIly6lcg==", + "license": "MIT", + "dependencies": { + "axios": "^1.8.3", + "ua-parser-js": "^0.7.34" } }, "node_modules/@alloc/quick-lru": { @@ -1217,6 +1254,12 @@ "win32" ] }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1270,6 +1313,16 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "node_modules/@types/node": { + "version": "24.3.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.1.tgz", + "integrity": "sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.10.0" + } + }, "node_modules/@types/prop-types": { "version": "15.7.13", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", @@ -1565,6 +1618,29 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agora-rtc-sdk-ng": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/agora-rtc-sdk-ng/-/agora-rtc-sdk-ng-4.24.0.tgz", + "integrity": "sha512-2apG/07EtsuX21ncSF77q+dr6/kDgu9B/RpKtstCtaq46l4/Eraoecewi4zXRUCY3Im+8dzTIXx6jUwyPdxdHQ==", + "license": "MIT", + "dependencies": { + "@agora-js/media": "4.24.0", + "@agora-js/report": "4.24.0", + "@agora-js/shared": "4.24.0", + "agora-rte-extension": "^1.2.4", + "axios": "^1.8.3", + "formdata-polyfill": "^4.0.7", + "pako": "^2.1.0", + "ua-parser-js": "^0.7.34", + "webrtc-adapter": "8.2.0" + } + }, + "node_modules/agora-rte-extension": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/agora-rte-extension/-/agora-rte-extension-1.2.4.tgz", + "integrity": "sha512-0ovZz1lbe30QraG1cU+ji7EnQ8aUu+Hf3F+a8xPml3wPOyUQEK6CTdxV9kMecr9t+fIDrGeW7wgJTsM1DQE7Nw==", + "license": "ISC" + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -1803,9 +1879,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001667", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001667.tgz", - "integrity": "sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw==", + "version": "1.0.30001747", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001747.tgz", + "integrity": "sha512-mzFa2DGIhuc5490Nd/G31xN1pnBnYMadtkyTjefPI7wzypqgCEpeWu9bJr0OnDsyKrW75zA9ZAt7pbQFmwLsQg==", "dev": true, "funding": [ { @@ -1820,7 +1896,8 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/chalk": { "version": "2.4.2", @@ -1965,7 +2042,6 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, "dependencies": { "ms": "^2.1.3" }, @@ -2037,6 +2113,28 @@ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, + "node_modules/engine.io-client": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", + "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2443,6 +2541,29 @@ "reusify": "^1.0.4" } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -2565,6 +2686,18 @@ "node": ">= 6" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -2977,6 +3110,15 @@ "node": ">=6" } }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -3139,8 +3281,7 @@ "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/mz": { "version": "2.7.0", @@ -3177,6 +3318,26 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-releases": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", @@ -3271,6 +3432,12 @@ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true }, + "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/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -3616,9 +3783,9 @@ } }, "node_modules/react-hot-toast": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.5.2.tgz", - "integrity": "sha512-Tun3BbCxzmXXM7C+NI4qiv6lT0uwGh4oAfeJyNOjYUejTsm35mK9iCaYLGv8cBz9L5YxZLx/2ii7zsIwPtPUdw==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz", + "integrity": "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==", "license": "MIT", "dependencies": { "csstype": "^3.1.3", @@ -3802,6 +3969,12 @@ "loose-envify": "^1.1.0" } }, + "node_modules/sdp": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.1.tgz", + "integrity": "sha512-lwsAIzOPlH8/7IIjjz3K0zYBk7aBVVcvjMwt3M4fLxpjMYyy7i3I97SLHebgn4YBjirkzfp3RvRDWSKsh/+WFw==", + "license": "MIT" + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -3844,6 +4017,34 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -4164,6 +4365,39 @@ } } }, + "node_modules/ua-parser-js": { + "version": "0.7.41", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.41.tgz", + "integrity": "sha512-O3oYyCMPYgNNHuO7Jjk3uacJWZF8loBgwrfd/5LE/HyZ3lUIOdniQ7DNXJcIgZbwioZxk0fLfI4EVnetdiX5jg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "license": "MIT", + "bin": { + "ua-parser-js": "script/cli.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/undici-types": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", + "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", + "dev": true, + "license": "MIT" + }, "node_modules/update-browserslist-db": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", @@ -4210,10 +4444,11 @@ "dev": true }, "node_modules/vite": { - "version": "5.4.8", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", - "integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==", + "version": "5.4.20", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.20.tgz", + "integrity": "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==", "dev": true, + "license": "MIT", "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -4268,6 +4503,28 @@ } } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/webrtc-adapter": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-8.2.0.tgz", + "integrity": "sha512-umxCMgedPAVq4Pe/jl3xmelLXLn4XZWFEMR5Iipb5wJ+k1xMX0yC4ZY9CueZUU1MjapFxai1tFGE7R/kotH6Ww==", + "license": "BSD-3-Clause", + "dependencies": { + "sdp": "^3.0.2" + }, + "engines": { + "node": ">=6.0.0", + "npm": ">=3.10.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -4413,6 +4670,35 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/package.json b/package.json index cdfd37b29..b88b4b271 100644 --- a/package.json +++ b/package.json @@ -10,17 +10,21 @@ "preview": "vite preview" }, "dependencies": { + "agora-rtc-sdk-ng": "^4.24.0", + "axios": "^1.6.7", + "date-fns": "^3.3.1", + "jwt-decode": "^4.0.0", "lucide-react": "^0.344.0", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-router-dom": "^6.22.1", - "axios": "^1.6.7", - "date-fns": "^3.3.1", "react-dropzone": "^14.2.3", - "react-hot-toast": "^2.4.1" + "react-hot-toast": "^2.6.0", + "react-router-dom": "^6.22.1", + "socket.io-client": "^4.8.1" }, "devDependencies": { "@eslint/js": "^9.9.1", + "@types/node": "^24.3.1", "@types/react": "^18.3.5", "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.3.1", @@ -34,5 +38,10 @@ "typescript": "^5.5.3", "typescript-eslint": "^8.3.0", "vite": "^5.4.2" + }, + "husky": { + "hooks": { + "pre-commit": "npm run lint" + } } -} \ No newline at end of file +} diff --git a/public/app logo.jpeg b/public/app logo.jpeg new file mode 100644 index 000000000..9f3abdca7 Binary files /dev/null and b/public/app logo.jpeg differ diff --git a/src/App.tsx b/src/App.tsx index 51b12d8bf..98afe7e7a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,104 +1,144 @@ -import React from 'react'; -import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; -import { AuthProvider } from './context/AuthContext'; +import { + BrowserRouter as Router, + Routes, + Route, + Navigate, +} from "react-router-dom"; +import { AuthProvider } from "./context/AuthContext"; +import { SocketProvider } from "./context/SocketContext"; // Layouts -import { DashboardLayout } from './components/layout/DashboardLayout'; +import { DashboardLayout } from "./components/layout/DashboardLayout"; // Auth Pages -import { LoginPage } from './pages/auth/LoginPage'; -import { RegisterPage } from './pages/auth/RegisterPage'; +import { LoginPage } from "./pages/auth/LoginPage"; +import { RegisterPage } from "./pages/auth/RegisterPage"; // Dashboard Pages -import { EntrepreneurDashboard } from './pages/dashboard/EntrepreneurDashboard'; -import { InvestorDashboard } from './pages/dashboard/InvestorDashboard'; +import { EntrepreneurDashboard } from "./pages/dashboard/EntrepreneurDashboard"; +import { InvestorDashboard } from "./pages/dashboard/InvestorDashboard"; +import { AdminDashboard } from "./pages/dashboard/AdminDashboard"; // Profile Pages -import { EntrepreneurProfile } from './pages/profile/EntrepreneurProfile'; -import { InvestorProfile } from './pages/profile/InvestorProfile'; +import { EntrepreneurProfile } from "./pages/profile/EntrepreneurProfile"; +import { InvestorProfile } from "./pages/profile/InvestorProfile"; // Feature Pages -import { InvestorsPage } from './pages/investors/InvestorsPage'; -import { EntrepreneursPage } from './pages/entrepreneurs/EntrepreneursPage'; -import { MessagesPage } from './pages/messages/MessagesPage'; -import { NotificationsPage } from './pages/notifications/NotificationsPage'; -import { DocumentsPage } from './pages/documents/DocumentsPage'; -import { SettingsPage } from './pages/settings/SettingsPage'; -import { HelpPage } from './pages/help/HelpPage'; -import { DealsPage } from './pages/deals/DealsPage'; +import { InvestorsPage } from "./pages/investors/InvestorsPage"; +import { EntrepreneursPage } from "./pages/entrepreneurs/EntrepreneursPage"; +import { MessagesPage } from "./pages/messages/MessagesPage"; +import { NotificationsPage } from "./pages/notifications/NotificationsPage"; +import { DocumentsPage } from "./pages/documents/DocumentsPage"; +import { SettingsPage } from "./pages/settings/SettingsPage"; +import { HelpPage } from "./pages/help/HelpPage"; +import { DealsPage } from "./pages/deals/DealsPage"; // Chat Pages -import { ChatPage } from './pages/chat/ChatPage'; +import { ChatPage } from "./pages/chat/ChatPage"; +import { ForgotPasswordPage } from "./pages/auth/ForgotPasswordPage"; +import { ResetPasswordPage } from "./pages/auth/ResetPasswordPage"; +//import { VideoCall } from "./components/webRTC/Videocall"; +//import { AudioCall } from "./components/webRTC/AudioCall"; +import { Toaster } from "react-hot-toast"; +import { UserManagement } from "./pages/admin/UserManagement"; +import { Activities } from "./pages/admin/activities"; +import { Entrepreneurj } from "./pages/admin/entrepreneur"; +import { Investors } from "./pages/admin/investors"; +import { Campaigns } from "./pages/admin/campaigns"; +import { HomePage } from "./pages/home/HomePage"; +import { LoginWithOAuthPage } from "./pages/auth/LoginWithOAuthPage"; function App() { return ( - - - {/* Authentication Routes */} - } /> - } /> - - {/* Dashboard Routes */} - }> - } /> - } /> - - - {/* Profile Routes */} - }> - } /> - } /> - - - {/* Feature Routes */} - }> - } /> - - - }> - } /> - - - }> - } /> - - - }> - } /> - - - }> - } /> - - - }> - } /> - - - }> - } /> - - - }> - } /> - - - {/* Chat Routes */} - }> - } /> - } /> - - - {/* Redirect root to login */} - } /> - - {/* Catch all other routes and redirect to login */} - } /> - - + + + + {/* Authentication Routes */} + } /> + } /> + } /> + } /> + } /> + + {/* Dashboard Routes */} + }> + } /> + } /> + } /> + + + }> + } /> + } /> + } /> + } /> + } /> + + + {/* Profile Routes */} + }> + } + /> + } /> + + + {/* Feature Routes */} + }> + } /> + + + }> + } /> + + + }> + } /> + + + }> + } /> + + + }> + } /> + + + }> + } /> + + + }> + } /> + + + }> + + }> + } /> + + + {/* Chat Routes */} + }> + } /> + + + {/* Redirect root to login */} + } /> + + {/* Catch all other routes and redirect to login */} + } + /> + + + + ); } -export default App; \ No newline at end of file +export default App; diff --git a/src/components/camp/CampForm.tsx b/src/components/camp/CampForm.tsx new file mode 100644 index 000000000..a474e71c0 --- /dev/null +++ b/src/components/camp/CampForm.tsx @@ -0,0 +1,167 @@ +"use client"; +import React, { useState } from "react"; +import axios from "axios"; +import toast from "react-hot-toast"; + +interface CampFormProps { + onSuccess: () => void; +} + +const CampForm: React.FC = ({ onSuccess }) => { + const [formData, setFormData] = useState({ + title: "", + description: "", + goalAmount: "", + startDate: "", + endDate: "", + category: "Other", + }); + const [images, setImages] = useState(null); + const [previewUrls, setPreviewUrls] = useState([]); + const [loading, setLoading] = useState(false); + + const handleFileChange = (e: React.ChangeEvent) => { + if (e.target.files) { + setImages(e.target.files); + const urls = Array.from(e.target.files).map((file) => + URL.createObjectURL(file) + ); + setPreviewUrls(urls); + } + }; + + const handleChange = (e: React.ChangeEvent) => { + setFormData({ ...formData, [e.target.name]: e.target.value }); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + + try { + const data = new FormData(); + Object.entries(formData).forEach(([key, value]) => data.append(key, value)); + if (images) { + for (let i = 0; i < images.length; i++) { + data.append("images", images[i]); + } + } + + await axios.post("http://localhost:5000/admin/campaigns", data, { + headers: { "Content-Type": "multipart/form-data" }, + }); + + toast.success("Campaign created!"); + setFormData({ + title: "", + description: "", + goalAmount: "", + startDate: "", + endDate: "", + category: "Other", + }); + setImages(null); + setPreviewUrls([]); + onSuccess(); + } catch (error: any) { + console.error(error); +toast.error(error.response?.data?.message || error.message || "Failed to create campaign"); + + } finally { + setLoading(false); + } + }; + + return ( +
+

Add New Campaign

+ + + - +
- - +
- + + {/* Update profile */} + + +

+ Profile Details +

+
+ + {currentUser?.role === "investor" ? ( + + ) : ( + + )} + +
+ {/* Security Settings */} -

Security Settings

+

+ Security Settings +

-

Two-Factor Authentication

+

+ Two-Factor Authentication +

Add an extra layer of security to your account

- Not Enabled + + Not Enabled +
- +
-

Change Password

+

+ Change Password +

- - - - - - + + + + + +
@@ -172,4 +259,4 @@ export const SettingsPage: React.FC = () => {
); -}; \ No newline at end of file +}; diff --git a/src/pages/webRTC/AudioCall.tsx b/src/pages/webRTC/AudioCall.tsx new file mode 100644 index 000000000..beb701a75 --- /dev/null +++ b/src/pages/webRTC/AudioCall.tsx @@ -0,0 +1,235 @@ +import React, { useEffect, useRef, useState } from "react"; +import { useNavigate, useParams } from "react-router-dom"; +import { useAuth } from "../../context/AuthContext"; +import { useSocket } from "../../context/SocketContext"; +import toast from "react-hot-toast"; +import AgoraRTC, { ILocalTrack } from "agora-rtc-sdk-ng"; +import axios from "axios"; + +export const AudioCall: React.FC = () => { + const { roomId, userId, isIncommingCall } = useParams(); + const [isMute, setIsMute] = useState(false); + const { socket } = useSocket(); + const { user } = useAuth(); + const navigate = useNavigate(); + const APP_ID = import.meta.env.VITE_APP_ID; + const CHANNEL = String(roomId); + const [joined, setJoined] = useState(false); + const localAudioRef = useRef(null); + const remoteAudioRef = useRef(null); + const localTracksRef = useRef([]); + const clientRef = useRef(null); + const [token, setToken] = useState(null); + const [uid, setUid] = useState(null); + const URL = import.meta.env.VITE_BACKEND_URL; + + useEffect(() => { + const fetchToken = async () => { + const uid = String(Date.now()); + setUid(uid); + + try { + const res = await axios.get(`${URL}/agora/rtc/${CHANNEL}/${uid}`, { + withCredentials: true, + }); + setToken(res.data.token); + } catch (err) { + console.error("Failed to fetch token:", err); + } + }; + + fetchToken(); + }, [CHANNEL, URL]); + + useEffect(() => { + if (!token || !uid) return; + + AgoraRTC.setLogLevel(0); + + const init = async () => { + const client = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" }); + clientRef.current = client; + + // 🔹 Set up listeners BEFORE joining/publishing + client.on("user-published", async (user, mediaType) => { + if (user.uid === uid) return; // skip own stream + await client.subscribe(user, mediaType); + console.log("Subscribed to:", user.uid); + + if (mediaType === "audio") { + user.audioTrack?.play(); + } + }); + + client.on("user-unpublished", (user, mediaType) => { + if (mediaType === "audio" && remoteAudioRef.current) { + remoteAudioRef.current.innerHTML = ""; + } + }); + + try { + await client.join(APP_ID, CHANNEL, token, uid); + + // create local tracks + localTracksRef.current = [await AgoraRTC.createMicrophoneAudioTrack()]; + + // publish AFTER listeners are ready + await client.publish(localTracksRef.current); + + localTracksRef.current[0].play(localAudioRef.current!); + setJoined(true); + } catch (err) { + console.error("Agora join error:", err); + } + + // Outgoing call + const isIncoming = isIncommingCall === "true"; + if (!isIncoming) { + socket?.emit("start-call", { + from: user?.userId, + to: userId, + roomId: CHANNEL, + callType: "audio", + fromName: user?.name, + }); + } + }; + + init(); + + return () => { + cleanup(); + }; + }, [CHANNEL, isIncommingCall,APP_ID, user, userId, token, uid, socket]); + + // === Cleanup === + const cleanup = async () => { + localTracksRef.current.forEach((track) => { + track.stop(); + track.close(); + }); + if (clientRef.current) { + clientRef.current.removeAllListeners(); // ✅ avoid duplicate handlers + await clientRef.current.leave(); + } + setJoined(false); + }; + + // Mute mic + const muteMic = async () => { + if (localTracksRef.current[0]) { + await localTracksRef.current[0].setEnabled(false); + setIsMute(true); + console.log("Microphone muted"); + } + }; + + // Unmute mic + const unmuteMic = async () => { + if (localTracksRef.current[0]) { + await localTracksRef.current[0].setEnabled(true); + + setIsMute(false); + console.log("Microphone unmuted"); + } + }; + + useEffect(() => { + socket?.on("call-accepted", () => { + toast.success("Call accepted"); + }); + + socket?.on("call-ended", () => { + toast.success("Call ended"); + navigate(`/chat/${userId}`); + }); + + socket?.on("call-rejected", () => { + toast.error("Your call is declined."); + navigate(`/chat/${userId}`); + }); + + socket?.on("receiver-offline", async () => { + toast.error("The receiver is offline."); + await cleanup(); + navigate(`/chat/${userId}`); + }); + + return () => { + socket?.off("call-accepted"); + socket?.off("receiver-offline"); + socket?.off("call-rejected"); + socket?.off("call-ended"); + }; + }, [socket, userId,navigate]); + return ( +
+

Agora 1-to-1 Audio Call

+
+
+
+
+

+ P +

+
+ {/* Local audio as overlay (picture-in-picture) */} +
+

+ U +

+
+
+
+ +
+ {joined ? ( +

📡 Call in Progress...

+ ) : ( +

Not connected

+ )} +
+ {/* End Call */} + + + {/* Mic Control */} + {isMute ? ( + + ) : ( + + )} +
+
+
+
+ ); +}; diff --git a/src/pages/webRTC/Videocall.tsx b/src/pages/webRTC/Videocall.tsx new file mode 100644 index 000000000..cb1414c6f --- /dev/null +++ b/src/pages/webRTC/Videocall.tsx @@ -0,0 +1,283 @@ +import React, { useEffect, useRef, useState } from "react"; +import { useNavigate, useParams } from "react-router-dom"; +import { useAuth } from "../../context/AuthContext"; +import { useSocket } from "../../context/SocketContext"; +import toast from "react-hot-toast"; +import AgoraRTC, { ILocalTrack } from "agora-rtc-sdk-ng"; +import axios from "axios"; + +export const VideoCall: React.FC = () => { + const { roomId, userId, isIncommingCall } = useParams(); + const [isMute, setIsMute] = useState(false); + const [isVideoOn, setIsVideoOn] = useState(false); + const { socket } = useSocket(); + const { user } = useAuth(); + const navigate = useNavigate(); + const APP_ID = "5e3db4d74aaa43ff8eeee3ad9f08efd8"; + const CHANNEL = String(roomId); + const [joined, setJoined] = useState(false); + const localVideoRef = useRef(null); + const remoteVideoRef = useRef(null); + const localTracksRef = useRef([]); + const clientRef = useRef(null); + const [token, setToken] = useState(null); + const [uid, setUid] = useState(null); + const URL = import.meta.env.VITE_BACKEND_URL; + + useEffect(() => { + const fetchToken = async () => { + // generate uid (string or number) + const uid = String(Date.now()); // simple unique uid + setUid(uid); + + try { + const res = await axios.get(`${URL}/agora/rtc/${CHANNEL}/${uid}`, { + withCredentials: true, + }); + setToken(res.data.token); + } catch (err) { + console.error("Failed to fetch token:", err); + } + }; + + fetchToken(); + }, [CHANNEL, URL]); + + useEffect(() => { + if (!token || !uid) return; + + AgoraRTC.setLogLevel(0); + + const init = async () => { + const client = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" }); + clientRef.current = client; + + // 🔹 Set up listeners BEFORE joining/publishing + client.on("user-published", async (user, mediaType) => { + if (user.uid === uid) return; // skip own stream + await client.subscribe(user, mediaType); + console.log("Subscribed to:", user.uid); + + if (mediaType === "video") { + user.videoTrack?.play(remoteVideoRef.current!); + } + if (mediaType === "audio") { + user.audioTrack?.play(); + } + }); + + client.on("user-unpublished", (user, mediaType) => { + if (mediaType === "video" && remoteVideoRef.current) { + remoteVideoRef.current.innerHTML = ""; + } + }); + + try { + await client.join(APP_ID, CHANNEL, token, uid); + + // create local tracks + localTracksRef.current = + await AgoraRTC.createMicrophoneAndCameraTracks(); + + // publish AFTER listeners are ready + await client.publish(localTracksRef.current); + + localTracksRef.current[1].play(localVideoRef.current!); + setIsVideoOn(true); + setJoined(true); + } catch (err) { + console.error("Agora join error:", err); + } + + // Outgoing call + const isIncoming = isIncommingCall === "true"; + if (!isIncoming) { + socket?.emit("start-call", { + from: user?.userId, + to: userId, + roomId: CHANNEL, + callType:"video", + fromName:user?.name + }); + } + }; + + init(); + + return () => { + cleanup(); + }; + }, [CHANNEL, isIncommingCall, user, userId, token, uid, socket]); + + // === Cleanup === + const cleanup = async () => { + localTracksRef.current.forEach((track) => { + track.stop(); + track.close(); + }); + if (clientRef.current) { + clientRef.current.removeAllListeners(); // ✅ avoid duplicate handlers + await clientRef.current.leave(); + } + setJoined(false); + }; + + // Mute mic + const muteMic = async () => { + if (localTracksRef.current[0]) { + await localTracksRef.current[0].setEnabled(false); + setIsMute(true); + console.log("Microphone muted"); + } + }; + + // Unmute mic + const unmuteMic = async () => { + if (localTracksRef.current[0]) { + await localTracksRef.current[0].setEnabled(true); + + setIsMute(false); + console.log("Microphone unmuted"); + } + }; + + // Stop video + const stopVideo = async () => { + if (localTracksRef.current[1]) { + await localTracksRef.current[1].setEnabled(false); + + setIsVideoOn(false); + console.log("Video stopped"); + } + }; + + // Resume video + const resumeVideo = async () => { + if (localTracksRef.current[1]) { + await localTracksRef.current[1].setEnabled(true); + setIsVideoOn(true); + console.log("Video resumed"); + } + }; + + useEffect(() => { + socket?.on("call-accepted", () => { + toast.success("Call accepted"); + }); + + socket?.on("call-ended", () => { + toast.success("Call ended"); + navigate(`/chat/${userId}`); + }); + + socket?.on("call-rejected", () => { + toast.error("Your call is declined."); + navigate(`/chat/${userId}`); + }); + + socket?.on("receiver-offline", async () => { + toast.error("The receiver is offline."); + await cleanup(); + navigate(`/chat/${userId}`); + }); + + return () => { + socket?.off("call-accepted"); + socket?.off("receiver-offline"); + socket?.off("call-rejected"); + socket?.off("call-ended"); + }; + }, [socket, userId]); + return ( +
+

Agora 1-to-1 Video Call

+
+
+
+
+ {remoteVideoRef.current === null && ( +

+ O +

+ )} +
+ {/* Local video as overlay (picture-in-picture) */} +
+
+ {!isVideoOn && ( +

+ Q +

+ )} +
+
+
+ +
+ {joined ? ( +

📡 Call in Progress...

+ ) : ( +

Not connected

+ )} +
+ {/* End Call */} + + + {/* Mic Control */} + {isMute ? ( + + ) : ( + + )} + + {/* Video Control */} + {isVideoOn ? ( + + ) : ( + + )} +
+
+
+
+ ); +}; diff --git a/src/types/index.ts b/src/types/index.ts index 02212bbf9..e9be5e8b2 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,44 +1,50 @@ -export type UserRole = 'entrepreneur' | 'investor'; +export type UserRole = 'entrepreneur' | 'investor' | 'admin'; + export interface User { - id: string; + userId: string; name: string; email: string; role: UserRole; - avatarUrl: string; + avatarUrl: string | File | null; + location: string; bio: string; isOnline?: boolean; - createdAt: string; } export interface Entrepreneur extends User { - role: 'entrepreneur'; - startupName: string; - pitchSummary: string; - fundingNeeded: string; - industry: string; - location: string; - foundedYear: number; - teamSize: number; + startupName: string | undefined; + pitchSummary: string | undefined; + fundingNeeded: number | undefined; + industry: string | undefined; + foundedYear: number | undefined; + teamSize: number | undefined; + revenue:string | undefined; + profitMargin:number | undefined; + growthRate:number | undefined; + marketOpportunity:string | undefined; + advantage:string | undefined; } export interface Investor extends User { - role: 'investor'; - investmentInterests: string[]; - investmentStage: string[]; - portfolioCompanies: string[]; - totalInvestments: number; - minimumInvestment: string; - maximumInvestment: string; + investmentInterests: string[] | undefined; + investmentStage: string[] | undefined; + portfolioCompanies: string[] | undefined; + totalInvestments: number | undefined; + minimumInvestment: string | undefined; + maximumInvestment: string | undefined; + investmentCriteria: string[] | undefined; + successfullExits: number | undefined; + minTimline: number | undefined; + maxTimline: number | undefined; } export interface Message { - id: string; - senderId: string; - receiverId: string; + sender: string; + receiver: string; content: string; - timestamp: string; isRead: boolean; + time: Date; } export interface ChatConversation { @@ -49,12 +55,11 @@ export interface ChatConversation { } export interface CollaborationRequest { - id: string; - investorId: string; - entrepreneurId: string; + inves_id: string; + enter_id: string; message: string; - status: 'pending' | 'accepted' | 'rejected'; - createdAt: string; + requestStatus: "pending" | "accepted" | "rejected"; + time: Date; } export interface Document { @@ -70,12 +75,23 @@ export interface Document { export interface AuthContextType { user: User | null; + userData: User | null; login: (email: string, password: string, role: UserRole) => Promise; - register: (name: string, email: string, password: string, role: UserRole) => Promise; + register: ( + name: string, + email: string, + password: string, + role: UserRole + ) => Promise; logout: () => void; forgotPassword: (email: string) => Promise; resetPassword: (token: string, newPassword: string) => Promise; updateProfile: (userId: string, updates: Partial) => Promise; + loginWithOauth: (userToken: string, role: UserRole) => Promise; isAuthenticated: boolean; isLoading: boolean; -} \ No newline at end of file +} + +export interface Socketcontext { + socket: string | null; +}