From f21e4432c2ce9b4456cc1c9220b5f9b93d6dc6e1 Mon Sep 17 00:00:00 2001 From: ujwalkarippalichandran Date: Wed, 16 Apr 2025 20:57:58 -0600 Subject: [PATCH 01/36] Added google sign in --- package-lock.json | 823 ++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + src/App.js | 109 ++++-- src/GoogleSignIn.js | 38 ++ src/firebase.js | 25 ++ 5 files changed, 976 insertions(+), 20 deletions(-) create mode 100644 src/GoogleSignIn.js create mode 100644 src/firebase.js diff --git a/package-lock.json b/package-lock.json index 90ef6da..e257384 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.2.0", "@testing-library/user-event": "^13.5.0", + "firebase": "^11.6.0", "mapbox-gl": "^3.10.0", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -2473,6 +2474,692 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@firebase/analytics": { + "version": "0.10.12", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.12.tgz", + "integrity": "sha512-iDCGnw6qdFqwI5ywkgece99WADJNoymu+nLIQI4fZM/vCZ3bEo4wlpEetW71s1HqGpI0hQStiPhqVjFxDb2yyw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.13", + "@firebase/installations": "0.6.13", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.11.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/analytics-compat": { + "version": "0.2.18", + "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.18.tgz", + "integrity": "sha512-Hw9mzsSMZaQu6wrTbi3kYYwGw9nBqOHr47pVLxfr5v8CalsdrG5gfs9XUlPOZjHRVISp3oQrh1j7d3E+ulHPjQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/analytics": "0.10.12", + "@firebase/analytics-types": "0.8.3", + "@firebase/component": "0.6.13", + "@firebase/util": "1.11.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "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==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/app": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.11.4.tgz", + "integrity": "sha512-GPREsZjfSaHzwyC6cI/Cqvzf6zxqMzya+25tSpUstdqC2w0IdfxEfOMjfdW7bDfVEf4Rb4Nb6gfoOAgVSp4c4g==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.13", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.11.0", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/app-check": { + "version": "0.8.13", + "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.8.13.tgz", + "integrity": "sha512-ONsgml8/dplUOAP42JQO6hhiWDEwR9+RUTLenxAN9S8N6gel/sDQ9Ci721Py1oASMGdDU8v9R7xAZxzvOX5lPg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.13", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.11.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/app-check-compat": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.20.tgz", + "integrity": "sha512-/twgmlnNAaZ/wbz3kcQrL/26b+X+zUX+lBmu5LwwEcWcpnb+mrVEAKhD7/ttm52dxYiSWtLDeuXy3FXBhqBC5A==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check": "0.8.13", + "@firebase/app-check-types": "0.5.3", + "@firebase/component": "0.6.13", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.11.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.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==", + "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==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/app-compat": { + "version": "0.2.53", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.2.53.tgz", + "integrity": "sha512-vDeZSit0q4NyaDIVcaiJF3zhLgguP6yc0JwQAfpTyllgt8XMtkMFyY/MxJtFrK2ocpQX/yCbV2DXwvpY2NVuJw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app": "0.11.4", + "@firebase/component": "0.6.13", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.11.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/app-types": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz", + "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/auth": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.10.0.tgz", + "integrity": "sha512-S7SqBsN7sIQsftNE3bitLlK+4bWrTHY+Rx2JFlNitgVYu2nK8W8ZQrkG8GCEwiFPq0B2vZ9pO5kVTFfq2sP96A==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.13", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.11.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@react-native-async-storage/async-storage": "^1.18.1" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + } + } + }, + "node_modules/@firebase/auth-compat": { + "version": "0.5.20", + "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.5.20.tgz", + "integrity": "sha512-8FwODTSBnaqGQbKfML7LcpzGGPyouB7YHg3dZq+CZMziVc7oBY1jJeNvpnM1hAQoVuTjWPXoRrCltdGeOlkKfQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/auth": "1.10.0", + "@firebase/auth-types": "0.13.0", + "@firebase/component": "0.6.13", + "@firebase/util": "1.11.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "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==", + "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==", + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/component": { + "version": "0.6.13", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.13.tgz", + "integrity": "sha512-I/Eg1NpAtZ8AAfq8mpdfXnuUpcLxIDdCDtTzWSh+FXnp/9eCKJ3SNbOCKrUCyhLzNa2SiPJYruei0sxVjaOTeg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/util": "1.11.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/data-connect": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.3.3.tgz", + "integrity": "sha512-JsgppNX1wcQYP5bg4Sg6WTS7S0XazklSjr1fG3ox9DHtt4LOQwJ3X1/c81mKMIZxocV22ujiwLYQWG6Y9D1FiQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/auth-interop-types": "0.2.4", + "@firebase/component": "0.6.13", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.11.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/database": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.14.tgz", + "integrity": "sha512-9nxYtkHAG02/Nh2Ssms1T4BbWPPjiwohCvkHDUl4hNxnki1kPgsLo5xe9kXNzbacOStmVys+RUXvwzynQSKmUQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.3", + "@firebase/auth-interop-types": "0.2.4", + "@firebase/component": "0.6.13", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.11.0", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/database-compat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.0.5.tgz", + "integrity": "sha512-CNf1UbvWh6qIaSf4sn6sx2DTDz/em/D7QxULH1LTxxDQHr9+CeYGvlAqrKnk4ZH0P0eIHyQFQU7RwkUJI0B9gQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.13", + "@firebase/database": "1.0.14", + "@firebase/database-types": "1.0.10", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.11.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/database-types": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.10.tgz", + "integrity": "sha512-mH6RC1E9/Pv8jf1/p+M8YFTX+iu+iHDN89hecvyO7wHrI4R1V0TXjxOHvX3nLJN1sfh0CWG6CHZ0VlrSmK/cwg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-types": "0.9.3", + "@firebase/util": "1.11.0" + } + }, + "node_modules/@firebase/firestore": { + "version": "4.7.10", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.7.10.tgz", + "integrity": "sha512-6nKsyo2U+jYSCcSE5sjMdDNA23DMUvYPUvsYGg09CNvcTO8GGKsPs7SpOhspsB91mbacq+u627CDAx3FUhPSSQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.13", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.11.0", + "@firebase/webchannel-wrapper": "1.0.3", + "@grpc/grpc-js": "~1.9.0", + "@grpc/proto-loader": "^0.7.8", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/firestore-compat": { + "version": "0.3.45", + "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.45.tgz", + "integrity": "sha512-uRvi7AYPmsDl7UZwPyV7jgDGYusEZ2+U2g7MndbQHKIA8fNHpYC6QrzMs58+/IjX+kF/lkUn67Vrr0AkVjlY+Q==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.13", + "@firebase/firestore": "4.7.10", + "@firebase/firestore-types": "3.0.3", + "@firebase/util": "1.11.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.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==", + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/functions": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.12.3.tgz", + "integrity": "sha512-Wv7JZMUkKLb1goOWRtsu3t7m97uK6XQvjQLPvn8rncY91+VgdU72crqnaYCDI/ophNuBEmuK8mn0/pAnjUeA6A==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.3", + "@firebase/auth-interop-types": "0.2.4", + "@firebase/component": "0.6.13", + "@firebase/messaging-interop-types": "0.2.3", + "@firebase/util": "1.11.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/functions-compat": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.20.tgz", + "integrity": "sha512-iIudmYDAML6n3c7uXO2YTlzra2/J6lnMzmJTXNthvrKVMgNMaseNoQP1wKfchK84hMuSF8EkM4AvufwbJ+Juew==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.13", + "@firebase/functions": "0.12.3", + "@firebase/functions-types": "0.6.3", + "@firebase/util": "1.11.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.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==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/installations": { + "version": "0.6.13", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.13.tgz", + "integrity": "sha512-6ZpkUiaygPFwgVneYxuuOuHnSPnTA4KefLEaw/sKk/rNYgC7X6twaGfYb0sYLpbi9xV4i5jXsqZ3WO+yaguNgg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.13", + "@firebase/util": "1.11.0", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/installations-compat": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.13.tgz", + "integrity": "sha512-f/o6MqCI7LD/ulY9gvgkv6w5k6diaReD8BFHd/y/fEdpsXmFWYS/g28GXCB72bRVBOgPpkOUNl+VsMvDwlRKmw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.13", + "@firebase/installations": "0.6.13", + "@firebase/installations-types": "0.5.3", + "@firebase/util": "1.11.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "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==", + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/logger": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.4.tgz", + "integrity": "sha512-mH0PEh1zoXGnaR8gD1DeGeNZtWFKbnz9hDO91dIml3iou1gpOnLqXQ2dJfB71dj6dpmUjcQ6phY3ZZJbjErr9g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/messaging": { + "version": "0.12.17", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.17.tgz", + "integrity": "sha512-W3CnGhTm6Nx8XGb6E5/+jZTuxX/EK8Vur4QXvO1DwZta/t0xqWMRgO9vNsZFMYBqFV4o3j4F9qK/iddGYwWS6g==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.13", + "@firebase/installations": "0.6.13", + "@firebase/messaging-interop-types": "0.2.3", + "@firebase/util": "1.11.0", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/messaging-compat": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.17.tgz", + "integrity": "sha512-5Q+9IG7FuedusdWHVQRjpA3OVD9KUWp/IPegcv0s5qSqRLBjib7FlAeWxN+VL0Ew43tuPJBY2HKhEecuizmO1Q==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.13", + "@firebase/messaging": "0.12.17", + "@firebase/util": "1.11.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "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==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/performance": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.7.2.tgz", + "integrity": "sha512-DXLLp0R0jdxH/yTmv+WTkOzsLl8YYecXh4lGZE0dzqC0IV8k+AxpLSSWvOTCkAETze8yEU/iF+PtgYVlGjfMMQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.13", + "@firebase/installations": "0.6.13", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.11.0", + "tslib": "^2.1.0", + "web-vitals": "^4.2.4" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/performance-compat": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.15.tgz", + "integrity": "sha512-wUxsw7hGBEMN6XfvYQqwPIQp5LcJXawWM5tmYp6L7ClCoTQuEiCKHWWVurJgN8Q1YHzoHVgjNfPQAOVu29iMVg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.13", + "@firebase/logger": "0.4.4", + "@firebase/performance": "0.7.2", + "@firebase/performance-types": "0.2.3", + "@firebase/util": "1.11.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "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==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/performance/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/@firebase/remote-config": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.6.0.tgz", + "integrity": "sha512-Yrk4l5+6FJLPHC6irNHMzgTtJ3NfHXlAXVChCBdNFtgmzyGmufNs/sr8oA0auEfIJ5VpXCaThRh3P4OdQxiAlQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.13", + "@firebase/installations": "0.6.13", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.11.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/remote-config-compat": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.13.tgz", + "integrity": "sha512-UmHoO7TxAEJPIZf8e1Hy6CeFGMeyjqSCpgoBkQZYXFI2JHhzxIyDpr8jVKJJN1dmAePKZ5EX7dC13CmcdTOl7Q==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.13", + "@firebase/logger": "0.4.4", + "@firebase/remote-config": "0.6.0", + "@firebase/remote-config-types": "0.4.0", + "@firebase/util": "1.11.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/remote-config-types": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.4.0.tgz", + "integrity": "sha512-7p3mRE/ldCNYt8fmWMQ/MSGRmXYlJ15Rvs9Rk17t8p0WwZDbeK7eRmoI1tvCPaDzn9Oqh+yD6Lw+sGLsLg4kKg==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/storage": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.13.7.tgz", + "integrity": "sha512-FkRyc24rK+Y6EaQ1tYFm3TevBnnfSNA0VyTfew2hrYyL/aYfatBg7HOgktUdB4kWMHNA9VoTotzZTGoLuK92wg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.13", + "@firebase/util": "1.11.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/storage-compat": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.17.tgz", + "integrity": "sha512-CBlODWEZ5b6MJWVh21VZioxwxNwVfPA9CAdsk+ZgVocJQQbE2oDW1XJoRcgthRY1HOitgbn4cVrM+NlQtuUYhw==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.13", + "@firebase/storage": "0.13.7", + "@firebase/storage-types": "0.8.3", + "@firebase/util": "1.11.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.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==", + "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/util": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.11.0.tgz", + "integrity": "sha512-PzSrhIr++KI6y4P6C/IdgBNMkEx0Ex6554/cYd0Hm+ovyFSJtJXqb/3OSIdnBoa2cpwZT1/GW56EmRc5qEc5fQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/vertexai": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@firebase/vertexai/-/vertexai-1.2.1.tgz", + "integrity": "sha512-cukZ5ne2RsOWB4PB1EO6nTXgOLxPMKDJfEn+XnSV5ZKWM0ID5o0DvbyS59XihFaBzmy2SwJldP5ap7/xUnW4jA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.3", + "@firebase/component": "0.6.13", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.11.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/webchannel-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.3.tgz", + "integrity": "sha512-2xCRM9q9FlzGZCdgDMJwc0gyUkWFtkosy7Xxr6sFgQwn+wMNIWd7xIvYNauU1r64B5L5rsGKy/n9TKJ0aAFeqQ==", + "license": "Apache-2.0" + }, + "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==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.7.8", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", + "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@grpc/proto-loader/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@grpc/proto-loader/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@grpc/proto-loader/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -3174,6 +3861,70 @@ } } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -8403,6 +9154,42 @@ "node": ">=8" } }, + "node_modules/firebase": { + "version": "11.6.0", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-11.6.0.tgz", + "integrity": "sha512-Xqm6j6zszIEmI5nW1MPR8yTafoRTSrW3mWG9Lk9elCJtQDQSiTEkKZiNtUm9y6XfOPl8xoF1TNpxZe8HjgA0Og==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/analytics": "0.10.12", + "@firebase/analytics-compat": "0.2.18", + "@firebase/app": "0.11.4", + "@firebase/app-check": "0.8.13", + "@firebase/app-check-compat": "0.3.20", + "@firebase/app-compat": "0.2.53", + "@firebase/app-types": "0.9.3", + "@firebase/auth": "1.10.0", + "@firebase/auth-compat": "0.5.20", + "@firebase/data-connect": "0.3.3", + "@firebase/database": "1.0.14", + "@firebase/database-compat": "2.0.5", + "@firebase/firestore": "4.7.10", + "@firebase/firestore-compat": "0.3.45", + "@firebase/functions": "0.12.3", + "@firebase/functions-compat": "0.3.20", + "@firebase/installations": "0.6.13", + "@firebase/installations-compat": "0.2.13", + "@firebase/messaging": "0.12.17", + "@firebase/messaging-compat": "0.2.17", + "@firebase/performance": "0.7.2", + "@firebase/performance-compat": "0.2.15", + "@firebase/remote-config": "0.6.0", + "@firebase/remote-config-compat": "0.2.13", + "@firebase/storage": "0.13.7", + "@firebase/storage-compat": "0.3.17", + "@firebase/util": "1.11.0", + "@firebase/vertexai": "1.2.1" + } + }, "node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -11480,6 +12267,12 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -11510,6 +12303,12 @@ "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", "license": "MIT" }, + "node_modules/long": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.1.tgz", + "integrity": "sha512-ka87Jz3gcx/I7Hal94xaN2tZEOPoUOEVftkQqZx2EeQRN7LGdfLlI3FvZ+7WDplm+vK2Urx9ULrvSowtdCieng==", + "license": "Apache-2.0" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -13991,6 +14790,30 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/protobufjs": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.0.tgz", + "integrity": "sha512-Z2E/kOY1QjoMlCytmexzYfDm/w5fKAiRwpSzGtdnXW1zC88Z2yXazHHrOtwCzn+7wSxyE8PYM4rvVcMphF9sOA==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/protocol-buffers-schema": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", diff --git a/package.json b/package.json index 459914d..b68c63f 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.2.0", "@testing-library/user-event": "^13.5.0", + "firebase": "^11.6.0", "mapbox-gl": "^3.10.0", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/src/App.js b/src/App.js index 3e9eb19..e1bd88b 100644 --- a/src/App.js +++ b/src/App.js @@ -1,22 +1,60 @@ -import React from "react"; +import React, { useState, useEffect } from "react"; import HeatMap from "./MapComponent"; +import GoogleSignIn from "./GoogleSignIn"; +import { auth } from "./firebase"; +import { onAuthStateChanged, signOut } from "firebase/auth"; function App() { + const [user, setUser] = useState(null); + + // 🔁 Auto-login listener + useEffect(() => { + const unsubscribe = onAuthStateChanged(auth, (firebaseUser) => { + setUser(firebaseUser); + }); + return () => unsubscribe(); // cleanup + }, []); + + const handleSignOut = () => { + signOut(auth) + .then(() => setUser(null)) + .catch((error) => console.error("Error signing out:", error)); + }; + return ( -
- {/* Heading Section with Background Image and Compact Overlay */} -
-
-

