diff --git a/firestore.rules b/firestore.rules index 153edad..db0f904 100644 --- a/firestore.rules +++ b/firestore.rules @@ -100,7 +100,8 @@ service cloud.firestore { && exists(/databases/$(database)/documents/users/$(request.auth.uid)) && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.isApproved == true; - allow update, delete: if isDbAdmin(); + allow update: if isDbAdmin() || (isSignedIn() && resource.data.userId == request.auth.uid && incoming().diff(existing()).affectedKeys().hasOnly(['result'])); + allow delete: if isDbAdmin(); } // --- Testimonials Collection --- diff --git a/package-lock.json b/package-lock.json index 630da50..8a22c18 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "embla-carousel-autoplay": "^8.6.0", "embla-carousel-react": "^8.6.0", "express": "^4.21.2", - "firebase": "^12.12.1", + "firebase": "^10.12.2", "lightweight-charts": "^5.2.0", "lucide-react": "^0.546.0", "metaapi.cloud-sdk": "^29.3.3", @@ -29,15 +29,17 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "react-dropzone": "^15.0.0", + "react-joyride": "^3.1.0", "react-onesignal": "^3.5.2", "recharts": "^3.8.1", "shadcn": "^4.7.0", + "sonner": "^2.0.7", "tailwind-merge": "^3.5.0", "tw-animate-css": "^1.4.0", "vite": "^6.2.0" }, "devDependencies": { - "@firebase/rules-unit-testing": "^5.0.0", + "@firebase/rules-unit-testing": "^3.0.4", "@types/express": "^4.17.21", "@types/node": "^22.14.0", "autoprefixer": "^10.4.21", @@ -1283,36 +1285,32 @@ "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@firebase/ai": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/@firebase/ai/-/ai-2.11.1.tgz", - "integrity": "sha512-WGTF81W3WBKJY+c7xqTzO15OGAkCAs8cpADqflAI0skhTZjIkhF0qyf55rq4Ctt6jKygkv99rPfMrjAHTgXaVQ==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/app-check-interop-types": "0.3.3", - "@firebase/component": "0.7.2", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.15.0", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@firebase/app": "0.x", - "@firebase/app-types": "0.x" - } + "node_modules/@fastify/deepmerge": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@fastify/deepmerge/-/deepmerge-3.2.1.tgz", + "integrity": "sha512-N5Oqvltoa2r9z1tbx4xjky0oRR60v+T47Ic4J1ukoVQcptLOrIdRnCSdTGmOmajZuHVKlTnfcmrjyqsGEW1ztA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" }, "node_modules/@firebase/analytics": { - "version": "0.10.21", - "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.21.tgz", - "integrity": "sha512-j2y2q65BlgLGB5Pwjhv/Jopw2X/TBTzvAtI5z/DSp56U4wBj7LfhBfzbdCtFPges+Wz0g55GdoawXibOH5jGng==", + "version": "0.10.8", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.8.tgz", + "integrity": "sha512-CVnHcS4iRJPqtIDc411+UmFldk0ShSK3OB+D0bKD8Ck5Vro6dbK5+APZpkuWpbfdL359DIQUnAaMLE+zs/PVyA==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/installations": "0.6.21", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.15.0", + "@firebase/component": "0.6.9", + "@firebase/installations": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", "tslib": "^2.1.0" }, "peerDependencies": { @@ -1320,15 +1318,15 @@ } }, "node_modules/@firebase/analytics-compat": { - "version": "0.2.27", - "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.27.tgz", - "integrity": "sha512-ZObpYpAxL6JfgH7GnvlDD0sbzGZ0o4nijV8skatV9ZX49hJtCYbFqaEcPYptT94rgX1KUoKEderC7/fa7hybtw==", + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.14.tgz", + "integrity": "sha512-unRVY6SvRqfNFIAA/kwl4vK+lvQAL2HVcgu9zTrUtTyYDmtIt/lOuHJynBMYEgLnKm39YKBDhtqdapP2e++ASw==", "license": "Apache-2.0", "dependencies": { - "@firebase/analytics": "0.10.21", - "@firebase/analytics-types": "0.8.3", - "@firebase/component": "0.7.2", - "@firebase/util": "1.15.0", + "@firebase/analytics": "0.10.8", + "@firebase/analytics-types": "0.8.2", + "@firebase/component": "0.6.9", + "@firebase/util": "1.10.0", "tslib": "^2.1.0" }, "peerDependencies": { @@ -1336,119 +1334,102 @@ } }, "node_modules/@firebase/analytics-types": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.3.tgz", - "integrity": "sha512-VrIp/d8iq2g501qO46uGz3hjbDb8xzYMrbu8Tp0ovzIzrvJZ2fvmj649gTjge/b7cCCcjT0H37g1gVtlNhnkbg==", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.2.tgz", + "integrity": "sha512-EnzNNLh+9/sJsimsA/FGqzakmrAUKLeJvjRHlg8df1f97NLUlFidk9600y0ZgWOp3CAxn6Hjtk+08tixlUOWyw==", "license": "Apache-2.0" }, "node_modules/@firebase/app": { - "version": "0.14.11", - "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.14.11.tgz", - "integrity": "sha512-yxADFW35LYkP8oSGobGsYIrI42I+GPCvKTNHx4meT9Yq3C950IVz1eANoBk822I9tbKv1wyv9P4Bv1G5TpucFw==", + "version": "0.10.13", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.10.13.tgz", + "integrity": "sha512-OZiDAEK/lDB6xy/XzYAyJJkaDqmQ+BCtOEPLqFvxWKUz5JbBmej7IiiRHdtiIOD/twW7O5AxVsfaaGA/V1bNsA==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.15.0", + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", "idb": "7.1.1", "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" } }, "node_modules/@firebase/app-check": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.11.2.tgz", - "integrity": "sha512-jcXQVMHAQ5AEKzVD5C7s5fmAYeFOuN6lAJeNTgZK2B9aLnofWaJt8u1A8Idm8gpsBBYSaY3cVyeH5SWMOVPBLQ==", + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.8.8.tgz", + "integrity": "sha512-O49RGF1xj7k6BuhxGpHmqOW5hqBIAEbt2q6POW0lIywx7emYtzPDeQI+ryQpC4zbKX646SoVZ711TN1DBLNSOQ==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.15.0", + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", "tslib": "^2.1.0" }, - "engines": { - "node": ">=20.0.0" - }, "peerDependencies": { "@firebase/app": "0.x" } }, "node_modules/@firebase/app-check-compat": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.4.2.tgz", - "integrity": "sha512-M91NhxqbSkI0ChkJWy69blC+rPr6HEgaeRllddSaU1pQ/7IiegeCQM9pPDIgvWnwnBSzKhUHpe6ro/jhJ+cvzw==", + "version": "0.3.15", + "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.15.tgz", + "integrity": "sha512-zFIvIFFNqDXpOT2huorz9cwf56VT3oJYRFjSFYdSbGYEJYEaXjLJbfC79lx/zjx4Fh+yuN8pry3TtvwaevrGbg==", "license": "Apache-2.0", "dependencies": { - "@firebase/app-check": "0.11.2", - "@firebase/app-check-types": "0.5.3", - "@firebase/component": "0.7.2", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.15.0", + "@firebase/app-check": "0.8.8", + "@firebase/app-check-types": "0.5.2", + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", "tslib": "^2.1.0" }, - "engines": { - "node": ">=20.0.0" - }, "peerDependencies": { "@firebase/app-compat": "0.x" } }, "node_modules/@firebase/app-check-interop-types": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz", - "integrity": "sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.2.tgz", + "integrity": "sha512-LMs47Vinv2HBMZi49C09dJxp0QT5LwDzFaVGf/+ITHe3BlIhUiLNttkATSXplc89A2lAaeTqjgqVkiRfUGyQiQ==", "license": "Apache-2.0" }, "node_modules/@firebase/app-check-types": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.3.tgz", - "integrity": "sha512-hyl5rKSj0QmwPdsAxrI5x1otDlByQ7bvNvVt8G/XPO2CSwE++rmSVf3VEhaeOR4J8ZFaF0Z0NDSmLejPweZ3ng==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.2.tgz", + "integrity": "sha512-FSOEzTzL5bLUbD2co3Zut46iyPWML6xc4x+78TeaXMSuJap5QObfb+rVvZJtla3asN4RwU7elaQaduP+HFizDA==", "license": "Apache-2.0" }, "node_modules/@firebase/app-compat": { - "version": "0.5.11", - "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.5.11.tgz", - "integrity": "sha512-KaACDjXkK5VLpI01vEs592R7/8s5DjFdIXfKoR385ly1SmK3Tu+jMHCIB4MsiY5jsez6v7VlEX/3rJ90dVkHyA==", + "version": "0.2.43", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.2.43.tgz", + "integrity": "sha512-HM96ZyIblXjAC7TzE8wIk2QhHlSvksYkQ4Ukh1GmEenzkucSNUmUX4QvoKrqeWsLEQ8hdcojABeCV8ybVyZmeg==", "license": "Apache-2.0", "dependencies": { - "@firebase/app": "0.14.11", - "@firebase/component": "0.7.2", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.15.0", + "@firebase/app": "0.10.13", + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" } }, "node_modules/@firebase/app-types": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.4.tgz", - "integrity": "sha512-crX9TA5SVYZwLPG7/R16IsH8FLlgkPXjJUVhsVpHVDSqJiq3D/NuFTM5ctxGTExXAOeIn//69tQw47CPerM8MQ==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/logger": "0.5.0" - } + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.2.tgz", + "integrity": "sha512-oMEZ1TDlBz479lmABwWsWjzHwheQKiAgnuKxE0pz0IXCVx7/rtlkx1fQ6GfgK24WCrxDKMplZrT50Kh04iMbXQ==", + "license": "Apache-2.0" }, "node_modules/@firebase/auth": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.13.0.tgz", - "integrity": "sha512-mKkSLNym3UbnnZ06dAmtqzp5EpPGCANGCZDJbkoR135aoUdKG6Aizwcnp29RzsQpwH0nmy5nay17Sfbsh9oY8A==", + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.7.9.tgz", + "integrity": "sha512-yLD5095kVgDw965jepMyUrIgDklD6qH/BZNHeKOgvu7pchOKNjVM+zQoOVYJIKWMWOWBq8IRNVU6NXzBbozaJg==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.15.0", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0", + "undici": "6.19.7" }, "peerDependencies": { "@firebase/app": "0.x", - "@react-native-async-storage/async-storage": "^2.2.0 || ^3.0.0" + "@react-native-async-storage/async-storage": "^1.18.1" }, "peerDependenciesMeta": { "@react-native-async-storage/async-storage": { @@ -1457,34 +1438,32 @@ } }, "node_modules/@firebase/auth-compat": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.6.5.tgz", - "integrity": "sha512-IfVsafZ3QiXbsydXTP/XMI0wVYbJLI1rkb8Qqf03/h5FnL+upbbPOb+6Yj3RpcX+Y1iP5Uh18lxTHlXfbiyAow==", + "version": "0.5.14", + "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.5.14.tgz", + "integrity": "sha512-2eczCSqBl1KUPJacZlFpQayvpilg3dxXLy9cSMTKtQMTQSmondUtPI47P3ikH3bQAXhzKLOE+qVxJ3/IRtu9pw==", "license": "Apache-2.0", "dependencies": { - "@firebase/auth": "1.13.0", - "@firebase/auth-types": "0.13.0", - "@firebase/component": "0.7.2", - "@firebase/util": "1.15.0", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" + "@firebase/auth": "1.7.9", + "@firebase/auth-types": "0.12.2", + "@firebase/component": "0.6.9", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0", + "undici": "6.19.7" }, "peerDependencies": { "@firebase/app-compat": "0.x" } }, "node_modules/@firebase/auth-interop-types": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz", - "integrity": "sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.3.tgz", + "integrity": "sha512-Fc9wuJGgxoxQeavybiuwgyi+0rssr76b+nHpj+eGhXFYAdudMWyfBHvFL/I5fEHniUM/UQdFzi9VXJK2iZF7FQ==", "license": "Apache-2.0" }, "node_modules/@firebase/auth-types": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.13.0.tgz", - "integrity": "sha512-S/PuIjni0AQRLF+l9ck0YpsMOdE8GO2KU6ubmBB7P+7TJUCQDa3R1dlgYm9UzGbbePMZsp0xzB93f2b/CgxMOg==", + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.12.2.tgz", + "integrity": "sha512-qsEBaRMoGvHO10unlDJhaKSuPn4pyoTtlQuP1ghZfzB6rNQPuhp/N/DcFZxm9i4v0SogjCbf9reWupwIvfmH6w==", "license": "Apache-2.0", "peerDependencies": { "@firebase/app-types": "0.x", @@ -1492,28 +1471,25 @@ } }, "node_modules/@firebase/component": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.7.2.tgz", - "integrity": "sha512-iyVDGc6Vjx7Rm0cAdccLH/NG6fADsgJak/XW9IA2lPf8AjIlsemOpFGKczYyPHxm4rnKdR8z6sK4+KEC7NwmEg==", + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.9.tgz", + "integrity": "sha512-gm8EUEJE/fEac86AvHn8Z/QW8BvR56TBw3hMW0O838J/1mThYQXAIQBgUv75EqlCZfdawpWLrKt1uXvp9ciK3Q==", "license": "Apache-2.0", "dependencies": { - "@firebase/util": "1.15.0", + "@firebase/util": "1.10.0", "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" } }, "node_modules/@firebase/data-connect": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.6.0.tgz", - "integrity": "sha512-OiugPRcdlhqXF97oR9CjVObILmsWU0dFUS0gXNYEe4bDfpW8pZmQ5GqhIPPtLWbT/0W2lMJJD7VILFMk+xuHPg==", + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.1.0.tgz", + "integrity": "sha512-vSe5s8dY13ilhLnfY0eYRmQsdTbH7PUFZtBbqU6JVX/j8Qp9A6G5gG6//ulbX9/1JFOF1IWNOne9c8S/DOCJaQ==", "license": "Apache-2.0", "dependencies": { - "@firebase/auth-interop-types": "0.2.4", - "@firebase/component": "0.7.2", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.15.0", + "@firebase/auth-interop-types": "0.2.3", + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", "tslib": "^2.1.0" }, "peerDependencies": { @@ -1521,94 +1497,86 @@ } }, "node_modules/@firebase/database": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.1.2.tgz", - "integrity": "sha512-lP96CMjMPy/+d1d9qaaHjHHdzdwvEOuyyLq9ehX89e2XMKwS1jHNzYBO+42bdSumuj5ukPbmnFtViZu8YOMT+w==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.8.tgz", + "integrity": "sha512-dzXALZeBI1U5TXt6619cv0+tgEhJiwlUtQ55WNZY7vGAjv7Q1QioV969iYwt1AQQ0ovHnEW0YW9TiBfefLvErg==", "license": "Apache-2.0", "dependencies": { - "@firebase/app-check-interop-types": "0.3.3", - "@firebase/auth-interop-types": "0.2.4", - "@firebase/component": "0.7.2", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.15.0", + "@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" - }, - "engines": { - "node": ">=20.0.0" } }, "node_modules/@firebase/database-compat": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.1.3.tgz", - "integrity": "sha512-GMyfWjD8mehjg/QpNkY/tl9G/MoeugPeg91n9D0atggxbWuKF/2KhVPHZDH+XmoP0EKYqMWYTtKxBsaBaNKLYQ==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-1.0.8.tgz", + "integrity": "sha512-OpeWZoPE3sGIRPBKYnW9wLad25RaWbGyk7fFQe4xnJQKRzlynWeFBSRRAoLE2Old01WXwskUiucNqUUVlFsceg==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/database": "1.1.2", - "@firebase/database-types": "1.0.19", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.15.0", + "@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" - }, - "engines": { - "node": ">=20.0.0" } }, "node_modules/@firebase/database-types": { - "version": "1.0.19", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.19.tgz", - "integrity": "sha512-FqewjUZmV9LqFfuEnmgdcUpiOUz7qwLXxnm/H8BcMFEzQXtd1yyUDm8ex5VRad2nuTE+ahOuCjUAM/cyDncO+g==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.5.tgz", + "integrity": "sha512-fTlqCNwFYyq/C6W7AJ5OCuq5CeZuBEsEwptnVxlNPkWCo5cTTyukzAHRSO/jaQcItz33FfYrrFk1SJofcu2AaQ==", "license": "Apache-2.0", "dependencies": { - "@firebase/app-types": "0.9.4", - "@firebase/util": "1.15.0" + "@firebase/app-types": "0.9.2", + "@firebase/util": "1.10.0" } }, "node_modules/@firebase/firestore": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.14.0.tgz", - "integrity": "sha512-bZc6YOjRkMBVA16527tgzi6iN9n//xRB3Mmx/R+Gr6UAP/+xrIKOejQIcn1hh+tCzNT8jO0jI+kWox5J4tB/qQ==", + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.7.3.tgz", + "integrity": "sha512-NwVU+JPZ/3bhvNSJMCSzfcBZZg8SUGyzZ2T0EW3/bkUeefCyzMISSt/TTIfEHc8cdyXGlMqfGe3/62u9s74UEg==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.15.0", - "@firebase/webchannel-wrapper": "1.0.5", + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "@firebase/webchannel-wrapper": "1.0.1", "@grpc/grpc-js": "~1.9.0", "@grpc/proto-loader": "^0.7.8", - "tslib": "^2.1.0" + "tslib": "^2.1.0", + "undici": "6.19.7" }, "engines": { - "node": ">=20.0.0" + "node": ">=10.10.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "node_modules/@firebase/firestore-compat": { - "version": "0.4.8", - "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.4.8.tgz", - "integrity": "sha512-WK9NJRpnosGD2nuyjdr7K+Ht7AxRYJlTF62myI4rRA7ibJOosbecvjacR5oirJ7s1BgNS6qzcBw7n4fD3a5w1w==", + "version": "0.3.38", + "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.38.tgz", + "integrity": "sha512-GoS0bIMMkjpLni6StSwRJarpu2+S5m346Na7gr9YZ/BZ/W3/8iHGNr9PxC+f0rNZXqS4fGRn88pICjrZEgbkqQ==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/firestore": "4.14.0", - "@firebase/firestore-types": "3.0.3", - "@firebase/util": "1.15.0", + "@firebase/component": "0.6.9", + "@firebase/firestore": "4.7.3", + "@firebase/firestore-types": "3.0.2", + "@firebase/util": "1.10.0", "tslib": "^2.1.0" }, - "engines": { - "node": ">=20.0.0" - }, "peerDependencies": { "@firebase/app-compat": "0.x" } }, "node_modules/@firebase/firestore-types": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.3.tgz", - "integrity": "sha512-hD2jGdiWRxB/eZWF89xcK9gF8wvENDJkzpVFb4aGkzfEaKxVRD1kjz1t1Wj8VZEp2LCB53Yx1zD8mrhQu87R6Q==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.2.tgz", + "integrity": "sha512-wp1A+t5rI2Qc/2q7r2ZpjUXkRVPtGMd6zCLsiWurjsQpqPgFin3AhNibKcIzoF2rnToNa/XYtyWXuifjOOwDgg==", "license": "Apache-2.0", "peerDependencies": { "@firebase/app-types": "0.x", @@ -1616,58 +1584,53 @@ } }, "node_modules/@firebase/functions": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.13.3.tgz", - "integrity": "sha512-csO7ckK3SSs+NUZW1nms9EK7ckHe/1QOjiP8uAkCYa7ND18s44vjE9g3KxEeIUpyEPqZaX1EhJuFyZjHigAcYw==", + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.11.8.tgz", + "integrity": "sha512-Lo2rTPDn96naFIlSZKVd1yvRRqqqwiJk7cf9TZhUerwnPKgBzXy+aHE22ry+6EjCaQusUoNai6mU6p+G8QZT1g==", "license": "Apache-2.0", "dependencies": { - "@firebase/app-check-interop-types": "0.3.3", - "@firebase/auth-interop-types": "0.2.4", - "@firebase/component": "0.7.2", - "@firebase/messaging-interop-types": "0.2.3", - "@firebase/util": "1.15.0", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" + "@firebase/app-check-interop-types": "0.3.2", + "@firebase/auth-interop-types": "0.2.3", + "@firebase/component": "0.6.9", + "@firebase/messaging-interop-types": "0.2.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0", + "undici": "6.19.7" }, "peerDependencies": { "@firebase/app": "0.x" } }, "node_modules/@firebase/functions-compat": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.4.3.tgz", - "integrity": "sha512-BxkEwWgx1of0tKaao/r2VR6WBLk/RAiyztatiONPrPE8gkitFkOnOCxf8i9cUyA5hX5RGt5H30uNn25Q6QNEmQ==", + "version": "0.3.14", + "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.14.tgz", + "integrity": "sha512-dZ0PKOKQFnOlMfcim39XzaXonSuPPAVuzpqA4ONTIdyaJK/OnBaIEVs/+BH4faa1a2tLeR+Jy15PKqDRQoNIJw==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/functions": "0.13.3", - "@firebase/functions-types": "0.6.3", - "@firebase/util": "1.15.0", + "@firebase/component": "0.6.9", + "@firebase/functions": "0.11.8", + "@firebase/functions-types": "0.6.2", + "@firebase/util": "1.10.0", "tslib": "^2.1.0" }, - "engines": { - "node": ">=20.0.0" - }, "peerDependencies": { "@firebase/app-compat": "0.x" } }, "node_modules/@firebase/functions-types": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.3.tgz", - "integrity": "sha512-EZoDKQLUHFKNx6VLipQwrSMh01A1SaL3Wg6Hpi//x6/fJ6Ee4hrAeswK99I5Ht8roiniKHw4iO0B1Oxj5I4plg==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.2.tgz", + "integrity": "sha512-0KiJ9lZ28nS2iJJvimpY4nNccV21rkQyor5Iheu/nq8aKXJqtJdeSlZDspjPSBBiHRzo7/GMUttegnsEITqR+w==", "license": "Apache-2.0" }, "node_modules/@firebase/installations": { - "version": "0.6.21", - "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.21.tgz", - "integrity": "sha512-xGFGTeICJZ5vhrmmDukeczIcFULFXybojML2+QSDFoKj5A7zbGN7KzFGSKNhDkIxpjzsYG9IleJyUebuAcmqWA==", + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.9.tgz", + "integrity": "sha512-hlT7AwCiKghOX3XizLxXOsTFiFCQnp/oj86zp1UxwDGmyzsyoxtX+UIZyVyH/oBF5+XtblFG9KZzZQ/h+dpy+Q==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/util": "1.15.0", + "@firebase/component": "0.6.9", + "@firebase/util": "1.10.0", "idb": "7.1.1", "tslib": "^2.1.0" }, @@ -1676,15 +1639,15 @@ } }, "node_modules/@firebase/installations-compat": { - "version": "0.2.21", - "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.21.tgz", - "integrity": "sha512-zahIUkaVKbR8zmTeBHkdfaVl6JGWlhVoSjF7CVH33nFqD3SlPEpEEegn2GNT5iAfsVdtlCyJJ9GW4YKjq+RJKQ==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.9.tgz", + "integrity": "sha512-2lfdc6kPXR7WaL4FCQSQUhXcPbI7ol3wF+vkgtU25r77OxPf8F/VmswQ7sgIkBBWtymn5ZF20TIKtnOj9rjb6w==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/installations": "0.6.21", - "@firebase/installations-types": "0.5.3", - "@firebase/util": "1.15.0", + "@firebase/component": "0.6.9", + "@firebase/installations": "0.6.9", + "@firebase/installations-types": "0.5.2", + "@firebase/util": "1.10.0", "tslib": "^2.1.0" }, "peerDependencies": { @@ -1692,36 +1655,33 @@ } }, "node_modules/@firebase/installations-types": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.3.tgz", - "integrity": "sha512-2FJI7gkLqIE0iYsNQ1P751lO3hER+Umykel+TkLwHj6plzWVxqvfclPUZhcKFVQObqloEBTmpi2Ozn7EkCABAA==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.2.tgz", + "integrity": "sha512-que84TqGRZJpJKHBlF2pkvc1YcXrtEDOVGiDjovP/a3s6W4nlbohGXEsBJo0JCeeg/UG9A+DEZVDUV9GpklUzA==", "license": "Apache-2.0", "peerDependencies": { "@firebase/app-types": "0.x" } }, "node_modules/@firebase/logger": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.5.0.tgz", - "integrity": "sha512-cGskaAvkrnh42b3BA3doDWeBmuHFO/Mx5A83rbRDYakPjO9bJtRL3dX7javzc2Rr/JHZf4HlterTW2lUkfeN4g==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.2.tgz", + "integrity": "sha512-Q1VuA5M1Gjqrwom6I6NUU4lQXdo9IAQieXlujeHZWvRt1b7qQ0KwBaNAjgxG27jgF9/mUwsNmO8ptBCGVYhB0A==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" } }, "node_modules/@firebase/messaging": { - "version": "0.12.25", - "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.25.tgz", - "integrity": "sha512-7RhDwoDHlOK1/ou0/LeubxmjcngsTjDdrY/ssg2vwAVpUuVAhQzQvuCAOYxcX5wNC1zCgQ54AP1vdngBwbCmOQ==", + "version": "0.12.12", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.12.tgz", + "integrity": "sha512-6q0pbzYBJhZEtUoQx7hnPhZvAbuMNuBXKQXOx2YlWhSrlv9N1m0ZzlNpBbu/ItTzrwNKTibdYzUyaaxdWLg+4w==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/installations": "0.6.21", - "@firebase/messaging-interop-types": "0.2.3", - "@firebase/util": "1.15.0", + "@firebase/component": "0.6.9", + "@firebase/installations": "0.6.9", + "@firebase/messaging-interop-types": "0.2.2", + "@firebase/util": "1.10.0", "idb": "7.1.1", "tslib": "^2.1.0" }, @@ -1730,14 +1690,14 @@ } }, "node_modules/@firebase/messaging-compat": { - "version": "0.2.25", - "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.25.tgz", - "integrity": "sha512-eoOQqGLtRlseTdiemTN44LlHZpltK5gnhq8XVUuLgtIOG+odtDzrz2UoTpcJWSzaJQVxNLb/x9f39tHdDM4N4w==", + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.12.tgz", + "integrity": "sha512-pKsiUVZrbmRgdImYqhBNZlkKJbqjlPkVdQRZGRbkTyX4OSGKR0F/oJeCt1a8jEg5UnBp4fdVwSWSp4DuCovvEQ==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/messaging": "0.12.25", - "@firebase/util": "1.15.0", + "@firebase/component": "0.6.9", + "@firebase/messaging": "0.12.12", + "@firebase/util": "1.10.0", "tslib": "^2.1.0" }, "peerDependencies": { @@ -1745,39 +1705,38 @@ } }, "node_modules/@firebase/messaging-interop-types": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.3.tgz", - "integrity": "sha512-xfzFaJpzcmtDjycpDeCUj0Ge10ATFi/VHVIvEEjDNc3hodVBQADZ7BWQU7CuFpjSHE+eLuBI13z5F/9xOoGX8Q==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.2.tgz", + "integrity": "sha512-l68HXbuD2PPzDUOFb3aG+nZj5KA3INcPwlocwLZOzPp9rFM9yeuI9YLl6DQfguTX5eAGxO0doTR+rDLDvQb5tA==", "license": "Apache-2.0" }, "node_modules/@firebase/performance": { - "version": "0.7.11", - "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.7.11.tgz", - "integrity": "sha512-V3uAhrz7IYJuji+OgT3qYTGKxpek/TViXti9OSsUJ4AexZ3jQjYH5Yrn7JvBxk8MGiSLsC872hh+BxQiPZsm7g==", + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.6.9.tgz", + "integrity": "sha512-PnVaak5sqfz5ivhua+HserxTJHtCar/7zM0flCX6NkzBNzJzyzlH4Hs94h2Il0LQB99roBqoE5QT1JqWqcLJHQ==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/installations": "0.6.21", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.15.0", - "tslib": "^2.1.0", - "web-vitals": "^4.2.4" + "@firebase/component": "0.6.9", + "@firebase/installations": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "node_modules/@firebase/performance-compat": { - "version": "0.2.24", - "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.24.tgz", - "integrity": "sha512-YRlejH8wLt7ThWao+HXoKUHUrZKGYq+otxkPS+8nuE5PeN1cBXX7NAJl9ueuUkBwMIrnKdnDqL/voHXxDAAt3g==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.9.tgz", + "integrity": "sha512-dNl95IUnpsu3fAfYBZDCVhXNkASE0uo4HYaEPd2/PKscfTvsgqFAOxfAXzBEDOnynDWiaGUnb5M1O00JQ+3FXA==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/logger": "0.5.0", - "@firebase/performance": "0.7.11", - "@firebase/performance-types": "0.2.3", - "@firebase/util": "1.15.0", + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/performance": "0.6.9", + "@firebase/performance-types": "0.2.2", + "@firebase/util": "1.10.0", "tslib": "^2.1.0" }, "peerDependencies": { @@ -1785,21 +1744,21 @@ } }, "node_modules/@firebase/performance-types": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.3.tgz", - "integrity": "sha512-IgkyTz6QZVPAq8GSkLYJvwSLr3LS9+V6vNPQr0x4YozZJiLF5jYixj0amDtATf1X0EtYHqoPO48a9ija8GocxQ==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.2.tgz", + "integrity": "sha512-gVq0/lAClVH5STrIdKnHnCo2UcPLjJlDUoEB/tB4KM+hAeHUxWKnpT0nemUPvxZ5nbdY/pybeyMe8Cs29gEcHA==", "license": "Apache-2.0" }, "node_modules/@firebase/remote-config": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.8.2.tgz", - "integrity": "sha512-5EXqOThV4upjK9D38d/qOSVwOqRhemlaOFk9vCkMNNALeIlwr+4pLjtLNo4qoY8etQmU/1q4aIATE9N8PFqg0g==", + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.4.9.tgz", + "integrity": "sha512-EO1NLCWSPMHdDSRGwZ73kxEEcTopAxX1naqLJFNApp4hO8WfKfmEpmjxmP5TrrnypjIf2tUkYaKsfbEA7+AMmA==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/installations": "0.6.21", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.15.0", + "@firebase/component": "0.6.9", + "@firebase/installations": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", "tslib": "^2.1.0" }, "peerDependencies": { @@ -1807,16 +1766,16 @@ } }, "node_modules/@firebase/remote-config-compat": { - "version": "0.2.23", - "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.23.tgz", - "integrity": "sha512-4+KqRRHEUUmKT6tFmnpWATOsaFfmSuBs1jXH8JzVtMLEYqq/WS9IDM92OdefFDSrAA2xGd0WN004z8mKeIIscw==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.9.tgz", + "integrity": "sha512-AxzGpWfWFYejH2twxfdOJt5Cfh/ATHONegTd/a0p5flEzsD5JsxXgfkFToop+mypEL3gNwawxrxlZddmDoNxyA==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/logger": "0.5.0", - "@firebase/remote-config": "0.8.2", - "@firebase/remote-config-types": "0.5.0", - "@firebase/util": "1.15.0", + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/remote-config": "0.4.9", + "@firebase/remote-config-types": "0.3.2", + "@firebase/util": "1.10.0", "tslib": "^2.1.0" }, "peerDependencies": { @@ -1824,64 +1783,84 @@ } }, "node_modules/@firebase/remote-config-types": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.5.0.tgz", - "integrity": "sha512-vI3bqLoF14L/GchtgayMiFpZJF+Ao3uR8WCde0XpYNkSokDpAKca2DxvcfeZv7lZUqkUwQPL2wD83d3vQ4vvrg==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.3.2.tgz", + "integrity": "sha512-0BC4+Ud7y2aPTyhXJTMTFfrGGLqdYXrUB9sJVAB8NiqJswDTc4/2qrE/yfUbnQJhbSi6ZaTTBKyG3n1nplssaA==", "license": "Apache-2.0" }, "node_modules/@firebase/rules-unit-testing": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@firebase/rules-unit-testing/-/rules-unit-testing-5.0.0.tgz", - "integrity": "sha512-C6+d3Msgjnqay2ml663ChvKYoD8VsQ+TIa0e+fGq0LFC0CKSPlacT1EVGL/ryo6Rc+wFs7Fpqz3fRlYdUEa2bA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@firebase/rules-unit-testing/-/rules-unit-testing-3.0.4.tgz", + "integrity": "sha512-FxDc5rnTtt266PTs3dOkf4ZDq+P223TrFWXka/yG6gSFy3Es/iKwWh3bX9pROobHgbbrAd7she9+687yOC2z+A==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@types/node-fetch": "2.6.4", + "node-fetch": "2.6.7" + }, "engines": { - "node": ">=20.0.0" + "node": ">=10.10.0" }, "peerDependencies": { - "firebase": "^12.0.0" + "firebase": "^10.0.0" + } + }, + "node_modules/@firebase/rules-unit-testing/node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, "node_modules/@firebase/storage": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.14.2.tgz", - "integrity": "sha512-o/culaTeJ8GRpKXRJov21rux/n9dRaSOWLebyatFP2sqEdCxQPjVA1H9Z2fzYwQxMIU0JVmC7SPPmU11v7L6vQ==", + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.13.2.tgz", + "integrity": "sha512-fxuJnHshbhVwuJ4FuISLu+/76Aby2sh+44ztjF2ppoe0TELIDxPW6/r1KGlWYt//AD0IodDYYA8ZTN89q8YqUw==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/util": "1.15.0", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" + "@firebase/component": "0.6.9", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0", + "undici": "6.19.7" }, "peerDependencies": { "@firebase/app": "0.x" } }, "node_modules/@firebase/storage-compat": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.4.2.tgz", - "integrity": "sha512-R+aB38wxCH5zjIO/xu9KznI7fgiPuZAG98uVm1NcidHyyupGgIDLKigGmRGBZMnxibe/m2oxNKoZpfEbUX2aQQ==", + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.12.tgz", + "integrity": "sha512-hA4VWKyGU5bWOll+uwzzhEMMYGu9PlKQc1w4DWxB3aIErWYzonrZjF0icqNQZbwKNIdh8SHjZlFeB2w6OSsjfg==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/storage": "0.14.2", - "@firebase/storage-types": "0.8.3", - "@firebase/util": "1.15.0", + "@firebase/component": "0.6.9", + "@firebase/storage": "0.13.2", + "@firebase/storage-types": "0.8.2", + "@firebase/util": "1.10.0", "tslib": "^2.1.0" }, - "engines": { - "node": ">=20.0.0" - }, "peerDependencies": { "@firebase/app-compat": "0.x" } }, "node_modules/@firebase/storage-types": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.3.tgz", - "integrity": "sha512-+Muk7g9uwngTpd8xn9OdF/D48uiQ7I1Fae7ULsWPuKoCH3HU7bfFPhxtJYzyhjdniowhuDpQcfPmuNRAqZEfvg==", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.2.tgz", + "integrity": "sha512-0vWu99rdey0g53lA7IShoA2Lol1jfnPovzLDUBuon65K7uKG9G+L5uO05brD9pMw+l4HRFw23ah3GwTGpEav6g==", "license": "Apache-2.0", "peerDependencies": { "@firebase/app-types": "0.x", @@ -1889,22 +1868,38 @@ } }, "node_modules/@firebase/util": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.15.0.tgz", - "integrity": "sha512-AmWf3cHAOMbrCPG4xdPKQaj5iHnyYfyLKZxwz+Xf55bqKbpAmcYifB4jQinT2W9XhDRHISOoPyBOariJpCG6FA==", - "hasInstallScript": true, + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.10.0.tgz", + "integrity": "sha512-xKtx4A668icQqoANRxyDLBLz51TAbDP9KRfpbKGxiCAW346d0BeJe5vN6/hKxxmWwnZ0mautyv39JxviwwQMOQ==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/vertexai-preview": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@firebase/vertexai-preview/-/vertexai-preview-0.0.4.tgz", + "integrity": "sha512-EBSqyu9eg8frQlVU9/HjKtHN7odqbh9MtAcVz3WwHj4gLCLOoN9F/o+oxlq3CxvFrd3CNTZwu6d2mZtVlEInng==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.2", + "@firebase/component": "0.6.9", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.10.0", + "tslib": "^2.1.0" }, "engines": { - "node": ">=20.0.0" + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" } }, "node_modules/@firebase/webchannel-wrapper": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.5.tgz", - "integrity": "sha512-+uGNN7rkfn41HLO0vekTFhTxk61eKa8mTpRGLO0QSqlQdKvIoGAvLp3ppdVIWbTGYJWM6Kp0iN+PjMIOcnVqTw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.1.tgz", + "integrity": "sha512-jmEnr/pk0yVkA7mIlHNnxCi+wWzOFUg0WyIotgkKAb2u1J7fAeDBcVNSTjTihbAYNusCLQdW5s9IJ5qwnEufcQ==", "license": "Apache-2.0" }, "node_modules/@floating-ui/core": { @@ -1954,6 +1949,45 @@ "url": "https://github.com/sponsors/ayuhito" } }, + "node_modules/@gilbarbara/deep-equal": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@gilbarbara/deep-equal/-/deep-equal-0.4.1.tgz", + "integrity": "sha512-QF2BGeQjsa59T59XvFdR3is5jrl28Eg0J6giXAC5919bcqvR8XP4B+07tpbs6Y6/IQd4FBncaL2WVXIBgSxt4w==", + "license": "MIT" + }, + "node_modules/@gilbarbara/hooks": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@gilbarbara/hooks/-/hooks-0.11.0.tgz", + "integrity": "sha512-CIVazdxqFRplUfm9wZL3/0X1TURJekhPMWGFdWzEmyJrGPiotX2yxA1KiB8N7VnhawIaMtb2Apnda4Y6DRwi2Q==", + "license": "MIT", + "dependencies": { + "@gilbarbara/deep-equal": "^0.4.1" + }, + "peerDependencies": { + "react": "16.8 - 19" + } + }, + "node_modules/@gilbarbara/types": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@gilbarbara/types/-/types-0.2.2.tgz", + "integrity": "sha512-QuQDBRRcm1Q8AbSac2W1YElurOhprj3Iko/o+P1fJxUWS4rOGKMVli98OXS7uo4z+cKAif6a+L9bcZFSyauQpQ==", + "license": "MIT", + "dependencies": { + "type-fest": "^4.1.0" + } + }, + "node_modules/@gilbarbara/types/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@google/genai": { "version": "1.50.1", "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.50.1.tgz", @@ -1978,9 +2012,9 @@ } }, "node_modules/@grpc/grpc-js": { - "version": "1.9.15", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz", - "integrity": "sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ==", + "version": "1.9.16", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.16.tgz", + "integrity": "sha512-wE4Ut/olIzfKqp631XrG+wbF0v1vWFN4YL9FyXC2LJiG33DsV7PLzURjrCvY/6je2ntdRkeLpPDluzSRGaVltQ==", "license": "Apache-2.0", "dependencies": { "@grpc/proto-loader": "^0.7.8", @@ -3634,6 +3668,34 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/node-fetch": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.4.tgz", + "integrity": "sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^3.0.0" + } + }, + "node_modules/@types/node-fetch/node_modules/form-data": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.4.tgz", + "integrity": "sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/@types/qs": { "version": "6.15.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", @@ -3648,6 +3710,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, "node_modules/@types/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", @@ -4953,6 +5025,13 @@ "node": ">=4" } }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT", + "peer": true + }, "node_modules/d3-array": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", @@ -6465,39 +6544,39 @@ } }, "node_modules/firebase": { - "version": "12.12.1", - "resolved": "https://registry.npmjs.org/firebase/-/firebase-12.12.1.tgz", - "integrity": "sha512-ee7xA+bTJLfjB9BP/8FQr3EkxmpAAGc1lNc5QkWgTDpUw24HYXFPm7FEWRdLtGnygxIdYpFmepSc5VjkI6NHhw==", + "version": "10.14.1", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-10.14.1.tgz", + "integrity": "sha512-0KZxU+Ela9rUCULqFsUUOYYkjh7OM1EWdIfG6///MtXd0t2/uUIf0iNV5i0KariMhRQ5jve/OY985nrAXFaZeQ==", "license": "Apache-2.0", "dependencies": { - "@firebase/ai": "2.11.1", - "@firebase/analytics": "0.10.21", - "@firebase/analytics-compat": "0.2.27", - "@firebase/app": "0.14.11", - "@firebase/app-check": "0.11.2", - "@firebase/app-check-compat": "0.4.2", - "@firebase/app-compat": "0.5.11", - "@firebase/app-types": "0.9.4", - "@firebase/auth": "1.13.0", - "@firebase/auth-compat": "0.6.5", - "@firebase/data-connect": "0.6.0", - "@firebase/database": "1.1.2", - "@firebase/database-compat": "2.1.3", - "@firebase/firestore": "4.14.0", - "@firebase/firestore-compat": "0.4.8", - "@firebase/functions": "0.13.3", - "@firebase/functions-compat": "0.4.3", - "@firebase/installations": "0.6.21", - "@firebase/installations-compat": "0.2.21", - "@firebase/messaging": "0.12.25", - "@firebase/messaging-compat": "0.2.25", - "@firebase/performance": "0.7.11", - "@firebase/performance-compat": "0.2.24", - "@firebase/remote-config": "0.8.2", - "@firebase/remote-config-compat": "0.2.23", - "@firebase/storage": "0.14.2", - "@firebase/storage-compat": "0.4.2", - "@firebase/util": "1.15.0" + "@firebase/analytics": "0.10.8", + "@firebase/analytics-compat": "0.2.14", + "@firebase/app": "0.10.13", + "@firebase/app-check": "0.8.8", + "@firebase/app-check-compat": "0.3.15", + "@firebase/app-compat": "0.2.43", + "@firebase/app-types": "0.9.2", + "@firebase/auth": "1.7.9", + "@firebase/auth-compat": "0.5.14", + "@firebase/data-connect": "0.1.0", + "@firebase/database": "1.0.8", + "@firebase/database-compat": "1.0.8", + "@firebase/firestore": "4.7.3", + "@firebase/firestore-compat": "0.3.38", + "@firebase/functions": "0.11.8", + "@firebase/functions-compat": "0.3.14", + "@firebase/installations": "0.6.9", + "@firebase/installations-compat": "0.2.9", + "@firebase/messaging": "0.12.12", + "@firebase/messaging-compat": "0.2.12", + "@firebase/performance": "0.6.9", + "@firebase/performance-compat": "0.2.9", + "@firebase/remote-config": "0.4.9", + "@firebase/remote-config-compat": "0.2.9", + "@firebase/storage": "0.13.2", + "@firebase/storage-compat": "0.3.12", + "@firebase/util": "1.10.0", + "@firebase/vertexai-preview": "0.0.4" } }, "node_modules/flat-cache": { @@ -7614,6 +7693,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-lite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-lite/-/is-lite-2.0.0.tgz", + "integrity": "sha512-70f2BMIQlbSUXVKaZUd9a9fJH3IH1PDckV0m4BIIO4LjnNYvOh4Ng7vXIXEwpA0KDZknRq+7fHwGTu0jIdx28g==", + "license": "MIT" + }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -9936,6 +10021,16 @@ "react": ">= 16.8 || 18.0.0" } }, + "node_modules/react-innertext": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/react-innertext/-/react-innertext-1.1.5.tgz", + "integrity": "sha512-PWAqdqhxhHIv80dT9znP2KvS+hfkbRovFp4zFYHFFlOoQLRiawIic81gKb3U1wEyJZgMwgs3JoLtwryASRWP3Q==", + "license": "MIT", + "peerDependencies": { + "@types/react": ">=0.0.0 <=99", + "react": ">=0.0.0 <=99" + } + }, "node_modules/react-is": { "version": "19.2.5", "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.5.tgz", @@ -9943,6 +10038,28 @@ "license": "MIT", "peer": true }, + "node_modules/react-joyride": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/react-joyride/-/react-joyride-3.1.0.tgz", + "integrity": "sha512-+UEDpNsYSHhhSW/OQcNl6+oODYx20EP6TykSD45if0MqAAZMYD+3DU64w9wP3fBjQswvq5BgK99w3rw6ing69g==", + "license": "MIT", + "dependencies": { + "@fastify/deepmerge": "^3.2.1", + "@floating-ui/react-dom": "^2.1.8", + "@gilbarbara/deep-equal": "^0.4.1", + "@gilbarbara/hooks": "^0.11.0", + "@gilbarbara/types": "^0.2.2", + "is-lite": "^2.0.0", + "react-innertext": "^1.1.5", + "scroll": "^3.0.1", + "scrollparent": "^2.1.0", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "react": "16.8 - 19", + "react-dom": "16.8 - 19" + } + }, "node_modules/react-onesignal": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/react-onesignal/-/react-onesignal-3.5.2.tgz", @@ -10530,6 +10647,18 @@ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, + "node_modules/scroll": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scroll/-/scroll-3.0.1.tgz", + "integrity": "sha512-pz7y517OVls1maEzlirKO5nPYle9AXsFzTMNJrRGmT951mzpIBy7sNHOg5o/0MQd/NqliCiWnAi0kZneMPFLcg==", + "license": "MIT" + }, + "node_modules/scrollparent": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/scrollparent/-/scrollparent-2.1.0.tgz", + "integrity": "sha512-bnnvJL28/Rtz/kz2+4wpBjHzWoEzXhVg/TE8BeVGJHUqE8THNIRnDxDWMktwM+qahvlRdvlLdsQfYe+cuqfZeA==", + "license": "ISC" + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -10902,6 +11031,16 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/sonner": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", + "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -11291,6 +11430,13 @@ "node": ">=16" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, "node_modules/ts-morph": { "version": "26.0.0", "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-26.0.0.tgz", @@ -12017,6 +12163,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici": { + "version": "6.19.7", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.19.7.tgz", + "integrity": "sha512-HR3W/bMGPSr90i8AAp2C4DM3wChFdJPLrWYpIS++LxS8K+W535qftjt+4MyjNYHeWabMj1nvtmLIi7l++iq91A==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -12764,16 +12919,17 @@ "node": ">= 8" } }, - "node_modules/web-vitals": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz", - "integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==", - "license": "Apache-2.0" + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" }, "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.5.tgz", + "integrity": "sha512-ZL2+3c7kMBdIRCMz6l8jQMHyGVxj+UL+xVk74Ombiciboca8rHa15L86B19E5oh1pL9Ii/uj54gtsIrZGMo6zA==", "license": "Apache-2.0", "dependencies": { "http-parser-js": ">=0.5.1", @@ -12793,6 +12949,17 @@ "node": ">=0.8.0" } }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 7b8fa0c..e30b99d 100644 --- a/package.json +++ b/package.json @@ -33,9 +33,11 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "react-dropzone": "^15.0.0", + "react-joyride": "^3.1.0", "react-onesignal": "^3.5.2", "recharts": "^3.8.1", "shadcn": "^4.7.0", + "sonner": "^2.0.7", "tailwind-merge": "^3.5.0", "tw-animate-css": "^1.4.0", "vite": "^6.2.0" diff --git a/server.ts b/server.ts index 47f1b12..42b1078 100644 --- a/server.ts +++ b/server.ts @@ -25,15 +25,58 @@ async function startServer() { // --- BACKGROUND WORKER NODE (CRON JOB) --- function getYfSymbol(pair: string): string { if (pair.includes('XAU')) return 'GC=F'; + if (pair.includes('XAG')) return 'SI=F'; if (pair.includes('US100')) return 'NQ=F'; if (pair.includes('US30')) return 'YM=F'; if (pair.includes('BTC')) return 'BTC-USD'; + if (pair.includes('ETH')) return 'ETH-USD'; // Clean punctuation and common broker suffixes const cleanPair = pair.toString().replace('/', '').replace(/\.M$/, '').replace(/\.std$/, ''); return cleanPair + '=X'; } +async function getCurrentPriceProxy(symbol: string): Promise { + const symStr = symbol.trim().toUpperCase(); + if (symStr.includes('INDEX') || symStr.includes('BOOM') || symStr.includes('CRASH') || symStr.includes('VIX') || symStr.includes('VOLATILITY') || symStr.includes('STEP')) { + return null; // Synthetic indices handled separately or simulated + } + + if (process.env.TWELVE_DATA_API_KEY) { + try { + let twelveSymbol = symStr; + if (twelveSymbol === 'GOLD' || twelveSymbol === 'OURO') twelveSymbol = 'XAU/USD'; + if (twelveSymbol === 'SILVER' || twelveSymbol === 'PRATA') twelveSymbol = 'XAG/USD'; + const res = await axios.get(`https://api.twelvedata.com/price?symbol=${twelveSymbol}&apikey=${process.env.TWELVE_DATA_API_KEY}`); + if (res.data && res.data.price) return parseFloat(res.data.price); + } catch (e: any) { + // Fallback silently if TwelveData is rate-limited or fails + } + } + + const isCrypto = symStr.includes('BTC') || symStr.includes('ETH') || symStr.includes('USDT') || symStr.includes('BNB'); + if (isCrypto) { + try { + const binanceSymbol = symStr.replace(/[^A-Z0-9]/g, ''); + const res = await axios.get(`https://api.binance.com/api/v3/ticker/price?symbol=${binanceSymbol}`); + if (res.data && res.data.price) return parseFloat(res.data.price); + } catch (e) {} + } + + try { + let symbolYF = getYfSymbol(symStr); + const yfRes = await axios.get(`https://query1.finance.yahoo.com/v8/finance/chart/${symbolYF}?interval=1m&range=1d`, { + headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" } + }); + const result = yfRes.data.chart.result[0]; + return parseFloat(result.meta.regularMarketPrice); + } catch(e: any) { + console.warn("Yahoo Finance error:", e.message); + } + + return null; +} + const sendTelegramAlertServer = async (text: string, botToken: string, chatId: string, replyToMessageId?: number) => { try { const payload: any = { @@ -63,7 +106,8 @@ const sendTelegramAlertServer = async (text: string, botToken: string, chatId: s const signal = docSnap.data(); let currentPrice = null; try { - if (signal.pair.includes('Index')) { + const symStr = signal.pair.toUpperCase(); + if (symStr.includes('INDEX') || symStr.includes('BOOM') || symStr.includes('CRASH') || symStr.includes('VIX') || symStr.includes('VOLATILITY') || symStr.includes('STEP')) { const tp = parseFloat(signal.takeProfit); const sl = parseFloat(signal.stopLoss); // Simulamos um fecho aleatório para os índices para testes rápidos @@ -73,13 +117,10 @@ const sendTelegramAlertServer = async (text: string, botToken: string, chatId: s currentPrice = (tp + sl) / 2; // price stays in the middle } } else { - let symbolYF = getYfSymbol(signal.pair); - const yfRes = await axios.get(`https://query1.finance.yahoo.com/v8/finance/chart/${symbolYF}?interval=1m&range=1d`); - const result = yfRes.data.chart.result[0]; - currentPrice = parseFloat(result.meta.regularMarketPrice); + currentPrice = await getCurrentPriceProxy(symStr); } } catch(e: any) { - console.error("Yahoo Finance data fetch error:", e.message); + console.error("Proxy price fetch error:", e.message); continue; } @@ -751,7 +792,29 @@ const sendTelegramAlertServer = async (text: string, botToken: string, chatId: s tradeResult = await connection.createMarketSellOrder(sym, volume, 0, 0, { comment: 'TEST SMART ENTRY' }); } - res.json({ success: true, tradeResult }); + let parsedResult: any = {}; + if (tradeResult) { + try { + // Extract plain properties to avoid circular references from MetaApi objects + parsedResult = { + orderId: tradeResult.orderId, + positionId: tradeResult.positionId, + volume: tradeResult.volume, + price: tradeResult.price, + type: tradeResult.type, + comment: tradeResult.comment, + done: tradeResult.done, + // For Deriv API fallback + buy: tradeResult.buy?.transaction_id, + sell: tradeResult.sell?.transaction_id, + error: tradeResult.error ? String(tradeResult.error) : undefined + }; + } catch(e) { + parsedResult = { message: "Trade executed, but could not parse result due to circular references." }; + } + } + + res.json({ success: true, tradeResult: parsedResult }); } catch(e: any) { const errString = e.message || e.toString(); if (errString.toLowerCase().includes("permission") || errString.toLowerCase().includes("insufficient")) { @@ -768,16 +831,19 @@ const sendTelegramAlertServer = async (text: string, botToken: string, chatId: s app.get("/api/twelve/quote", async (req, res) => { const symbol = req.query.symbol || "EUR/USD"; try { - if (symbol.toString().includes('Index') || symbol.toString().includes('BOOM') || symbol.toString().includes('CRASH')) { + const symStr = symbol.toString().toUpperCase(); + if (symStr.includes('INDEX') || symStr.includes('BOOM') || symStr.includes('CRASH') || symStr.includes('VIX') || symStr.includes('VOLATILITY') || symStr.includes('STEP')) { return res.json({ close: "15000" }); } - let symbolYF = getYfSymbol(symbol.toString()); - const response = await axios.get(`https://query1.finance.yahoo.com/v8/finance/chart/${symbolYF}?interval=1d&range=1d`); - const price = response.data.chart.result[0].meta.regularMarketPrice; - res.json({ close: price.toString() }); + + const price = await getCurrentPriceProxy(symbol.toString()); + if (price !== null) { + res.json({ close: price.toString() }); + } else { + res.json({ close: "1.000" }); + } } catch (error: any) { console.error(`Error fetching proxy quote for ${symbol}:`, error.message); - // Fallback res.json({ close: "1.000" }); } }); @@ -785,13 +851,43 @@ const sendTelegramAlertServer = async (text: string, botToken: string, chatId: s // Proxy for history mapped to Twelve format app.get("/api/twelve/history", async (req, res) => { const symbol = req.query.symbol as string; - let symbolYF = ""; try { - if (symbol.toString().includes('Index') || symbol.toString().includes('BOOM') || symbol.toString().includes('CRASH')) { + const symStr = symbol.toString().toUpperCase(); + if (symStr.includes('INDEX') || symStr.includes('BOOM') || symStr.includes('CRASH') || symStr.includes('VIX') || symStr.includes('VOLATILITY') || symStr.includes('STEP')) { return res.json({ values: [] }); } - symbolYF = getYfSymbol(symbol.toString()); - const response = await axios.get(`https://query1.finance.yahoo.com/v8/finance/chart/${symbolYF}?interval=15m&range=5d`); + + if (process.env.TWELVE_DATA_API_KEY) { + try { + let twelveSymbol = symStr; + if (twelveSymbol === 'GOLD' || twelveSymbol === 'OURO') twelveSymbol = 'XAU/USD'; + if (twelveSymbol === 'SILVER' || twelveSymbol === 'PRATA') twelveSymbol = 'XAG/USD'; + const tkres = await axios.get(`https://api.twelvedata.com/time_series?symbol=${twelveSymbol}&interval=15min&apikey=${process.env.TWELVE_DATA_API_KEY}`); + if (tkres.data && tkres.data.values) { + return res.json({ values: tkres.data.values }); + } + } catch(e) {} + } + + const isCrypto = symStr.includes('BTC') || symStr.includes('ETH') || symStr.includes('USDT') || symStr.includes('BNB'); + if (isCrypto) { + try { + const binanceSymbol = symStr.replace(/[^A-Z0-9]/g, ''); + const bRes = await axios.get(`https://api.binance.com/api/v3/klines?symbol=${binanceSymbol}&interval=15m&limit=200`); + if (bRes.data && Array.isArray(bRes.data)) { + const values = bRes.data.map((c: any) => ({ + datetime: new Date(c[0]).toISOString().replace('T', ' ').substring(0, 19), + open: c[1], high: c[2], low: c[3], close: c[4], volume: c[5] + })).reverse(); + return res.json({ values }); + } + } catch(e) {} + } + + let symbolYF = getYfSymbol(symbol.toString()); + const response = await axios.get(`https://query1.finance.yahoo.com/v8/finance/chart/${symbolYF}?interval=15m&range=5d`, { + headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" } + }); const result = response.data.chart.result[0]; const quote = result.indicators.quote[0]; const timestamps = result.timestamp; @@ -801,7 +897,7 @@ const sendTelegramAlertServer = async (text: string, botToken: string, chatId: s for (let i = timestamps.length - 1; i >= 0; i--) { if (quote.close[i] !== null) { values.push({ - datetime: new Date(timestamps[i] * 1000).toISOString(), + datetime: new Date(timestamps[i] * 1000).toISOString().replace('T', ' ').substring(0, 19), open: quote.open[i].toString(), high: quote.high[i].toString(), low: quote.low[i].toString(), @@ -842,8 +938,14 @@ const sendTelegramAlertServer = async (text: string, botToken: string, chatId: s included_segments: ['Subscribed Users'], }; - const response = await client.createNotification(notification); - res.json({ success: true, response }); + let finalResponse = "ok"; + try { + const response = await client.createNotification(notification); + finalResponse = (response as any).id || "ok"; + } catch (e) { + // ignore or handle + } + res.json({ success: true, response: finalResponse }); } catch (error) { console.error("Error sending OneSignal notification", error); res.status(500).json({ error: "Failed to send notification" }); diff --git a/src/App.tsx b/src/App.tsx index 6bd39e4..69ab916 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -16,6 +16,7 @@ import { ProfileView } from './components/ProfileView'; import { NotificationManager } from './components/NotificationManager'; import { MaintenancePage } from './components/MaintenancePage'; import { LiveMarketTicker } from './components/LiveMarketTicker'; +import { TourGuide } from './components/TourGuide'; import { TrendingUp, ShieldAlert, Ghost, Mail, Lock, UserPlus, LogIn, Loader2, ArrowLeft, User as UserIcon, Eye, EyeOff } from 'lucide-react'; import { motion, AnimatePresence } from 'motion/react'; import { onAuthStateChanged, signInWithEmailAndPassword, createUserWithEmailAndPassword, updateProfile, signOut, sendEmailVerification, sendPasswordResetEmail, User, setPersistence, browserLocalPersistence, browserSessionPersistence } from 'firebase/auth'; @@ -23,6 +24,8 @@ import { doc, setDoc, getDoc, updateDoc, onSnapshot } from 'firebase/firestore'; import { auth, db, handleFirestoreError, OperationType } from './lib/firebase'; import { cn } from './lib/utils'; +import { Toaster } from 'sonner'; + declare global { interface Window { OneSignalDeferred: any[]; @@ -34,6 +37,7 @@ export default function App() { const [userData, setUserData] = useState(null); const [loading, setLoading] = useState(true); const [activeTab, setActiveTab] = useState('scan'); + const [lang, setLang] = useState<'PT' | 'EN' | 'ES'>('PT'); const [maintenanceMode, setMaintenanceMode] = useState(false); const [maintenanceMessage, setMaintenanceMessage] = useState(''); @@ -86,7 +90,8 @@ export default function App() { useEffect(() => { const unsubscribe = onAuthStateChanged(auth, async (currentUser) => { - if (currentUser && currentUser.emailVerified) { + const isAdminEmail = currentUser?.email === "fxbrosinvestments00@gmail.com" || currentUser?.email === "desartstudiopro@gmail.com"; + if (currentUser && (currentUser.emailVerified || isAdminEmail)) { try { const userRef = doc(db, 'users', currentUser.uid); const userSnap = await getDoc(userRef).catch(err => { @@ -98,18 +103,18 @@ export default function App() { uid: currentUser.uid, email: currentUser.email, name: currentUser.displayName || 'User', - isAdmin: currentUser.email === "fxbrosinvestments00@gmail.com", + isAdmin: currentUser.email === "fxbrosinvestments00@gmail.com" || currentUser.email === "desartstudiopro@gmail.com", isPremium: false, plan: 'basic' as const, analysisLimit: 8, - isApproved: currentUser.email === "fxbrosinvestments00@gmail.com" + isApproved: currentUser.email === "fxbrosinvestments00@gmail.com" || currentUser.email === "desartstudiopro@gmail.com" }; if (userSnap.exists()) { data = { ...data, ...userSnap.data() }; // Force admin rights for root email - if (currentUser.email === "fxbrosinvestments00@gmail.com") { + if (currentUser.email === "fxbrosinvestments00@gmail.com" || currentUser.email === "desartstudiopro@gmail.com") { data.isAdmin = true; data.isApproved = true; @@ -153,7 +158,7 @@ export default function App() { } catch (err) { console.error("Firestore user initialization error:", err); // Set minimal data to allow UI to render, but it might be limited due to rules - setUserData({ isPremium: false, isAdmin: currentUser.email === "fxbrosinvestments00@gmail.com", isApproved: false }); + setUserData({ isPremium: false, isAdmin: currentUser.email === "fxbrosinvestments00@gmail.com" || currentUser.email === "desartstudiopro@gmail.com", isApproved: false }); } setUser(currentUser); @@ -169,7 +174,7 @@ export default function App() { return () => unsubscribe(); }, []); - const isAdmin = user?.email === "fxbrosinvestments00@gmail.com" || userData?.isAdmin; + const isAdmin = user?.email === "fxbrosinvestments00@gmail.com" || user?.email === "desartstudiopro@gmail.com" || userData?.isAdmin; const handleResetPassword = async (e: React.FormEvent) => { e.preventDefault(); @@ -197,7 +202,8 @@ export default function App() { if (isLogin) { const targetEmail = email.toLowerCase() === 'admin' ? 'fxbrosinvestments00@gmail.com' : email; const userCredential = await signInWithEmailAndPassword(auth, targetEmail, password); - if (!userCredential.user.emailVerified) { + const isAdminEmail = userCredential.user.email === "fxbrosinvestments00@gmail.com" || userCredential.user.email === "desartstudiopro@gmail.com"; + if (!userCredential.user.emailVerified && !isAdminEmail) { try { await sendEmailVerification(userCredential.user); } catch (e) { @@ -220,11 +226,11 @@ export default function App() { uid: userCredential.user.uid, email: userCredential.user.email, name: name, - isAdmin: userCredential.user.email === "fxbrosinvestments00@gmail.com", + isAdmin: userCredential.user.email === "fxbrosinvestments00@gmail.com" || userCredential.user.email === "desartstudiopro@gmail.com", isPremium: false, plan: 'basic', analysisLimit: 8, - isApproved: userCredential.user.email === "fxbrosinvestments00@gmail.com" + isApproved: userCredential.user.email === "fxbrosinvestments00@gmail.com" || userCredential.user.email === "desartstudiopro@gmail.com" }); } catch (e) { console.error("Failed to write new user to DB", e); @@ -598,6 +604,8 @@ export default function App() { return (
+ + {/* Background Image for App */}
@@ -636,6 +644,17 @@ export default function App() {
+
+ {(['PT', 'EN', 'ES'] as const).map(l => ( + + ))} +
@@ -652,13 +671,26 @@ export default function App() { referrerPolicy="no-referrer" />
- Olá Humano, Bem-Vindo + Olá Humano
QUANTSCAN
- +
+
+ {(['PT', 'EN', 'ES'] as const).map(l => ( + + ))} +
+ +
diff --git a/src/components/AnalysisView.tsx b/src/components/AnalysisView.tsx index 9a7d20f..b30c4b9 100644 --- a/src/components/AnalysisView.tsx +++ b/src/components/AnalysisView.tsx @@ -5,6 +5,7 @@ import { motion, AnimatePresence } from 'motion/react'; import { analyzeForexChart } from '../services/geminiService'; import { AnalysisResponse, SignalResult, SignalType } from '../types'; import { cn } from '../lib/utils'; +import { toast } from 'sonner'; import { ref, uploadBytes, getDownloadURL } from 'firebase/storage'; import axios from 'axios'; import { compressImage, cleanupStorage } from '../lib/imageUtils'; @@ -12,6 +13,7 @@ import { storage, auth, db, handleFirestoreError, OperationType } from '../lib/f import { collection, addDoc, serverTimestamp, query, where, getDocs, Timestamp } from 'firebase/firestore'; import { fetchCurrentPrice } from '../services/marketData'; import { sendTelegramAlert } from '../services/notifications'; +import { WatchlistPanel } from './WatchlistPanel'; export const AnalysisView: React.FC<{ userData?: any, onGoToHistory?: () => void }> = ({ userData, onGoToHistory }) => { const [file, setFile] = useState(null); @@ -20,9 +22,11 @@ export const AnalysisView: React.FC<{ userData?: any, onGoToHistory?: () => void const [result, setResult] = useState(null); const [error, setError] = useState(null); const [mode, setMode] = useState<'Técnico' | 'Fundamental' | 'Híbrido'>('Híbrido'); + const [tradingStyle, setTradingStyle] = useState<'Automático' | 'Scalping' | 'Day Trading' | 'Swing'>('Automático'); const [usageInfo, setUsageInfo] = useState<{ used: number, limit: number }>({ used: 0, limit: userData?.analysisLimit ?? 8 }); const [alerts, setAlerts] = useState<{type: string, price: string, targetValue: number}[]>([]); const [customAlertPrices, setCustomAlertPrices] = useState>({}); + const [alertOptions, setAlertOptions] = useState({ execution: true, expiration: true }); const [marketPrice, setMarketPrice] = useState(null); const now = Date.now(); @@ -168,7 +172,7 @@ export const AnalysisView: React.FC<{ userData?: any, onGoToHistory?: () => void } const base64 = preview.split(',')[1]; - const analysis = await analyzeForexChart(base64, undefined, mode, userData?.plan); + const analysis = await analyzeForexChart(base64, undefined, mode, userData?.plan, tradingStyle); analysis.score = Math.round(analysis.score); setResult(analysis); @@ -191,6 +195,10 @@ export const AnalysisView: React.FC<{ userData?: any, onGoToHistory?: () => void throw err; }); + toast.success(`Scanner Concluído com ${sanitizedAnalysis.score}% de Confiança no ${sanitizedAnalysis.pair || 'Ativo'}!`, { + description: `Decisão de ${sanitizedAnalysis.decision}` + }); + if (sanitizedAnalysis.decision !== 'WAIT') { axios.post('/api/onesignal/notify', { title: 'Novo Sinal QuantScan IA 🚨', @@ -210,6 +218,7 @@ export const AnalysisView: React.FC<{ userData?: any, onGoToHistory?: () => void errorMessage = `Erro na API: ${errorMessage}`; } setError(errorMessage); + toast.error('Erro na análise do Scanner', { description: errorMessage }); } finally { setIsAnalyzing(false); } @@ -313,6 +322,8 @@ export const AnalysisView: React.FC<{ userData?: any, onGoToHistory?: () => void
+ + {!result ? (
@@ -334,24 +345,47 @@ export const AnalysisView: React.FC<{ userData?: any, onGoToHistory?: () => void )} {!preview ? (
-
- {(['Técnico', 'Fundamental', 'Híbrido'] as const).map((m) => ( - - ))} +
+
+ Tipo de Análise +
+ {(['Técnico', 'Fundamental', 'Híbrido'] as const).map((m) => ( + + ))} +
+
+ +
+ Estilo de Trading +
+ {(['Automático', 'Scalping', 'Day Trading', 'Swing'] as const).map((s) => ( + + ))} +
+
void
)}
+ +
+

Opções de Notificação

+
+
+ + Notificar Horário de Negociação +
+ +
+
+
+ + Sinal Inválido ou Expirado +
+ +
+
@@ -754,6 +816,67 @@ export const AnalysisView: React.FC<{ userData?: any, onGoToHistory?: () => void /> {/* UI Overlays to simulate AI analysis */} + {/* Predictive Trend Overlay */} +
+ + + + + + + + {result.decision === SignalType.BUY && ( + + )} + {result.decision === SignalType.SELL && ( + + )} + {result.decision !== SignalType.WAIT && ( + <> + + + {result.score}% OVERLAY DE PREVISÃO + + + )} + +
+
diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 0fb5ad0..706dfaa 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -28,7 +28,7 @@ export const Navbar: React.FC = ({ activeTab, setActiveTab, isAdmin const allTabs = [...mainTabs, ...accountTabs]; // For mobile rendering return ( -
-
-

Integração Auto-Trading

- - {['pro', 'elite', 'lifetime'].includes(userData?.plan) ? ( - <> -
-
-

Ativar Auto-Trading

-

Executa sinais automaticamente na corretora

-
- setAutoTradingEnabled(e.target.checked)} - className="w-5 h-5 accent-brand-red rounded bg-brand-dark/50 border border-white/10 cursor-pointer" - /> -
- - ) : ( -
-

Upgrade Necessário

-

- A funcionalidade de Auto-Trading está disponível exclusivamente a partir do Plano Pro. Evolua sua conta para automatizar suas operações de forma profissional. -

-
- )} -
-

Change Password & Danger Zone

diff --git a/src/components/SignalHistory.tsx b/src/components/SignalHistory.tsx index d5adae7..be2e6f7 100644 --- a/src/components/SignalHistory.tsx +++ b/src/components/SignalHistory.tsx @@ -120,8 +120,11 @@ export const SignalHistory: React.FC = () => { // Update in Firebase try { await updateDoc(doc(db, 'signals', signal.id!), { result: finalResult }); - } catch (e) { - console.error("Failed to update closed signal", e); + } catch (e: any) { + // Ignore insufficient permissions as regular users might not have access to update global signals in demo/shared mode + if (e && e.code !== 'permission-denied') { + console.error("Failed to update closed signal", e); + } } } } diff --git a/src/components/TourGuide.tsx b/src/components/TourGuide.tsx new file mode 100644 index 0000000..8fec9eb --- /dev/null +++ b/src/components/TourGuide.tsx @@ -0,0 +1,125 @@ +import React, { useState, useEffect } from 'react'; +import { db, auth } from '../lib/firebase'; +import { doc, updateDoc } from 'firebase/firestore'; +import { motion, AnimatePresence } from 'motion/react'; +import { X } from 'lucide-react'; + +export const TourGuide = ({ hasCompletedTour }: { hasCompletedTour?: boolean }) => { + const [currentStep, setCurrentStep] = useState(0); + const [show, setShow] = useState(false); + const [positions, setPositions] = useState>({}); + + const steps = [ + { target: '.tour-step-1', content: 'Bem-vindo ao QuantScan! Aqui é onde tudo acontece. Envie a foto de um gráfico para análise.', title: 'Análise por IA' }, + { target: '.tour-step-2', content: 'Escolha entre Análise Técnica, Fundamentalista ou Híbrida baseado em sua estratégia.', title: 'Tipo de Análise' }, + { target: '.tour-step-3', content: 'Defina seu Estilo de Trading (Scalping, Day Trading). A IA adapta o risco e alvos para você.', title: 'Estilo de Trading' }, + { target: '.tour-step-4', content: 'Navegue pelo menu para acessar a aba Histórico, Dashboard Elite e o Auto-Trading!', title: 'Menu Principal' } + ]; + + useEffect(() => { + if (hasCompletedTour === false) { + const timer = setTimeout(() => { + setShow(true); + measureTarget(0); + }, 1000); + return () => clearTimeout(timer); + } + }, [hasCompletedTour]); + + const measureTarget = (stepIndex: number) => { + const el = document.querySelector(steps[stepIndex].target); + if (el) { + const rect = el.getBoundingClientRect(); + setPositions(prev => ({ + ...prev, + [stepIndex]: { top: rect.top, left: rect.left, width: rect.width, height: rect.height } + })); + } + }; + + const handleNext = () => { + if (currentStep < steps.length - 1) { + setCurrentStep(c => c + 1); + setTimeout(() => measureTarget(currentStep + 1), 100); + } else { + handleFinish(); + } + }; + + const handleFinish = async () => { + setShow(false); + if (auth.currentUser) { + try { + await updateDoc(doc(db, 'users', auth.currentUser.uid), { + hasCompletedTour: true + }); + } catch (error) { + // ignore + } + } + }; + + useEffect(() => { + const handleResize = () => { + if (show) measureTarget(currentStep); + }; + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, [show, currentStep]); + + if (!show || hasCompletedTour) return null; + + const pos = positions[currentStep]; + + return ( +
+
+ + {pos && ( +
+ )} + + + {pos && ( + window.innerHeight - 200 ? pos.top - 200 : pos.top + pos.height + 16, + left: Math.max(16, Math.min(pos.left + pos.width / 2 - 160, window.innerWidth - 336)) + }} + > + +
+ Passo {currentStep + 1} de {steps.length} +

{steps[currentStep].title}

+

{steps[currentStep].content}

+
+ + {steps.map((_, i) => ( +
+ ))} + + +
+
+ + )} + +
+ ); +}; diff --git a/src/components/WatchlistPanel.tsx b/src/components/WatchlistPanel.tsx new file mode 100644 index 0000000..aa6e661 --- /dev/null +++ b/src/components/WatchlistPanel.tsx @@ -0,0 +1,148 @@ +import React, { useState, useEffect } from 'react'; +import { fetchCurrentPrice } from '../services/marketData'; +import { Star, Plus, X, Activity } from 'lucide-react'; +import { cn } from '../lib/utils'; +import { motion, AnimatePresence } from 'motion/react'; + +export const WatchlistPanel: React.FC = () => { + const [watchlist, setWatchlist] = useState(() => { + const saved = localStorage.getItem('quantscan_watchlist'); + return saved ? JSON.parse(saved) : ['XAU/USD', 'BTC/USDT']; + }); + const [prices, setPrices] = useState>({}); + const [previousPrices, setPreviousPrices] = useState>({}); + const [isAdding, setIsAdding] = useState(false); + const [newPair, setNewPair] = useState(''); + + useEffect(() => { + localStorage.setItem('quantscan_watchlist', JSON.stringify(watchlist)); + }, [watchlist]); + + useEffect(() => { + if (watchlist.length === 0) return; + + let isMounted = true; + + const updatePrices = async () => { + const results = await Promise.all( + watchlist.map(async (pair) => { + const price = await fetchCurrentPrice(pair); + return { pair, price }; + }) + ); + + if (isMounted) { + setPreviousPrices(prev => ({ ...prev, ...prices })); + const newPrices: Record = {}; + results.forEach(({ pair, price }) => { + if (price !== null) newPrices[pair] = price; + }); + setPrices(prev => ({ ...prev, ...newPrices })); + } + }; + + updatePrices(); + const interval = setInterval(updatePrices, 30000); + return () => { + isMounted = false; + clearInterval(interval); + }; + }, [watchlist]); + + const addPair = (e: React.FormEvent) => { + e.preventDefault(); + if (!newPair) return; + const pairToAdd = newPair.toUpperCase().trim(); + if (!watchlist.includes(pairToAdd)) { + setWatchlist(prev => [...prev, pairToAdd]); + } + setNewPair(''); + setIsAdding(false); + }; + + const removePair = (pairToRemove: string) => { + setWatchlist(prev => prev.filter(p => p !== pairToRemove)); + }; + + return ( +
+
+

+ Watchlist Favoritos +

+ +
+ + + {isAdding && ( + + setNewPair(e.target.value)} + className="flex-1 bg-black/40 border border-white/5 rounded-lg px-3 py-1.5 text-xs text-white uppercase focus:outline-none focus:border-brand-red" + /> + + + )} + + +
+ {watchlist.length === 0 && ( +

Nenhum ativo adicionado.

+ )} + {watchlist.map((pair) => { + const price = prices[pair]; + const prevPrice = previousPrices[pair]; + const isUp = price && prevPrice ? price > prevPrice : true; + const isDown = price && prevPrice ? price < prevPrice : false; + + return ( +
+
+ {pair} + +
+
+ {price !== undefined ? ( + <> + + {price > 100 ? price.toFixed(2) : price.toFixed(5)} + + {(isUp || isDown) && ( + + )} + + ) : ( + --- + )} +
+
+ ); + })} +
+
+ ); +}; diff --git a/src/services/geminiService.ts b/src/services/geminiService.ts index 41131de..c7c814c 100644 --- a/src/services/geminiService.ts +++ b/src/services/geminiService.ts @@ -3,7 +3,7 @@ import { AnalysisResponse, SignalType } from "../types"; const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY || '' }); -export const analyzeForexChart = async (imageBase64: string, userNotes?: string, preferredMode?: 'Técnico' | 'Fundamental' | 'Híbrido', userPlan?: string): Promise => { +export const analyzeForexChart = async (imageBase64: string, userNotes?: string, preferredMode?: 'Técnico' | 'Fundamental' | 'Híbrido', userPlan?: string, preferredStyle?: string): Promise => { const model = 'gemini-3.1-pro-preview'; const systemInstruction = ` @@ -24,9 +24,11 @@ O sistema deve operar em dois modos: RESTRIÇÕES DE PLANO ================================================== Se o plano do usuário for "basic" ou "experimental", você é **PROIBIDO** de realizar análise LONG TERM / SWING / POSITION. -Você deve focar nas estruturas de curto prazo e gerar sinais APENAS para Scalping ou Intraday. +Você deve focar nas estruturas de curto prazo e gerar sinais APENAS para Scalping ou Intraday (Day Trading). Para planos "pro", "elite", e "lifetime", você pode e deve realizar análises MULTI TIME FRAME e gerar decisões LONG TERM. +${preferredStyle && preferredStyle !== 'Automático' ? `\nESTILO DE TRADING PREFERIDO PELO USUÁRIO NESTE SCAN: **${preferredStyle}**.\nVocê DEVE adaptar os seus stops, alvos e timeframe para gerar um sinal focado primeiramente em ${preferredStyle}.\nSe for Day Trading focar em alvos que batem no mesmo dia.\n` : ''} + ================================================== PILARES DA ESTRATÉGIA SMC OBRIGATÓRIA ==================================================