diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml index 05fcdba3..26b78a09 100644 --- a/.github/workflows/frontend.yml +++ b/.github/workflows/frontend.yml @@ -17,3 +17,8 @@ jobs: npm ci --legacy-peer-deps npm run lint-check npm run build + env: + NEXT_PUBLIC_BACKEND_URL: ${{ vars.NEXT_PUBLIC_BACKEND_URL }} + NEXT_PUBLIC_FIREBASE_SETTINGS: ${{ vars.NEXT_PUBLIC_FIREBASE_SETTINGS }} + NEXT_PUBLIC_PAYPAL_CLIENT_SECRET: ${{ vars.NEXT_PUBLIC_PAYPAL_CLIENT_SECRET }} + NEXT_PUBLIC_PAYPAL_CLIENT_ID: ${{ vars.NEXT_PUBLIC_PAYPAL_CLIENT_ID }} diff --git a/backend/package-lock.json b/backend/package-lock.json index 0bc37e9d..91babb66 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -22,7 +22,7 @@ "devDependencies": { "@types/cors": "^2.8.17", "@types/express": "^4.17.21", - "@types/nodemailer": "^6.4.14", + "@types/nodemailer": "^6.4.15", "@typescript-eslint/eslint-plugin": "^6.18.0", "@typescript-eslint/parser": "^6.18.0", "eslint": "^8.56.0", @@ -381,9 +381,9 @@ } }, "node_modules/@types/nodemailer": { - "version": "6.4.14", - "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.14.tgz", - "integrity": "sha512-fUWthHO9k9DSdPCSPRqcu6TWhYyxTBg382vlNIttSe9M7XfsT06y0f24KHXtbnijPGGRIcVvdKHTNikOI6qiHA==", + "version": "6.4.15", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.15.tgz", + "integrity": "sha512-0EBJxawVNjPkng1zm2vopRctuWVCxk34JcIlRuXSf54habUWdz1FB7wHDqOqvDa8Mtpt0Q3LTXQkAs2LNyK5jQ==", "dev": true, "dependencies": { "@types/node": "*" diff --git a/backend/package.json b/backend/package.json index cf78e7e1..f1469385 100644 --- a/backend/package.json +++ b/backend/package.json @@ -28,7 +28,7 @@ "devDependencies": { "@types/cors": "^2.8.17", "@types/express": "^4.17.21", - "@types/nodemailer": "^6.4.14", + "@types/nodemailer": "^6.4.15", "@typescript-eslint/eslint-plugin": "^6.18.0", "@typescript-eslint/parser": "^6.18.0", "eslint": "^8.56.0", diff --git a/backend/src/util/validateEnv.ts b/backend/src/util/validateEnv.ts index 7134e7c9..c780bbd9 100644 --- a/backend/src/util/validateEnv.ts +++ b/backend/src/util/validateEnv.ts @@ -15,4 +15,6 @@ export default cleanEnv(process.env, { EMAIL_NOTIFICATIONS_RECIPIENT: email(), // Recipient of VSR notification emails BACKEND_FIREBASE_SETTINGS: json(), // Firebase settings for backend, stored as a JSON string SERVICE_ACCOUNT_KEY: json(), // Private service account key for backend, stored as a JSON string + PAYPAL_CLIENT_ID: str(), // Client ID credential for PayPal account + PAYPAL_CLIENT_SECRET: str(), // Client secret credential for PayPal account }); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e61f51e5..fac4fc07 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -24,6 +24,7 @@ "nodemailer": "^6.9.11", "react": "^18", "react-dom": "^18", + "react-firebase-hooks": "^5.1.1", "react-material-symbols": "^4.3.1" }, "devDependencies": { @@ -489,14 +490,14 @@ } }, "node_modules/@firebase/analytics": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.2.tgz", - "integrity": "sha512-6Gv/Fndih+dOEEfsBJEeKlwxw9EvCO9D/y+yJMasblvCmj78wUVtn+T96zguSrbhfZ2yBhLS1vukYiPg6hI49w==", - "dependencies": { - "@firebase/component": "0.6.6", - "@firebase/installations": "0.6.6", - "@firebase/logger": "0.4.1", - "@firebase/util": "1.9.5", + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.4.tgz", + "integrity": "sha512-OJEl/8Oye/k+vJ1zV/1L6eGpc1XzAj+WG2TPznJ7PszL7sOFLBXkL9IjHfOCGDGpXeO3btozy/cYUqv4zgNeHg==", + "dependencies": { + "@firebase/component": "0.6.7", + "@firebase/installations": "0.6.7", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.9.6", "tslib": "^2.1.0" }, "peerDependencies": { @@ -504,14 +505,14 @@ } }, "node_modules/@firebase/analytics-compat": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.8.tgz", - "integrity": "sha512-scvzDPIsP9HcLWM77YQD7F3yLQksGvPUzyfqUrPo9XxIx26txJvGMJAS8O8BHa6jIvsjUenaTZ5oXEtKqNZQ9Q==", - "dependencies": { - "@firebase/analytics": "0.10.2", - "@firebase/analytics-types": "0.8.1", - "@firebase/component": "0.6.6", - "@firebase/util": "1.9.5", + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.10.tgz", + "integrity": "sha512-ia68RcLQLLMFWrM10JfmFod7eJGwqr4/uyrtzHpTDnxGX/6gNCBTOuxdAbyWIqXI5XmcMQdz9hDijGKOHgDfPw==", + "dependencies": { + "@firebase/analytics": "0.10.4", + "@firebase/analytics-types": "0.8.2", + "@firebase/component": "0.6.7", + "@firebase/util": "1.9.6", "tslib": "^2.1.0" }, "peerDependencies": { @@ -519,30 +520,30 @@ } }, "node_modules/@firebase/analytics-types": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.1.tgz", - "integrity": "sha512-niv/67/EOkTlGUxyiOYfIkysSMGYxkIUHJzT9pNkeIGt6zOz759oCUXOAwwjJzckh11dMBFjIYBmtWrdSgbmJw==" + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.2.tgz", + "integrity": "sha512-EnzNNLh+9/sJsimsA/FGqzakmrAUKLeJvjRHlg8df1f97NLUlFidk9600y0ZgWOp3CAxn6Hjtk+08tixlUOWyw==" }, "node_modules/@firebase/app": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.10.1.tgz", - "integrity": "sha512-H8hvbSVxNt+QaUQ1O0Gqidksi5ilj6eL8iMYxUNZgsMwZ1yOTgXc2C9zktbPQKokgcMq+EbF0k/t5iouslSkiA==", + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.10.4.tgz", + "integrity": "sha512-oKd5cT+fDbQ22X8Am3tBOrSFdDp8n4NJDqld4uo+H/PL9F+D3ogtTeiPyDWw1lZK7FsMbmtRrPRozlmJFzSKAQ==", "dependencies": { - "@firebase/component": "0.6.6", - "@firebase/logger": "0.4.1", - "@firebase/util": "1.9.5", + "@firebase/component": "0.6.7", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.9.6", "idb": "7.1.1", "tslib": "^2.1.0" } }, "node_modules/@firebase/app-check": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.8.3.tgz", - "integrity": "sha512-nvlsj5oZBtYDjFTygQJ6xpyiYj8Jao2bFFyNJkUUPdg/QB8uhqDeG74P+gUH6iY9qzd1g5ZokmmGsoIhv9tdSQ==", + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.8.4.tgz", + "integrity": "sha512-2tjRDaxcM5G7BEpytiDcIl+NovV99q8yEqRMKDbn4J4i/XjjuThuB4S+4PkmTnZiCbdLXQiBhkVxNlUDcfog5Q==", "dependencies": { - "@firebase/component": "0.6.6", - "@firebase/logger": "0.4.1", - "@firebase/util": "1.9.5", + "@firebase/component": "0.6.7", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.9.6", "tslib": "^2.1.0" }, "peerDependencies": { @@ -550,15 +551,15 @@ } }, "node_modules/@firebase/app-check-compat": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.10.tgz", - "integrity": "sha512-v+jiLG3rQ1fhpIuNIm3WqrL4dkPUIkgOWoic7QABVsZKSAv2YhOFvAenp7IhSP/pz/aiPniJ8G7el/MWieECTg==", - "dependencies": { - "@firebase/app-check": "0.8.3", - "@firebase/app-check-types": "0.5.1", - "@firebase/component": "0.6.6", - "@firebase/logger": "0.4.1", - "@firebase/util": "1.9.5", + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.11.tgz", + "integrity": "sha512-t01zaH3RJpKEey0nGduz3Is+uSz7Sj4U5nwOV6lWb+86s5xtxpIvBJzu/lKxJfYyfZ29eJwpdjEgT1/lm4iQyA==", + "dependencies": { + "@firebase/app-check": "0.8.4", + "@firebase/app-check-types": "0.5.2", + "@firebase/component": "0.6.7", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.9.6", "tslib": "^2.1.0" }, "peerDependencies": { @@ -566,40 +567,40 @@ } }, "node_modules/@firebase/app-check-interop-types": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.1.tgz", - "integrity": "sha512-NILZbe6RH3X1pZmJnfOfY2gLIrlKmrkUMMrrK6VSXHcSE0eQv28xFEcw16D198i9JYZpy5Kwq394My62qCMaIw==" + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.2.tgz", + "integrity": "sha512-LMs47Vinv2HBMZi49C09dJxp0QT5LwDzFaVGf/+ITHe3BlIhUiLNttkATSXplc89A2lAaeTqjgqVkiRfUGyQiQ==" }, "node_modules/@firebase/app-check-types": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.1.tgz", - "integrity": "sha512-NqeIcuGzZjl+khpXV0qsyOoaTqLeiG/K0kIDrebol+gb7xpmfOvXXqPEls+1WFBgHcPGdu+XRLhBA7xLzrVdpA==" + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.2.tgz", + "integrity": "sha512-FSOEzTzL5bLUbD2co3Zut46iyPWML6xc4x+78TeaXMSuJap5QObfb+rVvZJtla3asN4RwU7elaQaduP+HFizDA==" }, "node_modules/@firebase/app-compat": { - "version": "0.2.31", - "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.2.31.tgz", - "integrity": "sha512-TP9EwOiqDDL4tsP9EyOJn+RYUTkopS0nCg6TZ0PH8XiUgLlgDAF2waAZNha0+18elUkVjbWoXcudCgJ0iVWEVA==", - "dependencies": { - "@firebase/app": "0.10.1", - "@firebase/component": "0.6.6", - "@firebase/logger": "0.4.1", - "@firebase/util": "1.9.5", + "version": "0.2.34", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.2.34.tgz", + "integrity": "sha512-enteBla1gBYObauvsC9bRRoqHZnOW48ahYABZ+l+FEiWil1rw0gVihl8D4eLqtQp/ci8+fbOBf3ZL19uFq/OCw==", + "dependencies": { + "@firebase/app": "0.10.4", + "@firebase/component": "0.6.7", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.9.6", "tslib": "^2.1.0" } }, "node_modules/@firebase/app-types": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.1.tgz", - "integrity": "sha512-nFGqTYsnDFn1oXf1tCwPAc+hQPxyvBT/QB7qDjwK+IDYThOn63nGhzdUTXxVD9Ca8gUY/e5PQMngeo0ZW/E3uQ==" + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.2.tgz", + "integrity": "sha512-oMEZ1TDlBz479lmABwWsWjzHwheQKiAgnuKxE0pz0IXCVx7/rtlkx1fQ6GfgK24WCrxDKMplZrT50Kh04iMbXQ==" }, "node_modules/@firebase/auth": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.7.1.tgz", - "integrity": "sha512-h1nTQ/bKuKmXnwhQP1hi73aSnEp3YQnw+9k8ICwvNB9FhG0XJS5VNtR08cpLUpwl9clSTujg3EP/Hs/chZnq4A==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.7.3.tgz", + "integrity": "sha512-RiU1PjziOxLuyswtYtLK2qSjHIQJQGCk1h986SUFRbMQfzLXbQg8ZgXwxac1UAfDOzgzqPNCXhBuIlSK2UomoQ==", "dependencies": { - "@firebase/component": "0.6.6", - "@firebase/logger": "0.4.1", - "@firebase/util": "1.9.5", + "@firebase/component": "0.6.7", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.9.6", "tslib": "^2.1.0", "undici": "5.28.4" }, @@ -614,14 +615,14 @@ } }, "node_modules/@firebase/auth-compat": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.5.6.tgz", - "integrity": "sha512-zXo0CnGG8UqFtXW76XfXdKmDaAUW7QEN0BYXYH04VuzdPCmkWaR5Uybjp/Tglh3+UqE4AhYcYe0p2n+mxmkLqA==", - "dependencies": { - "@firebase/auth": "1.7.1", - "@firebase/auth-types": "0.12.1", - "@firebase/component": "0.6.6", - "@firebase/util": "1.9.5", + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.5.8.tgz", + "integrity": "sha512-qUgmv/mcth9wHPTOCKgAOeHe5c+BIOJVcbX2RfcjlXO3xnd8nRafwEkZKBNJUjy4oihYhqFMEMnTHLhwLJwLig==", + "dependencies": { + "@firebase/auth": "1.7.3", + "@firebase/auth-types": "0.12.2", + "@firebase/component": "0.6.7", + "@firebase/util": "1.9.6", "tslib": "^2.1.0", "undici": "5.28.4" }, @@ -630,73 +631,73 @@ } }, "node_modules/@firebase/auth-interop-types": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.2.tgz", - "integrity": "sha512-k3NA28Jfoo0+o391bFjoV9X5QLnUL1WbLhZZRbTQhZdmdGYJfX8ixtNNlHsYQ94bwG0QRbsmvkzDnzuhHrV11w==" + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.3.tgz", + "integrity": "sha512-Fc9wuJGgxoxQeavybiuwgyi+0rssr76b+nHpj+eGhXFYAdudMWyfBHvFL/I5fEHniUM/UQdFzi9VXJK2iZF7FQ==" }, "node_modules/@firebase/auth-types": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.12.1.tgz", - "integrity": "sha512-B3dhiWRWf/njWosx4zdhSEoD4WHJmr4zbnBw6t20mRG/IZ4u0rWUBlMP1vFjhMstKIow1XmoGhTwD65X5ZXLjw==", + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.12.2.tgz", + "integrity": "sha512-qsEBaRMoGvHO10unlDJhaKSuPn4pyoTtlQuP1ghZfzB6rNQPuhp/N/DcFZxm9i4v0SogjCbf9reWupwIvfmH6w==", "peerDependencies": { "@firebase/app-types": "0.x", "@firebase/util": "1.x" } }, "node_modules/@firebase/component": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.6.tgz", - "integrity": "sha512-pp7sWqHmAAlA3os6ERgoM3k5Cxff510M9RLXZ9Mc8KFKMBc2ct3RkZTWUF7ixJNvMiK/iNgRLPDrLR2gtRJ9iQ==", + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.7.tgz", + "integrity": "sha512-baH1AA5zxfaz4O8w0vDwETByrKTQqB5CDjRls79Sa4eAGAoERw4Tnung7XbMl3jbJ4B/dmmtsMrdki0KikwDYA==", "dependencies": { - "@firebase/util": "1.9.5", + "@firebase/util": "1.9.6", "tslib": "^2.1.0" } }, "node_modules/@firebase/database": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.4.tgz", - "integrity": "sha512-k84cXh+dtpzvY6yOhfyr1B+I1vjvSMtmlqotE0lTNVylc8m5nmOohjzpTLEQDrBWvwACX/VP5fEyajAdmnOKqA==", - "dependencies": { - "@firebase/app-check-interop-types": "0.3.1", - "@firebase/auth-interop-types": "0.2.2", - "@firebase/component": "0.6.6", - "@firebase/logger": "0.4.1", - "@firebase/util": "1.9.5", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.5.tgz", + "integrity": "sha512-cAfwBqMQuW6HbhwI3Cb/gDqZg7aR0OmaJ85WUxlnoYW2Tm4eR0hFl5FEijI3/gYPUiUcUPQvTkGV222VkT7KPw==", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.2", + "@firebase/auth-interop-types": "0.2.3", + "@firebase/component": "0.6.7", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.9.6", "faye-websocket": "0.11.4", "tslib": "^2.1.0" } }, "node_modules/@firebase/database-compat": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-1.0.4.tgz", - "integrity": "sha512-GEEDAvsSMAkqy0BIFSVtFzoOIIcKHFfDM4aXHtWL/JCaNn4OOjH7td73jDfN3ALvpIN4hQki0FcxQ89XjqaTjQ==", - "dependencies": { - "@firebase/component": "0.6.6", - "@firebase/database": "1.0.4", - "@firebase/database-types": "1.0.2", - "@firebase/logger": "0.4.1", - "@firebase/util": "1.9.5", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-1.0.5.tgz", + "integrity": "sha512-NDSMaDjQ+TZEMDMmzJwlTL05kh1+0Y84C+kVMaOmNOzRGRM7VHi29I6YUhCetXH+/b1Wh4ZZRyp1CuWkd8s6hg==", + "dependencies": { + "@firebase/component": "0.6.7", + "@firebase/database": "1.0.5", + "@firebase/database-types": "1.0.3", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.9.6", "tslib": "^2.1.0" } }, "node_modules/@firebase/database-types": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.2.tgz", - "integrity": "sha512-JRigr5JNLEHqOkI99tAGHDZF47469/cJz1tRAgGs8Feh+3ZmQy/vVChSqwMp2DuVUGp9PlmGsNSlpINJ/hDuIA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.3.tgz", + "integrity": "sha512-39V/Riv2R3O/aUjYKh0xypj7NTNXNAK1bcgY5Kx+hdQPRS/aPTS8/5c0CGFYKgVuFbYlnlnhrCTYsh2uNhGwzA==", "dependencies": { - "@firebase/app-types": "0.9.1", - "@firebase/util": "1.9.5" + "@firebase/app-types": "0.9.2", + "@firebase/util": "1.9.6" } }, "node_modules/@firebase/firestore": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.6.0.tgz", - "integrity": "sha512-mul4L2Bp+Q5R5mV1nf5Z6OmsHHFid7uSEeR8oTM89p5G0nMam4GKaBAvgLSxwsXQbyy2WW9nNnuAWLfD7HDxFA==", - "dependencies": { - "@firebase/component": "0.6.6", - "@firebase/logger": "0.4.1", - "@firebase/util": "1.9.5", - "@firebase/webchannel-wrapper": "0.10.6", + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.6.3.tgz", + "integrity": "sha512-d/+N2iUsiJ/Dc7fApdpdmmTXzwuTCromsdA1lKwYfZtMIOd1fI881NSLwK2wV4I38wkLnvfKJUV6WpU1f3/ONg==", + "dependencies": { + "@firebase/component": "0.6.7", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.9.6", + "@firebase/webchannel-wrapper": "1.0.0", "@grpc/grpc-js": "~1.9.0", "@grpc/proto-loader": "^0.7.8", "tslib": "^2.1.0", @@ -710,14 +711,14 @@ } }, "node_modules/@firebase/firestore-compat": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.29.tgz", - "integrity": "sha512-ylBtvIQo2Caj1qXUd7ksj8xcL9l1b/F2Et6rq0smogPvl5CGvrv49xC5wVLJDmkMmH7IBEJb26KKC/RW1XYymg==", - "dependencies": { - "@firebase/component": "0.6.6", - "@firebase/firestore": "4.6.0", - "@firebase/firestore-types": "3.0.1", - "@firebase/util": "1.9.5", + "version": "0.3.32", + "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.32.tgz", + "integrity": "sha512-at71mwK7a/mUXH0OgyY0+gUzedm/EUydDFYSFsBoO8DYowZ23Mgd6P4Rzq/Ll3zI/3xJN7LGe7Qp4iE/V/3Arg==", + "dependencies": { + "@firebase/component": "0.6.7", + "@firebase/firestore": "4.6.3", + "@firebase/firestore-types": "3.0.2", + "@firebase/util": "1.9.6", "tslib": "^2.1.0" }, "peerDependencies": { @@ -725,24 +726,24 @@ } }, "node_modules/@firebase/firestore-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.1.tgz", - "integrity": "sha512-mVhPcHr5FICjF67m6JHgj+XRvAz/gZ62xifeGfcm00RFl6tNKfCzCfKeyB2BDIEc9dUnEstkmIXlmLIelOWoaA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.2.tgz", + "integrity": "sha512-wp1A+t5rI2Qc/2q7r2ZpjUXkRVPtGMd6zCLsiWurjsQpqPgFin3AhNibKcIzoF2rnToNa/XYtyWXuifjOOwDgg==", "peerDependencies": { "@firebase/app-types": "0.x", "@firebase/util": "1.x" } }, "node_modules/@firebase/functions": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.11.4.tgz", - "integrity": "sha512-FeMpXtlZG8hnxUauI5J8BSmIbY/Gcv7UVlByxHuHmGxxeS8mJPuAdIxPLUBNtV/naf+MeimIPcpPMslYr6tN6w==", - "dependencies": { - "@firebase/app-check-interop-types": "0.3.1", - "@firebase/auth-interop-types": "0.2.2", - "@firebase/component": "0.6.6", - "@firebase/messaging-interop-types": "0.2.1", - "@firebase/util": "1.9.5", + "version": "0.11.5", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.11.5.tgz", + "integrity": "sha512-qrHJ+l62mZiU5UZiVi84t/iLXZlhRuSvBQsa2qvNLgPsEWR7wdpWhRmVdB7AU8ndkSHJjGlMICqrVnz47sgU7Q==", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.2", + "@firebase/auth-interop-types": "0.2.3", + "@firebase/component": "0.6.7", + "@firebase/messaging-interop-types": "0.2.2", + "@firebase/util": "1.9.6", "tslib": "^2.1.0", "undici": "5.28.4" }, @@ -751,14 +752,14 @@ } }, "node_modules/@firebase/functions-compat": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.10.tgz", - "integrity": "sha512-2Yidp6Dgf2k8LqJDQUTqdYFdf4ySNmZ71yeDX4lThby1HRMww+Y3nN98YaM6hHarZX3PUfaMUiMBZMHCRRT2IA==", - "dependencies": { - "@firebase/component": "0.6.6", - "@firebase/functions": "0.11.4", - "@firebase/functions-types": "0.6.1", - "@firebase/util": "1.9.5", + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.11.tgz", + "integrity": "sha512-Qn+ts/M6Lj2/6i1cp5V5TRR+Hi9kyXyHbo+w9GguINJ87zxrCe6ulx3TI5AGQkoQa8YFHUhT3DMGmLFiJjWTSQ==", + "dependencies": { + "@firebase/component": "0.6.7", + "@firebase/functions": "0.11.5", + "@firebase/functions-types": "0.6.2", + "@firebase/util": "1.9.6", "tslib": "^2.1.0" }, "peerDependencies": { @@ -766,17 +767,17 @@ } }, "node_modules/@firebase/functions-types": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.1.tgz", - "integrity": "sha512-DirqgTXSBzyKsQwcKnx/YdGMaRdJhywnThrINP+Iog8QfQnrL7aprTXHDFHlpZEMwykS54YRk53xzz7j396QXQ==" + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.2.tgz", + "integrity": "sha512-0KiJ9lZ28nS2iJJvimpY4nNccV21rkQyor5Iheu/nq8aKXJqtJdeSlZDspjPSBBiHRzo7/GMUttegnsEITqR+w==" }, "node_modules/@firebase/installations": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.6.tgz", - "integrity": "sha512-dNGRGoHmstgEJqh9Kzk22fR2ZrVBH1JWliaL6binQ6pIzlWscreHNczzJDgOKoVT0PjWTrAmh/azztiX/e2uTw==", + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.7.tgz", + "integrity": "sha512-i6iGoXRu5mX4rTsiMSSKrgh9pSEzD4hwBEzRh5kEhOTr8xN/wvQcCPZDSMVYKwM2XyCPBLVq0JzjyerwL0Rihg==", "dependencies": { - "@firebase/component": "0.6.6", - "@firebase/util": "1.9.5", + "@firebase/component": "0.6.7", + "@firebase/util": "1.9.6", "idb": "7.1.1", "tslib": "^2.1.0" }, @@ -785,14 +786,14 @@ } }, "node_modules/@firebase/installations-compat": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.6.tgz", - "integrity": "sha512-uxBAt2WsuEMT5dalA/1O+Uyi9DS25zKHgIPdrQ7KO1ZUdBURiGScIyjdhIM/7NMSvHGYugK4PUVdK9NFIffeiw==", - "dependencies": { - "@firebase/component": "0.6.6", - "@firebase/installations": "0.6.6", - "@firebase/installations-types": "0.5.1", - "@firebase/util": "1.9.5", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.7.tgz", + "integrity": "sha512-RPcbD+3nqHbnhVjIOpWK2H5qzZ8pAAAScceiWph0VNTqpKyPQ5tDcp4V5fS0ELpfgsHYvroMLDKfeHxpfvm8cw==", + "dependencies": { + "@firebase/component": "0.6.7", + "@firebase/installations": "0.6.7", + "@firebase/installations-types": "0.5.2", + "@firebase/util": "1.9.6", "tslib": "^2.1.0" }, "peerDependencies": { @@ -800,30 +801,30 @@ } }, "node_modules/@firebase/installations-types": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.1.tgz", - "integrity": "sha512-OyREnRTfe2wIWTrzCz65ajyo4lFm6VgbeVqMMP+3GJLfCtNvY9VXkmqs3WFEsyYezzdcRqOt39FynZoLlkO+cQ==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.2.tgz", + "integrity": "sha512-que84TqGRZJpJKHBlF2pkvc1YcXrtEDOVGiDjovP/a3s6W4nlbohGXEsBJo0JCeeg/UG9A+DEZVDUV9GpklUzA==", "peerDependencies": { "@firebase/app-types": "0.x" } }, "node_modules/@firebase/logger": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.1.tgz", - "integrity": "sha512-tTIixB5UJbG9ZHSGZSZdX7THr3KWOLrejZ9B7jYsm6fpwgRNngKznQKA2wgYVyvBc1ta7dGFh9NtJ8n7qfiYIw==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.2.tgz", + "integrity": "sha512-Q1VuA5M1Gjqrwom6I6NUU4lQXdo9IAQieXlujeHZWvRt1b7qQ0KwBaNAjgxG27jgF9/mUwsNmO8ptBCGVYhB0A==", "dependencies": { "tslib": "^2.1.0" } }, "node_modules/@firebase/messaging": { - "version": "0.12.8", - "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.8.tgz", - "integrity": "sha512-FbCTNhv5DUBo8It+Wj3XbKM1xf3PeoHsHk8PjMWBNm0yP+LL8Jhd3ejRsukEYdysTMvgxY4sU5Cs5YNTK44qTQ==", - "dependencies": { - "@firebase/component": "0.6.6", - "@firebase/installations": "0.6.6", - "@firebase/messaging-interop-types": "0.2.1", - "@firebase/util": "1.9.5", + "version": "0.12.9", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.9.tgz", + "integrity": "sha512-IH+JJmzbFGZXV3+TDyKdqqKPVfKRqBBg2BfYYOy7cm7J+SwV+uJMe8EnDKYeQLEQhtpwciPfJ3qQXJs2lbxDTw==", + "dependencies": { + "@firebase/component": "0.6.7", + "@firebase/installations": "0.6.7", + "@firebase/messaging-interop-types": "0.2.2", + "@firebase/util": "1.9.6", "idb": "7.1.1", "tslib": "^2.1.0" }, @@ -832,13 +833,13 @@ } }, "node_modules/@firebase/messaging-compat": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.8.tgz", - "integrity": "sha512-/2ibL9u64jn76g67qjAZutVnPTV6euu0z3BvCjcqlNbMMdtoyNjyHOBRe/D7eVcrRt0uB4rTPnjr3A6sVKdjuA==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.9.tgz", + "integrity": "sha512-5jN6wyhwPgBH02zOtmmoOeyfsmoD7ty48D1m0vVPsFg55RqN2Z3Q9gkZ5GmPklFPjTPLcxB1ObcHOZvThTkm7g==", "dependencies": { - "@firebase/component": "0.6.6", - "@firebase/messaging": "0.12.8", - "@firebase/util": "1.9.5", + "@firebase/component": "0.6.7", + "@firebase/messaging": "0.12.9", + "@firebase/util": "1.9.6", "tslib": "^2.1.0" }, "peerDependencies": { @@ -846,19 +847,19 @@ } }, "node_modules/@firebase/messaging-interop-types": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.1.tgz", - "integrity": "sha512-jfGJ7Jc32BDHXvXHyXi34mVLzZY8X0t929DTMwz7Tj2Hc40Zuzx8VRCIPLRrRUyvBrJCd5EpIcQgCygXhtaN1A==" + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.2.tgz", + "integrity": "sha512-l68HXbuD2PPzDUOFb3aG+nZj5KA3INcPwlocwLZOzPp9rFM9yeuI9YLl6DQfguTX5eAGxO0doTR+rDLDvQb5tA==" }, "node_modules/@firebase/performance": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.6.6.tgz", - "integrity": "sha512-UOUHhvj2GJcjyJewdX1ShnON0/eqTswHvYzzQPC4nrIuMFvHwMGk8NpCaqh7JZmpaxh9AMr6kM+M/p37DrKWXA==", - "dependencies": { - "@firebase/component": "0.6.6", - "@firebase/installations": "0.6.6", - "@firebase/logger": "0.4.1", - "@firebase/util": "1.9.5", + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.6.7.tgz", + "integrity": "sha512-d+Q4ltjdJZqjzcdms5i0UC9KLYX7vKGcygZ+7zHA/Xk+bAbMD2CPU0nWTnlNFWifZWIcXZ/2mAMvaGMW3lypUA==", + "dependencies": { + "@firebase/component": "0.6.7", + "@firebase/installations": "0.6.7", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.9.6", "tslib": "^2.1.0" }, "peerDependencies": { @@ -866,15 +867,15 @@ } }, "node_modules/@firebase/performance-compat": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.6.tgz", - "integrity": "sha512-JSGdNNHBAMRTocGpN+m+7tk+9rulBcwuG+Ejw/ooDj45FGcON1Eymxh/qbe5M6Dlj5P1ClbkHLj4yf7MiCHOag==", - "dependencies": { - "@firebase/component": "0.6.6", - "@firebase/logger": "0.4.1", - "@firebase/performance": "0.6.6", - "@firebase/performance-types": "0.2.1", - "@firebase/util": "1.9.5", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.7.tgz", + "integrity": "sha512-cb8ge/5iTstxfIGW+iiY+7l3FtN8gobNh9JSQNZgLC9xmcfBYWEs8IeEWMI6S8T+At0oHc3lv+b2kpRMUWr8zQ==", + "dependencies": { + "@firebase/component": "0.6.7", + "@firebase/logger": "0.4.2", + "@firebase/performance": "0.6.7", + "@firebase/performance-types": "0.2.2", + "@firebase/util": "1.9.6", "tslib": "^2.1.0" }, "peerDependencies": { @@ -882,19 +883,19 @@ } }, "node_modules/@firebase/performance-types": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.1.tgz", - "integrity": "sha512-kQ8pEr4d6ArhPoYrngcFlEJMNWMdEZTpvMAttWH0C2vegBgj47cm6xXFy9+0j27OBhOIiPn48Z+2WE2XNu33CQ==" + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.2.tgz", + "integrity": "sha512-gVq0/lAClVH5STrIdKnHnCo2UcPLjJlDUoEB/tB4KM+hAeHUxWKnpT0nemUPvxZ5nbdY/pybeyMe8Cs29gEcHA==" }, "node_modules/@firebase/remote-config": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.4.6.tgz", - "integrity": "sha512-qtanFS+AX5k/7e/+Azf27Hq4reX28QsUvRcYWyS5cOaRMS9jtll4MK4winWmzX8MdJY637nFzIx43PlMKVnaKw==", - "dependencies": { - "@firebase/component": "0.6.6", - "@firebase/installations": "0.6.6", - "@firebase/logger": "0.4.1", - "@firebase/util": "1.9.5", + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.4.7.tgz", + "integrity": "sha512-5oPNrPFLsbsjpq0lUEIXoDF2eJK7vAbyXe/DEuZQxnwJlfR7aQbtUlEkRgQWcicXpyDmAmDLo7q7lDbCYa6CpA==", + "dependencies": { + "@firebase/component": "0.6.7", + "@firebase/installations": "0.6.7", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.9.6", "tslib": "^2.1.0" }, "peerDependencies": { @@ -902,15 +903,15 @@ } }, "node_modules/@firebase/remote-config-compat": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.6.tgz", - "integrity": "sha512-cFdpmN/rzDhm4pbk0WpOzK9JQ9I1ZhXzhtYbKRBwUag3pG1odEfIORygMDCGQniPpcae/QGXho4srJHfoijKuw==", - "dependencies": { - "@firebase/component": "0.6.6", - "@firebase/logger": "0.4.1", - "@firebase/remote-config": "0.4.6", - "@firebase/remote-config-types": "0.3.1", - "@firebase/util": "1.9.5", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.7.tgz", + "integrity": "sha512-Fq0oneQ4SluLnfr5/HfzRS1TZf1ANj1rWbCCW3+oC98An3nE+sCdp+FSuHsEVNwgMg4Tkwx9Oom2lkKeU+Vn+w==", + "dependencies": { + "@firebase/component": "0.6.7", + "@firebase/logger": "0.4.2", + "@firebase/remote-config": "0.4.7", + "@firebase/remote-config-types": "0.3.2", + "@firebase/util": "1.9.6", "tslib": "^2.1.0" }, "peerDependencies": { @@ -918,17 +919,17 @@ } }, "node_modules/@firebase/remote-config-types": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.3.1.tgz", - "integrity": "sha512-PgmfUugcJAinPLsJlYcBbNZe7KE2omdQw1WCT/z46nKkNVGkuHdVFSq54s3wiFa9BoHmLZ01u4hGXIhm6MdLOw==" + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.3.2.tgz", + "integrity": "sha512-0BC4+Ud7y2aPTyhXJTMTFfrGGLqdYXrUB9sJVAB8NiqJswDTc4/2qrE/yfUbnQJhbSi6ZaTTBKyG3n1nplssaA==" }, "node_modules/@firebase/storage": { - "version": "0.12.4", - "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.12.4.tgz", - "integrity": "sha512-HcmUcp2kSSr5cHkIqFrgUW+i20925EEjkXepQxgBcI2Vx0cyqshr8iETtGow2+cMBFeY8H2swsKKabOKAjIwlQ==", + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.12.5.tgz", + "integrity": "sha512-nGWBOGFNr10j0LA4NJ3/Yh3us/lb0Q1xSIKZ38N6FcS+vY54nqJ7k3zE3PENregHC8+8txRow++A568G3v8hOA==", "dependencies": { - "@firebase/component": "0.6.6", - "@firebase/util": "1.9.5", + "@firebase/component": "0.6.7", + "@firebase/util": "1.9.6", "tslib": "^2.1.0", "undici": "5.28.4" }, @@ -937,14 +938,14 @@ } }, "node_modules/@firebase/storage-compat": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.7.tgz", - "integrity": "sha512-pTlNAm8/QPN7vhYRyd5thr2ouCykP+wIFXHY1AV42WTrk98sTGdIlt/tusHzmrH4mJ34MPaICS0cn2lYikiq8w==", - "dependencies": { - "@firebase/component": "0.6.6", - "@firebase/storage": "0.12.4", - "@firebase/storage-types": "0.8.1", - "@firebase/util": "1.9.5", + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.8.tgz", + "integrity": "sha512-qDfY9kMb6Ch2hZb40sBjDQ8YPxbjGOxuT+gU1Z0iIVSSpSX0f4YpGJCypUXiA0T11n6InCXB+T/Dknh2yxVTkg==", + "dependencies": { + "@firebase/component": "0.6.7", + "@firebase/storage": "0.12.5", + "@firebase/storage-types": "0.8.2", + "@firebase/util": "1.9.6", "tslib": "^2.1.0" }, "peerDependencies": { @@ -952,26 +953,45 @@ } }, "node_modules/@firebase/storage-types": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.1.tgz", - "integrity": "sha512-yj0vypPT9UbbfYYwzpXPYchnjWqCADcTbGNawAIebww8rnQYPGbESYTKQdFRPXiLspYPB7xCHTXThmMJuvDcsQ==", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.2.tgz", + "integrity": "sha512-0vWu99rdey0g53lA7IShoA2Lol1jfnPovzLDUBuon65K7uKG9G+L5uO05brD9pMw+l4HRFw23ah3GwTGpEav6g==", "peerDependencies": { "@firebase/app-types": "0.x", "@firebase/util": "1.x" } }, "node_modules/@firebase/util": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.9.5.tgz", - "integrity": "sha512-PP4pAFISDxsf70l3pEy34Mf3GkkUcVQ3MdKp6aSVb7tcpfUQxnsdV7twDd8EkfB6zZylH6wpUAoangQDmCUMqw==", + "version": "1.9.6", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.9.6.tgz", + "integrity": "sha512-IBr1MZbp4d5MjBCXL3TW1dK/PDXX4yOGbiwRNh1oAbE/+ci5Uuvy9KIrsFYY80as1I0iOaD5oOMA9Q8j4TJWcw==", "dependencies": { "tslib": "^2.1.0" } }, + "node_modules/@firebase/vertexai-preview": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@firebase/vertexai-preview/-/vertexai-preview-0.0.1.tgz", + "integrity": "sha512-N8m9Xr0YZKy0t9SpQDuHrL2ppEAT/iqf88Y/O00QNA/Td/BMCL8sJ0c+Savh1TVrqh0rNp9n6HkZ39e/O5mwhA==", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.2", + "@firebase/component": "0.6.7", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.9.6", + "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": "0.10.6", - "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.10.6.tgz", - "integrity": "sha512-EnfRJvrnzkHwN3BPMCayCFT5lCqInzg3RdlRsDjDvB1EJli6Usj26T6lJ67BU2UcYXBS5xcp1Wj4+zRzj2NaZg==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.0.tgz", + "integrity": "sha512-zuWxyfXNbsKbm96HhXzainONPFqRcoZblQ++e9cAIGUuHfl2cFSBzW01jtesqWG/lqaUyX3H8O1y9oWboGNQBA==" }, "node_modules/@floating-ui/core": { "version": "1.6.0", @@ -1020,13 +1040,13 @@ } }, "node_modules/@grpc/proto-loader": { - "version": "0.7.12", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.12.tgz", - "integrity": "sha512-DCVwMxqYzpUCiDMl7hQ384FqP4T3DbNpXU8pt681l3UWCip1WUiD5JrkImUwCB9a7f2cq4CUTmi5r/xIMRPY1Q==", + "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==", "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", - "protobufjs": "^7.2.4", + "protobufjs": "^7.2.5", "yargs": "^17.7.2" }, "bin": { @@ -3947,36 +3967,37 @@ } }, "node_modules/firebase": { - "version": "10.11.0", - "resolved": "https://registry.npmjs.org/firebase/-/firebase-10.11.0.tgz", - "integrity": "sha512-stWqB0cmUBFidaWCgDV6on6uQyAV8jFe9XdOp0Y1GRM/LUn0MjPSgW06Tc3pFlaefQ+WTLR/CNwL+0qGhxDLIA==", - "dependencies": { - "@firebase/analytics": "0.10.2", - "@firebase/analytics-compat": "0.2.8", - "@firebase/app": "0.10.1", - "@firebase/app-check": "0.8.3", - "@firebase/app-check-compat": "0.3.10", - "@firebase/app-compat": "0.2.31", - "@firebase/app-types": "0.9.1", - "@firebase/auth": "1.7.1", - "@firebase/auth-compat": "0.5.6", - "@firebase/database": "1.0.4", - "@firebase/database-compat": "1.0.4", - "@firebase/firestore": "4.6.0", - "@firebase/firestore-compat": "0.3.29", - "@firebase/functions": "0.11.4", - "@firebase/functions-compat": "0.3.10", - "@firebase/installations": "0.6.6", - "@firebase/installations-compat": "0.2.6", - "@firebase/messaging": "0.12.8", - "@firebase/messaging-compat": "0.2.8", - "@firebase/performance": "0.6.6", - "@firebase/performance-compat": "0.2.6", - "@firebase/remote-config": "0.4.6", - "@firebase/remote-config-compat": "0.2.6", - "@firebase/storage": "0.12.4", - "@firebase/storage-compat": "0.3.7", - "@firebase/util": "1.9.5" + "version": "10.12.1", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-10.12.1.tgz", + "integrity": "sha512-B/R3BX26OAgreA64JN0lYspYRHMS36E19/Sv9WsyQu1RqPGBzWkBlt1RW6+38SdtMDlAnk3ibKL/SRSQHb1xRw==", + "dependencies": { + "@firebase/analytics": "0.10.4", + "@firebase/analytics-compat": "0.2.10", + "@firebase/app": "0.10.4", + "@firebase/app-check": "0.8.4", + "@firebase/app-check-compat": "0.3.11", + "@firebase/app-compat": "0.2.34", + "@firebase/app-types": "0.9.2", + "@firebase/auth": "1.7.3", + "@firebase/auth-compat": "0.5.8", + "@firebase/database": "1.0.5", + "@firebase/database-compat": "1.0.5", + "@firebase/firestore": "4.6.3", + "@firebase/firestore-compat": "0.3.32", + "@firebase/functions": "0.11.5", + "@firebase/functions-compat": "0.3.11", + "@firebase/installations": "0.6.7", + "@firebase/installations-compat": "0.2.7", + "@firebase/messaging": "0.12.9", + "@firebase/messaging-compat": "0.2.9", + "@firebase/performance": "0.6.7", + "@firebase/performance-compat": "0.2.7", + "@firebase/remote-config": "0.4.7", + "@firebase/remote-config-compat": "0.2.7", + "@firebase/storage": "0.12.5", + "@firebase/storage-compat": "0.3.8", + "@firebase/util": "1.9.6", + "@firebase/vertexai-preview": "0.0.1" } }, "node_modules/flat-cache": { @@ -5952,9 +5973,9 @@ } }, "node_modules/protobufjs": { - "version": "7.2.6", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.6.tgz", - "integrity": "sha512-dgJaEDDL6x8ASUZ1YqWciTRrdOuYNzoOf27oHNfdyvKqHr5i0FV7FSLU+aIeFjyFgVxrpTOtQUi0BLLBymZaBw==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.3.0.tgz", + "integrity": "sha512-YWD03n3shzV9ImZRX3ccbjqLxj7NokGN0V/ESiBV5xWqrommYHYiihuIyavq03pWSGqlyvYUFmfoMKd+1rPA/g==", "hasInstallScript": true, "dependencies": { "@protobufjs/aspromise": "^1.1.2", @@ -6047,6 +6068,15 @@ "react": "^18.2.0" } }, + "node_modules/react-firebase-hooks": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/react-firebase-hooks/-/react-firebase-hooks-5.1.1.tgz", + "integrity": "sha512-y2UpWs82xs+39q5Rc/wq316ca52QsC0n8m801V+yM4IC4hbfOL4yQPVSh7w+ydstdvjN9F+lvs1WrO2VYxpmdA==", + "peerDependencies": { + "firebase": ">= 9.0.0", + "react": ">= 16.8.0" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 31167bff..8fcdeea2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,9 +19,9 @@ "@mui/icons-material": "^5.15.12", "@mui/material": "^5.15.12", "@mui/x-data-grid": "^7.1.1", + "@paypal/react-paypal-js": "^8.2.0", "envalid": "^8.0.0", "firebase": "^10.11.0", - "@paypal/react-paypal-js": "^8.2.0", "html2canvas": "^1.4.1", "html2pdf.js": "^0.9.3", "jspdf": "^2.5.1", @@ -30,6 +30,7 @@ "nodemailer": "^6.9.11", "react": "^18", "react-dom": "^18", + "react-firebase-hooks": "^5.1.1", "react-material-symbols": "^4.3.1" }, "devDependencies": { diff --git a/frontend/public/Checker.png b/frontend/public/Checker.png new file mode 100644 index 00000000..8c3abb01 Binary files /dev/null and b/frontend/public/Checker.png differ diff --git a/frontend/public/admin/loginLogo.svg b/frontend/public/admin/loginLogo.svg new file mode 100644 index 00000000..789761df --- /dev/null +++ b/frontend/public/admin/loginLogo.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/frontend/src/app/(web app)/layout.tsx b/frontend/src/app/(web app)/layout.tsx index 60d84462..5483266c 100644 --- a/frontend/src/app/(web app)/layout.tsx +++ b/frontend/src/app/(web app)/layout.tsx @@ -1,7 +1,7 @@ import Footer from "@/components/Footer"; import HeaderBar from "@/components/HeaderBar"; import HeaderBarSpace from "@/components/HeaderBarSpace"; -import "./globals.css"; +import "../globals.css"; import "react-material-symbols/rounded"; export default function Layout({ children }: { children: React.ReactNode }) { diff --git a/frontend/src/app/(web app)/page.tsx b/frontend/src/app/(web app)/page.tsx index 0b55c2bb..c4d8e11c 100644 --- a/frontend/src/app/(web app)/page.tsx +++ b/frontend/src/app/(web app)/page.tsx @@ -1,9 +1,8 @@ "use client"; import React, { useEffect, useState } from "react"; +import "../globals.css"; import { getPageText } from "../../api/pageeditor"; - -import "./globals.css"; import BackgroundHeader from "../../components/BackgroundHeader"; import styles from "./page.module.css"; diff --git a/frontend/src/app/admin/dashboard/page.tsx b/frontend/src/app/admin/dashboard/page.tsx index e497cabc..dd2be1cd 100644 --- a/frontend/src/app/admin/dashboard/page.tsx +++ b/frontend/src/app/admin/dashboard/page.tsx @@ -1,4 +1,5 @@ -// Admin Dashboard landing page +"use client"; + import styles from "./page.module.css"; import DashboardCard from "@/components/DashboardCard"; @@ -19,14 +20,12 @@ export default function Dashboard() { title="Page Editor" last_updated="Month XX, XXXX, XX:XX" /> - - { +export type FirebaseServices = { auth: Auth; storage: FirebaseStorage }; + +/** + * Initialize Firebase with the Firebase config + * @returns FirebaseServices array instance + */ +export const initFirebase = (): FirebaseServices => { if (!env.NEXT_PUBLIC_FIREBASE_SETTINGS) { - throw new Error("Cannot get firebase settings"); + throw new Error("Cannot get firebase settings. " + process.env.NEXT_PUBLIC_FIREBASE_SETTINGS); } const firebaseConfig = env.NEXT_PUBLIC_FIREBASE_SETTINGS as FirebaseOptions; - const app = initializeApp(firebaseConfig); - const auth = getAuth(app); + const app: FirebaseApp = initializeApp(firebaseConfig); + const auth: Auth = getAuth(app); + const storage: FirebaseStorage = getStorage(app); + + return { auth, storage }; +}; + +/** + * Uses Firebase to login with email and password + * @param auth Firebase Auth instance. In components get with useFirebase() + * @param email email address + * @param password password + * @returns boolean true if sign in successful, false otherwise + */ +export const firebaseSignIn = async (auth: Auth, email: string, password: string) => { + try { + await signInWithEmailAndPassword(auth, email, password); + return true; + } catch (error) { + return false; + } +}; - return { app, auth }; +/** + * Sign out using Firebase and delete client data holding the JWT + * @param auth Firebase Auth instance. In components get with useFirebase() + */ +export const firebaseSignOut = async (auth: Auth) => { + try { + await signOut(auth); + } catch (error) { + alert(error); + } +}; + +/** + * Get's the firebase config for use with reactfire components + * + */ +export const getFirebaseConfig = (): FirebaseOptions => { + if (!env.NEXT_PUBLIC_FIREBASE_SETTINGS) { + throw new Error("Cannot get firebase settings"); + } + return env.NEXT_PUBLIC_FIREBASE_SETTINGS as FirebaseOptions; }; diff --git a/frontend/src/app/admin/firebase/firebaseProvider.tsx b/frontend/src/app/admin/firebase/firebaseProvider.tsx new file mode 100644 index 00000000..03e17c1c --- /dev/null +++ b/frontend/src/app/admin/firebase/firebaseProvider.tsx @@ -0,0 +1,27 @@ +"use client"; + +import { Auth } from "firebase/auth"; +import { FirebaseStorage } from "firebase/storage"; +import React, { FC, ReactNode, createContext, useContext } from "react"; + +import { FirebaseServices, initFirebase } from "./firebase"; + +const AuthContext = createContext(undefined); +const StorageContext = createContext(undefined); + +let firebaseServices: FirebaseServices | undefined; + +export const FirebaseProvider: FC<{ children: ReactNode }> = ({ children }) => { + if (firebaseServices === undefined) { + firebaseServices = initFirebase(); + } + + return ( + + {children} + + ); +}; + +export const useAuth = () => useContext(AuthContext); +export const useStorage = () => useContext(StorageContext); diff --git a/frontend/src/app/admin/layout.tsx b/frontend/src/app/admin/layout.tsx index 36a5c964..19e51bc4 100644 --- a/frontend/src/app/admin/layout.tsx +++ b/frontend/src/app/admin/layout.tsx @@ -1,13 +1,31 @@ +"use client"; + +import { usePathname } from "next/navigation"; + +import { FirebaseProvider } from "./firebase/firebaseProvider"; + +import "../globals.css"; import HeaderBarSpace from "@/components/HeaderBarSpace"; import NavigationBar from "@/components/NavigationBar"; -import "./globals.css"; +import PrivatePage from "@/components/admin/PrivatePage"; export default function AdminLayout({ children }: { children: React.ReactNode }) { + const pathname = usePathname(); + const isLoginPage = pathname === "/admin"; + return ( -
- - - {children} -
+ + {!isLoginPage ? ( + +
+ + + {children} +
+
+ ) : ( + children + )} +
); } diff --git a/frontend/src/app/admin/page.module.css b/frontend/src/app/admin/page.module.css index deda61db..d52ad143 100644 --- a/frontend/src/app/admin/page.module.css +++ b/frontend/src/app/admin/page.module.css @@ -1 +1,2 @@ /* Styles for admin login screen */ +@import url("https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&family=Roboto+Slab:wght@100..900&display=swap"); diff --git a/frontend/src/app/admin/page.tsx b/frontend/src/app/admin/page.tsx index 2a14d2bb..f0da5c4b 100644 --- a/frontend/src/app/admin/page.tsx +++ b/frontend/src/app/admin/page.tsx @@ -2,6 +2,8 @@ import React from "react"; +import LoginSection from "@/components/admin/LoginSection"; + export default function Login() { - return
Login
; + return ; } diff --git a/frontend/src/app/admin/util/validateEnv.ts b/frontend/src/app/admin/util/validateEnv.ts index 46df19c8..f362f9e8 100644 --- a/frontend/src/app/admin/util/validateEnv.ts +++ b/frontend/src/app/admin/util/validateEnv.ts @@ -4,7 +4,7 @@ */ import { cleanEnv } from "envalid"; -import { json, str } from "envalid/dist/validators"; +import { str, json } from "envalid/dist/validators"; /** * NextJS only allows the frontend to access environment variables if they start with @@ -14,9 +14,13 @@ export default cleanEnv( { NEXT_PUBLIC_BACKEND_URL: process.env.NEXT_PUBLIC_BACKEND_URL, NEXT_PUBLIC_FIREBASE_SETTINGS: process.env.NEXT_PUBLIC_FIREBASE_SETTINGS, + NEXT_PUBLIC_PAYPAL_CLIENT_SECRET: process.env.NEXT_PUBLIC_PAYPAL_CLIENT_SECRET, + NEXT_PUBLIC_PAYPAL_CLIENT_ID: process.env.NEXT_PUBLIC_PAYPAL_CLIENT_ID, }, { NEXT_PUBLIC_BACKEND_URL: str(), // URL of our backend - NEXT_PUBLIC_FIREBASE_SETTINGS: json(), // Firebase settings for frontend, stored as a JSON string + NEXT_PUBLIC_FIREBASE_SETTINGS: json(), // Firebase settings for frontend + NEXT_PUBLIC_PAYPAL_CLIENT_SECRET: str(), + NEXT_PUBLIC_PAYPAL_CLIENT_ID: str(), }, ); diff --git a/frontend/src/app/(web app)/globals.css b/frontend/src/app/globals.css similarity index 100% rename from frontend/src/app/(web app)/globals.css rename to frontend/src/app/globals.css diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 19fcfc20..6d3e80ff 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -25,14 +25,7 @@ const robotoSlab = Roboto_Slab({ export default function RootLayout({ children }: { children: React.ReactNode }) { return ( - + {children} ); diff --git a/frontend/src/components/NavigationBar.tsx b/frontend/src/components/NavigationBar.tsx index 9e84cfd2..38b18306 100644 --- a/frontend/src/components/NavigationBar.tsx +++ b/frontend/src/components/NavigationBar.tsx @@ -4,6 +4,7 @@ import Link from "next/link"; import React, { useEffect, useState } from "react"; import styles from "./NavigationBar.module.css"; +import UserIcon from "./UserIcon"; const NavigationBar = () => { const [activeMenu, setActiveMenu] = useState(""); @@ -83,9 +84,7 @@ const NavigationBar = () => { )}
- userImage -

John Doe

- upArrow +
diff --git a/frontend/src/components/UserIcon.tsx b/frontend/src/components/UserIcon.tsx new file mode 100644 index 00000000..e901111e --- /dev/null +++ b/frontend/src/components/UserIcon.tsx @@ -0,0 +1,86 @@ +import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; +import Logout from "@mui/icons-material/Logout"; +import Box from "@mui/material/Box"; +import IconButton from "@mui/material/IconButton"; +import ListItemIcon from "@mui/material/ListItemIcon"; +import Menu from "@mui/material/Menu"; +import MenuItem from "@mui/material/MenuItem"; +import Image from "next/image"; +import { redirect } from "next/navigation"; +import * as React from "react"; + +import { firebaseSignOut } from "@/app/admin/firebase/firebase"; +import { useAuth } from "@/app/admin/firebase/firebaseProvider"; + +export default function UserIcon() { + const [anchorEl, setAnchorEl] = React.useState(null); + const open = Boolean(anchorEl); + const auth = useAuth(); + + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const logout = async () => { + if (auth) { + await firebaseSignOut(auth); + } + }; + + return ( + + + Profile Picture + +

John Doe

+ + + +
+ + { + logout().catch((error) => { + alert(error); + }); + handleClose(); + redirect("/admin"); + }} + > + + + +

Logout

+
+
+
+ ); +} diff --git a/frontend/src/components/admin/ForgotPassword.tsx b/frontend/src/components/admin/ForgotPassword.tsx new file mode 100644 index 00000000..5114666c --- /dev/null +++ b/frontend/src/components/admin/ForgotPassword.tsx @@ -0,0 +1,119 @@ +"use client"; + +import { sendPasswordResetEmail } from "firebase/auth"; +import React, { Dispatch, SetStateAction, useState } from "react"; + +import { useAuth } from "@/app/admin/firebase/firebaseProvider"; + +type ForgotPasswordProps = { + setForgotPass: Dispatch>; +}; + +const ForgotPassword = ({ setForgotPass }: ForgotPasswordProps) => { + const [email, setEmail] = useState(""); + const [state, setState] = useState("unclicked"); + const [errorMsg, setErrorMsg] = useState(""); + + const auth = useAuth(); + + const isValidEmail = (_email: string) => { + // Regular expression to validate email format + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return emailRegex.test(_email); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + if (!isValidEmail(email)) { + setErrorMsg("Invalid email address"); + return; + } + + setErrorMsg(""); + if (auth) { + setState("loading"); + sendPasswordResetEmail(auth, email) + .then(() => { + // Successfully sent email + setState("clicked"); + }) + .catch((error) => { + setState("unclicked"); + alert(error); + }); + } + }; + + return ( + <> +
+
+ +

+ Forgot Password? +

+

+ For instructions to reset your password, enter the email address associated with your + account. +

+
+ +
+
+
+ +
+ { + setEmail(e.target.value); + setErrorMsg(""); + }} + /> + {errorMsg &&

{errorMsg}

} +
+
+
+ +
+
+
+
+ + ); +}; + +export default ForgotPassword; diff --git a/frontend/src/components/admin/LoadingSpinner.module.css b/frontend/src/components/admin/LoadingSpinner.module.css new file mode 100644 index 00000000..0ec3f58d --- /dev/null +++ b/frontend/src/components/admin/LoadingSpinner.module.css @@ -0,0 +1,17 @@ +.loader { + border: 10px solid transparent; + border-top: 10px solid #694c97; + border-radius: 50%; + width: 128px; + height: 128px; + animation: loaderSpin 2s linear infinite; +} + +@keyframes loaderSpin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/frontend/src/components/admin/LoadingSpinner.tsx b/frontend/src/components/admin/LoadingSpinner.tsx new file mode 100644 index 00000000..ee58c476 --- /dev/null +++ b/frontend/src/components/admin/LoadingSpinner.tsx @@ -0,0 +1,13 @@ +import React from "react"; + +import styles from "./LoadingSpinner.module.css"; + +const LoadingSpinner = () => { + return ( +
+
+
+ ); +}; + +export default LoadingSpinner; diff --git a/frontend/src/components/admin/LoginForm.module.css b/frontend/src/components/admin/LoginForm.module.css new file mode 100644 index 00000000..79a29788 --- /dev/null +++ b/frontend/src/components/admin/LoginForm.module.css @@ -0,0 +1,15 @@ +/* Styles for admin login screen */ +@import url("https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&family=Roboto+Slab:wght@100..900&display=swap"); + +.title { + font: var(--font-body); + font-size: 24px; + font-style: normal; + font-weight: 400; + line-height: 32px; + color: #909090; +} + +.subtitle { + font: var(--font-title-l); +} diff --git a/frontend/src/components/admin/LoginForm.tsx b/frontend/src/components/admin/LoginForm.tsx new file mode 100644 index 00000000..f71ba704 --- /dev/null +++ b/frontend/src/components/admin/LoginForm.tsx @@ -0,0 +1,144 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import React, { Dispatch, SetStateAction, useState } from "react"; + +import styles from "./LoginForm.module.css"; + +import { firebaseSignIn } from "@/app/admin/firebase/firebase"; +import { useAuth } from "@/app/admin/firebase/firebaseProvider"; + +type LoginFormProps = { + setForgotPass: Dispatch>; +}; + +const LoginForm = ({ setForgotPass }: LoginFormProps) => { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [valid, setValid] = useState(true); + + const router = useRouter(); + const auth = useAuth(); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + let signInSuccessful = false; + try { + if (auth) { + signInSuccessful = await firebaseSignIn(auth, email, password); + } else { + throw new Error("Firebase Auth not initialized."); + } + } catch (error) { + // firebaseSignIn should not throw an error because + // it has error handling inside the funciton + // something has gone very wrong + alert(error); + } + + if (signInSuccessful) { + setValid(true); + router.push("/admin/dashboard"); + } else { + setValid(false); + } + }; + + return ( + <> +
+
+

4 Future Leaders of Tomorrow

+

Log Into Your Account

+
+ +
+
+
+ +
+ { + setEmail(e.target.value); + }} + /> +
+
+ +
+
+ +
+
+ { + setPassword(e.target.value); + }} + onKeyDown={(e) => { + if (e.key === "Enter") { + e.preventDefault(); + handleSubmit(e).catch((error) => { + console.log(error); + }); + } + }} + /> +
+ {!valid &&

Invalid email or password.

} +
+ +
+
+ +
+ +
+
+
+
+ + ); +}; + +export default LoginForm; diff --git a/frontend/src/components/admin/LoginSection.tsx b/frontend/src/components/admin/LoginSection.tsx new file mode 100644 index 00000000..9f0d9abd --- /dev/null +++ b/frontend/src/components/admin/LoginSection.tsx @@ -0,0 +1,38 @@ +"use client"; + +import Image from "next/image"; +import { useState } from "react"; + +import ForgotPassword from "./ForgotPassword"; +import LoginForm from "./LoginForm"; + +const LoginSection = () => { + // false shows regular login, true shows forgot password + const [forgotPass, setForgotPass] = useState(false); + + return ( +
+
+
+ For Future Leaders of Tomorrow Logo +
+
+
+
+ {forgotPass ? ( + + ) : ( + + )} +
+
+
+ ); +}; + +export default LoginSection; diff --git a/frontend/src/components/admin/PrivatePage.tsx b/frontend/src/components/admin/PrivatePage.tsx new file mode 100644 index 00000000..c6d554d4 --- /dev/null +++ b/frontend/src/components/admin/PrivatePage.tsx @@ -0,0 +1,37 @@ +"use client"; + +import { getAuth } from "firebase/auth"; +import { redirect } from "next/navigation"; +import { FC, ReactNode } from "react"; +import { useAuthState } from "react-firebase-hooks/auth"; + +import LoadingSpinner from "./LoadingSpinner"; + +const PrivatePage: FC<{ children: ReactNode }> = ({ children }) => { + const auth = getAuth(); + + const [user, loading, error] = useAuthState(auth); + + // still loading auth token + if (loading) { + return ; + } + + // firebase threw AuthError, non-critical + if (error) { + console.log(error); + redirect("/admin"); + return ; + } + + // user is signed in + if (user) { + return children; + } + + // otherwise go to login + redirect("/admin"); + return ; +}; + +export default PrivatePage;