Global News Map

-

Trending and Popular World Events Visualization

+
+ {/* Heading */} +
+
+

Global News Map

+

Trending and Popular World Events Visualization

+ + {/* 🧑 User Info + Logout */} + {user && ( +
+ User + {user.displayName} + +
+ )} +
-
- {/* Heatmap Component */} -
- + {/* Map or Login */} +
+ {user ? ( + + ) : ( + + )} +
-
); } @@ -32,7 +70,7 @@ const styles = { heroSection: { width: "100%", textAlign: "center", - padding: "8px 6px", // Reduced padding to make it more compact + padding: "8px 6px", backgroundImage: "url('/images/news.jpg')", backgroundSize: "cover", backgroundPosition: "center", @@ -42,20 +80,20 @@ const styles = { boxShadow: "0px 2px 4px rgba(0, 0, 0, 0.1)", }, overlay: { - backgroundColor: "rgba(0, 0, 0, 0.5)", // Slightly reduced opacity - padding: "10px 15px", // Smaller padding for a more compact look + backgroundColor: "rgba(0, 0, 0, 0.5)", + padding: "10px 15px", borderRadius: "4px", display: "inline-block", }, title: { - fontSize: "20px", // Reduced font size slightly + fontSize: "20px", fontWeight: "bold", color: "#ffffff", - textShadow: "1px 1px 3px rgba(0,0,0,0.6)", // Adjusted shadow for readability + textShadow: "1px 1px 3px rgba(0,0,0,0.6)", margin: "0", }, subtitle: { - fontSize: "12px", // Slightly smaller subtitle + fontSize: "12px", fontWeight: "lighter", color: "#f0f0f0", textShadow: "1px 1px 2px rgba(0,0,0,0.4)", @@ -63,8 +101,39 @@ const styles = { }, mapContainer: { width: "100%", - height: "calc(100vh - 80px)", // Reduced height taken up by title bar + height: "calc(100vh - 80px)", + display: "flex", + justifyContent: "center", + alignItems: "center", + }, + userInfo: { + marginTop: "10px", + display: "flex", + alignItems: "center", + gap: "10px", + justifyContent: "center", + }, + avatar: { + width: "32px", + height: "32px", + borderRadius: "50%", + border: "1px solid #fff", + }, + userName: { + color: "#fff", + fontSize: "14px", + fontWeight: "500", + }, + signOutButton: { + padding: "5px 10px", + fontSize: "12px", + backgroundColor: "#ffffff", + color: "#000", + border: "none", + borderRadius: "4px", + cursor: "pointer", + fontWeight: "bold", }, }; -export default App; +export default App; \ No newline at end of file diff --git a/src/GoogleSignIn.js b/src/GoogleSignIn.js new file mode 100644 index 0000000..8ba2272 --- /dev/null +++ b/src/GoogleSignIn.js @@ -0,0 +1,38 @@ +// GoogleSignIn.js +import React from "react"; +import { auth, provider, signInWithPopup } from "./firebase"; + +const GoogleSignIn = ({ onSignIn }) => { + const handleGoogleLogin = async () => { + try { + const result = await signInWithPopup(auth, provider); + const user = result.user; + console.log("User signed in:", user); + if (onSignIn) onSignIn(user); // Send user info back to parent if needed + } catch (error) { + console.error("Google Sign-In error:", error); + } + }; + + return ( +
+ +
+ ); +}; + +export default GoogleSignIn; \ No newline at end of file diff --git a/src/firebase.js b/src/firebase.js new file mode 100644 index 0000000..a93f58b --- /dev/null +++ b/src/firebase.js @@ -0,0 +1,25 @@ +// firebase.js +import { initializeApp } from "firebase/app"; +import { getAuth, GoogleAuthProvider, signInWithPopup } from "firebase/auth"; +import { getAnalytics } from "firebase/analytics"; + +// Load from .env +const firebaseConfig = { + apiKey: process.env.REACT_APP_FIREBASE_API_KEY, + authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN, + projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID, + storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET, + messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID, + appId: process.env.REACT_APP_FIREBASE_APP_ID, + measurementId: process.env.REACT_APP_FIREBASE_MEASUREMENT_ID +}; + +// Initialize Firebase +const app = initializeApp(firebaseConfig); +const analytics = getAnalytics(app); + +// Auth setup +const auth = getAuth(app); +const provider = new GoogleAuthProvider(); + +export { auth, provider, signInWithPopup }; \ No newline at end of file From a4b7b5dbb46d1bd449941ace772503a5f6b83cb3 Mon Sep 17 00:00:00 2001 From: ujwalkarippalichandran Date: Wed, 16 Apr 2025 21:04:40 -0600 Subject: [PATCH 02/36] Added google sign in --- src/App.js | 62 +++++++++++++++++---------------- src/GoogleSignIn.js | 84 +++++++++++++++++++++++++++++++++++---------- 2 files changed, 98 insertions(+), 48 deletions(-) diff --git a/src/App.js b/src/App.js index e1bd88b..cfcb30a 100644 --- a/src/App.js +++ b/src/App.js @@ -23,26 +23,22 @@ function App() { return (
- {/* Heading */} + {/* 🧑 Floating User Info */} + {user && ( +
+ User + {user.displayName} + +
+ )} + + {/* Hero Section */}

