feat: major UI overhaul with new components and enhanced UX
- Add comprehensive Company Wiki feature with complete state management - CompanyWikiManager, empty states, invite modals - Implement new Chat system with enhanced layout and components - ChatLayout, ChatSidebar, MessageThread, FileUploadInput - Create modern Login and OTP verification flows - LoginNew page, OTPVerification component - Add new Employee Forms system with enhanced controller - Introduce Figma-based design components and multiple choice inputs - Add new font assets (NeueMontreal) and robot images for onboarding - Enhance existing components with improved styling and functionality - Update build configuration and dependencies - Remove deprecated ModernLogin component
This commit is contained in:
@@ -4,8 +4,8 @@
|
||||
"": {
|
||||
"name": "auditly-functions",
|
||||
"dependencies": {
|
||||
"firebase-admin": "^13.4.0",
|
||||
"firebase-functions": "^4.5.0",
|
||||
"firebase-admin": "^12.1.1",
|
||||
"firebase-functions": "^5.0.1",
|
||||
"openai": "^5.12.2",
|
||||
"stripe": "^18.4.0",
|
||||
},
|
||||
@@ -95,23 +95,23 @@
|
||||
|
||||
"@fastify/busboy": ["@fastify/busboy@3.2.0", "", {}, "sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA=="],
|
||||
|
||||
"@firebase/app-check-interop-types": ["@firebase/app-check-interop-types@0.3.3", "", {}, "sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A=="],
|
||||
"@firebase/app-check-interop-types": ["@firebase/app-check-interop-types@0.3.2", "", {}, "sha512-LMs47Vinv2HBMZi49C09dJxp0QT5LwDzFaVGf/+ITHe3BlIhUiLNttkATSXplc89A2lAaeTqjgqVkiRfUGyQiQ=="],
|
||||
|
||||
"@firebase/app-types": ["@firebase/app-types@0.9.3", "", {}, "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw=="],
|
||||
"@firebase/app-types": ["@firebase/app-types@0.9.2", "", {}, "sha512-oMEZ1TDlBz479lmABwWsWjzHwheQKiAgnuKxE0pz0IXCVx7/rtlkx1fQ6GfgK24WCrxDKMplZrT50Kh04iMbXQ=="],
|
||||
|
||||
"@firebase/auth-interop-types": ["@firebase/auth-interop-types@0.2.4", "", {}, "sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA=="],
|
||||
"@firebase/auth-interop-types": ["@firebase/auth-interop-types@0.2.3", "", {}, "sha512-Fc9wuJGgxoxQeavybiuwgyi+0rssr76b+nHpj+eGhXFYAdudMWyfBHvFL/I5fEHniUM/UQdFzi9VXJK2iZF7FQ=="],
|
||||
|
||||
"@firebase/component": ["@firebase/component@0.7.0", "", { "dependencies": { "@firebase/util": "1.13.0", "tslib": "^2.1.0" } }, "sha512-wR9En2A+WESUHexjmRHkqtaVH94WLNKt6rmeqZhSLBybg4Wyf0Umk04SZsS6sBq4102ZsDBFwoqMqJYj2IoDSg=="],
|
||||
"@firebase/component": ["@firebase/component@0.6.9", "", { "dependencies": { "@firebase/util": "1.10.0", "tslib": "^2.1.0" } }, "sha512-gm8EUEJE/fEac86AvHn8Z/QW8BvR56TBw3hMW0O838J/1mThYQXAIQBgUv75EqlCZfdawpWLrKt1uXvp9ciK3Q=="],
|
||||
|
||||
"@firebase/database": ["@firebase/database@1.1.0", "", { "dependencies": { "@firebase/app-check-interop-types": "0.3.3", "@firebase/auth-interop-types": "0.2.4", "@firebase/component": "0.7.0", "@firebase/logger": "0.5.0", "@firebase/util": "1.13.0", "faye-websocket": "0.11.4", "tslib": "^2.1.0" } }, "sha512-gM6MJFae3pTyNLoc9VcJNuaUDej0ctdjn3cVtILo3D5lpp0dmUHHLFN/pUKe7ImyeB1KAvRlEYxvIHNF04Filg=="],
|
||||
"@firebase/database": ["@firebase/database@1.0.8", "", { "dependencies": { "@firebase/app-check-interop-types": "0.3.2", "@firebase/auth-interop-types": "0.2.3", "@firebase/component": "0.6.9", "@firebase/logger": "0.4.2", "@firebase/util": "1.10.0", "faye-websocket": "0.11.4", "tslib": "^2.1.0" } }, "sha512-dzXALZeBI1U5TXt6619cv0+tgEhJiwlUtQ55WNZY7vGAjv7Q1QioV969iYwt1AQQ0ovHnEW0YW9TiBfefLvErg=="],
|
||||
|
||||
"@firebase/database-compat": ["@firebase/database-compat@2.1.0", "", { "dependencies": { "@firebase/component": "0.7.0", "@firebase/database": "1.1.0", "@firebase/database-types": "1.0.16", "@firebase/logger": "0.5.0", "@firebase/util": "1.13.0", "tslib": "^2.1.0" } }, "sha512-8nYc43RqxScsePVd1qe1xxvWNf0OBnbwHxmXJ7MHSuuTVYFO3eLyLW3PiCKJ9fHnmIz4p4LbieXwz+qtr9PZDg=="],
|
||||
"@firebase/database-compat": ["@firebase/database-compat@1.0.8", "", { "dependencies": { "@firebase/component": "0.6.9", "@firebase/database": "1.0.8", "@firebase/database-types": "1.0.5", "@firebase/logger": "0.4.2", "@firebase/util": "1.10.0", "tslib": "^2.1.0" } }, "sha512-OpeWZoPE3sGIRPBKYnW9wLad25RaWbGyk7fFQe4xnJQKRzlynWeFBSRRAoLE2Old01WXwskUiucNqUUVlFsceg=="],
|
||||
|
||||
"@firebase/database-types": ["@firebase/database-types@1.0.16", "", { "dependencies": { "@firebase/app-types": "0.9.3", "@firebase/util": "1.13.0" } }, "sha512-xkQLQfU5De7+SPhEGAXFBnDryUWhhlFXelEg2YeZOQMCdoe7dL64DDAd77SQsR+6uoXIZY5MB4y/inCs4GTfcw=="],
|
||||
"@firebase/database-types": ["@firebase/database-types@1.0.5", "", { "dependencies": { "@firebase/app-types": "0.9.2", "@firebase/util": "1.10.0" } }, "sha512-fTlqCNwFYyq/C6W7AJ5OCuq5CeZuBEsEwptnVxlNPkWCo5cTTyukzAHRSO/jaQcItz33FfYrrFk1SJofcu2AaQ=="],
|
||||
|
||||
"@firebase/logger": ["@firebase/logger@0.5.0", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-cGskaAvkrnh42b3BA3doDWeBmuHFO/Mx5A83rbRDYakPjO9bJtRL3dX7javzc2Rr/JHZf4HlterTW2lUkfeN4g=="],
|
||||
"@firebase/logger": ["@firebase/logger@0.4.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-Q1VuA5M1Gjqrwom6I6NUU4lQXdo9IAQieXlujeHZWvRt1b7qQ0KwBaNAjgxG27jgF9/mUwsNmO8ptBCGVYhB0A=="],
|
||||
|
||||
"@firebase/util": ["@firebase/util@1.13.0", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-0AZUyYUfpMNcztR5l09izHwXkZpghLgCUaAGjtMwXnCg3bj4ml5VgiwqOMOxJ+Nw4qN/zJAaOQBcJ7KGkWStqQ=="],
|
||||
"@firebase/util": ["@firebase/util@1.10.0", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-xKtx4A668icQqoANRxyDLBLz51TAbDP9KRfpbKGxiCAW346d0BeJe5vN6/hKxxmWwnZ0mautyv39JxviwwQMOQ=="],
|
||||
|
||||
"@google-cloud/firestore": ["@google-cloud/firestore@7.11.3", "", { "dependencies": { "@opentelemetry/api": "^1.3.0", "fast-deep-equal": "^3.1.1", "functional-red-black-tree": "^1.0.1", "google-gax": "^4.3.3", "protobufjs": "^7.2.6" } }, "sha512-qsM3/WHpawF07SRVvEJJVRwhYzM7o9qtuksyuqnrMig6fxIrwWnsezECWsG/D5TyYru51Fv5c/RTqNDQ2yU+4w=="],
|
||||
|
||||
@@ -501,9 +501,9 @@
|
||||
|
||||
"find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
|
||||
|
||||
"firebase-admin": ["firebase-admin@13.4.0", "", { "dependencies": { "@fastify/busboy": "^3.0.0", "@firebase/database-compat": "^2.0.0", "@firebase/database-types": "^1.0.6", "@types/node": "^22.8.7", "farmhash-modern": "^1.1.0", "google-auth-library": "^9.14.2", "jsonwebtoken": "^9.0.0", "jwks-rsa": "^3.1.0", "node-forge": "^1.3.1", "uuid": "^11.0.2" }, "optionalDependencies": { "@google-cloud/firestore": "^7.11.0", "@google-cloud/storage": "^7.14.0" } }, "sha512-Y8DcyKK+4pl4B93ooiy1G8qvdyRMkcNFfBSh+8rbVcw4cW8dgG0VXCCTp5NUwub8sn9vSPsOwpb9tE2OuFmcfQ=="],
|
||||
"firebase-admin": ["firebase-admin@12.7.0", "", { "dependencies": { "@fastify/busboy": "^3.0.0", "@firebase/database-compat": "1.0.8", "@firebase/database-types": "1.0.5", "@types/node": "^22.0.1", "farmhash-modern": "^1.1.0", "jsonwebtoken": "^9.0.0", "jwks-rsa": "^3.1.0", "node-forge": "^1.3.1", "uuid": "^10.0.0" }, "optionalDependencies": { "@google-cloud/firestore": "^7.7.0", "@google-cloud/storage": "^7.7.0" } }, "sha512-raFIrOyTqREbyXsNkSHyciQLfv8AUZazehPaQS1lZBSCDYW74FYXU0nQZa3qHI4K+hawohlDbywZ4+qce9YNxA=="],
|
||||
|
||||
"firebase-functions": ["firebase-functions@4.9.0", "", { "dependencies": { "@types/cors": "^2.8.5", "@types/express": "4.17.3", "cors": "^2.8.5", "express": "^4.17.1", "protobufjs": "^7.2.2" }, "peerDependencies": { "firebase-admin": "^10.0.0 || ^11.0.0 || ^12.0.0" }, "bin": { "firebase-functions": "lib/bin/firebase-functions.js" } }, "sha512-IqxOEsVAWGcRv9KRGzWQR5mOFuNsil3vsfkRPPiaV1U/ATC27/jbahh4z8I4rW8Xqa6cQE5xqnw0ueyMH7i7Ag=="],
|
||||
"firebase-functions": ["firebase-functions@5.1.1", "", { "dependencies": { "@types/cors": "^2.8.5", "@types/express": "4.17.3", "cors": "^2.8.5", "express": "^4.17.1", "protobufjs": "^7.2.2" }, "peerDependencies": { "firebase-admin": "^11.10.0 || ^12.0.0" }, "bin": { "firebase-functions": "lib/bin/firebase-functions.js" } }, "sha512-KkyKZE98Leg/C73oRyuUYox04PQeeBThdygMfeX+7t1cmKWYKa/ZieYa89U8GHgED+0mF7m7wfNZOfbURYxIKg=="],
|
||||
|
||||
"firebase-functions-test": ["firebase-functions-test@3.4.1", "", { "dependencies": { "@types/lodash": "^4.14.104", "lodash": "^4.17.5", "ts-deepmerge": "^2.0.1" }, "peerDependencies": { "firebase-admin": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0", "firebase-functions": ">=4.9.0", "jest": ">=28.0.0" } }, "sha512-qAq0oszrBGdf4bnCF6t4FoSgMsepeIXh0Pi/FhikSE6e+TvKKGpfrfUP/5pFjJZxFcLsweoau88KydCql4xSeg=="],
|
||||
|
||||
@@ -677,11 +677,11 @@
|
||||
|
||||
"jsonwebtoken": ["jsonwebtoken@9.0.2", "", { "dependencies": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ=="],
|
||||
|
||||
"jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
|
||||
"jwa": ["jwa@1.4.2", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw=="],
|
||||
|
||||
"jwks-rsa": ["jwks-rsa@3.2.0", "", { "dependencies": { "@types/express": "^4.17.20", "@types/jsonwebtoken": "^9.0.4", "debug": "^4.3.4", "jose": "^4.15.4", "limiter": "^1.1.5", "lru-memoizer": "^2.2.0" } }, "sha512-PwchfHcQK/5PSydeKCs1ylNym0w/SSv8a62DgHJ//7x2ZclCoinlsjAfDxAAbpoTPybOum/Jgy+vkvMmKz89Ww=="],
|
||||
|
||||
"jws": ["jws@4.0.0", "", { "dependencies": { "jwa": "^2.0.0", "safe-buffer": "^5.0.1" } }, "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg=="],
|
||||
"jws": ["jws@3.2.2", "", { "dependencies": { "jwa": "^1.4.1", "safe-buffer": "^5.0.1" } }, "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA=="],
|
||||
|
||||
"leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="],
|
||||
|
||||
@@ -943,7 +943,7 @@
|
||||
|
||||
"utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="],
|
||||
|
||||
"uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="],
|
||||
"uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="],
|
||||
|
||||
"v8-to-istanbul": ["v8-to-istanbul@9.3.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", "convert-source-map": "^2.0.0" } }, "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA=="],
|
||||
|
||||
@@ -1007,14 +1007,16 @@
|
||||
|
||||
"gaxios/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
|
||||
|
||||
"google-auth-library/jws": ["jws@4.0.0", "", { "dependencies": { "jwa": "^2.0.0", "safe-buffer": "^5.0.1" } }, "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg=="],
|
||||
|
||||
"google-gax/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
|
||||
|
||||
"gtoken/jws": ["jws@4.0.0", "", { "dependencies": { "jwa": "^2.0.0", "safe-buffer": "^5.0.1" } }, "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg=="],
|
||||
|
||||
"http-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="],
|
||||
|
||||
"jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
|
||||
|
||||
"jsonwebtoken/jws": ["jws@3.2.2", "", { "dependencies": { "jwa": "^1.4.1", "safe-buffer": "^5.0.1" } }, "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA=="],
|
||||
|
||||
"jwks-rsa/@types/express": ["@types/express@4.17.23", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", "@types/serve-static": "*" } }, "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ=="],
|
||||
|
||||
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
@@ -1053,7 +1055,9 @@
|
||||
|
||||
"finalhandler/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
||||
|
||||
"jsonwebtoken/jws/jwa": ["jwa@1.4.2", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw=="],
|
||||
"google-auth-library/jws/jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
|
||||
|
||||
"gtoken/jws/jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
|
||||
|
||||
"send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
const { onRequest } = require("firebase-functions/v2/https");
|
||||
const admin = require("firebase-admin");
|
||||
const serviceAccount = require("./auditly-c0027-firebase-adminsdk-fbsvc-1db7c58141.json");
|
||||
const functions = require("firebase-functions");
|
||||
const OpenAI = require("openai");
|
||||
const Stripe = require("stripe");
|
||||
|
||||
const serviceAccount = require("./auditly-c0027-firebase-adminsdk-fbsvc-1db7c58141.json");
|
||||
|
||||
admin.initializeApp({
|
||||
credential: admin.credential.cert(serviceAccount)
|
||||
});
|
||||
@@ -177,206 +177,179 @@ const generateOTP = () => {
|
||||
return Math.floor(100000 + Math.random() * 900000).toString();
|
||||
};
|
||||
|
||||
// CORS middleware
|
||||
const cors = (req, res, next) => {
|
||||
res.set('Access-Control-Allow-Origin', '*');
|
||||
res.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
||||
res.set('Access-Control-Allow-Headers', 'Content-Type,Authorization');
|
||||
res.set('Access-Control-Max-Age', '3600');
|
||||
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.status(204).send('');
|
||||
return;
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
// Helper function to set CORS headers
|
||||
const setCorsHeaders = (res) => {
|
||||
res.set('Access-Control-Allow-Origin', '*');
|
||||
res.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
||||
res.set('Access-Control-Allow-Headers', 'Content-Type,Authorization');
|
||||
res.set('Access-Control-Max-Age', '3600');
|
||||
};
|
||||
|
||||
// Send OTP Function
|
||||
exports.sendOTP = functions.https.onRequest((req, res) => {
|
||||
cors(req, res, async () => {
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({ error: "Method not allowed" });
|
||||
}
|
||||
exports.sendOTP = onRequest({ cors: true }, async (req, res) => {
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({ error: "Method not allowed" });
|
||||
}
|
||||
|
||||
const { email, inviteCode } = req.body;
|
||||
const { email, inviteCode } = req.body;
|
||||
|
||||
if (!email) {
|
||||
return res.status(400).json({ error: "Email is required" });
|
||||
}
|
||||
if (!email) {
|
||||
return res.status(400).json({ error: "Email is required" });
|
||||
}
|
||||
|
||||
try {
|
||||
// Generate OTP
|
||||
const otp = generateOTP();
|
||||
const expiresAt = Date.now() + (5 * 60 * 1000); // 5 minutes expiry
|
||||
try {
|
||||
// Generate OTP
|
||||
const otp = generateOTP();
|
||||
const expiresAt = Date.now() + (5 * 60 * 1000); // 5 minutes expiry
|
||||
|
||||
// Store OTP in Firestore
|
||||
await db.collection("otps").doc(email).set({
|
||||
otp,
|
||||
expiresAt,
|
||||
attempts: 0,
|
||||
inviteCode: inviteCode || null,
|
||||
createdAt: Date.now(),
|
||||
});
|
||||
// Store OTP in Firestore
|
||||
await db.collection("otps").doc(email).set({
|
||||
otp,
|
||||
expiresAt,
|
||||
attempts: 0,
|
||||
inviteCode: inviteCode || null,
|
||||
createdAt: Date.now(),
|
||||
});
|
||||
|
||||
// In production, send actual email
|
||||
console.log(`📧 OTP for ${email}: ${otp} (expires in 5 minutes)`);
|
||||
// In production, send actual email
|
||||
console.log(`📧 OTP for ${email}: ${otp} (expires in 5 minutes)`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: "Verification code sent to your email",
|
||||
// Always include OTP in emulator mode for testing
|
||||
otp,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Send OTP error:", error);
|
||||
res.status(500).json({ error: "Failed to send verification code" });
|
||||
}
|
||||
});
|
||||
res.json({
|
||||
success: true,
|
||||
message: "Verification code sent to your email",
|
||||
// Always include OTP in emulator mode for testing
|
||||
otp,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Send OTP error:", error);
|
||||
res.status(500).json({ error: "Failed to send verification code" });
|
||||
}
|
||||
});
|
||||
|
||||
// Verify OTP Function
|
||||
exports.verifyOTP = functions.https.onRequest((req, res) => {
|
||||
cors(req, res, async () => {
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({ error: "Method not allowed" });
|
||||
exports.verifyOTP = onRequest({ cors: true }, async (req, res) => {
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({ error: "Method not allowed" });
|
||||
}
|
||||
|
||||
const { email, otp } = req.body;
|
||||
|
||||
if (!email || !otp) {
|
||||
return res.status(400).json({ error: "Email and OTP are required" });
|
||||
}
|
||||
|
||||
try {
|
||||
// Retrieve OTP document
|
||||
const otpDoc = await db.collection("otps").doc(email).get();
|
||||
|
||||
if (!otpDoc.exists) {
|
||||
return res.status(400).json({ error: "Invalid verification code" });
|
||||
}
|
||||
|
||||
const { email, otp } = req.body;
|
||||
const otpData = otpDoc.data();
|
||||
|
||||
if (!email || !otp) {
|
||||
return res.status(400).json({ error: "Email and OTP are required" });
|
||||
}
|
||||
|
||||
try {
|
||||
// Retrieve OTP document
|
||||
const otpDoc = await db.collection("otps").doc(email).get();
|
||||
|
||||
if (!otpDoc.exists) {
|
||||
return res.status(400).json({ error: "Invalid verification code" });
|
||||
}
|
||||
|
||||
const otpData = otpDoc.data();
|
||||
|
||||
// Check if OTP is expired
|
||||
if (Date.now() > otpData.expiresAt) {
|
||||
await otpDoc.ref.delete();
|
||||
return res.status(400).json({ error: "Verification code has expired" });
|
||||
}
|
||||
|
||||
// Check if too many attempts
|
||||
if (otpData.attempts >= 5) {
|
||||
await otpDoc.ref.delete();
|
||||
return res.status(400).json({ error: "Too many failed attempts" });
|
||||
}
|
||||
|
||||
// Verify OTP
|
||||
if (otpData.otp !== otp) {
|
||||
await otpDoc.ref.update({
|
||||
attempts: (otpData.attempts || 0) + 1,
|
||||
});
|
||||
return res.status(400).json({ error: "Invalid verification code" });
|
||||
}
|
||||
|
||||
// OTP is valid - clean up and create/find user
|
||||
// Check if OTP is expired
|
||||
if (Date.now() > otpData.expiresAt) {
|
||||
await otpDoc.ref.delete();
|
||||
|
||||
// Generate a unique user ID for this email if it doesn't exist
|
||||
let userId;
|
||||
let userDoc;
|
||||
|
||||
// Check if user already exists by email
|
||||
const existingUserQuery = await db.collection("users")
|
||||
.where("email", "==", email)
|
||||
.limit(1)
|
||||
.get();
|
||||
|
||||
if (!existingUserQuery.empty) {
|
||||
// User exists, get their ID
|
||||
userDoc = existingUserQuery.docs[0];
|
||||
userId = userDoc.id;
|
||||
} else {
|
||||
// Create new user
|
||||
userId = `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
userDoc = null;
|
||||
}
|
||||
|
||||
// Prepare user object for response
|
||||
const user = {
|
||||
uid: userId,
|
||||
email: email,
|
||||
displayName: email.split("@")[0],
|
||||
emailVerified: true,
|
||||
};
|
||||
|
||||
// Create or update user document in Firestore
|
||||
const userRef = db.collection("users").doc(userId);
|
||||
|
||||
const userData = {
|
||||
id: userId,
|
||||
email: email,
|
||||
displayName: email.split("@")[0],
|
||||
emailVerified: true,
|
||||
lastLoginAt: Date.now(),
|
||||
};
|
||||
|
||||
if (!userDoc) {
|
||||
// Create new user document
|
||||
userData.createdAt = Date.now();
|
||||
await userRef.set(userData);
|
||||
} else {
|
||||
// Update existing user with latest login info
|
||||
await userRef.update({
|
||||
lastLoginAt: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
// Generate a simple session token (in production, use proper JWT)
|
||||
const customToken = `session_${userId}_${Date.now()}`;
|
||||
|
||||
// Handle invitation if present
|
||||
let inviteData = null;
|
||||
if (otpData.inviteCode) {
|
||||
try {
|
||||
const inviteDoc = await db
|
||||
.collectionGroup("invites")
|
||||
.where("code", "==", otpData.inviteCode)
|
||||
.where("status", "==", "pending")
|
||||
.limit(1)
|
||||
.get();
|
||||
|
||||
if (!inviteDoc.empty) {
|
||||
inviteData = inviteDoc.docs[0].data();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching invite:", error);
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
user,
|
||||
token: customToken,
|
||||
invite: inviteData,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Verify OTP error:", error);
|
||||
res.status(500).json({ error: "Failed to verify code" });
|
||||
return res.status(400).json({ error: "Verification code has expired" });
|
||||
}
|
||||
});
|
||||
|
||||
// Check if too many attempts
|
||||
if (otpData.attempts >= 5) {
|
||||
await otpDoc.ref.delete();
|
||||
return res.status(400).json({ error: "Too many failed attempts" });
|
||||
}
|
||||
|
||||
// Verify OTP
|
||||
if (otpData.otp !== otp) {
|
||||
await otpDoc.ref.update({
|
||||
attempts: (otpData.attempts || 0) + 1,
|
||||
});
|
||||
return res.status(400).json({ error: "Invalid verification code" });
|
||||
}
|
||||
|
||||
// OTP is valid - clean up and create/find user
|
||||
await otpDoc.ref.delete();
|
||||
|
||||
// Generate a unique user ID for this email if it doesn't exist
|
||||
let userId;
|
||||
let userDoc;
|
||||
|
||||
// Check if user already exists by email
|
||||
const existingUserQuery = await db.collection("users")
|
||||
.where("email", "==", email)
|
||||
.limit(1)
|
||||
.get();
|
||||
|
||||
if (!existingUserQuery.empty) {
|
||||
// User exists, get their ID
|
||||
userDoc = existingUserQuery.docs[0];
|
||||
userId = userDoc.id;
|
||||
} else {
|
||||
// Create new user
|
||||
userId = `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
userDoc = null;
|
||||
}
|
||||
|
||||
// Prepare user object for response
|
||||
const user = {
|
||||
uid: userId,
|
||||
email: email,
|
||||
displayName: email.split("@")[0],
|
||||
emailVerified: true,
|
||||
};
|
||||
|
||||
// Create or update user document in Firestore
|
||||
const userRef = db.collection("users").doc(userId);
|
||||
|
||||
const userData = {
|
||||
id: userId,
|
||||
email: email,
|
||||
displayName: email.split("@")[0],
|
||||
emailVerified: true,
|
||||
lastLoginAt: Date.now(),
|
||||
};
|
||||
|
||||
if (!userDoc) {
|
||||
// Create new user document
|
||||
userData.createdAt = Date.now();
|
||||
await userRef.set(userData);
|
||||
} else {
|
||||
// Update existing user with latest login info
|
||||
await userRef.update({
|
||||
lastLoginAt: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
// Generate a simple session token (in production, use proper JWT)
|
||||
const customToken = `session_${userId}_${Date.now()}`;
|
||||
|
||||
// Handle invitation if present
|
||||
let inviteData = null;
|
||||
if (otpData.inviteCode) {
|
||||
try {
|
||||
const inviteDoc = await db
|
||||
.collectionGroup("invites")
|
||||
.where("code", "==", otpData.inviteCode)
|
||||
.where("status", "==", "pending")
|
||||
.limit(1)
|
||||
.get();
|
||||
|
||||
if (!inviteDoc.empty) {
|
||||
inviteData = inviteDoc.docs[0].data();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching invite:", error);
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
user,
|
||||
token: customToken,
|
||||
invite: inviteData,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Verify OTP error:", error);
|
||||
res.status(500).json({ error: "Failed to verify code" });
|
||||
}
|
||||
});
|
||||
|
||||
// Create Invitation Function
|
||||
exports.createInvitation = functions.https.onRequest(async (req, res) => {
|
||||
setCorsHeaders(res);
|
||||
exports.createInvitation = onRequest({ cors: true }, async (req, res) => {
|
||||
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.status(204).send('');
|
||||
@@ -396,7 +369,7 @@ exports.createInvitation = functions.https.onRequest(async (req, res) => {
|
||||
try {
|
||||
// Generate invite code
|
||||
const code = Math.random().toString(36).substring(2, 15);
|
||||
|
||||
|
||||
// Generate employee ID
|
||||
const employeeId = `emp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
@@ -429,7 +402,7 @@ exports.createInvitation = functions.https.onRequest(async (req, res) => {
|
||||
});
|
||||
|
||||
// Generate invite links
|
||||
const baseUrl = process.env.CLIENT_URL || 'http://localhost:5174';
|
||||
const baseUrl = process.env.CLIENT_URL || 'http://localhost:5173';
|
||||
const inviteLink = `${baseUrl}/#/employee-form/${code}`;
|
||||
const emailLink = `mailto:${email}?subject=You're invited to join our organization&body=Hi ${name},%0A%0AYou've been invited to complete a questionnaire for our organization. Please click the link below to get started:%0A%0A${inviteLink}%0A%0AThis link will expire in 7 days.%0A%0AThank you!`;
|
||||
|
||||
@@ -452,9 +425,7 @@ exports.createInvitation = functions.https.onRequest(async (req, res) => {
|
||||
});
|
||||
|
||||
// Get Invitation Status Function
|
||||
exports.getInvitationStatus = functions.https.onRequest(async (req, res) => {
|
||||
setCorsHeaders(res);
|
||||
|
||||
exports.getInvitationStatus = onRequest({ cors: true }, async (req, res) => {
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.status(204).send('');
|
||||
return;
|
||||
@@ -501,9 +472,7 @@ exports.getInvitationStatus = functions.https.onRequest(async (req, res) => {
|
||||
});
|
||||
|
||||
// Consume Invitation Function
|
||||
exports.consumeInvitation = functions.https.onRequest(async (req, res) => {
|
||||
setCorsHeaders(res);
|
||||
|
||||
exports.consumeInvitation = onRequest({ cors: true }, async (req, res) => {
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.status(204).send('');
|
||||
return;
|
||||
@@ -581,8 +550,7 @@ exports.consumeInvitation = functions.https.onRequest(async (req, res) => {
|
||||
});
|
||||
|
||||
// Submit Employee Answers Function
|
||||
exports.submitEmployeeAnswers = functions.https.onRequest(async (req, res) => {
|
||||
setCorsHeaders(res);
|
||||
exports.submitEmployeeAnswers = onRequest({ cors: true }, async (req, res) => {
|
||||
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.status(204).send('');
|
||||
@@ -659,8 +627,7 @@ exports.submitEmployeeAnswers = functions.https.onRequest(async (req, res) => {
|
||||
});
|
||||
|
||||
// Generate Employee Report Function
|
||||
exports.generateEmployeeReport = functions.https.onRequest(async (req, res) => {
|
||||
setCorsHeaders(res);
|
||||
exports.generateEmployeeReport = onRequest({ cors: true }, async (req, res) => {
|
||||
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.status(204).send('');
|
||||
@@ -794,9 +761,7 @@ Return ONLY valid JSON that matches this structure. Be thorough but professional
|
||||
});
|
||||
|
||||
// Generate Company Wiki Function
|
||||
exports.generateCompanyWiki = functions.https.onRequest(async (req, res) => {
|
||||
setCorsHeaders(res);
|
||||
|
||||
exports.generateCompanyWiki = onRequest({ cors: true }, async (req, res) => {
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.status(204).send('');
|
||||
return;
|
||||
@@ -840,6 +805,8 @@ exports.generateCompanyWiki = functions.https.onRequest(async (req, res) => {
|
||||
});
|
||||
|
||||
// content is guaranteed to be schema-conformant JSON
|
||||
console.log(completion.choices[0].message);
|
||||
console.log(completion.choices[0].message.content);
|
||||
const parsed = JSON.parse(completion.choices[0].message.content);
|
||||
|
||||
const report = {
|
||||
@@ -852,6 +819,9 @@ exports.generateCompanyWiki = functions.https.onRequest(async (req, res) => {
|
||||
generatedAt: Date.now(),
|
||||
...parsed.wiki,
|
||||
};
|
||||
console.log(report);
|
||||
console.log(wiki);
|
||||
|
||||
} else {
|
||||
// Fallback to mock data when OpenAI is not available
|
||||
report = {
|
||||
@@ -921,8 +891,8 @@ exports.generateCompanyWiki = functions.https.onRequest(async (req, res) => {
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
report,
|
||||
wiki,
|
||||
...report,
|
||||
...wiki,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Generate company wiki error:", error);
|
||||
@@ -931,8 +901,7 @@ exports.generateCompanyWiki = functions.https.onRequest(async (req, res) => {
|
||||
});
|
||||
|
||||
// Chat Function
|
||||
exports.chat = functions.https.onRequest(async (req, res) => {
|
||||
setCorsHeaders(res);
|
||||
exports.chat = onRequest({ cors: true }, async (req, res) => {
|
||||
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.status(204).send('');
|
||||
@@ -1007,9 +976,7 @@ Provide helpful, actionable insights while maintaining professional confidential
|
||||
});
|
||||
|
||||
// Create Organization Function
|
||||
exports.createOrganization = functions.https.onRequest(async (req, res) => {
|
||||
setCorsHeaders(res);
|
||||
|
||||
exports.createOrganization = onRequest({ cors: true }, async (req, res) => {
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.status(204).send('');
|
||||
return;
|
||||
@@ -1121,8 +1088,7 @@ exports.createOrganization = functions.https.onRequest(async (req, res) => {
|
||||
});
|
||||
|
||||
// Get User Organizations Function
|
||||
exports.getUserOrganizations = functions.https.onRequest(async (req, res) => {
|
||||
setCorsHeaders(res);
|
||||
exports.getUserOrganizations = onRequest({ cors: true }, async (req, res) => {
|
||||
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.status(204).send('');
|
||||
@@ -1166,9 +1132,7 @@ exports.getUserOrganizations = functions.https.onRequest(async (req, res) => {
|
||||
});
|
||||
|
||||
// Join Organization Function (via invite)
|
||||
exports.joinOrganization = functions.https.onRequest(async (req, res) => {
|
||||
setCorsHeaders(res);
|
||||
|
||||
exports.joinOrganization = onRequest({ cors: true }, async (req, res) => {
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.status(204).send('');
|
||||
return;
|
||||
@@ -1282,9 +1246,7 @@ exports.joinOrganization = functions.https.onRequest(async (req, res) => {
|
||||
});
|
||||
|
||||
// Create Stripe Checkout Session Function
|
||||
exports.createCheckoutSession = functions.https.onRequest(async (req, res) => {
|
||||
setCorsHeaders(res);
|
||||
|
||||
exports.createCheckoutSession = onRequest({ cors: true }, async (req, res) => {
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.status(204).send('');
|
||||
return;
|
||||
@@ -1374,7 +1336,7 @@ exports.createCheckoutSession = functions.https.onRequest(async (req, res) => {
|
||||
});
|
||||
|
||||
// Handle Stripe Webhook Function
|
||||
exports.stripeWebhook = functions.https.onRequest(async (req, res) => {
|
||||
exports.stripeWebhook = onRequest(async (req, res) => {
|
||||
if (!stripe) {
|
||||
return res.status(500).send('Stripe not configured');
|
||||
}
|
||||
@@ -1385,7 +1347,7 @@ exports.stripeWebhook = functions.https.onRequest(async (req, res) => {
|
||||
let event;
|
||||
|
||||
try {
|
||||
event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
|
||||
event = stripe.webhooks.constructEvent(req.rawBody, sig, endpointSecret);
|
||||
} catch (err) {
|
||||
console.error('Webhook signature verification failed:', err.message);
|
||||
return res.status(400).send(`Webhook Error: ${err.message}`);
|
||||
@@ -1424,9 +1386,7 @@ exports.stripeWebhook = functions.https.onRequest(async (req, res) => {
|
||||
});
|
||||
|
||||
// Get Subscription Status Function
|
||||
exports.getSubscriptionStatus = functions.https.onRequest(async (req, res) => {
|
||||
setCorsHeaders(res);
|
||||
|
||||
exports.getSubscriptionStatus = onRequest({ cors: true }, async (req, res) => {
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.status(204).send('');
|
||||
return;
|
||||
@@ -1652,9 +1612,7 @@ async function handlePaymentFailed(invoice) {
|
||||
// });
|
||||
|
||||
// Save Company Report Function
|
||||
exports.saveCompanyReport = functions.https.onRequest(async (req, res) => {
|
||||
setCorsHeaders(res);
|
||||
|
||||
exports.saveCompanyReport = onRequest({ cors: true }, async (req, res) => {
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.status(204).send('');
|
||||
return;
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
},
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"firebase-admin": "^13.4.0",
|
||||
"firebase-functions": "^4.5.0",
|
||||
"firebase-admin": "^12.1.1",
|
||||
"firebase-functions": "^5.0.1",
|
||||
"openai": "^5.12.2",
|
||||
"stripe": "^18.4.0"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user