Global News Map

Trending and Popular World Events Visualization

- - {/* 🧑 User Info + Logout */} - {user && ( -
- User - {user.displayName} - -
- )}
@@ -66,11 +62,12 @@ const styles = { alignItems: "center", backgroundColor: "#f4f4f4", minHeight: "100vh", + position: "relative", }, heroSection: { width: "100%", textAlign: "center", - padding: "8px 6px", + padding: "40px 10px", backgroundImage: "url('/images/news.jpg')", backgroundSize: "cover", backgroundPosition: "center", @@ -81,37 +78,44 @@ const styles = { }, overlay: { backgroundColor: "rgba(0, 0, 0, 0.5)", - padding: "10px 15px", - borderRadius: "4px", + padding: "20px 25px", + borderRadius: "8px", display: "inline-block", }, title: { - fontSize: "20px", + fontSize: "32px", fontWeight: "bold", color: "#ffffff", - textShadow: "1px 1px 3px rgba(0,0,0,0.6)", + textShadow: "1px 1px 4px rgba(0,0,0,0.6)", margin: "0", }, subtitle: { - fontSize: "12px", + fontSize: "16px", fontWeight: "lighter", color: "#f0f0f0", - textShadow: "1px 1px 2px rgba(0,0,0,0.4)", - marginTop: "3px", + textShadow: "1px 1px 3px rgba(0,0,0,0.4)", + marginTop: "8px", }, mapContainer: { width: "100%", - height: "calc(100vh - 80px)", + height: "calc(100vh - 160px)", display: "flex", justifyContent: "center", alignItems: "center", }, - userInfo: { - marginTop: "10px", + floatingUserInfo: { + position: "absolute", + top: "15px", + right: "25px", display: "flex", alignItems: "center", - gap: "10px", - justifyContent: "center", + gap: "12px", + backgroundColor: "rgba(255, 255, 255, 0.1)", + backdropFilter: "blur(8px)", + padding: "6px 14px", + borderRadius: "20px", + zIndex: 10, + color: "#fff", }, avatar: { width: "32px", diff --git a/src/GoogleSignIn.js b/src/GoogleSignIn.js index 8ba2272..aab0b4e 100644 --- a/src/GoogleSignIn.js +++ b/src/GoogleSignIn.js @@ -1,4 +1,3 @@ -// GoogleSignIn.js import React from "react"; import { auth, provider, signInWithPopup } from "./firebase"; @@ -7,32 +6,79 @@ const GoogleSignIn = ({ onSignIn }) => { try { const result = await signInWithPopup(auth, provider); const user = result.user; - console.log("User signed in:", user); - if (onSignIn) onSignIn(user); // Send user info back to parent if needed + if (onSignIn) onSignIn(user); } catch (error) { console.error("Google Sign-In error:", error); } }; return ( -
- +
+
+

🌍 Click4News

+

See the world’s pulse — visualized on a map.

+

+ Discover trending events, breaking stories, and global insights updated in real-time. Zoom into countries and cities to explore what's buzzing near and far. +

+ +
); }; +const styles = { + container: { + minHeight: "100vh", + width: "100%", + background: "linear-gradient(135deg, #1f1c2c, #928dab)", + display: "flex", + justifyContent: "center", + alignItems: "center", + padding: "20px", + }, + glassCard: { + backdropFilter: "blur(16px)", + WebkitBackdropFilter: "blur(16px)", + background: "rgba(255, 255, 255, 0.1)", + borderRadius: "16px", + padding: "40px 30px", + maxWidth: "480px", + textAlign: "center", + boxShadow: "0 8px 30px rgba(0, 0, 0, 0.3)", + border: "1px solid rgba(255, 255, 255, 0.2)", + color: "#fff", + }, + heading: { + fontSize: "32px", + fontWeight: "800", + marginBottom: "10px", + }, + tagline: { + fontSize: "18px", + fontWeight: "500", + marginBottom: "18px", + color: "#f1f1f1", + }, + description: { + fontSize: "15px", + lineHeight: "1.6", + color: "#ddd", + marginBottom: "30px", + }, + signInButton: { + padding: "12px 20px", + background: "linear-gradient(to right, #ff416c, #ff4b2b)", + border: "none", + borderRadius: "30px", + fontSize: "16px", + fontWeight: "bold", + color: "#fff", + cursor: "pointer", + boxShadow: "0 4px 12px rgba(255, 75, 43, 0.4)", + transition: "transform 0.2s ease", + }, +}; + export default GoogleSignIn; \ No newline at end of file From af56402a352d6a1902f9b8da00c75f487c540091 Mon Sep 17 00:00:00 2001 From: ujwalkarippalichandran Date: Wed, 16 Apr 2025 21:11:34 -0600 Subject: [PATCH 03/36] Added google sign in --- src/firebase.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/firebase.js b/src/firebase.js index a93f58b..b64f920 100644 --- a/src/firebase.js +++ b/src/firebase.js @@ -16,7 +16,7 @@ const firebaseConfig = { // Initialize Firebase const app = initializeApp(firebaseConfig); -const analytics = getAnalytics(app); +// const analytics = getAnalytics(app); // Auth setup const auth = getAuth(app); From 34ec1917acd68047070c06c39d07c0aae4366392 Mon Sep 17 00:00:00 2001 From: ujwalkarippalichandran Date: Wed, 16 Apr 2025 21:32:17 -0600 Subject: [PATCH 04/36] Added google sign in --- src/App.js | 23 ++--- src/EmailAuth.js | 261 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 270 insertions(+), 14 deletions(-) create mode 100644 src/EmailAuth.js diff --git a/src/App.js b/src/App.js index cfcb30a..c492d0a 100644 --- a/src/App.js +++ b/src/App.js @@ -1,18 +1,18 @@ import React, { useState, useEffect } from "react"; import HeatMap from "./MapComponent"; -import GoogleSignIn from "./GoogleSignIn"; +import EmailAuth from "./EmailAuth"; // Includes both email & Google login import { auth } from "./firebase"; import { onAuthStateChanged, signOut } from "firebase/auth"; function App() { const [user, setUser] = useState(null); - // 🔁 Auto-login listener + // Auto-login listener useEffect(() => { const unsubscribe = onAuthStateChanged(auth, (firebaseUser) => { setUser(firebaseUser); }); - return () => unsubscribe(); // cleanup + return () => unsubscribe(); }, []); const handleSignOut = () => { @@ -23,11 +23,11 @@ function App() { return (
- {/* 🧑 Floating User Info */} + {/* Floating User Info */} {user && (
- User - {user.displayName} + User + {user.displayName || user.email} @@ -42,19 +42,14 @@ function App() {
- {/* Map or Login */} + {/* Content */}
- {user ? ( - - ) : ( - - )} + {user ? : }
); } -// Inline CSS for Styling const styles = { appContainer: { display: "flex", @@ -74,7 +69,6 @@ const styles = { backgroundRepeat: "no-repeat", position: "relative", color: "white", - boxShadow: "0px 2px 4px rgba(0, 0, 0, 0.1)", }, overlay: { backgroundColor: "rgba(0, 0, 0, 0.5)", @@ -122,6 +116,7 @@ const styles = { height: "32px", borderRadius: "50%", border: "1px solid #fff", + objectFit: "cover", }, userName: { color: "#fff", diff --git a/src/EmailAuth.js b/src/EmailAuth.js new file mode 100644 index 0000000..fa7f20f --- /dev/null +++ b/src/EmailAuth.js @@ -0,0 +1,261 @@ +import React, { useState } from "react"; +import { auth, provider, signInWithPopup } from "./firebase"; +import { + signInWithEmailAndPassword, + createUserWithEmailAndPassword, + sendPasswordResetEmail, +} from "firebase/auth"; + +const EmailAuth = ({ onSignIn }) => { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + const [showPwd, setShowPwd] = useState(false); + const [error, setError] = useState(""); + const [mode, setMode] = useState("login"); // 'login' or 'register' + const [showConfirmPwd, setShowConfirmPwd] = useState(false); + const handleSubmit = async (e) => { + e.preventDefault(); + setError(""); + + if (mode === "register" && password !== confirmPassword) { + return setError("Passwords do not match."); + } + + try { + const method = + mode === "register" + ? createUserWithEmailAndPassword + : signInWithEmailAndPassword; + const result = await method(auth, email, password); + onSignIn(result.user); + } catch (err) { + setError(err.message); + } + }; + + const handleResetPassword = async () => { + if (!email) return setError("Enter your email to reset password."); + try { + await sendPasswordResetEmail(auth, email); + alert("Password reset email sent."); + } catch (err) { + setError(err.message); + } + }; + + const handleGoogleLogin = async () => { + try { + const result = await signInWithPopup(auth, provider); + const user = result.user; + onSignIn(user); + } catch (error) { + setError(error.message); + } + }; + + return ( +
+
+

{mode === "login" ? "Sign in" : "Join Click4News"}

+

+ {mode === "login" + ? "Stay updated on real-time global stories." + : "Create your account to explore breaking news by location."} +

+ + setEmail(e.target.value)} + style={styles.input} + /> + +
+ setPassword(e.target.value)} + style={{ ...styles.input, marginBottom: 0 }} + /> + setShowPwd(!showPwd)} style={styles.showBtn}> + {showPwd ? "hide" : "show"} + +
+ + {mode === "register" && ( +
+ setConfirmPassword(e.target.value)} + style={{ ...styles.input, marginBottom: 0 }} + /> + setShowConfirmPwd(!showConfirmPwd)} style={styles.showBtn}> + {showConfirmPwd ? "hide" : "show"} + +
+ )} + +
+ {mode === "login" && ( + + Forgot password? + + )} +
+ + {error &&

{error}

} + + + +
+ + or + +
+ + + +

+ {mode === "login" ? "New to Click4News?" : "Already a user?"}{" "} + setMode(mode === "login" ? "register" : "login")} style={styles.link}> + {mode === "login" ? "Join now" : "Sign in"} + +

+
+
+ ); +}; + +const styles = { + container: { + width: "100%", + display: "flex", + justifyContent: "center", + padding: "40px 20px", + }, + card: { + width: "100%", + maxWidth: "400px", + background: "#fff", + borderRadius: "12px", + padding: "30px", + boxShadow: "0 4px 20px rgba(0,0,0,0.1)", + }, + title: { + fontSize: "24px", + fontWeight: "600", + marginBottom: "8px", + }, + subtitle: { + fontSize: "14px", + color: "#666", + marginBottom: "24px", + }, + input: { + width: "100%", + padding: "12px", + fontSize: "14px", + border: "1px solid #ccc", + borderRadius: "6px", + marginBottom: "16px", + boxSizing: "border-box", // ensures perfect alignment + }, + passwordField: { + position: "relative", + marginBottom: "16px", + }, + showBtn: { + position: "absolute", + right: "12px", + top: "12px", + fontSize: "13px", + color: "#0a66c2", + cursor: "pointer", + }, + links: { + textAlign: "right", + marginBottom: "12px", + }, + link: { + color: "#0a66c2", + fontSize: "13px", + cursor: "pointer", + textDecoration: "none", + }, + submitBtn: { + width: "100%", + padding: "12px", + backgroundColor: "#0a66c2", + color: "#fff", + fontSize: "14px", + fontWeight: "bold", + border: "none", + borderRadius: "6px", // match input borderRadius + cursor: "pointer", + marginTop: "16px", // spacing from confirm field + }, + orDivider: { + display: "flex", + alignItems: "center", + margin: "20px 0", + }, + line: { + flex: 1, + height: "1px", + backgroundColor: "#ccc", + }, + or: { + margin: "0 10px", + color: "#666", + fontSize: "12px", + }, + googleBtn: { + width: "100%", + padding: "10px", + fontSize: "14px", + borderRadius: "24px", + border: "1px solid #000", + background: "#fff", + color: "#000", + cursor: "pointer", + fontWeight: "600", + display: "flex", + alignItems: "center", + justifyContent: "center", + gap: "8px", + }, + googleIcon: { + width: "18px", + height: "18px", + }, + footer: { + marginTop: "24px", + textAlign: "center", + fontSize: "14px", + color: "#555", + }, + error: { + color: "red", + fontSize: "13px", + marginBottom: "10px", + }, +}; + +export default EmailAuth; \ No newline at end of file From 744d15fc549859cf92b6747561366d6fdb26503d Mon Sep 17 00:00:00 2001 From: ujwalkarippalichandran Date: Wed, 16 Apr 2025 21:34:02 -0600 Subject: [PATCH 05/36] Added google sign in --- src/firebase.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/firebase.js b/src/firebase.js index b64f920..5d39278 100644 --- a/src/firebase.js +++ b/src/firebase.js @@ -1,7 +1,7 @@ // firebase.js import { initializeApp } from "firebase/app"; import { getAuth, GoogleAuthProvider, signInWithPopup } from "firebase/auth"; -import { getAnalytics } from "firebase/analytics"; +// import { getAnalytics } from "firebase/analytics"; // Load from .env const firebaseConfig = { From 4780136f261b40bc0b9acc1b366207bf54e9356d Mon Sep 17 00:00:00 2001 From: ujwalkarippalichandran Date: Wed, 16 Apr 2025 21:46:19 -0600 Subject: [PATCH 06/36] Added google sign in --- .github/workflows/firebase-hosting-merge.yml | 9 ++++++++- .github/workflows/firebase-hosting-pull-request.yml | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/.github/workflows/firebase-hosting-merge.yml b/.github/workflows/firebase-hosting-merge.yml index c3b1647..ac77f52 100644 --- a/.github/workflows/firebase-hosting-merge.yml +++ b/.github/workflows/firebase-hosting-merge.yml @@ -13,11 +13,18 @@ jobs: steps: - uses: actions/checkout@v4 - # 👇 Inject secret into build env + # ✅ Inject secrets into build environment - name: Install and Build run: npm ci && npm run build env: REACT_APP_MAPBOX_ACCESS_TOKEN: ${{ secrets.REACT_APP_MAPBOX_ACCESS_TOKEN }} + REACT_APP_FIREBASE_API_KEY: ${{ secrets.REACT_APP_FIREBASE_API_KEY }} + REACT_APP_FIREBASE_AUTH_DOMAIN: ${{ secrets.REACT_APP_FIREBASE_AUTH_DOMAIN }} + REACT_APP_FIREBASE_PROJECT_ID: ${{ secrets.REACT_APP_FIREBASE_PROJECT_ID }} + REACT_APP_FIREBASE_STORAGE_BUCKET: ${{ secrets.REACT_APP_FIREBASE_STORAGE_BUCKET }} + REACT_APP_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.REACT_APP_FIREBASE_MESSAGING_SENDER_ID }} + REACT_APP_FIREBASE_APP_ID: ${{ secrets.REACT_APP_FIREBASE_APP_ID }} + REACT_APP_FIREBASE_MEASUREMENT_ID: ${{ secrets.REACT_APP_FIREBASE_MEASUREMENT_ID }} - uses: FirebaseExtended/action-hosting-deploy@v0 with: diff --git a/.github/workflows/firebase-hosting-pull-request.yml b/.github/workflows/firebase-hosting-pull-request.yml index 732362c..629a5e0 100644 --- a/.github/workflows/firebase-hosting-pull-request.yml +++ b/.github/workflows/firebase-hosting-pull-request.yml @@ -15,11 +15,18 @@ jobs: steps: - uses: actions/checkout@v4 - # 👇 Inject secret for PR build + # ✅ Inject secrets for PR build - name: Install and Build run: npm ci && npm run build env: REACT_APP_MAPBOX_ACCESS_TOKEN: ${{ secrets.REACT_APP_MAPBOX_ACCESS_TOKEN }} + REACT_APP_FIREBASE_API_KEY: ${{ secrets.REACT_APP_FIREBASE_API_KEY }} + REACT_APP_FIREBASE_AUTH_DOMAIN: ${{ secrets.REACT_APP_FIREBASE_AUTH_DOMAIN }} + REACT_APP_FIREBASE_PROJECT_ID: ${{ secrets.REACT_APP_FIREBASE_PROJECT_ID }} + REACT_APP_FIREBASE_STORAGE_BUCKET: ${{ secrets.REACT_APP_FIREBASE_STORAGE_BUCKET }} + REACT_APP_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.REACT_APP_FIREBASE_MESSAGING_SENDER_ID }} + REACT_APP_FIREBASE_APP_ID: ${{ secrets.REACT_APP_FIREBASE_APP_ID }} + REACT_APP_FIREBASE_MEASUREMENT_ID: ${{ secrets.REACT_APP_FIREBASE_MEASUREMENT_ID }} - uses: FirebaseExtended/action-hosting-deploy@v0 with: From ea13138928b910d9f18229a85433bfc61ad7931c Mon Sep 17 00:00:00 2001 From: Hima-Varshith Date: Thu, 17 Apr 2025 01:02:43 -0600 Subject: [PATCH 07/36] Modified initial layout App.js --- src/App.js | 175 +++++++++++++++++++++++++++++------------------------ 1 file changed, 97 insertions(+), 78 deletions(-) diff --git a/src/App.js b/src/App.js index c492d0a..fc47d12 100644 --- a/src/App.js +++ b/src/App.js @@ -1,13 +1,12 @@ import React, { useState, useEffect } from "react"; -import HeatMap from "./MapComponent"; -import EmailAuth from "./EmailAuth"; // Includes both email & Google login +import HeatMap from "./components/Map/HeatMap"; +import EmailAuth from "./EmailAuth"; import { auth } from "./firebase"; import { onAuthStateChanged, signOut } from "firebase/auth"; function App() { const [user, setUser] = useState(null); - // Auto-login listener useEffect(() => { const unsubscribe = onAuthStateChanged(auth, (firebaseUser) => { setUser(firebaseUser); @@ -17,36 +16,46 @@ function App() { const handleSignOut = () => { signOut(auth) - .then(() => setUser(null)) - .catch((error) => console.error("Error signing out:", error)); + .then(() => setUser(null)) + .catch((error) => console.error("Error signing out:", error)); }; return ( -
- {/* Floating User Info */} - {user && ( -
- User - {user.displayName || user.email} - -
- )} - - {/* Hero Section */} -
-
-

Global News Map

-

Trending and Popular World Events Visualization

+
+ {/* Floating User Info */} + {user && ( +
+ User +
+ + {user.displayName || user.email} + +
+ )} - {/* Content */} -
- {user ? : } + {/* Hero Section */} +
+
+

Global News Map

+

+ Trending and Popular World Events Visualization +

+ + {/* Map or Auth */} +
+ {user ? : } +
+
); } @@ -54,85 +63,95 @@ const styles = { appContainer: { display: "flex", flexDirection: "column", + width: "100vw", + height: "100vh", + overflow: "hidden", + margin: 0, + padding: 0, + }, + floatingUserInfo: { + position: "absolute", + top: 10, + right: 10, + backgroundColor: "rgba(0, 0, 0, 0.75)", + color: "white", + padding: "10px", + borderRadius: "8px", + display: "flex", alignItems: "center", - backgroundColor: "#f4f4f4", - minHeight: "100vh", - position: "relative", + gap: "10px", + zIndex: 10, + maxWidth: "80%", + }, + avatar: { + width: "36px", + height: "36px", + borderRadius: "50%", + objectFit: "cover", + }, + userDetails: { + display: "flex", + flexDirection: "column", + alignItems: "flex-start", + }, + userName: { + fontSize: "14px", + fontWeight: "bold", + color: "#fff", + lineHeight: "1.2", + maxWidth: "200px", + overflow: "hidden", + textOverflow: "ellipsis", + whiteSpace: "nowrap", + }, + signOutButton: { + marginTop: "4px", + padding: "4px 8px", + backgroundColor: "#f44336", + color: "#fff", + border: "none", + borderRadius: "4px", + fontSize: "12px", + cursor: "pointer", }, heroSection: { + flex: "0 0 auto", width: "100%", textAlign: "center", - padding: "40px 10px", + padding: "8px 6px", backgroundImage: "url('/images/news.jpg')", backgroundSize: "cover", backgroundPosition: "center", backgroundRepeat: "no-repeat", position: "relative", - color: "white", }, overlay: { backgroundColor: "rgba(0, 0, 0, 0.5)", - padding: "20px 25px", - borderRadius: "8px", + padding: "12px 18px", + borderRadius: "6px", display: "inline-block", }, title: { - fontSize: "32px", + fontSize: "22px", fontWeight: "bold", color: "#ffffff", - textShadow: "1px 1px 4px rgba(0,0,0,0.6)", margin: "0", + textShadow: "1px 1px 3px rgba(0,0,0,0.6)", }, subtitle: { - fontSize: "16px", + fontSize: "13px", fontWeight: "lighter", color: "#f0f0f0", - textShadow: "1px 1px 3px rgba(0,0,0,0.4)", - marginTop: "8px", + marginTop: "6px", + textShadow: "1px 1px 2px rgba(0,0,0,0.3)", }, mapContainer: { + flex: "1 1 auto", width: "100%", - height: "calc(100vh - 160px)", - display: "flex", - justifyContent: "center", - alignItems: "center", - }, - floatingUserInfo: { - position: "absolute", - top: "15px", - right: "25px", - display: "flex", - alignItems: "center", - gap: "12px", - backgroundColor: "rgba(255, 255, 255, 0.1)", - backdropFilter: "blur(8px)", - padding: "6px 14px", - borderRadius: "20px", - zIndex: 10, - color: "#fff", - }, - avatar: { - width: "32px", - height: "32px", - borderRadius: "50%", - border: "1px solid #fff", - objectFit: "cover", - }, - userName: { - color: "#fff", - fontSize: "14px", - fontWeight: "500", - }, - signOutButton: { - padding: "5px 10px", - fontSize: "12px", - backgroundColor: "#ffffff", - color: "#000", - border: "none", - borderRadius: "4px", - cursor: "pointer", - fontWeight: "bold", + height: "100%", + position: "relative", + overflow: "hidden", }, }; -export default App; \ No newline at end of file +export default App; From 0a9dfe90480ec8b00bfb664eee3792e0b61151de Mon Sep 17 00:00:00 2001 From: Hima-Varshith Date: Thu, 17 Apr 2025 01:03:44 -0600 Subject: [PATCH 08/36] Pulled changes from main for email auth --- src/EmailAuth.js | 463 ++++++++++++++++++++++++----------------------- src/firebase.js | 26 ++- 2 files changed, 244 insertions(+), 245 deletions(-) diff --git a/src/EmailAuth.js b/src/EmailAuth.js index fa7f20f..df94c23 100644 --- a/src/EmailAuth.js +++ b/src/EmailAuth.js @@ -1,261 +1,262 @@ import React, { useState } from "react"; -import { auth, provider, signInWithPopup } from "./firebase"; +import { auth, provider } from "./firebase"; import { - signInWithEmailAndPassword, - createUserWithEmailAndPassword, - sendPasswordResetEmail, + signInWithPopup, + signInWithEmailAndPassword, + createUserWithEmailAndPassword, + sendPasswordResetEmail, } from "firebase/auth"; const EmailAuth = ({ onSignIn }) => { - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [confirmPassword, setConfirmPassword] = useState(""); - const [showPwd, setShowPwd] = useState(false); - const [error, setError] = useState(""); - const [mode, setMode] = useState("login"); // 'login' or 'register' - const [showConfirmPwd, setShowConfirmPwd] = useState(false); - const handleSubmit = async (e) => { - e.preventDefault(); - setError(""); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + const [showPwd, setShowPwd] = useState(false); + const [error, setError] = useState(""); + const [mode, setMode] = useState("login"); // 'login' or 'register' + const [showConfirmPwd, setShowConfirmPwd] = useState(false); - if (mode === "register" && password !== confirmPassword) { - return setError("Passwords do not match."); - } + const handleSubmit = async (e) => { + e.preventDefault(); + setError(""); - try { - const method = - mode === "register" - ? createUserWithEmailAndPassword - : signInWithEmailAndPassword; - const result = await method(auth, email, password); - onSignIn(result.user); - } catch (err) { - setError(err.message); - } - }; + if (mode === "register" && password !== confirmPassword) { + return setError("Passwords do not match."); + } - const handleResetPassword = async () => { - if (!email) return setError("Enter your email to reset password."); - try { - await sendPasswordResetEmail(auth, email); - alert("Password reset email sent."); - } catch (err) { - setError(err.message); - } - }; + try { + const method = + mode === "register" + ? createUserWithEmailAndPassword + : signInWithEmailAndPassword; + const result = await method(auth, email, password); + onSignIn(result.user); + } catch (err) { + setError(err.message); + } + }; - const handleGoogleLogin = async () => { - try { - const result = await signInWithPopup(auth, provider); - const user = result.user; - onSignIn(user); - } catch (error) { - setError(error.message); - } - }; + const handleResetPassword = async () => { + if (!email) return setError("Enter your email to reset password."); + try { + await sendPasswordResetEmail(auth, email); + alert("Password reset email sent."); + } catch (err) { + setError(err.message); + } + }; - return ( -
-
-

{mode === "login" ? "Sign in" : "Join Click4News"}

-

- {mode === "login" - ? "Stay updated on real-time global stories." - : "Create your account to explore breaking news by location."} -

+ const handleGoogleLogin = async () => { + try { + const result = await signInWithPopup(auth, provider); + onSignIn(result.user); + } catch (error) { + setError(error.message); + } + }; - setEmail(e.target.value)} - style={styles.input} - /> + return ( +
+ +

{mode === "login" ? "Sign in" : "Join Click4News"}

+

+ {mode === "login" + ? "Stay updated on real-time global stories." + : "Create your account to explore breaking news by location."} +

-
- setPassword(e.target.value)} - style={{ ...styles.input, marginBottom: 0 }} - /> - setShowPwd(!showPwd)} style={styles.showBtn}> + setEmail(e.target.value)} + style={styles.input} + /> + +
+ setPassword(e.target.value)} + style={{ ...styles.input, marginBottom: 0 }} + /> + setShowPwd(!showPwd)} style={styles.showBtn}> {showPwd ? "hide" : "show"} -
+
- {mode === "register" && ( -
- setConfirmPassword(e.target.value)} - style={{ ...styles.input, marginBottom: 0 }} - /> - setShowConfirmPwd(!showConfirmPwd)} style={styles.showBtn}> - {showConfirmPwd ? "hide" : "show"} - -
- )} + {mode === "register" && ( +
+ setConfirmPassword(e.target.value)} + style={{ ...styles.input, marginBottom: 0 }} + /> + setShowConfirmPwd(!showConfirmPwd)} style={styles.showBtn}> + {showConfirmPwd ? "hide" : "show"} + +
+ )} -
- {mode === "login" && ( - +
+ {mode === "login" && ( + Forgot password? - )} -
+ )} +
- {error &&

{error}

} + {error &&

{error}

} - + -
- - or - -
+
+ + or + +
- + -

- {mode === "login" ? "New to Click4News?" : "Already a user?"}{" "} - setMode(mode === "login" ? "register" : "login")} style={styles.link}> +

+ {mode === "login" ? "New to Click4News?" : "Already a user?"}{" "} + setMode(mode === "login" ? "register" : "login")} style={styles.link}> {mode === "login" ? "Join now" : "Sign in"} -

- -
- ); +

+ +
+ ); }; const styles = { - container: { - width: "100%", - display: "flex", - justifyContent: "center", - padding: "40px 20px", - }, - card: { - width: "100%", - maxWidth: "400px", - background: "#fff", - borderRadius: "12px", - padding: "30px", - boxShadow: "0 4px 20px rgba(0,0,0,0.1)", - }, - title: { - fontSize: "24px", - fontWeight: "600", - marginBottom: "8px", - }, - subtitle: { - fontSize: "14px", - color: "#666", - marginBottom: "24px", - }, - input: { - width: "100%", - padding: "12px", - fontSize: "14px", - border: "1px solid #ccc", - borderRadius: "6px", - marginBottom: "16px", - boxSizing: "border-box", // ensures perfect alignment - }, - passwordField: { - position: "relative", - marginBottom: "16px", - }, - showBtn: { - position: "absolute", - right: "12px", - top: "12px", - fontSize: "13px", - color: "#0a66c2", - cursor: "pointer", - }, - links: { - textAlign: "right", - marginBottom: "12px", - }, - link: { - color: "#0a66c2", - fontSize: "13px", - cursor: "pointer", - textDecoration: "none", - }, - submitBtn: { - width: "100%", - padding: "12px", - backgroundColor: "#0a66c2", - color: "#fff", - fontSize: "14px", - fontWeight: "bold", - border: "none", - borderRadius: "6px", // match input borderRadius - cursor: "pointer", - marginTop: "16px", // spacing from confirm field - }, - orDivider: { - display: "flex", - alignItems: "center", - margin: "20px 0", - }, - line: { - flex: 1, - height: "1px", - backgroundColor: "#ccc", - }, - or: { - margin: "0 10px", - color: "#666", - fontSize: "12px", - }, - googleBtn: { - width: "100%", - padding: "10px", - fontSize: "14px", - borderRadius: "24px", - border: "1px solid #000", - background: "#fff", - color: "#000", - cursor: "pointer", - fontWeight: "600", - display: "flex", - alignItems: "center", - justifyContent: "center", - gap: "8px", - }, - googleIcon: { - width: "18px", - height: "18px", - }, - footer: { - marginTop: "24px", - textAlign: "center", - fontSize: "14px", - color: "#555", - }, - error: { - color: "red", - fontSize: "13px", - marginBottom: "10px", - }, + container: { + width: "100%", + display: "flex", + justifyContent: "center", + padding: "40px 20px", + }, + card: { + width: "100%", + maxWidth: "400px", + background: "#fff", + borderRadius: "12px", + padding: "30px", + boxShadow: "0 4px 20px rgba(0,0,0,0.1)", + }, + title: { + fontSize: "24px", + fontWeight: "600", + marginBottom: "8px", + }, + subtitle: { + fontSize: "14px", + color: "#666", + marginBottom: "24px", + }, + input: { + width: "100%", + padding: "12px", + fontSize: "14px", + border: "1px solid #ccc", + borderRadius: "6px", + marginBottom: "16px", + boxSizing: "border-box", + }, + passwordField: { + position: "relative", + marginBottom: "16px", + }, + showBtn: { + position: "absolute", + right: "12px", + top: "12px", + fontSize: "13px", + color: "#0a66c2", + cursor: "pointer", + }, + links: { + textAlign: "right", + marginBottom: "12px", + }, + link: { + color: "#0a66c2", + fontSize: "13px", + cursor: "pointer", + textDecoration: "none", + }, + submitBtn: { + width: "100%", + padding: "12px", + backgroundColor: "#0a66c2", + color: "#fff", + fontSize: "14px", + fontWeight: "bold", + border: "none", + borderRadius: "6px", + cursor: "pointer", + marginTop: "16px", + }, + orDivider: { + display: "flex", + alignItems: "center", + margin: "20px 0", + }, + line: { + flex: 1, + height: "1px", + backgroundColor: "#ccc", + }, + or: { + margin: "0 10px", + color: "#666", + fontSize: "12px", + }, + googleBtn: { + width: "100%", + padding: "10px", + fontSize: "14px", + borderRadius: "24px", + border: "1px solid #000", + background: "#fff", + color: "#000", + cursor: "pointer", + fontWeight: "600", + display: "flex", + alignItems: "center", + justifyContent: "center", + gap: "8px", + }, + googleIcon: { + width: "18px", + height: "18px", + }, + footer: { + marginTop: "24px", + textAlign: "center", + fontSize: "14px", + color: "#555", + }, + error: { + color: "red", + fontSize: "13px", + marginBottom: "10px", + }, }; -export default EmailAuth; \ No newline at end of file +export default EmailAuth; diff --git a/src/firebase.js b/src/firebase.js index 5d39278..d35e36f 100644 --- a/src/firebase.js +++ b/src/firebase.js @@ -1,25 +1,23 @@ // firebase.js import { initializeApp } from "firebase/app"; -import { getAuth, GoogleAuthProvider, signInWithPopup } from "firebase/auth"; -// import { getAnalytics } from "firebase/analytics"; +import { getAuth, GoogleAuthProvider } from "firebase/auth"; -// Load from .env +// Load Firebase config from environment variables const firebaseConfig = { - apiKey: process.env.REACT_APP_FIREBASE_API_KEY, - authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN, - projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID, - storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET, - messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID, - appId: process.env.REACT_APP_FIREBASE_APP_ID, - measurementId: process.env.REACT_APP_FIREBASE_MEASUREMENT_ID + apiKey: process.env.REACT_APP_FIREBASE_API_KEY, + authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN, + projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID, + storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET, + messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID, + appId: process.env.REACT_APP_FIREBASE_APP_ID, + measurementId: process.env.REACT_APP_FIREBASE_MEASUREMENT_ID }; -// Initialize Firebase +// Initialize Firebase app const app = initializeApp(firebaseConfig); -// const analytics = getAnalytics(app); -// Auth setup +// Setup Auth const auth = getAuth(app); const provider = new GoogleAuthProvider(); -export { auth, provider, signInWithPopup }; \ No newline at end of file +export { auth, provider }; From c0c891f3e3b48e23653a645c21afacca7fabec66 Mon Sep 17 00:00:00 2001 From: Hima-Varshith Date: Thu, 17 Apr 2025 01:04:19 -0600 Subject: [PATCH 09/36] Deleted old files for refactoring --- src/MapComponent.js | 295 -------------------------------------------- src/MapFilters.js | 56 --------- 2 files changed, 351 deletions(-) delete mode 100644 src/MapComponent.js delete mode 100644 src/MapFilters.js diff --git a/src/MapComponent.js b/src/MapComponent.js deleted file mode 100644 index 64da216..0000000 --- a/src/MapComponent.js +++ /dev/null @@ -1,295 +0,0 @@ -import React, { Component, createRef } from "react"; -import Map, { Popup, Source, Layer } from "react-map-gl/mapbox"; -import "mapbox-gl/dist/mapbox-gl.css"; -import MapFilters from "./MapFilters"; - -// 🔧 Load token from .env -const MAPBOX_ACCESS_TOKEN = process.env.REACT_APP_MAPBOX_ACCESS_TOKEN; - -class HeatMap extends Component { - constructor(props) { - super(props); - this.state = { - sortedNearbyNews: [], - currentNewsIndex: 0, - theme: "light", - zoomLevel: "world", - viewState: { - longitude: -105.2705, - latitude: 40.015, - zoom: 2, - }, - geoJsonData: null, - clickedLocation: null, - circleRadius: 0, - showPopup: false, - selectedNews: null, - }; - this.mapRef = createRef(); - } - - componentDidMount() { - fetch("https://fastapi-service-34404463322.us-central1.run.app/geojson") - .then((response) => response.json()) - .then((data) => { - const jitteredFeatures = this.jitterOverlappingPoints(data.features); - const jitteredData = { - type: "FeatureCollection", - features: jitteredFeatures, - }; - this.setState({ geoJsonData: jitteredData }); - }) - .catch((error) => console.error("Error loading GeoJSON from API:", error)); - } - - jitterOverlappingPoints = (features) => { - const jittered = []; - const seenCoords = new Set(); - - features.forEach((feature) => { - const [lng, lat] = feature.geometry.coordinates; - const key = `${lng.toFixed(5)},${lat.toFixed(5)}`; - - let jitteredLng = lng; - let jitteredLat = lat; - - if (seenCoords.has(key)) { - const offsetLng = (Math.random() - 0.5) * 0.02; - const offsetLat = (Math.random() - 0.5) * 0.02; - jitteredLng += offsetLng; - jitteredLat += offsetLat; - } - - seenCoords.add(key); - - jittered.push({ - ...feature, - geometry: { - ...feature.geometry, - coordinates: [jitteredLng, jitteredLat], - }, - }); - }); - - return jittered; - }; - - zoomLevels = { - world: 2, - country: 4, - state: 6, - city: 10, - }; - - handleThemeChange = (theme) => this.setState({ theme }); - - handleZoomChange = (selectedZoom) => { - this.setState({ zoomLevel: selectedZoom }); - if (this.mapRef.current) { - this.mapRef.current.flyTo({ - zoom: this.zoomLevels[selectedZoom], - essential: true, - duration: 1000, - }); - } - }; - - handleMapClick = (event) => { - event.originalEvent.stopPropagation(); - const { lng, lat } = event.lngLat; - if (!this.state.geoJsonData) return; - - // ✅ Get all nearby features (within rough radius) - const nearbyNews = this.state.geoJsonData.features.filter((f) => { - const [fx, fy] = f.geometry.coordinates; - const dist = Math.hypot(fx - lng, fy - lat); - return dist < 3; // adjust as needed (3 is loose distance filter) - }); - - if (nearbyNews.length === 0) return; - - this.setState({ - clickedLocation: { longitude: lng, latitude: lat }, - sortedNearbyNews: nearbyNews, - currentNewsIndex: 0, - selectedNews: nearbyNews[0], - circleRadius: 5, - showPopup: false, - }, () => this.animateCircle(() => this.setState({ showPopup: true }))); - }; - - animateCircle = (callback) => { - let radius = 5; - const interval = setInterval(() => { - radius += 5; - this.setState({ circleRadius: radius }); - if (radius > 50) { - clearInterval(interval); - if (callback) callback(); - } - }, 50); - }; - - render() { - const { - theme, viewState, zoomLevel, - clickedLocation, circleRadius, - selectedNews, showPopup, geoJsonData - } = this.state; - - const mapStyles = { - light: "mapbox://styles/mapbox/outdoors-v12", - dark: "mapbox://styles/mapbox/dark-v11", - }; - - if (!geoJsonData) { - return
Loading map data...
; - } - - return ( -
- - - - - - - - {clickedLocation && ( - - - - )} - - {selectedNews && showPopup && ( - -
- -

{selectedNews.properties.title}

-

{selectedNews.properties.summary}

- - {this.state.sortedNearbyNews.length > 1 && ( - - )} -
-
- )} -
-
- ); - } -} - -const styles = { - popupContainer: { - width: "300px", - backgroundColor: "#1a1a1a", - padding: "15px", - borderRadius: "8px", - textAlign: "left", - color: "#ffffff", - }, - popupTitle: { - fontSize: "16px", - fontWeight: "bold", - marginBottom: "8px", - color: "#ffcc00", - }, - popupSummary: { - fontSize: "14px", - lineHeight: "1.5", - color: "#cccccc", - marginBottom: "10px", - }, - readMoreButton: { - width: "100%", - padding: "8px", - backgroundColor: "#ffcc00", - color: "#000", - border: "none", - borderRadius: "4px", - cursor: "pointer", - fontSize: "14px", - fontWeight: "bold", - }, - closeButton: { - position: "absolute", - top: 5, - right: 10, - background: "transparent", - border: "none", - color: "#fff", - fontSize: "16px", - cursor: "pointer" - } -}; - -export default HeatMap; diff --git a/src/MapFilters.js b/src/MapFilters.js deleted file mode 100644 index d614fd1..0000000 --- a/src/MapFilters.js +++ /dev/null @@ -1,56 +0,0 @@ -import React from "react"; - -const MapFilters = ({ theme, zoomLevel, onThemeChange, onZoomChange }) => { - return ( -
- - - - - -
- ); -}; - -// Styles for the UI Components -const styles = { - filterContainer: { - position: "absolute", - top: 10, - left: 10, - zIndex: 2, - backgroundColor: "rgba(255, 255, 255, 0.9)", - padding: "8px 12px", - borderRadius: "6px", - boxShadow: "0px 2px 4px rgba(0, 0, 0, 0.1)", - display: "flex", - flexDirection: "column", - gap: "6px", - }, - label: { - fontSize: "13px", - fontWeight: "bold", - color: "#333", - }, - dropdown: { - backgroundColor: "white", - color: "#333", - padding: "4px 8px", - fontSize: "13px", - borderRadius: "4px", - border: "1px solid #ccc", - cursor: "pointer", - outline: "none", - transition: "all 0.2s ease-in-out", - }, -}; - -export default MapFilters; From cdf585a706f1c8e607b4ada8c966373bb22a41b5 Mon Sep 17 00:00:00 2001 From: Hima-Varshith Date: Thu, 17 Apr 2025 01:05:07 -0600 Subject: [PATCH 10/36] Modified index and styles for css --- src/components/Map/styles.js | 106 +++++++++++++++++++++++++++++++++++ src/index.css | 32 ++++++++--- 2 files changed, 129 insertions(+), 9 deletions(-) create mode 100644 src/components/Map/styles.js diff --git a/src/components/Map/styles.js b/src/components/Map/styles.js new file mode 100644 index 0000000..aad517e --- /dev/null +++ b/src/components/Map/styles.js @@ -0,0 +1,106 @@ +const styles = { + popupContainer: { + width: "300px", + backgroundColor: "#1a1a1a", + padding: "15px", + borderRadius: "8px", + textAlign: "left", + color: "#ffffff", + position: "relative", + }, + popupTitle: { + fontSize: "16px", + fontWeight: "bold", + marginBottom: "8px", + color: "#ffcc00", + }, + popupSummary: { + fontSize: "14px", + lineHeight: "1.5", + color: "#cccccc", + marginBottom: "10px", + }, + readMoreButton: { + width: "100%", + padding: "8px", + backgroundColor: "#ff4d4f", + color: "#fff", + border: "none", + borderRadius: "4px", + cursor: "pointer", + fontSize: "14px", + fontWeight: "bold", + }, + closeButton: { + position: "absolute", + top: 5, + right: 10, + background: "transparent", + border: "none", + color: "#fff", + fontSize: "16px", + cursor: "pointer", + }, + voteRow: { + display: "flex", + justifyContent: "space-between", + marginTop: "12px", + gap: "8px", + }, + voteButton: { + flex: 1, + padding: "8px", + borderRadius: "6px", + fontWeight: "bold", + fontSize: "14px", + cursor: "pointer", + border: "none", + }, + + // 🔴 Modal Styles for AddNewsModal + modalBackdrop: { + position: "fixed", + top: 0, + left: 0, + width: "100vw", + height: "100vh", + backgroundColor: "rgba(0, 0, 0, 0.7)", + backdropFilter: "blur(3px)", + display: "flex", + alignItems: "center", + justifyContent: "center", + zIndex: 9999, + }, + modalContent: { + width: "360px", + backgroundColor: "#2c2c2c", + borderRadius: "10px", + padding: "20px", + boxShadow: "0 4px 12px rgba(0, 0, 0, 0.4)", + display: "flex", + flexDirection: "column", + gap: "12px", + color: "#fff", + }, + input: { + padding: "8px 12px", + fontSize: "14px", + borderRadius: "6px", + border: "1px solid #888", + backgroundColor: "#1e1e1e", + color: "#f5f5f5", + outline: "none", + }, + clearButton: { + padding: "8px 16px", + backgroundColor: "#666", + color: "#fff", + fontSize: "13px", + fontWeight: "bold", + border: "none", + borderRadius: "6px", + cursor: "pointer", + }, +}; + +export default styles; diff --git a/src/index.css b/src/index.css index 417a3e7..28eced9 100644 --- a/src/index.css +++ b/src/index.css @@ -1,5 +1,10 @@ -body { +/* Reset and base styles */ +html, body, #root { margin: 0; + padding: 0; + height: 100%; + width: 100%; + overflow: hidden; /* Prevent both vertical and horizontal scroll */ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; @@ -12,13 +17,9 @@ code { monospace; } - -/* Force the popup to be centered */ - - -/* Ensure the content inside the popup is centered */ +/* Customize Mapbox popup appearance */ .mapboxgl-popup-content { - background: none !important; /* ✅ Remove default white background */ + background: none !important; /* Remove default white background */ box-shadow: none !important; border: none !important; padding: 0px !important; @@ -27,7 +28,20 @@ code { align-items: center !important; } -/* Hide the default Mapbox popup arrow */ .mapboxgl-popup-tip { - display: none !important; + display: none !important; /* Hide arrow */ +} + +/* Ensure map canvas doesn't overflow */ +.mapboxgl-map { + width: 100% !important; + height: 100% !important; + overflow: hidden !important; +} + +.mapboxgl-ctrl-attrib { + font-size: 10px !important; + opacity: 0.5 !important; + bottom: 4px !important; + right: 6px !important; } \ No newline at end of file From 496a5b3c3cdf45d670f1a5fbb2085f47226a769b Mon Sep 17 00:00:00 2001 From: Hima-Varshith Date: Thu, 17 Apr 2025 01:06:04 -0600 Subject: [PATCH 11/36] Refactored the main Map Components --- src/components/Map/HeatLayers.js | 61 +++++++ src/components/Map/HeatMap.js | 276 +++++++++++++++++++++++++++++++ src/components/Map/PopupCard.js | 95 +++++++++++ 3 files changed, 432 insertions(+) create mode 100644 src/components/Map/HeatLayers.js create mode 100644 src/components/Map/HeatMap.js create mode 100644 src/components/Map/PopupCard.js diff --git a/src/components/Map/HeatLayers.js b/src/components/Map/HeatLayers.js new file mode 100644 index 0000000..1d913e2 --- /dev/null +++ b/src/components/Map/HeatLayers.js @@ -0,0 +1,61 @@ +import React from "react"; +import { Source, Layer } from "react-map-gl/mapbox"; + +const HeatLayers = ({ filteredData, clickedLocation, circleRadius }) => { + const emptyGeoJSON = { + type: "FeatureCollection", + features: [], + }; + + return ( + <> + {/* 🔥 Heatmap Layer */} + + + + + {/* 🌀 Click Circle Animation Layer */} + {clickedLocation && ( + + + + )} + + ); +}; + +export default HeatLayers; diff --git a/src/components/Map/HeatMap.js b/src/components/Map/HeatMap.js new file mode 100644 index 0000000..1b1dd36 --- /dev/null +++ b/src/components/Map/HeatMap.js @@ -0,0 +1,276 @@ +import React, { Component, createRef } from "react"; +import Map, { Popup } from "react-map-gl/mapbox"; +import "mapbox-gl/dist/mapbox-gl.css"; + +import DisplayControls from "../Features/DisplayControls"; +import NewsFilters from "../Features/NewsFilters"; +import HeatLayers from "./HeatLayers"; +import PopupCard from "./PopupCard"; +import AddNewsModal from "../User/AddNewsModal"; + +const MAPBOX_ACCESS_TOKEN = process.env.REACT_APP_MAPBOX_ACCESS_TOKEN; + +const viewZoomMap = { + world: 2, + country: 4, + state: 6, + city: 10, +}; + +class HeatMap extends Component { + constructor(props) { + super(props); + this.mapRef = createRef(); + this.circleInterval = null; + this.state = { + theme: "light", + viewLevel: "world", + zoom: viewZoomMap.world, + viewState: { + longitude: -105.0, + latitude: 39.7392, + zoom: viewZoomMap.world, + }, + geoJsonData: null, + clickedLocation: null, + sortedNearbyNews: [], + currentNewsIndex: 0, + showPopup: false, + showAddModal: false, + circleRadius: 0, + keyword: "", + selectedCategories: [], + }; + } + + componentDidMount() { + fetch("https://fastapi-service-34404463322.us-central1.run.app/geojson") + .then((res) => res.json()) + .then((data) => { + const jittered = this.jitterOverlappingPoints(data.features); + this.setState({ + geoJsonData: { + type: "FeatureCollection", + features: jittered, + }, + }); + }); + } + + jitterOverlappingPoints = (features) => { + const seen = new Set(); + return features.map((f) => { + let [lng, lat] = f.geometry.coordinates; + const key = `${lng.toFixed(4)}|${lat.toFixed(4)}`; + if (seen.has(key)) { + lng += (Math.random() - 0.5) * 0.01; + lat += (Math.random() - 0.5) * 0.01; + } + seen.add(key); + return { ...f, geometry: { ...f.geometry, coordinates: [lng, lat] } }; + }); + }; + + handleThemeChange = (theme) => this.setState({ theme }); + + handleViewLevelChange = (viewLevel) => { + const zoom = viewZoomMap[viewLevel]; + this.setState((prev) => ({ + viewLevel, + zoom, + viewState: { ...prev.viewState, zoom }, + })); + if (this.mapRef.current) { + this.mapRef.current.flyTo({ zoom, duration: 300 }); + } + }; + + handleZoomChange = (zoom, source = "manual") => { + const matched = Object.entries(viewZoomMap).find(([_, z]) => Math.abs(z - zoom) < 0.6); + const newLevel = matched ? matched[0] : this.state.viewLevel; + + this.setState((prev) => ({ + zoom, + viewLevel: newLevel, + viewState: { ...prev.viewState, zoom }, + })); + + if (source === "button" && this.mapRef.current) { + this.mapRef.current.flyTo({ zoom, duration: 300 }); + } + }; + + handleKeywordChange = (keyword) => this.setState({ keyword }); + + handleCategoryChange = (selectedCategories) => this.setState({ selectedCategories }); + + getFilteredFeatures = () => { + const { geoJsonData, keyword, selectedCategories } = this.state; + if (!geoJsonData) return []; + + return geoJsonData.features.filter((f) => { + const combinedText = `${f.properties.title || ""} ${f.properties.summary || ""} ${f.properties.category || ""}`; + const textMatch = combinedText.toLowerCase().includes(keyword.toLowerCase()); + const catMatch = selectedCategories.length === 0 || selectedCategories.includes(f.properties.category); + return textMatch && catMatch; + }); + }; + + handleMapClick = (event) => { + const { lng, lat } = event.lngLat; + const filteredFeatures = this.getFilteredFeatures(); + if (!filteredFeatures || !this.mapRef.current) return; + + const map = this.mapRef.current.getMap(); + const clickedPixel = map.project([lng, lat]); + const thresholdPixels = 30; + + const nearbyNews = filteredFeatures.filter((f) => { + const screenCoords = map.project(f.geometry.coordinates); + const dx = screenCoords.x - clickedPixel.x; + const dy = screenCoords.y - clickedPixel.y; + const dist = Math.sqrt(dx * dx + dy * dy); + return dist < thresholdPixels; + }); + + if (nearbyNews.length === 0) return; + + this.setState({ + clickedLocation: { longitude: lng, latitude: lat }, + sortedNearbyNews: nearbyNews, + currentNewsIndex: 0, + showPopup: true, + circleRadius: 10, + }); + + if (this.circleInterval) clearInterval(this.circleInterval); + let radius = 10; + this.circleInterval = setInterval(() => { + radius += 3; + if (radius >= 50) { + clearInterval(this.circleInterval); + this.setState({ circleRadius: 50 }); + } else { + this.setState({ circleRadius: radius }); + } + }, 30); + }; + + render() { + const { + theme, viewLevel, zoom, viewState, + clickedLocation, sortedNearbyNews, currentNewsIndex, + showPopup, circleRadius, keyword, selectedCategories, showAddModal + } = this.state; + + const selectedNews = sortedNearbyNews[currentNewsIndex]; + const filteredData = { + type: "FeatureCollection", + features: this.getFilteredFeatures(), + }; + + return ( +
+
+ this.handleViewLevelChange("world")} + /> + +
+ + { + this.handleZoomChange(evt.viewState.zoom, "scroll"); + }} + mapStyle={{ + light: "mapbox://styles/mapbox/outdoors-v12", + dark: "mapbox://styles/mapbox/dark-v11", + }[theme]} + style={{ width: "100%", height: "100%" }} + > + + + {selectedNews && showPopup && ( + + 1} + onClose={() => this.setState({ showPopup: false })} + onNext={() => { + const nextIndex = (currentNewsIndex + 1) % sortedNearbyNews.length; + this.setState({ currentNewsIndex: nextIndex }); + }} + /> + + )} + + + + + {showAddModal && ( + this.setState({ showAddModal: false })} /> + )} +
+ ); + } +} + +const styles = { + controlsWrapper: { + position: "absolute", + top: "12px", + left: "10px", + zIndex: 2, + display: "flex", + flexDirection: "column", + gap: "12px", + width: "260px", + }, +}; + +export default HeatMap; \ No newline at end of file diff --git a/src/components/Map/PopupCard.js b/src/components/Map/PopupCard.js new file mode 100644 index 0000000..cfbaa56 --- /dev/null +++ b/src/components/Map/PopupCard.js @@ -0,0 +1,95 @@ +import React, { useState, useEffect } from "react"; +import styles from "./styles"; + +const PopupCard = ({ selectedNews, multipleNews, onClose, onNext }) => { + const [userVote, setUserVote] = useState(null); // "like" | "dislike" | null + const [likes, setLikes] = useState(0); + const [dislikes, setDislikes] = useState(0); + + // 🔄 Reset vote state when selected article changes + useEffect(() => { + setUserVote(null); + setLikes(selectedNews.properties.likes || 0); + setDislikes(selectedNews.properties.dislikes || 0); + }, [selectedNews]); + + const handleLike = () => { + if (userVote === "like") { + setLikes((prev) => prev - 1); + setUserVote(null); + } else { + if (userVote === "dislike") setDislikes((prev) => prev - 1); + setLikes((prev) => prev + 1); + setUserVote("like"); + } + }; + + const handleDislike = () => { + if (userVote === "dislike") { + setDislikes((prev) => prev - 1); + setUserVote(null); + } else { + if (userVote === "like") setLikes((prev) => prev - 1); + setDislikes((prev) => prev + 1); + setUserVote("dislike"); + } + }; + + return ( +
+ + +

{selectedNews.properties.title}

+

{selectedNews.properties.summary}

+ + + +
+ + + +
+ + {multipleNews && ( + + )} +
+ ); +}; + +export default PopupCard; From 036163d8a217f87f636744377a2ec999f7a569ea Mon Sep 17 00:00:00 2001 From: Hima-Varshith Date: Thu, 17 Apr 2025 01:06:27 -0600 Subject: [PATCH 12/36] Added display controls and news filters --- src/components/Features/DisplayControls.js | 213 +++++++++++++++++++ src/components/Features/NewsFilters.js | 225 +++++++++++++++++++++ 2 files changed, 438 insertions(+) create mode 100644 src/components/Features/DisplayControls.js create mode 100644 src/components/Features/NewsFilters.js diff --git a/src/components/Features/DisplayControls.js b/src/components/Features/DisplayControls.js new file mode 100644 index 0000000..baa870e --- /dev/null +++ b/src/components/Features/DisplayControls.js @@ -0,0 +1,213 @@ +import React, { useState } from "react"; + +const DisplayControls = ({ + theme, + viewLevel, + zoom, + onThemeChange, + onViewLevelChange, + onZoomChange, + onResetView, +}) => { + const [isExpanded, setIsExpanded] = useState(true); + + return ( +
+ + + {isExpanded && ( +
+ {/* Theme */} +
+ +
+ + +
+
+ +
+ + {/* View Level */} +
+ +
+ {Object.entries(viewIcons).map(([level, icon]) => ( + + ))} +
+
+ +
+ + {/* Zoom */} +
+ +
+ + onZoomChange(parseFloat(e.target.value), "button")} + style={styles.slider} + /> + +
+
+ +
+ +
+ +
+
+ )} +
+ ); +}; + +const viewIcons = { + world: "🌍", + country: "🏳️", + state: "🏔️", + city: "📍", +}; + +const styles = { + wrapper: { + width: "100%", // ✅ Fill parent width (260px from HeatMap) + position: "relative", // ✅ No absolute positioning + }, + toggleButton: { + width: "100%", + padding: "8px 12px", + background: "#5c6bc0", + color: "#fff", + border: "none", + borderRadius: "6px", + cursor: "pointer", + fontSize: "14px", + fontWeight: "bold", + }, + container: { + backgroundColor: "#fff", + border: "1px solid #ddd", + padding: "10px 12px", + borderRadius: "8px", + marginTop: "8px", + display: "flex", + flexDirection: "column", + gap: "14px", + }, + section: { + display: "flex", + flexDirection: "column", + gap: "6px", + }, + label: { + fontSize: "13px", + fontWeight: "bold", + color: "#333", + }, + toggleRow: { + display: "flex", + gap: "8px", + }, + themeButton: { + flex: 1, + padding: "6px", + borderRadius: "6px", + fontWeight: "bold", + cursor: "pointer", + border: "1px solid #ccc", + }, + radioGroup: { + display: "flex", + flexDirection: "column", + gap: "4px", + paddingLeft: "4px", + }, + radioLabel: { + fontSize: "13px", + display: "flex", + alignItems: "center", + gap: "6px", + }, + zoomControls: { + display: "flex", + alignItems: "center", + gap: "6px", + }, + zoomButton: { + padding: "4px 8px", + fontSize: "14px", + backgroundColor: "#eee", + border: "1px solid #ccc", + borderRadius: "6px", + cursor: "pointer", + }, + slider: { + flex: 1, + width: "100%", // ✅ responsive slider + }, + resetButton: { + padding: "6px 10px", + backgroundColor: "#eee", + color: "#000", + border: "1px solid #bbb", + borderRadius: "6px", + cursor: "pointer", + fontSize: "13px", + }, + divider: { + height: "1px", + border: "none", + backgroundColor: "#ccc", + margin: "6px 0", + }, +}; + +export default DisplayControls; diff --git a/src/components/Features/NewsFilters.js b/src/components/Features/NewsFilters.js new file mode 100644 index 0000000..92f4dc1 --- /dev/null +++ b/src/components/Features/NewsFilters.js @@ -0,0 +1,225 @@ +import React, { useState } from "react"; + +const emojiCategories = { + AI: "🤖", + Basketball: "🏀", + Business: "💼", + Celebrities: "🌟", + Conflict: "⚔️", + Crime: "🚔", + Diplomacy: "🕊️", + Economy: "💰", + Elections: "🗳️", + Energy: "⚡", + Entertainment: "🎬", + Environment: "🌿", + Fashion: "👗", + Finance: "📊", + Food: "🍽️", + Football: "🏈", + Gaming: "🎮", + Government: "🏛️", + Health: "🩺", + "Higher Education": "🎓", + Hurricanes: "🌪️", + Justice: "⚖️", + Law: "📜", + Lifestyle: "💅", + Medicine: "💊", + "Mental Health": "🧠", + Military: "🪖", + Music: "🎵", + "Natural Disasters": "🌋", + Olympics: "🥇", + Parenting: "👨‍👩‍👧", + Physics: "🔭", + Police: "👮‍♂️", + Politics: "🏛️", + Research: "🔍", + Science: "🔬", + Sports: "🏅", + Startups: "🚀", + Sustainability: "🌱", + "TV Shows": "📺", + Technology: "💻", + Travel: "✈️", + Universities: "🏫", + War: "💣", + Weather: "🌦️", + Wildlife: "🦁", + World: "🌍", +}; + +const NewsFilters = ({ keyword, selectedCategories, onKeywordChange, onCategoryChange }) => { + const [isExpanded, setIsExpanded] = useState(true); + const [searchText, setSearchText] = useState(""); + + const categoryList = Object.keys(emojiCategories).filter((cat) => + cat.toLowerCase().includes(searchText.toLowerCase()) + ); + + const handleCheckboxToggle = (cat) => { + let updated = [...selectedCategories]; + if (updated.includes(cat)) { + updated = updated.filter((c) => c !== cat); + } else { + updated.push(cat); + } + onCategoryChange(updated); + }; + + const clearAll = () => onCategoryChange([]); + + return ( +
+ + + {isExpanded && ( +
+ {/* Keyword Input */} + + onKeywordChange(e.target.value)} + style={styles.input} + /> + + {/* Category Search Input */} + + setSearchText(e.target.value)} + style={styles.input} + /> + + {/* Selected Chips */} +
+ {selectedCategories.map((cat) => ( +
+ {emojiCategories[cat]} {cat} + handleCheckboxToggle(cat)}> + × + +
+ ))} +
+ + {/* Checkbox List */} +
+ {categoryList.map((cat) => ( + + ))} +
+ + {/* Clear All Button (Moved Below) */} + +
+ )} +
+ ); +}; + +const styles = { + wrapper: { + width: "100%", + position: "relative", + }, + toggleButton: { + width: "100%", + padding: "8px 12px", + background: "#5c6bc0", + color: "#fff", + border: "none", + borderRadius: "6px", + cursor: "pointer", + fontSize: "14px", + fontWeight: "bold", + boxShadow: "0px 1px 3px rgba(0, 0, 0, 0.15)", + }, + container: { + backgroundColor: "#fff", + border: "1px solid #ddd", + padding: "10px 12px", + borderRadius: "8px", + marginTop: "8px", + display: "flex", + flexDirection: "column", + gap: "12px", + }, + label: { + fontSize: "13px", + fontWeight: "bold", + color: "#333", + marginBottom: "4px", + }, + input: { + width: "100%", + padding: "6px 10px", + borderRadius: "6px", + border: "1px solid #ccc", + fontSize: "13px", + boxSizing: "border-box", + }, + chipContainer: { + display: "flex", + flexWrap: "wrap", + gap: "6px", + marginBottom: "4px", + }, + chip: { + display: "flex", + alignItems: "center", + backgroundColor: "#e0e0e0", + borderRadius: "16px", + padding: "4px 8px", + fontSize: "12px", + color: "#333", + }, + chipClose: { + marginLeft: "6px", + cursor: "pointer", + fontWeight: "bold", + }, + checkboxList: { + maxHeight: "140px", + overflowY: "auto", + border: "1px solid #ddd", + borderRadius: "6px", + padding: "6px", + backgroundColor: "#f9f9f9", + }, + checkboxLabel: { + display: "block", + marginBottom: "6px", + fontSize: "13px", + color: "#333", + }, + clearButton: { + backgroundColor: "#eee", + color: "#000", + fontSize: "13px", + fontWeight: "bold", + border: "1px solid #bbb", + borderRadius: "6px", + padding: "6px 10px", + cursor: "pointer", + width: "100%", + }, +}; + +export default NewsFilters; From b00500d0db5bd88b42c270a92b36604f37219034 Mon Sep 17 00:00:00 2001 From: Hima-Varshith Date: Thu, 17 Apr 2025 01:06:55 -0600 Subject: [PATCH 13/36] Craeted button for adding news --- src/components/User/AddNewsButton.js | 176 +++++++++++++++++++++++++++ src/components/User/AddNewsModal.js | 70 +++++++++++ 2 files changed, 246 insertions(+) create mode 100644 src/components/User/AddNewsButton.js create mode 100644 src/components/User/AddNewsModal.js diff --git a/src/components/User/AddNewsButton.js b/src/components/User/AddNewsButton.js new file mode 100644 index 0000000..94cd370 --- /dev/null +++ b/src/components/User/AddNewsButton.js @@ -0,0 +1,176 @@ +// Floating Button + News Form Modal +import React, { useState } from "react"; + +const AddNewsButton = () => { + const [showModal, setShowModal] = useState(false); + const [formData, setFormData] = useState({ + title: "", + summary: "", + category: "", + link: "" + }); + + const handleInputChange = (e) => { + const { name, value } = e.target; + setFormData((prev) => ({ ...prev, [name]: value })); + }; + + const handleSubmit = (e) => { + e.preventDefault(); + console.log("Submitting News:", formData); + setShowModal(false); + setFormData({ title: "", summary: "", category: "", link: "" }); + }; + + return ( + <> + + + {showModal && ( +
+
+

Add News Article

+
+ +