From 0bf7acab780cb7c010e9833f2dfd04d8b500e470 Mon Sep 17 00:00:00 2001 From: James Daniels Date: Thu, 5 Sep 2024 13:51:42 -0400 Subject: [PATCH 01/38] Fixing the service worker config --- nextjs-end/auth-service-worker.js | 42 ++++++++++++++--------------- nextjs-start/auth-service-worker.js | 25 ++++++++--------- 2 files changed, 33 insertions(+), 34 deletions(-) diff --git a/nextjs-end/auth-service-worker.js b/nextjs-end/auth-service-worker.js index f9740869..8cb5b245 100644 --- a/nextjs-end/auth-service-worker.js +++ b/nextjs-end/auth-service-worker.js @@ -1,20 +1,21 @@ import { initializeApp } from "firebase/app"; import { getAuth, getIdToken } from "firebase/auth"; -import { getInstallations, getToken } from "firebase/installations"; -// this is set during install -let firebaseConfig; +// extract firebase config from query string +const serializedFirebaseConfig = new URLSearchParams(self.location.search).get('firebaseConfig'); +if (!serializedFirebaseConfig) { + throw new Error('Firebase Config object not found in service worker query string.'); +} -self.addEventListener('install', event => { - // extract firebase config from query string - const serializedFirebaseConfig = new URL(location).searchParams.get('firebaseConfig'); - - if (!serializedFirebaseConfig) { - throw new Error('Firebase Config object not found in service worker query string.'); - } - - firebaseConfig = JSON.parse(serializedFirebaseConfig); +const firebaseConfig = JSON.parse(serializedFirebaseConfig); + +self.addEventListener("install", () => { console.log("Service worker installed with Firebase config", firebaseConfig); + self.skipWaiting(); +}); + +self.addEventListener("activate", () => { + self.clients.claim(); }); self.addEventListener("fetch", (event) => { @@ -26,16 +27,13 @@ self.addEventListener("fetch", (event) => { async function fetchWithFirebaseHeaders(request) { const app = initializeApp(firebaseConfig); const auth = getAuth(app); - const installations = getInstallations(app); - const headers = new Headers(request.headers); - const [authIdToken, installationToken] = await Promise.all([ - getAuthIdToken(auth), - getToken(installations), - ]); - headers.append("Firebase-Instance-ID-Token", installationToken); - if (authIdToken) headers.append("Authorization", `Bearer ${authIdToken}`); - const newRequest = new Request(request, { headers }); - return await fetch(newRequest); + const authIdToken = await getAuthIdToken(auth); + if (authIdToken) { + const headers = new Headers(request.headers); + headers.append("Authorization", `Bearer ${authIdToken}`); + request = new Request(request, { headers }); + } + return await fetch(request); } async function getAuthIdToken(auth) { diff --git a/nextjs-start/auth-service-worker.js b/nextjs-start/auth-service-worker.js index a27cf9b0..9fe0cf74 100644 --- a/nextjs-start/auth-service-worker.js +++ b/nextjs-start/auth-service-worker.js @@ -1,20 +1,21 @@ import { initializeApp } from "firebase/app"; import { getAuth, getIdToken } from "firebase/auth"; -import { getInstallations, getToken } from "firebase/installations"; -// this is set during install -let firebaseConfig; +// extract firebase config from query string +const serializedFirebaseConfig = new URLSearchParams(self.location.search).get('firebaseConfig'); +if (!serializedFirebaseConfig) { + throw new Error('Firebase Config object not found in service worker query string.'); +} + +const firebaseConfig = JSON.parse(serializedFirebaseConfig); -self.addEventListener('install', event => { - // extract firebase config from query string - const serializedFirebaseConfig = new URL(location).searchParams.get('firebaseConfig'); - - if (!serializedFirebaseConfig) { - throw new Error('Firebase Config object not found in service worker query string.'); - } - - firebaseConfig = JSON.parse(serializedFirebaseConfig); +self.addEventListener("install", () => { console.log("Service worker installed with Firebase config", firebaseConfig); + self.skipWaiting(); +}); + +self.addEventListener("activate", () => { + self.clients.claim(); }); self.addEventListener("fetch", (event) => { From 1a3307e0c8a98e889e95967fa58eea5cbe302b6b Mon Sep 17 00:00:00 2001 From: James Daniels Date: Thu, 5 Sep 2024 15:37:52 -0400 Subject: [PATCH 02/38] Simplify things --- .gitignore | 10 ++++-- nextjs-end/.gitignore | 3 +- nextjs-end/auth-service-worker.js | 42 +++++++++++++++++------- nextjs-end/next.config.js | 6 +--- nextjs-end/package.json | 6 ++-- nextjs-end/src/components/Header.jsx | 36 +++++++++++--------- nextjs-end/src/lib/firebase/auth.js | 7 ++++ nextjs-end/src/lib/firebase/serverApp.js | 13 +++----- nextjs-start/src/components/Header.jsx | 4 ++- 9 files changed, 80 insertions(+), 47 deletions(-) diff --git a/.gitignore b/.gitignore index 56e05587..ac9176eb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,10 @@ **/.firebaserc +**/.DS_Store + **/node_modules/ -**/*-debug.log -**/.DS_Store \ No newline at end of file +**/package-lock.json +**/pnpm-lock.yaml +**/npm-debug.log* +**/yarn-debug.log* +**/yarn-error.log* +**/yarn.lock diff --git a/nextjs-end/.gitignore b/nextjs-end/.gitignore index 8fb25514..88b2e728 100644 --- a/nextjs-end/.gitignore +++ b/nextjs-end/.gitignore @@ -1,4 +1,5 @@ lib/firebase/config.js +public/auth-service-worker.js .next/ .firebase/ -node_modules/ \ No newline at end of file +node_modules/ diff --git a/nextjs-end/auth-service-worker.js b/nextjs-end/auth-service-worker.js index 8cb5b245..e0564cec 100644 --- a/nextjs-end/auth-service-worker.js +++ b/nextjs-end/auth-service-worker.js @@ -1,5 +1,5 @@ import { initializeApp } from "firebase/app"; -import { getAuth, getIdToken } from "firebase/auth"; +import { getAuth, getIdToken, onIdTokenChanged } from "firebase/auth"; // extract firebase config from query string const serializedFirebaseConfig = new URLSearchParams(self.location.search).get('firebaseConfig'); @@ -9,6 +9,9 @@ if (!serializedFirebaseConfig) { const firebaseConfig = JSON.parse(serializedFirebaseConfig); +const app = initializeApp(firebaseConfig); +const auth = getAuth(app); + self.addEventListener("install", () => { console.log("Service worker installed with Firebase config", firebaseConfig); self.skipWaiting(); @@ -18,16 +21,39 @@ self.addEventListener("activate", () => { self.clients.claim(); }); +// Allow the client to send a temporary override to the idToken, this allows for +// router.refresh() without worrying about the client and service worker having +// race conditions. +self.addEventListener("message", (event) => { + if (event.data && event.data.type === "FIREBASE_ID_TOKEN") { + getAuthIdToken = () => Promise.resolve(event.data.idToken); + } +}); + +// Once the idTokenChanged event fires, change back to the original method +auth.authStateReady().then(() => { + onIdTokenChanged(auth, () => { + getAuthIdToken = DEFAULT_GET_AUTH_ID_TOKEN; + }); +}); + +const DEFAULT_GET_AUTH_ID_TOKEN = async () => { + await auth.authStateReady(); + if (!auth.currentUser) return; + return await getIdToken(auth.currentUser); +}; + +let getAuthIdToken = DEFAULT_GET_AUTH_ID_TOKEN; + self.addEventListener("fetch", (event) => { - const { origin } = new URL(event.request.url); + const { origin, pathname } = new URL(event.request.url); if (origin !== self.location.origin) return; + if (pathname.includes(".")) return; event.respondWith(fetchWithFirebaseHeaders(event.request)); }); async function fetchWithFirebaseHeaders(request) { - const app = initializeApp(firebaseConfig); - const auth = getAuth(app); - const authIdToken = await getAuthIdToken(auth); + const authIdToken = await getAuthIdToken(); if (authIdToken) { const headers = new Headers(request.headers); headers.append("Authorization", `Bearer ${authIdToken}`); @@ -35,9 +61,3 @@ async function fetchWithFirebaseHeaders(request) { } return await fetch(request); } - -async function getAuthIdToken(auth) { - await auth.authStateReady(); - if (!auth.currentUser) return; - return await getIdToken(auth.currentUser); -} diff --git a/nextjs-end/next.config.js b/nextjs-end/next.config.js index b03e6c1d..658404ac 100644 --- a/nextjs-end/next.config.js +++ b/nextjs-end/next.config.js @@ -1,8 +1,4 @@ /** @type {import('next').NextConfig} */ -const nextConfig = { - experimental: { - serverActions: true, - } -}; +const nextConfig = {}; module.exports = nextConfig; diff --git a/nextjs-end/package.json b/nextjs-end/package.json index ba1dfd8f..b38f79c3 100644 --- a/nextjs-end/package.json +++ b/nextjs-end/package.json @@ -4,8 +4,10 @@ "private": true, "scripts": { "dev": "next dev", - "build": "npm run build-service-worker && next build", - "build-service-worker": "npx esbuild auth-service-worker.js --bundle --outfile=public/auth-service-worker.js", + "build": "next build", + "build-service-worker": "npx -y esbuild auth-service-worker.js --bundle --outfile=public/auth-service-worker.js", + "prebuild": "npm run build-service-worker", + "predev": "npm run build-service-worker", "start": "next start", "lint": "next lint" }, diff --git a/nextjs-end/src/components/Header.jsx b/nextjs-end/src/components/Header.jsx index bac460d9..2c7896bf 100644 --- a/nextjs-end/src/components/Header.jsx +++ b/nextjs-end/src/components/Header.jsx @@ -4,15 +4,18 @@ import Link from "next/link"; import { signInWithGoogle, signOut, - onAuthStateChanged + onAuthStateChanged, + onIdTokenChanged, } from "@/src/lib/firebase/auth.js"; import { addFakeRestaurantsAndReviews } from "@/src/lib/firebase/firestore.js"; import { useRouter } from "next/navigation"; import { firebaseConfig } from "@/src/lib/firebase/config"; +import { getIdToken } from "firebase/auth"; function useUserSession(initialUser) { // The initialUser comes from the server via a server component const [user, setUser] = useState(initialUser); + const [serviceWorker, setServiceWorker] = useState(undefined); const router = useRouter(); // Register the service worker that sends auth state back to server @@ -24,30 +27,31 @@ function useUserSession(initialUser) { navigator.serviceWorker .register(serviceWorkerUrl) - .then((registration) => console.log("scope is: ", registration.scope)); + .then((registration) => { + setServiceWorker(registration.active); + console.log("scope is: ", registration.scope) + }); } - }, []); + }, []); useEffect(() => { - const unsubscribe = onAuthStateChanged((authUser) => { + return onAuthStateChanged((authUser) => { setUser(authUser) - }) - - return () => unsubscribe() - // eslint-disable-next-line react-hooks/exhaustive-deps + }); }, []); useEffect(() => { - onAuthStateChanged((authUser) => { - if (user === undefined) return - - // refresh when user changed to ease testing - if (user?.email !== authUser?.email) { - router.refresh() + // refresh when user changed to ease testing + return onIdTokenChanged(async (authUser) => { + if (user?.email === authUser?.email) return; + // Send the new ID_TOKEN to the worker so we don't have a race condition + if (serviceWorker) { + const idToken = authUser && await getIdToken(authUser); + serviceWorker.postMessage({ type: 'FIREBASE_ID_TOKEN', idToken }); } + router.refresh(); }) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [user]) + }, [user, serviceWorker]); return user; } diff --git a/nextjs-end/src/lib/firebase/auth.js b/nextjs-end/src/lib/firebase/auth.js index 362984b3..95e08709 100644 --- a/nextjs-end/src/lib/firebase/auth.js +++ b/nextjs-end/src/lib/firebase/auth.js @@ -2,10 +2,17 @@ import { GoogleAuthProvider, signInWithPopup, onAuthStateChanged as _onAuthStateChanged, + onIdTokenChanged as _onIdTokenChanged, } from "firebase/auth"; import { auth } from "@/src/lib/firebase/clientApp"; +export { getIdToken } from "firebase/auth"; + +export function onIdTokenChanged(cb) { + return _onIdTokenChanged(auth, cb); +} + export function onAuthStateChanged(cb) { return _onAuthStateChanged(auth, cb); } diff --git a/nextjs-end/src/lib/firebase/serverApp.js b/nextjs-end/src/lib/firebase/serverApp.js index cdfd8f4a..1af32c71 100644 --- a/nextjs-end/src/lib/firebase/serverApp.js +++ b/nextjs-end/src/lib/firebase/serverApp.js @@ -9,16 +9,11 @@ import { firebaseConfig } from "./config"; import { getAuth } from "firebase/auth"; export async function getAuthenticatedAppForUser() { - const idToken = headers().get("Authorization")?.split("Bearer ")[1]; + const authIdToken = headers().get("Authorization")?.split("Bearer ")[1]; - const firebaseServerApp = initializeServerApp( - firebaseConfig, - idToken - ? { - authIdToken: idToken, - } - : {} - ); + const firebaseServerApp = initializeServerApp(firebaseConfig, { + authIdToken + }); const auth = getAuth(firebaseServerApp); await auth.authStateReady(); diff --git a/nextjs-start/src/components/Header.jsx b/nextjs-start/src/components/Header.jsx index 72b14316..5020ae4e 100644 --- a/nextjs-start/src/components/Header.jsx +++ b/nextjs-start/src/components/Header.jsx @@ -4,11 +4,13 @@ import Link from "next/link"; import { signInWithGoogle, signOut, - onAuthStateChanged + onAuthStateChanged, + onIdTokenChanged, } from "@/src/lib/firebase/auth.js"; import { addFakeRestaurantsAndReviews } from "@/src/lib/firebase/firestore.js"; import { useRouter } from "next/navigation"; import { firebaseConfig } from "@/src/lib/firebase/config"; +import { getIdToken } from "firebase/auth"; function useUserSession(initialUser) { return; From e642b45d6e7209a9f99df3c106ee52b086822f22 Mon Sep 17 00:00:00 2001 From: James Daniels Date: Thu, 5 Sep 2024 15:54:41 -0400 Subject: [PATCH 03/38] Already covered by -log --- .gitignore | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index ac9176eb..e6b2ce72 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,8 @@ **/.firebaserc **/.DS_Store +**/*-debug.log **/node_modules/ **/package-lock.json **/pnpm-lock.yaml -**/npm-debug.log* -**/yarn-debug.log* -**/yarn-error.log* **/yarn.lock From 91ac2912925072474d13485de3ddf6411e9decea Mon Sep 17 00:00:00 2001 From: James Daniels Date: Thu, 5 Sep 2024 16:15:52 -0400 Subject: [PATCH 04/38] Dont crash service worker on network failures --- nextjs-end/.gitignore | 2 +- nextjs-end/auth-service-worker.js | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/nextjs-end/.gitignore b/nextjs-end/.gitignore index 88b2e728..aaaab22a 100644 --- a/nextjs-end/.gitignore +++ b/nextjs-end/.gitignore @@ -2,4 +2,4 @@ lib/firebase/config.js public/auth-service-worker.js .next/ .firebase/ -node_modules/ +node_modules/ \ No newline at end of file diff --git a/nextjs-end/auth-service-worker.js b/nextjs-end/auth-service-worker.js index e0564cec..e702f207 100644 --- a/nextjs-end/auth-service-worker.js +++ b/nextjs-end/auth-service-worker.js @@ -48,7 +48,9 @@ let getAuthIdToken = DEFAULT_GET_AUTH_ID_TOKEN; self.addEventListener("fetch", (event) => { const { origin, pathname } = new URL(event.request.url); if (origin !== self.location.origin) return; - if (pathname.includes(".")) return; + if (pathname.startsWith('/_next/')) return; + // Ignore resources with an extension—this skips css, images, fonts, json, etc. + if (!pathname.startsWith("/api/") && pathname.includes(".")) return; event.respondWith(fetchWithFirebaseHeaders(event.request)); }); @@ -59,5 +61,8 @@ async function fetchWithFirebaseHeaders(request) { headers.append("Authorization", `Bearer ${authIdToken}`); request = new Request(request, { headers }); } - return await fetch(request); + return await fetch(request).catch((reason) => { + console.error(reason); + return new Response("Fail.", { status: 500, headers: { "content-type": "text/html" } }); + }); } From 684514331c5dbb2a47fe7fefa29e904fdd0c160c Mon Sep 17 00:00:00 2001 From: James Daniels Date: Thu, 5 Sep 2024 17:24:17 -0400 Subject: [PATCH 05/38] Cleanup --- nextjs-end/auth-service-worker.js | 31 ++++++++++++---------------- nextjs-end/src/components/Header.jsx | 25 ++++++++++++---------- 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/nextjs-end/auth-service-worker.js b/nextjs-end/auth-service-worker.js index e702f207..12db968c 100644 --- a/nextjs-end/auth-service-worker.js +++ b/nextjs-end/auth-service-worker.js @@ -1,5 +1,5 @@ import { initializeApp } from "firebase/app"; -import { getAuth, getIdToken, onIdTokenChanged } from "firebase/auth"; +import { getAuth, getIdToken, onAuthStateChanged } from "firebase/auth"; // extract firebase config from query string const serializedFirebaseConfig = new URLSearchParams(self.location.search).get('firebaseConfig'); @@ -21,36 +21,31 @@ self.addEventListener("activate", () => { self.clients.claim(); }); -// Allow the client to send a temporary override to the idToken, this allows for -// router.refresh() without worrying about the client and service worker having -// race conditions. -self.addEventListener("message", (event) => { - if (event.data && event.data.type === "FIREBASE_ID_TOKEN") { - getAuthIdToken = () => Promise.resolve(event.data.idToken); - } -}); - -// Once the idTokenChanged event fires, change back to the original method +// Notify clients of onAuthStateChanged events, so they can coordinate +// any actions which may normally be prone to race conditions, such as +// router.refresh(); auth.authStateReady().then(() => { - onIdTokenChanged(auth, () => { - getAuthIdToken = DEFAULT_GET_AUTH_ID_TOKEN; + onAuthStateChanged(auth, async (user) => { + const uid = user?.uid; + const clients = await self.clients.matchAll(); + for (const client of clients) { + client.postMessage({ type: "onAuthStateChanged", uid }); + } }); }); -const DEFAULT_GET_AUTH_ID_TOKEN = async () => { +async function getAuthIdToken() { await auth.authStateReady(); if (!auth.currentUser) return; return await getIdToken(auth.currentUser); }; -let getAuthIdToken = DEFAULT_GET_AUTH_ID_TOKEN; - self.addEventListener("fetch", (event) => { const { origin, pathname } = new URL(event.request.url); if (origin !== self.location.origin) return; if (pathname.startsWith('/_next/')) return; - // Ignore resources with an extension—this skips css, images, fonts, json, etc. - if (!pathname.startsWith("/api/") && pathname.includes(".")) return; + // Don't add haeders to GET requests with an extension—this skips css, images, fonts, json, etc. + if (event.request.method === "GET" && !pathname.startsWith("/api/") && pathname.includes(".")) return; event.respondWith(fetchWithFirebaseHeaders(event.request)); }); diff --git a/nextjs-end/src/components/Header.jsx b/nextjs-end/src/components/Header.jsx index 2c7896bf..14245ecb 100644 --- a/nextjs-end/src/components/Header.jsx +++ b/nextjs-end/src/components/Header.jsx @@ -27,7 +27,7 @@ function useUserSession(initialUser) { navigator.serviceWorker .register(serviceWorkerUrl) - .then((registration) => { + .then(async (registration) => { setServiceWorker(registration.active); console.log("scope is: ", registration.scope) }); @@ -41,16 +41,19 @@ function useUserSession(initialUser) { }, []); useEffect(() => { - // refresh when user changed to ease testing - return onIdTokenChanged(async (authUser) => { - if (user?.email === authUser?.email) return; - // Send the new ID_TOKEN to the worker so we don't have a race condition - if (serviceWorker) { - const idToken = authUser && await getIdToken(authUser); - serviceWorker.postMessage({ type: 'FIREBASE_ID_TOKEN', idToken }); - } - router.refresh(); - }) + if (serviceWorker) { + // listen to an onAuthStateChanged event from the service worker when + // refreshing the router, this is preferred over onAuthStateChanged as + // that can introduce race conditions as the client & service worker state + // can be out of sync + return navigator.serviceWorker.addEventListener("message", (event) => { + if (event.source !== serviceWorker) return; + if (event.data.type !== "onAuthStateChanged") return; + event.preventDefault(); + if (user?.uid === event.data?.uid) return; + router.refresh(); + }); + } }, [user, serviceWorker]); return user; From 4be2a4b43173d5173d1079ac499cf79039d450a0 Mon Sep 17 00:00:00 2001 From: James Daniels Date: Fri, 6 Sep 2024 23:51:22 -0400 Subject: [PATCH 06/38] Use magic url for sw sync --- nextjs-end/auth-service-worker.js | 37 +++++++++++++++---------- nextjs-end/src/components/Header.jsx | 41 +++++++++------------------- 2 files changed, 36 insertions(+), 42 deletions(-) diff --git a/nextjs-end/auth-service-worker.js b/nextjs-end/auth-service-worker.js index 12db968c..f80c9abf 100644 --- a/nextjs-end/auth-service-worker.js +++ b/nextjs-end/auth-service-worker.js @@ -21,19 +21,6 @@ self.addEventListener("activate", () => { self.clients.claim(); }); -// Notify clients of onAuthStateChanged events, so they can coordinate -// any actions which may normally be prone to race conditions, such as -// router.refresh(); -auth.authStateReady().then(() => { - onAuthStateChanged(auth, async (user) => { - const uid = user?.uid; - const clients = await self.clients.matchAll(); - for (const client of clients) { - client.postMessage({ type: "onAuthStateChanged", uid }); - } - }); -}); - async function getAuthIdToken() { await auth.authStateReady(); if (!auth.currentUser) return; @@ -43,12 +30,34 @@ async function getAuthIdToken() { self.addEventListener("fetch", (event) => { const { origin, pathname } = new URL(event.request.url); if (origin !== self.location.origin) return; + // Use a magic url to ensure that auth state is in sync between + // the client and the sw, this helps with actions such as router.refresh(); + if (pathname.startsWith('/__/auth/wait/')) { + const uid = pathname.split('/').at(-1); + event.respondWith(waitForMatchingUid(uid)); + return; + } if (pathname.startsWith('/_next/')) return; - // Don't add haeders to GET requests with an extension—this skips css, images, fonts, json, etc. + // Don't add headers to non-get requests or those with an extension—this + // helps with css, images, fonts, json, etc. if (event.request.method === "GET" && !pathname.startsWith("/api/") && pathname.includes(".")) return; event.respondWith(fetchWithFirebaseHeaders(event.request)); }); +async function waitForMatchingUid(_uid) { + const uid = _uid === "undefined" ? undefined : _uid; + await auth.authStateReady(); + await new Promise((resolve) => { + const unsubscribe = onAuthStateChanged(auth, (user) => { + if (user?.uid === uid) { + unsubscribe(); + resolve(); + } + }); + }); + return new Response(undefined, { status: 200, headers: { "cache-control": "no-store" } }); +} + async function fetchWithFirebaseHeaders(request) { const authIdToken = await getAuthIdToken(); if (authIdToken) { diff --git a/nextjs-end/src/components/Header.jsx b/nextjs-end/src/components/Header.jsx index 14245ecb..27a4c14e 100644 --- a/nextjs-end/src/components/Header.jsx +++ b/nextjs-end/src/components/Header.jsx @@ -5,17 +5,14 @@ import { signInWithGoogle, signOut, onAuthStateChanged, - onIdTokenChanged, } from "@/src/lib/firebase/auth.js"; import { addFakeRestaurantsAndReviews } from "@/src/lib/firebase/firestore.js"; import { useRouter } from "next/navigation"; import { firebaseConfig } from "@/src/lib/firebase/config"; -import { getIdToken } from "firebase/auth"; function useUserSession(initialUser) { // The initialUser comes from the server via a server component const [user, setUser] = useState(initialUser); - const [serviceWorker, setServiceWorker] = useState(undefined); const router = useRouter(); // Register the service worker that sends auth state back to server @@ -25,36 +22,24 @@ function useUserSession(initialUser) { const serializedFirebaseConfig = encodeURIComponent(JSON.stringify(firebaseConfig)); const serviceWorkerUrl = `/auth-service-worker.js?firebaseConfig=${serializedFirebaseConfig}` - navigator.serviceWorker - .register(serviceWorkerUrl) - .then(async (registration) => { - setServiceWorker(registration.active); - console.log("scope is: ", registration.scope) - }); + navigator + .serviceWorker + .register(serviceWorkerUrl, { scope: "/", updateViaCache: "none" }) + .then((registration) => { + console.log("scope is: ", registration.scope); + registration.update(); + }); } }, []); useEffect(() => { - return onAuthStateChanged((authUser) => { - setUser(authUser) + return onAuthStateChanged(async (authUser) => { + if (user?.uid === authUser?.uid) return; + await fetch(`/__/auth/wait/${authUser?.uid}`, { method: "HEAD" }).catch(() => undefined); + setUser(authUser); + router.refresh(); }); - }, []); - - useEffect(() => { - if (serviceWorker) { - // listen to an onAuthStateChanged event from the service worker when - // refreshing the router, this is preferred over onAuthStateChanged as - // that can introduce race conditions as the client & service worker state - // can be out of sync - return navigator.serviceWorker.addEventListener("message", (event) => { - if (event.source !== serviceWorker) return; - if (event.data.type !== "onAuthStateChanged") return; - event.preventDefault(); - if (user?.uid === event.data?.uid) return; - router.refresh(); - }); - } - }, [user, serviceWorker]); + }, [user]); return user; } From 1eb7b811b40fc57909465bba0307832cad03be05 Mon Sep 17 00:00:00 2001 From: James Daniels Date: Tue, 10 Sep 2024 09:54:04 -0400 Subject: [PATCH 07/38] More small cleanup --- nextjs-end/auth-service-worker.js | 6 +++--- nextjs-end/package.json | 10 ++++++---- nextjs-end/src/components/Header.jsx | 5 ++++- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/nextjs-end/auth-service-worker.js b/nextjs-end/auth-service-worker.js index f80c9abf..fd8cd538 100644 --- a/nextjs-end/auth-service-worker.js +++ b/nextjs-end/auth-service-worker.js @@ -17,8 +17,8 @@ self.addEventListener("install", () => { self.skipWaiting(); }); -self.addEventListener("activate", () => { - self.clients.claim(); +self.addEventListener("activate", (event) => { + event.waitUntil(self.clients.claim()); }); async function getAuthIdToken() { @@ -40,7 +40,7 @@ self.addEventListener("fetch", (event) => { if (pathname.startsWith('/_next/')) return; // Don't add headers to non-get requests or those with an extension—this // helps with css, images, fonts, json, etc. - if (event.request.method === "GET" && !pathname.startsWith("/api/") && pathname.includes(".")) return; + if (event.request.method === "GET" && pathname.includes(".")) return; event.respondWith(fetchWithFirebaseHeaders(event.request)); }); diff --git a/nextjs-end/package.json b/nextjs-end/package.json index b38f79c3..80f3f766 100644 --- a/nextjs-end/package.json +++ b/nextjs-end/package.json @@ -3,11 +3,12 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev", + "dev": "concurrently -r -k \"npm:dev:next\" \"npm:dev:sw\"", + "dev:next": "next dev", + "dev:sw": "npm run build:sw -- --watch", "build": "next build", - "build-service-worker": "npx -y esbuild auth-service-worker.js --bundle --outfile=public/auth-service-worker.js", - "prebuild": "npm run build-service-worker", - "predev": "npm run build-service-worker", + "build:sw": "npx -y esbuild auth-service-worker.js --bundle --main-fields=webworker,browser,module,main --outfile=public/auth-service-worker.js", + "prebuild": "npm run build:sw", "start": "next start", "lint": "next lint" }, @@ -29,6 +30,7 @@ "tls": false }, "devDependencies": { + "concurrently": "^9.0.0", "esbuild": "^0.20.2" } } diff --git a/nextjs-end/src/components/Header.jsx b/nextjs-end/src/components/Header.jsx index 27a4c14e..63607341 100644 --- a/nextjs-end/src/components/Header.jsx +++ b/nextjs-end/src/components/Header.jsx @@ -35,7 +35,10 @@ function useUserSession(initialUser) { useEffect(() => { return onAuthStateChanged(async (authUser) => { if (user?.uid === authUser?.uid) return; - await fetch(`/__/auth/wait/${authUser?.uid}`, { method: "HEAD" }).catch(() => undefined); + if ("serviceWorker" in navigator) { + await navigator.serviceWorker.ready; + await fetch(`/__/auth/wait/${authUser?.uid}`, { method: "HEAD" }).catch(() => undefined); + } setUser(authUser); router.refresh(); }); From 19c921e18dbff26e8a90cea935737c4a73803431 Mon Sep 17 00:00:00 2001 From: James Daniels Date: Tue, 10 Sep 2024 10:08:28 -0400 Subject: [PATCH 08/38] Add comments --- nextjs-end/src/lib/firebase/serverApp.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/nextjs-end/src/lib/firebase/serverApp.js b/nextjs-end/src/lib/firebase/serverApp.js index 1af32c71..635b9302 100644 --- a/nextjs-end/src/lib/firebase/serverApp.js +++ b/nextjs-end/src/lib/firebase/serverApp.js @@ -8,9 +8,16 @@ import { initializeServerApp } from "firebase/app"; import { firebaseConfig } from "./config"; import { getAuth } from "firebase/auth"; +// Returns an authenticated client SDK instance for use in Server Side Rendering +// and Static Site Generation export async function getAuthenticatedAppForUser() { + // Firebase App Hosting currently does not support cookies, so we transmit + // the client idToken via an Authorization header with Service Workers const authIdToken = headers().get("Authorization")?.split("Bearer ")[1]; + // Firebase Server App is a new feature in the JS SDK that allows you to + // instantiate the SDK with credentials retrieved from the client & has + // other afforances for use in server environments. const firebaseServerApp = initializeServerApp(firebaseConfig, { authIdToken }); From 9d2a3442628fd2766b24df47aafb6180f8dc5ee5 Mon Sep 17 00:00:00 2001 From: James Daniels Date: Tue, 10 Sep 2024 13:27:14 -0400 Subject: [PATCH 09/38] Forgot miniify --- nextjs-end/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nextjs-end/package.json b/nextjs-end/package.json index 80f3f766..3f973ec9 100644 --- a/nextjs-end/package.json +++ b/nextjs-end/package.json @@ -7,7 +7,7 @@ "dev:next": "next dev", "dev:sw": "npm run build:sw -- --watch", "build": "next build", - "build:sw": "npx -y esbuild auth-service-worker.js --bundle --main-fields=webworker,browser,module,main --outfile=public/auth-service-worker.js", + "build:sw": "npx -y esbuild auth-service-worker.js --bundle --minify --main-fields=webworker,browser,module,main --outfile=public/auth-service-worker.js", "prebuild": "npm run build:sw", "start": "next start", "lint": "next lint" From 9d754b7ad4aba49dbd75cc5582ba9625bfbd60fc Mon Sep 17 00:00:00 2001 From: James Daniels Date: Tue, 10 Sep 2024 13:52:54 -0400 Subject: [PATCH 10/38] Eslint --- nextjs-end/.eslintrc | 9 +++++++++ nextjs-end/next.config.js | 6 +++++- nextjs-end/package.json | 7 +++++-- nextjs-end/src/components/Header.jsx | 8 ++++---- nextjs-end/src/components/Restaurant.jsx | 8 ++++---- nextjs-end/src/components/RestaurantDetails.jsx | 1 + nextjs-end/src/components/RestaurantListings.jsx | 2 +- nextjs-end/src/components/ReviewDialog.jsx | 4 ++-- nextjs-end/src/components/Reviews/ReviewSummary.jsx | 2 +- nextjs-end/src/lib/firebase/storage.js | 4 ++-- 10 files changed, 34 insertions(+), 17 deletions(-) create mode 100644 nextjs-end/.eslintrc diff --git a/nextjs-end/.eslintrc b/nextjs-end/.eslintrc new file mode 100644 index 00000000..734ed6cc --- /dev/null +++ b/nextjs-end/.eslintrc @@ -0,0 +1,9 @@ +{ + "extends": ["next"], + "rules": { + "indent": "off", + "quotes": "off", + "no-mixed-spaces-and-tabs": "off", + "@next/next/no-img-element": "off" + } +} \ No newline at end of file diff --git a/nextjs-end/next.config.js b/nextjs-end/next.config.js index 658404ac..4a171357 100644 --- a/nextjs-end/next.config.js +++ b/nextjs-end/next.config.js @@ -1,4 +1,8 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {}; +const nextConfig = { + eslint: { + ignoreDuringBuilds: true, + } +}; module.exports = nextConfig; diff --git a/nextjs-end/package.json b/nextjs-end/package.json index 3f973ec9..973266e2 100644 --- a/nextjs-end/package.json +++ b/nextjs-end/package.json @@ -7,7 +7,7 @@ "dev:next": "next dev", "dev:sw": "npm run build:sw -- --watch", "build": "next build", - "build:sw": "npx -y esbuild auth-service-worker.js --bundle --minify --main-fields=webworker,browser,module,main --outfile=public/auth-service-worker.js", + "build:sw": "esbuild auth-service-worker.js --bundle --minify --main-fields=webworker,browser,module,main --outfile=public/auth-service-worker.js", "prebuild": "npm run build:sw", "start": "next start", "lint": "next lint" @@ -30,7 +30,10 @@ "tls": false }, "devDependencies": { + "@next/eslint-plugin-next": "^14.2.9", "concurrently": "^9.0.0", - "esbuild": "^0.20.2" + "esbuild": "^0.20.2", + "eslint": "^8.0.0", + "eslint-config-next": "^14.2.9" } } diff --git a/nextjs-end/src/components/Header.jsx b/nextjs-end/src/components/Header.jsx index 63607341..06d1a14e 100644 --- a/nextjs-end/src/components/Header.jsx +++ b/nextjs-end/src/components/Header.jsx @@ -1,4 +1,4 @@ -'use client' +'use client'; import React, { useState, useEffect } from "react"; import Link from "next/link"; import { @@ -20,7 +20,7 @@ function useUserSession(initialUser) { useEffect(() => { if ("serviceWorker" in navigator) { const serializedFirebaseConfig = encodeURIComponent(JSON.stringify(firebaseConfig)); - const serviceWorkerUrl = `/auth-service-worker.js?firebaseConfig=${serializedFirebaseConfig}` + const serviceWorkerUrl = `/auth-service-worker.js?firebaseConfig=${serializedFirebaseConfig}`; navigator .serviceWorker @@ -34,7 +34,7 @@ function useUserSession(initialUser) { useEffect(() => { return onAuthStateChanged(async (authUser) => { - if (user?.uid === authUser?.uid) return; + if (user?.uid === authUser?.uid) {return;} if ("serviceWorker" in navigator) { await navigator.serviceWorker.ready; await fetch(`/__/auth/wait/${authUser?.uid}`, { method: "HEAD" }).catch(() => undefined); @@ -42,7 +42,7 @@ function useUserSession(initialUser) { setUser(authUser); router.refresh(); }); - }, [user]); + }, [user, router]); return user; } diff --git a/nextjs-end/src/components/Restaurant.jsx b/nextjs-end/src/components/Restaurant.jsx index 85848ab6..9a126963 100644 --- a/nextjs-end/src/components/Restaurant.jsx +++ b/nextjs-end/src/components/Restaurant.jsx @@ -4,11 +4,11 @@ // It receives data from src/app/restaurant/[id]/page.jsx import { React, useState, useEffect, Suspense } from "react"; -import dynamic from 'next/dynamic' +import dynamic from 'next/dynamic'; import { getRestaurantSnapshotById, } from "@/src/lib/firebase/firestore.js"; -import {useUser} from '@/src/lib/getUser' +import {useUser} from '@/src/lib/getUser'; import RestaurantDetails from "@/src/components/RestaurantDetails.jsx"; import { updateRestaurantImage } from "@/src/lib/firebase/storage.js"; @@ -41,7 +41,7 @@ export default function Restaurant({ } const imageURL = await updateRestaurantImage(id, image); - setRestaurantDetails({ ...restaurant, photo: imageURL }); + setRestaurantDetails({ ...restaurantDetails, photo: imageURL }); } const handleClose = () => { @@ -57,7 +57,7 @@ export default function Restaurant({ return () => { unsubscribeFromRestaurant(); }; - }, []); + }, [id]); return ( <> diff --git a/nextjs-end/src/components/RestaurantDetails.jsx b/nextjs-end/src/components/RestaurantDetails.jsx index 6f65ad8e..7dadf5ce 100644 --- a/nextjs-end/src/components/RestaurantDetails.jsx +++ b/nextjs-end/src/components/RestaurantDetails.jsx @@ -18,6 +18,7 @@ const RestaurantDetails = ({
{userId && ( review { setIsOpen(!isOpen); diff --git a/nextjs-end/src/components/RestaurantListings.jsx b/nextjs-end/src/components/RestaurantListings.jsx index 4252640e..55ccd11d 100644 --- a/nextjs-end/src/components/RestaurantListings.jsx +++ b/nextjs-end/src/components/RestaurantListings.jsx @@ -74,7 +74,7 @@ export default function RestaurantListings({ useEffect(() => { routerWithFilters(router, filters); - }, [filters]); + }, [router, filters]); useEffect(() => { const unsubscribe = getRestaurantsSnapshot(data => { diff --git a/nextjs-end/src/components/ReviewDialog.jsx b/nextjs-end/src/components/ReviewDialog.jsx index 5ab19c60..113c43c3 100644 --- a/nextjs-end/src/components/ReviewDialog.jsx +++ b/nextjs-end/src/components/ReviewDialog.jsx @@ -24,14 +24,14 @@ const ReviewDialog = ({ dialog.current.close(); } - }, [isOpen, dialog.current]); + }, [isOpen, dialog]); const handleClick = (e) => { // close if clicked outside the modal if (e.target === dialog.current) { handleClose(); } - } + }; return ( diff --git a/nextjs-end/src/components/Reviews/ReviewSummary.jsx b/nextjs-end/src/components/Reviews/ReviewSummary.jsx index 8d3cf931..a11f35c8 100644 --- a/nextjs-end/src/components/Reviews/ReviewSummary.jsx +++ b/nextjs-end/src/components/Reviews/ReviewSummary.jsx @@ -38,7 +38,7 @@ export async function GeminiSummary({ restaurantId }) { if (e.message.includes("403 Forbidden")) { return (

- This service account doesn't have permission to talk to Gemini via + This service account doesn't have permission to talk to Gemini via Vertex

); diff --git a/nextjs-end/src/lib/firebase/storage.js b/nextjs-end/src/lib/firebase/storage.js index a5c7825a..99417d08 100644 --- a/nextjs-end/src/lib/firebase/storage.js +++ b/nextjs-end/src/lib/firebase/storage.js @@ -7,10 +7,10 @@ import { updateRestaurantImageReference } from "@/src/lib/firebase/firestore"; export async function updateRestaurantImage(restaurantId, image) { try { if (!restaurantId) - throw new Error("No restaurant ID has been provided."); + {throw new Error("No restaurant ID has been provided.");} if (!image || !image.name) - throw new Error("A valid image has not been provided."); + {throw new Error("A valid image has not been provided.");} const publicImageUrl = await uploadImage(restaurantId, image); await updateRestaurantImageReference(restaurantId, publicImageUrl); From 790a11de2ca2623e1fb3e88a09bb9683a8719be1 Mon Sep 17 00:00:00 2001 From: James Daniels Date: Tue, 10 Sep 2024 14:10:52 -0400 Subject: [PATCH 11/38] Use implied unsubscribe --- nextjs-end/.eslintrc | 2 +- nextjs-end/src/components/Restaurant.jsx | 6 +----- nextjs-end/src/components/RestaurantListings.jsx | 6 +----- nextjs-end/src/components/ReviewDialog.jsx | 2 +- nextjs-end/src/lib/firebase/firestore.js | 11 +++-------- nextjs-end/src/lib/getUser.js | 6 +----- 6 files changed, 8 insertions(+), 25 deletions(-) diff --git a/nextjs-end/.eslintrc b/nextjs-end/.eslintrc index 734ed6cc..c8027ead 100644 --- a/nextjs-end/.eslintrc +++ b/nextjs-end/.eslintrc @@ -1,5 +1,5 @@ { - "extends": ["next"], + "extends": ["eslint:recommended", "next"], "rules": { "indent": "off", "quotes": "off", diff --git a/nextjs-end/src/components/Restaurant.jsx b/nextjs-end/src/components/Restaurant.jsx index 9a126963..9f584299 100644 --- a/nextjs-end/src/components/Restaurant.jsx +++ b/nextjs-end/src/components/Restaurant.jsx @@ -50,13 +50,9 @@ export default function Restaurant({ }; useEffect(() => { - const unsubscribeFromRestaurant = getRestaurantSnapshotById(id, (data) => { + return getRestaurantSnapshotById(id, (data) => { setRestaurantDetails(data); }); - - return () => { - unsubscribeFromRestaurant(); - }; }, [id]); return ( diff --git a/nextjs-end/src/components/RestaurantListings.jsx b/nextjs-end/src/components/RestaurantListings.jsx index 55ccd11d..e173e6be 100644 --- a/nextjs-end/src/components/RestaurantListings.jsx +++ b/nextjs-end/src/components/RestaurantListings.jsx @@ -77,13 +77,9 @@ export default function RestaurantListings({ }, [router, filters]); useEffect(() => { - const unsubscribe = getRestaurantsSnapshot(data => { + return getRestaurantsSnapshot(data => { setRestaurants(data); }, filters); - - return () => { - unsubscribe(); - }; }, [filters]); return ( diff --git a/nextjs-end/src/components/ReviewDialog.jsx b/nextjs-end/src/components/ReviewDialog.jsx index 113c43c3..a7279626 100644 --- a/nextjs-end/src/components/ReviewDialog.jsx +++ b/nextjs-end/src/components/ReviewDialog.jsx @@ -2,7 +2,7 @@ // This components handles the review dialog and uses a next.js feature known as Server Actions to handle the form submission -import {useEffect, useLayoutEffect, useRef} from "react"; +import { useLayoutEffect, useRef } from "react"; import RatingPicker from "@/src/components/RatingPicker.jsx"; import { handleReviewFormSubmission } from "@/src/app/actions.js"; diff --git a/nextjs-end/src/lib/firebase/firestore.js b/nextjs-end/src/lib/firebase/firestore.js index 69da95b6..5880dc72 100644 --- a/nextjs-end/src/lib/firebase/firestore.js +++ b/nextjs-end/src/lib/firebase/firestore.js @@ -13,7 +13,6 @@ import { runTransaction, where, addDoc, - getFirestore, } from "firebase/firestore"; import { db } from "@/src/lib/firebase/clientApp"; @@ -122,7 +121,7 @@ export function getRestaurantsSnapshot(cb, filters = {}) { let q = query(collection(db, "restaurants")); q = applyQueryFilters(q, filters); - const unsubscribe = onSnapshot(q, querySnapshot => { + return onSnapshot(q, querySnapshot => { const results = querySnapshot.docs.map(doc => { return { id: doc.id, @@ -134,8 +133,6 @@ export function getRestaurantsSnapshot(cb, filters = {}) { cb(results); }); - - return unsubscribe; } export async function getRestaurantById(db, restaurantId) { @@ -163,13 +160,12 @@ export function getRestaurantSnapshotById(restaurantId, cb) { } const docRef = doc(db, "restaurants", restaurantId); - const unsubscribe = onSnapshot(docRef, docSnap => { + return onSnapshot(docRef, docSnap => { cb({ ...docSnap.data(), timestamp: docSnap.data().timestamp.toDate(), }); }); - return unsubscribe; } export async function getReviewsByRestaurantId(db, restaurantId) { @@ -204,7 +200,7 @@ export function getReviewsSnapshotByRestaurantId(restaurantId, cb) { collection(db, "restaurants", restaurantId, "ratings"), orderBy("timestamp", "desc") ); - const unsubscribe = onSnapshot(q, querySnapshot => { + return onSnapshot(q, querySnapshot => { const results = querySnapshot.docs.map(doc => { return { id: doc.id, @@ -215,7 +211,6 @@ export function getReviewsSnapshotByRestaurantId(restaurantId, cb) { }); cb(results); }); - return unsubscribe; } export async function addFakeRestaurantsAndReviews() { diff --git a/nextjs-end/src/lib/getUser.js b/nextjs-end/src/lib/getUser.js index ecba2318..35659d93 100644 --- a/nextjs-end/src/lib/getUser.js +++ b/nextjs-end/src/lib/getUser.js @@ -4,18 +4,14 @@ import { onAuthStateChanged } from "firebase/auth"; import { useEffect, useState } from "react"; import { auth } from "@/src/lib/firebase/clientApp.js"; -import { useRouter } from "next/navigation"; export function useUser() { const [user, setUser] = useState(); useEffect(() => { - const unsubscribe = onAuthStateChanged(auth, (authUser) => { + return onAuthStateChanged(auth, (authUser) => { setUser(authUser); }); - - return () => unsubscribe(); - // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return user; From b36ad74d07d2a1bb0accb6d41022fce2e9a823bb Mon Sep 17 00:00:00 2001 From: James Daniels Date: Tue, 10 Sep 2024 14:15:19 -0400 Subject: [PATCH 12/38] Port fixes from -end --- nextjs-start/.eslintrc | 10 + nextjs-start/next.config.js | 6 +- nextjs-start/package.json | 15 +- nextjs-start/public/auth-service-worker.js | 1761 +++++++++++++++++ nextjs-start/src/components/Header.jsx | 2 +- nextjs-start/src/components/Restaurant.jsx | 14 +- .../src/components/RestaurantDetails.jsx | 1 + .../src/components/RestaurantListings.jsx | 8 +- nextjs-start/src/components/ReviewDialog.jsx | 4 +- nextjs-start/src/lib/firebase/firestore.js | 3 +- nextjs-start/src/lib/getUser.js | 5 +- 11 files changed, 1798 insertions(+), 31 deletions(-) create mode 100644 nextjs-start/.eslintrc create mode 100644 nextjs-start/public/auth-service-worker.js diff --git a/nextjs-start/.eslintrc b/nextjs-start/.eslintrc new file mode 100644 index 00000000..bb088a83 --- /dev/null +++ b/nextjs-start/.eslintrc @@ -0,0 +1,10 @@ +{ + "extends": ["eslint:recommended", "next"], + "rules": { + "indent": "off", + "quotes": "off", + "no-mixed-spaces-and-tabs": "off", + "@next/next/no-img-element": "off", + "no-unused-vars": "off" + } +} \ No newline at end of file diff --git a/nextjs-start/next.config.js b/nextjs-start/next.config.js index b03e6c1d..2d747871 100644 --- a/nextjs-start/next.config.js +++ b/nextjs-start/next.config.js @@ -1,8 +1,8 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - experimental: { - serverActions: true, - } + eslint: { + ignoreDuringBuilds: true, + } }; module.exports = nextConfig; diff --git a/nextjs-start/package.json b/nextjs-start/package.json index ba1dfd8f..973266e2 100644 --- a/nextjs-start/package.json +++ b/nextjs-start/package.json @@ -3,9 +3,12 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev", - "build": "npm run build-service-worker && next build", - "build-service-worker": "npx esbuild auth-service-worker.js --bundle --outfile=public/auth-service-worker.js", + "dev": "concurrently -r -k \"npm:dev:next\" \"npm:dev:sw\"", + "dev:next": "next dev", + "dev:sw": "npm run build:sw -- --watch", + "build": "next build", + "build:sw": "esbuild auth-service-worker.js --bundle --minify --main-fields=webworker,browser,module,main --outfile=public/auth-service-worker.js", + "prebuild": "npm run build:sw", "start": "next start", "lint": "next lint" }, @@ -27,6 +30,10 @@ "tls": false }, "devDependencies": { - "esbuild": "^0.20.2" + "@next/eslint-plugin-next": "^14.2.9", + "concurrently": "^9.0.0", + "esbuild": "^0.20.2", + "eslint": "^8.0.0", + "eslint-config-next": "^14.2.9" } } diff --git a/nextjs-start/public/auth-service-worker.js b/nextjs-start/public/auth-service-worker.js new file mode 100644 index 00000000..b31ed67c --- /dev/null +++ b/nextjs-start/public/auth-service-worker.js @@ -0,0 +1,1761 @@ +(()=>{var Pt=function(n){let e=[],t=0;for(let r=0;r>6|192,e[t++]=i&63|128):(i&64512)===55296&&r+1>18|240,e[t++]=i>>12&63|128,e[t++]=i>>6&63|128,e[t++]=i&63|128):(e[t++]=i>>12|224,e[t++]=i>>6&63|128,e[t++]=i&63|128)}return e},xn=function(n){let e=[],t=0,r=0;for(;t191&&i<224){let s=n[t++];e[r++]=String.fromCharCode((i&31)<<6|s&63)}else if(i>239&&i<365){let s=n[t++],o=n[t++],c=n[t++],a=((i&7)<<18|(s&63)<<12|(o&63)<<6|c&63)-65536;e[r++]=String.fromCharCode(55296+(a>>10)),e[r++]=String.fromCharCode(56320+(a&1023))}else{let s=n[t++],o=n[t++];e[r++]=String.fromCharCode((i&15)<<12|(s&63)<<6|o&63)}}return e.join("")},kt={byteToCharMap_:null,charToByteMap_:null,byteToCharMapWebSafe_:null,charToByteMapWebSafe_:null,ENCODED_VALS_BASE:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",get ENCODED_VALS(){return this.ENCODED_VALS_BASE+"+/="},get ENCODED_VALS_WEBSAFE(){return this.ENCODED_VALS_BASE+"-_."},HAS_NATIVE_SUPPORT:typeof atob=="function",encodeByteArray(n,e){if(!Array.isArray(n))throw Error("encodeByteArray takes an array as a parameter");this.init_();let t=e?this.byteToCharMapWebSafe_:this.byteToCharMap_,r=[];for(let i=0;i>2,p=(s&3)<<4|c>>4,_=(c&15)<<2|u>>6,O=u&63;a||(O=64,o||(_=64)),r.push(t[h],t[p],t[_],t[O])}return r.join("")},encodeString(n,e){return this.HAS_NATIVE_SUPPORT&&!e?btoa(n):this.encodeByteArray(Pt(n),e)},decodeString(n,e){return this.HAS_NATIVE_SUPPORT&&!e?atob(n):xn(this.decodeStringToByteArray(n,e))},decodeStringToByteArray(n,e){this.init_();let t=e?this.charToByteMapWebSafe_:this.charToByteMap_,r=[];for(let i=0;i>4;if(r.push(_),u!==64){let O=c<<4&240|u>>2;if(r.push(O),p!==64){let ce=u<<6&192|p;r.push(ce)}}}return r},init_(){if(!this.byteToCharMap_){this.byteToCharMap_={},this.charToByteMap_={},this.byteToCharMapWebSafe_={},this.charToByteMapWebSafe_={};for(let n=0;n=this.ENCODED_VALS_BASE.length&&(this.charToByteMap_[this.ENCODED_VALS_WEBSAFE.charAt(n)]=n,this.charToByteMapWebSafe_[this.ENCODED_VALS.charAt(n)]=n)}}},xe=class extends Error{constructor(){super(...arguments),this.name="DecodeBase64StringError"}},Fn=function(n){let e=Pt(n);return kt.encodeByteArray(e,!0)},Ve=function(n){return Fn(n).replace(/\./g,"")},je=function(n){try{return kt.decodeString(n,!0)}catch(e){console.error("base64Decode failed: ",e)}return null};function Vn(){if(typeof self<"u")return self;if(typeof window<"u")return window;if(typeof global<"u")return global;throw new Error("Unable to locate global object.")}var jn=()=>Vn().__FIREBASE_DEFAULTS__,Hn=()=>{if(typeof process>"u"||typeof process.env>"u")return;let n=process.env.__FIREBASE_DEFAULTS__;if(n)return JSON.parse(n)},Wn=()=>{if(typeof document>"u")return;let n;try{n=document.cookie.match(/__FIREBASE_DEFAULTS__=([^;]+)/)}catch{return}let e=n&&je(n[1]);return e&&JSON.parse(e)},Bn=()=>{try{return jn()||Hn()||Wn()}catch(n){console.info(`Unable to get __FIREBASE_DEFAULTS__ due to: ${n}`);return}};var Ct=n=>{var e;return(e=Bn())===null||e===void 0?void 0:e[`_${n}`]};function g(){return typeof navigator<"u"&&typeof navigator.userAgent=="string"?navigator.userAgent:""}function Nt(){return typeof window<"u"&&!!(window.cordova||window.phonegap||window.PhoneGap)&&/ios|iphone|ipod|ipad|android|blackberry|iemobile/i.test(g())}function Dt(){let n=typeof chrome=="object"?chrome.runtime:typeof browser=="object"?browser.runtime:void 0;return typeof n=="object"&&n.id!==void 0}function Lt(){return typeof navigator=="object"&&navigator.product==="ReactNative"}function Mt(){let n=g();return n.indexOf("MSIE ")>=0||n.indexOf("Trident/")>=0}function Ut(){try{return typeof indexedDB=="object"}catch{return!1}}function xt(){return new Promise((n,e)=>{try{let t=!0,r="validate-browser-context-for-indexeddb-analytics-module",i=self.indexedDB.open(r);i.onsuccess=()=>{i.result.close(),t||self.indexedDB.deleteDatabase(r),n(!0)},i.onupgradeneeded=()=>{t=!1},i.onerror=()=>{var s;e(((s=i.error)===null||s===void 0?void 0:s.message)||"")}}catch(t){e(t)}})}var zn="FirebaseError",I=class n extends Error{constructor(e,t,r){super(t),this.code=e,this.customData=r,this.name=zn,Object.setPrototypeOf(this,n.prototype),Error.captureStackTrace&&Error.captureStackTrace(this,E.prototype.create)}},E=class{constructor(e,t,r){this.service=e,this.serviceName=t,this.errors=r}create(e,...t){let r=t[0]||{},i=`${this.service}/${e}`,s=this.errors[e],o=s?$n(s,r):"Error",c=`${this.serviceName}: ${o} (${i}).`;return new I(i,c,r)}};function $n(n,e){return n.replace(qn,(t,r)=>{let i=e[r];return i!=null?String(i):`<${r}?>`})}var qn=/\{\$([^}]+)}/g;function ue(n){let e=[];for(let[t,r]of Object.entries(n))Array.isArray(r)?r.forEach(i=>{e.push(encodeURIComponent(t)+"="+encodeURIComponent(i))}):e.push(encodeURIComponent(t)+"="+encodeURIComponent(r));return e.length?"&"+e.join("&"):""}function U(n){let e={};return n.replace(/^\?/,"").split("&").forEach(r=>{if(r){let[i,s]=r.split("=");e[decodeURIComponent(i)]=decodeURIComponent(s)}}),e}function x(n){let e=n.indexOf("?");if(!e)return"";let t=n.indexOf("#",e);return n.substring(e,t>0?t:void 0)}function Ft(n,e){let t=new Fe(n,e);return t.subscribe.bind(t)}var Fe=class{constructor(e,t){this.observers=[],this.unsubscribes=[],this.observerCount=0,this.task=Promise.resolve(),this.finalized=!1,this.onNoObservers=t,this.task.then(()=>{e(this)}).catch(r=>{this.error(r)})}next(e){this.forEachObserver(t=>{t.next(e)})}error(e){this.forEachObserver(t=>{t.error(e)}),this.close(e)}complete(){this.forEachObserver(e=>{e.complete()}),this.close()}subscribe(e,t,r){let i;if(e===void 0&&t===void 0&&r===void 0)throw new Error("Missing Observer.");Gn(e,["next","error","complete"])?i=e:i={next:e,error:t,complete:r},i.next===void 0&&(i.next=Ue),i.error===void 0&&(i.error=Ue),i.complete===void 0&&(i.complete=Ue);let s=this.unsubscribeOne.bind(this,this.observers.length);return this.finalized&&this.task.then(()=>{try{this.finalError?i.error(this.finalError):i.complete()}catch{}}),this.observers.push(i),s}unsubscribeOne(e){this.observers===void 0||this.observers[e]===void 0||(delete this.observers[e],this.observerCount-=1,this.observerCount===0&&this.onNoObservers!==void 0&&this.onNoObservers(this))}forEachObserver(e){if(!this.finalized)for(let t=0;t{if(this.observers!==void 0&&this.observers[e]!==void 0)try{t(this.observers[e])}catch(r){typeof console<"u"&&console.error&&console.error(r)}})}close(e){this.finalized||(this.finalized=!0,e!==void 0&&(this.finalError=e),this.task.then(()=>{this.observers=void 0,this.onNoObservers=void 0}))}};function Gn(n,e){if(typeof n!="object"||n===null)return!1;for(let t of e)if(t in n&&typeof n[t]=="function")return!0;return!1}function Ue(){}var ms=4*60*60*1e3;function F(n){return n&&n._delegate?n._delegate:n}var w=class{constructor(e,t,r){this.name=e,this.instanceFactory=t,this.type=r,this.multipleInstances=!1,this.serviceProps={},this.instantiationMode="LAZY",this.onInstanceCreated=null}setInstantiationMode(e){return this.instantiationMode=e,this}setMultipleInstances(e){return this.multipleInstances=e,this}setServiceProps(e){return this.serviceProps=e,this}setInstanceCreatedCallback(e){return this.onInstanceCreated=e,this}};var Kn=[],d;(function(n){n[n.DEBUG=0]="DEBUG",n[n.VERBOSE=1]="VERBOSE",n[n.INFO=2]="INFO",n[n.WARN=3]="WARN",n[n.ERROR=4]="ERROR",n[n.SILENT=5]="SILENT"})(d||(d={}));var Jn={debug:d.DEBUG,verbose:d.VERBOSE,info:d.INFO,warn:d.WARN,error:d.ERROR,silent:d.SILENT},Yn=d.INFO,Xn={[d.DEBUG]:"log",[d.VERBOSE]:"log",[d.INFO]:"info",[d.WARN]:"warn",[d.ERROR]:"error"},Qn=(n,e,...t)=>{if(ee.some(t=>n instanceof t),Vt,jt;function er(){return Vt||(Vt=[IDBDatabase,IDBObjectStore,IDBIndex,IDBCursor,IDBTransaction])}function tr(){return jt||(jt=[IDBCursor.prototype.advance,IDBCursor.prototype.continue,IDBCursor.prototype.continuePrimaryKey])}var Ht=new WeakMap,We=new WeakMap,Wt=new WeakMap,He=new WeakMap,ze=new WeakMap;function nr(n){let e=new Promise((t,r)=>{let i=()=>{n.removeEventListener("success",s),n.removeEventListener("error",o)},s=()=>{t(v(n.result)),i()},o=()=>{r(n.error),i()};n.addEventListener("success",s),n.addEventListener("error",o)});return e.then(t=>{t instanceof IDBCursor&&Ht.set(t,n)}).catch(()=>{}),ze.set(e,n),e}function rr(n){if(We.has(n))return;let e=new Promise((t,r)=>{let i=()=>{n.removeEventListener("complete",s),n.removeEventListener("error",o),n.removeEventListener("abort",o)},s=()=>{t(),i()},o=()=>{r(n.error||new DOMException("AbortError","AbortError")),i()};n.addEventListener("complete",s),n.addEventListener("error",o),n.addEventListener("abort",o)});We.set(n,e)}var Be={get(n,e,t){if(n instanceof IDBTransaction){if(e==="done")return We.get(n);if(e==="objectStoreNames")return n.objectStoreNames||Wt.get(n);if(e==="store")return t.objectStoreNames[1]?void 0:t.objectStore(t.objectStoreNames[0])}return v(n[e])},set(n,e,t){return n[e]=t,!0},has(n,e){return n instanceof IDBTransaction&&(e==="done"||e==="store")?!0:e in n}};function Bt(n){Be=n(Be)}function ir(n){return n===IDBDatabase.prototype.transaction&&!("objectStoreNames"in IDBTransaction.prototype)?function(e,...t){let r=n.call(le(this),e,...t);return Wt.set(r,e.sort?e.sort():[e]),v(r)}:tr().includes(n)?function(...e){return n.apply(le(this),e),v(Ht.get(this))}:function(...e){return v(n.apply(le(this),e))}}function sr(n){return typeof n=="function"?ir(n):(n instanceof IDBTransaction&&rr(n),Zn(n,er())?new Proxy(n,Be):n)}function v(n){if(n instanceof IDBRequest)return nr(n);if(He.has(n))return He.get(n);let e=sr(n);return e!==n&&(He.set(n,e),ze.set(e,n)),e}var le=n=>ze.get(n);function $t(n,e,{blocked:t,upgrade:r,blocking:i,terminated:s}={}){let o=indexedDB.open(n,e),c=v(o);return r&&o.addEventListener("upgradeneeded",a=>{r(v(o.result),a.oldVersion,a.newVersion,v(o.transaction),a)}),t&&o.addEventListener("blocked",a=>t(a.oldVersion,a.newVersion,a)),c.then(a=>{s&&a.addEventListener("close",()=>s()),i&&a.addEventListener("versionchange",u=>i(u.oldVersion,u.newVersion,u))}).catch(()=>{}),c}var or=["get","getKey","getAll","getAllKeys","count"],ar=["put","add","delete","clear"],$e=new Map;function zt(n,e){if(!(n instanceof IDBDatabase&&!(e in n)&&typeof e=="string"))return;if($e.get(e))return $e.get(e);let t=e.replace(/FromIndex$/,""),r=e!==t,i=ar.includes(t);if(!(t in(r?IDBIndex:IDBObjectStore).prototype)||!(i||or.includes(t)))return;let s=async function(o,...c){let a=this.transaction(o,i?"readwrite":"readonly"),u=a.store;return r&&(u=u.index(c.shift())),(await Promise.all([u[t](...c),i&&a.done]))[0]};return $e.set(e,s),s}Bt(n=>({...n,get:(e,t,r)=>zt(e,t)||n.get(e,t,r),has:(e,t)=>!!zt(e,t)||n.has(e,t)}));var Ge=class{constructor(e){this.container=e}getPlatformInfoString(){return this.container.getProviders().map(t=>{if(ur(t)){let r=t.getImmediate();return`${r.library}/${r.version}`}else return null}).filter(t=>t).join(" ")}};function ur(n){let e=n.getComponent();return e?.type==="VERSION"}var Ke="@firebase/app",qt="0.10.10";var T=new V("@firebase/app"),lr="@firebase/app-compat",dr="@firebase/analytics-compat",hr="@firebase/analytics",fr="@firebase/app-check-compat",pr="@firebase/app-check",mr="@firebase/auth",gr="@firebase/auth-compat",_r="@firebase/database",Ir="@firebase/database-compat",vr="@firebase/functions",yr="@firebase/functions-compat",Er="@firebase/installations",wr="@firebase/installations-compat",Tr="@firebase/messaging",br="@firebase/messaging-compat",Ar="@firebase/performance",Sr="@firebase/performance-compat",Rr="@firebase/remote-config",Or="@firebase/remote-config-compat",Pr="@firebase/storage",kr="@firebase/storage-compat",Cr="@firebase/firestore",Nr="@firebase/vertexai-preview",Dr="@firebase/firestore-compat",Lr="firebase",Mr="10.13.1";var Ur={[Ke]:"fire-core",[lr]:"fire-core-compat",[hr]:"fire-analytics",[dr]:"fire-analytics-compat",[pr]:"fire-app-check",[fr]:"fire-app-check-compat",[mr]:"fire-auth",[gr]:"fire-auth-compat",[_r]:"fire-rtdb",[Ir]:"fire-rtdb-compat",[vr]:"fire-fn",[yr]:"fire-fn-compat",[Er]:"fire-iid",[wr]:"fire-iid-compat",[Tr]:"fire-fcm",[br]:"fire-fcm-compat",[Ar]:"fire-perf",[Sr]:"fire-perf-compat",[Rr]:"fire-rc",[Or]:"fire-rc-compat",[Pr]:"fire-gcs",[kr]:"fire-gcs-compat",[Cr]:"fire-fst",[Dr]:"fire-fst-compat",[Nr]:"fire-vertex","fire-js":"fire-js",[Lr]:"fire-js-all"};var xr=new Map,Fr=new Map,Gt=new Map;function Kt(n,e){try{n.container.addComponent(e)}catch(t){T.debug(`Component ${e.name} failed to register with FirebaseApp ${n.name}`,t)}}function j(n){let e=n.name;if(Gt.has(e))return T.debug(`There were multiple attempts to register component ${e}.`),!1;Gt.set(e,n);for(let t of xr.values())Kt(t,n);for(let t of Fr.values())Kt(t,n);return!0}function A(n){return n.settings!==void 0}var Vr={"no-app":"No Firebase App '{$appName}' has been created - call initializeApp() first","bad-app-name":"Illegal App name: '{$appName}'","duplicate-app":"Firebase App named '{$appName}' already exists with different options or config","app-deleted":"Firebase App named '{$appName}' already deleted","server-app-deleted":"Firebase Server App has been deleted","no-options":"Need to provide options, when not being deployed to hosting via source.","invalid-app-argument":"firebase.{$appName}() takes either no argument or a Firebase App instance.","invalid-log-argument":"First argument to `onLog` must be null or a function.","idb-open":"Error thrown when opening IndexedDB. Original error: {$originalErrorMessage}.","idb-get":"Error thrown when reading from IndexedDB. Original error: {$originalErrorMessage}.","idb-set":"Error thrown when writing to IndexedDB. Original error: {$originalErrorMessage}.","idb-delete":"Error thrown when deleting from IndexedDB. Original error: {$originalErrorMessage}.","finalization-registry-not-supported":"FirebaseServerApp deleteOnDeref field defined but the JS runtime does not support FinalizationRegistry.","invalid-server-app-environment":"FirebaseServerApp is not for use in browser environments."},Xe=new E("app","Firebase",Vr);var de=Mr;function b(n,e,t){var r;let i=(r=Ur[n])!==null&&r!==void 0?r:n;t&&(i+=`-${t}`);let s=i.match(/\s|\//),o=e.match(/\s|\//);if(s||o){let c=[`Unable to register library "${i}" with version "${e}":`];s&&c.push(`library name "${i}" contains illegal characters (whitespace or "/")`),s&&o&&c.push("and"),o&&c.push(`version name "${e}" contains illegal characters (whitespace or "/")`),T.warn(c.join(" "));return}j(new w(`${i}-version`,()=>({library:i,version:e}),"VERSION"))}var jr="firebase-heartbeat-database",Hr=1,G="firebase-heartbeat-store",qe=null;function Qt(){return qe||(qe=$t(jr,Hr,{upgrade:(n,e)=>{switch(e){case 0:try{n.createObjectStore(G)}catch(t){console.warn(t)}}}}).catch(n=>{throw Xe.create("idb-open",{originalErrorMessage:n.message})})),qe}async function Wr(n){try{let t=(await Qt()).transaction(G),r=await t.objectStore(G).get(Zt(n));return await t.done,r}catch(e){if(e instanceof I)T.warn(e.message);else{let t=Xe.create("idb-get",{originalErrorMessage:e?.message});T.warn(t.message)}}}async function Jt(n,e){try{let r=(await Qt()).transaction(G,"readwrite");await r.objectStore(G).put(e,Zt(n)),await r.done}catch(t){if(t instanceof I)T.warn(t.message);else{let r=Xe.create("idb-set",{originalErrorMessage:t?.message});T.warn(r.message)}}}function Zt(n){return`${n.name}!${n.options.appId}`}var Br=1024,zr=30*24*60*60*1e3,Je=class{constructor(e){this.container=e,this._heartbeatsCache=null;let t=this.container.getProvider("app").getImmediate();this._storage=new Ye(t),this._heartbeatsCachePromise=this._storage.read().then(r=>(this._heartbeatsCache=r,r))}async triggerHeartbeat(){var e,t;try{let i=this.container.getProvider("platform-logger").getImmediate().getPlatformInfoString(),s=Yt();return((e=this._heartbeatsCache)===null||e===void 0?void 0:e.heartbeats)==null&&(this._heartbeatsCache=await this._heartbeatsCachePromise,((t=this._heartbeatsCache)===null||t===void 0?void 0:t.heartbeats)==null)||this._heartbeatsCache.lastSentHeartbeatDate===s||this._heartbeatsCache.heartbeats.some(o=>o.date===s)?void 0:(this._heartbeatsCache.heartbeats.push({date:s,agent:i}),this._heartbeatsCache.heartbeats=this._heartbeatsCache.heartbeats.filter(o=>{let c=new Date(o.date).valueOf();return Date.now()-c<=zr}),this._storage.overwrite(this._heartbeatsCache))}catch(r){T.warn(r)}}async getHeartbeatsHeader(){var e;try{if(this._heartbeatsCache===null&&await this._heartbeatsCachePromise,((e=this._heartbeatsCache)===null||e===void 0?void 0:e.heartbeats)==null||this._heartbeatsCache.heartbeats.length===0)return"";let t=Yt(),{heartbeatsToSend:r,unsentEntries:i}=$r(this._heartbeatsCache.heartbeats),s=Ve(JSON.stringify({version:2,heartbeats:r}));return this._heartbeatsCache.lastSentHeartbeatDate=t,i.length>0?(this._heartbeatsCache.heartbeats=i,await this._storage.overwrite(this._heartbeatsCache)):(this._heartbeatsCache.heartbeats=[],this._storage.overwrite(this._heartbeatsCache)),s}catch(t){return T.warn(t),""}}};function Yt(){return new Date().toISOString().substring(0,10)}function $r(n,e=Br){let t=[],r=n.slice();for(let i of n){let s=t.find(o=>o.agent===i.agent);if(s){if(s.dates.push(i.date),Xt(t)>e){s.dates.pop();break}}else if(t.push({agent:i.agent,dates:[i.date]}),Xt(t)>e){t.pop();break}r=r.slice(1)}return{heartbeatsToSend:t,unsentEntries:r}}var Ye=class{constructor(e){this.app=e,this._canUseIndexedDBPromise=this.runIndexedDBEnvironmentCheck()}async runIndexedDBEnvironmentCheck(){return Ut()?xt().then(()=>!0).catch(()=>!1):!1}async read(){if(await this._canUseIndexedDBPromise){let t=await Wr(this.app);return t?.heartbeats?t:{heartbeats:[]}}else return{heartbeats:[]}}async overwrite(e){var t;if(await this._canUseIndexedDBPromise){let i=await this.read();return Jt(this.app,{lastSentHeartbeatDate:(t=e.lastSentHeartbeatDate)!==null&&t!==void 0?t:i.lastSentHeartbeatDate,heartbeats:e.heartbeats})}else return}async add(e){var t;if(await this._canUseIndexedDBPromise){let i=await this.read();return Jt(this.app,{lastSentHeartbeatDate:(t=e.lastSentHeartbeatDate)!==null&&t!==void 0?t:i.lastSentHeartbeatDate,heartbeats:[...i.heartbeats,...e.heartbeats]})}else return}};function Xt(n){return Ve(JSON.stringify({version:2,heartbeats:n})).length}function qr(n){j(new w("platform-logger",e=>new Ge(e),"PRIVATE")),j(new w("heartbeat",e=>new Je(e),"PRIVATE")),b(Ke,qt,n),b(Ke,qt,"esm2017"),b("fire-js","")}qr("");var Gr="firebase",Kr="10.13.1";b(Gr,Kr,"app");function he(n,e){var t={};for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&e.indexOf(r)<0&&(t[r]=n[r]);if(n!=null&&typeof Object.getOwnPropertySymbols=="function")for(var i=0,r=Object.getOwnPropertySymbols(n);i"u")return null;let n=navigator;return n.languages&&n.languages[0]||n.language||null}var C=class{constructor(e,t){this.shortDelay=e,this.longDelay=t,k(t>e,"Short delay should be less than long delay!"),this.isMobile=Nt()||Lt()}get(){return Xr()?this.isMobile?this.longDelay:this.shortDelay:Math.min(5e3,this.shortDelay)}};function Zr(n,e){k(n.emulator,"Emulator should always be set here");let{url:t}=n.emulator;return e?`${t}${e.startsWith("/")?e.slice(1):e}`:t}var ge=class{static initialize(e,t,r){this.fetchImpl=e,t&&(this.headersImpl=t),r&&(this.responseImpl=r)}static fetch(){if(this.fetchImpl)return this.fetchImpl;if(typeof self<"u"&&"fetch"in self)return self.fetch;if(typeof globalThis<"u"&&globalThis.fetch)return globalThis.fetch;if(typeof fetch<"u")return fetch;y("Could not find fetch implementation, make sure you call FetchProvider.initialize() with an appropriate polyfill")}static headers(){if(this.headersImpl)return this.headersImpl;if(typeof self<"u"&&"Headers"in self)return self.Headers;if(typeof globalThis<"u"&&globalThis.Headers)return globalThis.Headers;if(typeof Headers<"u")return Headers;y("Could not find Headers implementation, make sure you call FetchProvider.initialize() with an appropriate polyfill")}static response(){if(this.responseImpl)return this.responseImpl;if(typeof self<"u"&&"Response"in self)return self.Response;if(typeof globalThis<"u"&&globalThis.Response)return globalThis.Response;if(typeof Response<"u")return Response;y("Could not find Response implementation, make sure you call FetchProvider.initialize() with an appropriate polyfill")}};var ei={CREDENTIAL_MISMATCH:"custom-token-mismatch",MISSING_CUSTOM_TOKEN:"internal-error",INVALID_IDENTIFIER:"invalid-email",MISSING_CONTINUE_URI:"internal-error",INVALID_PASSWORD:"wrong-password",MISSING_PASSWORD:"missing-password",INVALID_LOGIN_CREDENTIALS:"invalid-credential",EMAIL_EXISTS:"email-already-in-use",PASSWORD_LOGIN_DISABLED:"operation-not-allowed",INVALID_IDP_RESPONSE:"invalid-credential",INVALID_PENDING_TOKEN:"invalid-credential",FEDERATED_USER_ID_ALREADY_LINKED:"credential-already-in-use",MISSING_REQ_TYPE:"internal-error",EMAIL_NOT_FOUND:"user-not-found",RESET_PASSWORD_EXCEED_LIMIT:"too-many-requests",EXPIRED_OOB_CODE:"expired-action-code",INVALID_OOB_CODE:"invalid-action-code",MISSING_OOB_CODE:"internal-error",CREDENTIAL_TOO_OLD_LOGIN_AGAIN:"requires-recent-login",INVALID_ID_TOKEN:"invalid-user-token",TOKEN_EXPIRED:"user-token-expired",USER_NOT_FOUND:"user-token-expired",TOO_MANY_ATTEMPTS_TRY_LATER:"too-many-requests",PASSWORD_DOES_NOT_MEET_REQUIREMENTS:"password-does-not-meet-requirements",INVALID_CODE:"invalid-verification-code",INVALID_SESSION_INFO:"invalid-verification-id",INVALID_TEMPORARY_PROOF:"invalid-credential",MISSING_SESSION_INFO:"missing-verification-id",SESSION_EXPIRED:"code-expired",MISSING_ANDROID_PACKAGE_NAME:"missing-android-pkg-name",UNAUTHORIZED_DOMAIN:"unauthorized-continue-uri",INVALID_OAUTH_CLIENT_ID:"invalid-oauth-client-id",ADMIN_ONLY_OPERATION:"admin-restricted-operation",INVALID_MFA_PENDING_CREDENTIAL:"invalid-multi-factor-session",MFA_ENROLLMENT_NOT_FOUND:"multi-factor-info-not-found",MISSING_MFA_ENROLLMENT_ID:"missing-multi-factor-info",MISSING_MFA_PENDING_CREDENTIAL:"missing-multi-factor-session",SECOND_FACTOR_EXISTS:"second-factor-already-in-use",SECOND_FACTOR_LIMIT_EXCEEDED:"maximum-second-factor-count-exceeded",BLOCKING_FUNCTION_ERROR_RESPONSE:"internal-error",RECAPTCHA_NOT_ENABLED:"recaptcha-not-enabled",MISSING_RECAPTCHA_TOKEN:"missing-recaptcha-token",INVALID_RECAPTCHA_TOKEN:"invalid-recaptcha-token",INVALID_RECAPTCHA_ACTION:"invalid-recaptcha-action",MISSING_CLIENT_TYPE:"missing-client-type",MISSING_RECAPTCHA_VERSION:"missing-recaptcha-version",INVALID_RECAPTCHA_VERSION:"invalid-recaptcha-version",INVALID_REQ_TYPE:"invalid-req-type"};var ti=new C(3e4,6e4);function f(n,e){return n.tenantId&&!e.tenantId?Object.assign(Object.assign({},e),{tenantId:n.tenantId}):e}async function m(n,e,t,r,i={}){return In(n,i,async()=>{let s={},o={};r&&(e==="GET"?o=r:s={body:JSON.stringify(r)});let c=ue(Object.assign({key:n.config.apiKey},o)).slice(1),a=await n._getAdditionalHeaders();return a["Content-Type"]="application/json",n.languageCode&&(a["X-Firebase-Locale"]=n.languageCode),ge.fetch()(vn(n,n.config.apiHost,t,c),Object.assign({method:e,headers:a,referrerPolicy:"no-referrer"},s))})}async function In(n,e,t){n._canInitEmulator=!1;let r=Object.assign(Object.assign({},ei),e);try{let i=new et(n),s=await Promise.race([t(),i.promise]);i.clearNetworkTimeout();let o=await s.json();if("needConfirmation"in o)throw K(n,"account-exists-with-different-credential",o);if(s.ok&&!("errorMessage"in o))return o;{let c=s.ok?o.errorMessage:o.error.message,[a,u]=c.split(" : ");if(a==="FEDERATED_USER_ID_ALREADY_LINKED")throw K(n,"credential-already-in-use",o);if(a==="EMAIL_EXISTS")throw K(n,"email-already-in-use",o);if(a==="USER_DISABLED")throw K(n,"user-disabled",o);let h=r[a]||a.toLowerCase().replace(/[_\s]+/g,"-");if(u)throw _n(n,h,u);R(n,h)}}catch(i){if(i instanceof I)throw i;R(n,"network-request-failed",{message:String(i)})}}async function M(n,e,t,r,i={}){let s=await m(n,e,t,r,i);return"mfaPendingCredential"in s&&R(n,"multi-factor-auth-required",{_serverResponse:s}),s}function vn(n,e,t,r){let i=`${e}${t}?${r}`;return n.config.emulator?Zr(n.config,i):`${n.config.apiScheme}://${i}`}function ni(n){switch(n){case"ENFORCE":return"ENFORCE";case"AUDIT":return"AUDIT";case"OFF":return"OFF";default:return"ENFORCEMENT_STATE_UNSPECIFIED"}}var et=class{constructor(e){this.auth=e,this.timer=null,this.promise=new Promise((t,r)=>{this.timer=setTimeout(()=>r(H(this.auth,"network-request-failed")),ti.get())})}clearNetworkTimeout(){clearTimeout(this.timer)}};function K(n,e,t){let r={appName:n.name};t.email&&(r.email=t.email),t.phoneNumber&&(r.phoneNumber=t.phoneNumber);let i=H(n,e,r);return i.customData._tokenResponse=t,i}function tn(n){return n!==void 0&&n.enterprise!==void 0}var tt=class{constructor(e){if(this.siteKey="",this.recaptchaEnforcementState=[],e.recaptchaKey===void 0)throw new Error("recaptchaKey undefined");this.siteKey=e.recaptchaKey.split("/")[3],this.recaptchaEnforcementState=e.recaptchaEnforcementState}getProviderEnforcementState(e){if(!this.recaptchaEnforcementState||this.recaptchaEnforcementState.length===0)return null;for(let t of this.recaptchaEnforcementState)if(t.provider&&t.provider===e)return ni(t.enforcementState);return null}isProviderEnabled(e){return this.getProviderEnforcementState(e)==="ENFORCE"||this.getProviderEnforcementState(e)==="AUDIT"}};async function ri(n,e){return m(n,"GET","/v2/recaptchaConfig",f(n,e))}async function ii(n,e){return m(n,"POST","/v1/accounts:delete",e)}async function yn(n,e){return m(n,"POST","/v1/accounts:lookup",e)}function J(n){if(n)try{let e=new Date(Number(n));if(!isNaN(e.getTime()))return e.toUTCString()}catch{}}async function En(n,e=!1){let t=F(n),r=await t.getIdToken(e),i=yt(r);l(i&&i.exp&&i.auth_time&&i.iat,t.auth,"internal-error");let s=typeof i.firebase=="object"?i.firebase:void 0,o=s?.sign_in_provider;return{claims:i,token:r,authTime:J(Qe(i.auth_time)),issuedAtTime:J(Qe(i.iat)),expirationTime:J(Qe(i.exp)),signInProvider:o||null,signInSecondFactor:s?.sign_in_second_factor||null}}function Qe(n){return Number(n)*1e3}function yt(n){let[e,t,r]=n.split(".");if(e===void 0||t===void 0||r===void 0)return pe("JWT malformed, contained fewer than 3 sections"),null;try{let i=je(t);return i?JSON.parse(i):(pe("Failed to decode base64 JWT payload"),null)}catch(i){return pe("Caught error parsing JWT payload as JSON",i?.toString()),null}}function nn(n){let e=yt(n);return l(e,"internal-error"),l(typeof e.exp<"u","internal-error"),l(typeof e.iat<"u","internal-error"),Number(e.exp)-Number(e.iat)}async function X(n,e,t=!1){if(t)return e;try{return await e}catch(r){throw r instanceof I&&si(r)&&n.auth.currentUser===n&&await n.auth.signOut(),r}}function si({code:n}){return n==="auth/user-disabled"||n==="auth/user-token-expired"}var nt=class{constructor(e){this.user=e,this.isRunning=!1,this.timerId=null,this.errorBackoff=3e4}_start(){this.isRunning||(this.isRunning=!0,this.schedule())}_stop(){this.isRunning&&(this.isRunning=!1,this.timerId!==null&&clearTimeout(this.timerId))}getInterval(e){var t;if(e){let r=this.errorBackoff;return this.errorBackoff=Math.min(this.errorBackoff*2,96e4),r}else{this.errorBackoff=3e4;let i=((t=this.user.stsTokenManager.expirationTime)!==null&&t!==void 0?t:0)-Date.now()-3e5;return Math.max(0,i)}}schedule(e=!1){if(!this.isRunning)return;let t=this.getInterval(e);this.timerId=setTimeout(async()=>{await this.iteration()},t)}async iteration(){try{await this.user.getIdToken(!0)}catch(e){e?.code==="auth/network-request-failed"&&this.schedule(!0);return}this.schedule()}};var Q=class{constructor(e,t){this.createdAt=e,this.lastLoginAt=t,this._initializeTime()}_initializeTime(){this.lastSignInTime=J(this.lastLoginAt),this.creationTime=J(this.createdAt)}_copy(e){this.createdAt=e.createdAt,this.lastLoginAt=e.lastLoginAt,this._initializeTime()}toJSON(){return{createdAt:this.createdAt,lastLoginAt:this.lastLoginAt}}};async function _e(n){var e;let t=n.auth,r=await n.getIdToken(),i=await X(n,yn(t,{idToken:r}));l(i?.users.length,t,"internal-error");let s=i.users[0];n._notifyReloadListener(s);let o=!((e=s.providerUserInfo)===null||e===void 0)&&e.length?Tn(s.providerUserInfo):[],c=oi(n.providerData,o),a=n.isAnonymous,u=!(n.email&&s.passwordHash)&&!c?.length,h=a?u:!1,p={uid:s.localId,displayName:s.displayName||null,photoURL:s.photoUrl||null,email:s.email||null,emailVerified:s.emailVerified||!1,phoneNumber:s.phoneNumber||null,tenantId:s.tenantId||null,providerData:c,metadata:new Q(s.createdAt,s.lastLoginAt),isAnonymous:h};Object.assign(n,p)}async function wn(n){let e=F(n);await _e(e),await e.auth._persistUserIfCurrent(e),e.auth._notifyListenersIfCurrent(e)}function oi(n,e){return[...n.filter(r=>!e.some(i=>i.providerId===r.providerId)),...e]}function Tn(n){return n.map(e=>{var{providerId:t}=e,r=he(e,["providerId"]);return{providerId:t,uid:r.rawId||"",displayName:r.displayName||null,email:r.email||null,phoneNumber:r.phoneNumber||null,photoURL:r.photoUrl||null}})}async function ai(n,e){let t=await In(n,{},async()=>{let r=ue({grant_type:"refresh_token",refresh_token:e}).slice(1),{tokenApiHost:i,apiKey:s}=n.config,o=vn(n,i,"/v1/token",`key=${s}`),c=await n._getAdditionalHeaders();return c["Content-Type"]="application/x-www-form-urlencoded",ge.fetch()(o,{method:"POST",headers:c,body:r})});return{accessToken:t.access_token,expiresIn:t.expires_in,refreshToken:t.refresh_token}}async function ci(n,e){return m(n,"POST","/v2/accounts:revokeToken",f(n,e))}var Y=class n{constructor(){this.refreshToken=null,this.accessToken=null,this.expirationTime=null}get isExpired(){return!this.expirationTime||Date.now()>this.expirationTime-3e4}updateFromServerResponse(e){l(e.idToken,"internal-error"),l(typeof e.idToken<"u","internal-error"),l(typeof e.refreshToken<"u","internal-error");let t="expiresIn"in e&&typeof e.expiresIn<"u"?Number(e.expiresIn):nn(e.idToken);this.updateTokensAndExpiration(e.idToken,e.refreshToken,t)}updateFromIdToken(e){l(e.length!==0,"internal-error");let t=nn(e);this.updateTokensAndExpiration(e,null,t)}async getToken(e,t=!1){return!t&&this.accessToken&&!this.isExpired?this.accessToken:(l(this.refreshToken,e,"user-token-expired"),this.refreshToken?(await this.refresh(e,this.refreshToken),this.accessToken):null)}clearRefreshToken(){this.refreshToken=null}async refresh(e,t){let{accessToken:r,refreshToken:i,expiresIn:s}=await ai(e,t);this.updateTokensAndExpiration(r,i,Number(s))}updateTokensAndExpiration(e,t,r){this.refreshToken=t||null,this.accessToken=e||null,this.expirationTime=Date.now()+r*1e3}static fromJSON(e,t){let{refreshToken:r,accessToken:i,expirationTime:s}=t,o=new n;return r&&(l(typeof r=="string","internal-error",{appName:e}),o.refreshToken=r),i&&(l(typeof i=="string","internal-error",{appName:e}),o.accessToken=i),s&&(l(typeof s=="number","internal-error",{appName:e}),o.expirationTime=s),o}toJSON(){return{refreshToken:this.refreshToken,accessToken:this.accessToken,expirationTime:this.expirationTime}}_assign(e){this.accessToken=e.accessToken,this.refreshToken=e.refreshToken,this.expirationTime=e.expirationTime}_clone(){return Object.assign(new n,this.toJSON())}_performRefresh(){return y("not implemented")}};function S(n,e){l(typeof n=="string"||typeof n>"u","internal-error",{appName:e})}var z=class n{constructor(e){var{uid:t,auth:r,stsTokenManager:i}=e,s=he(e,["uid","auth","stsTokenManager"]);this.providerId="firebase",this.proactiveRefresh=new nt(this),this.reloadUserInfo=null,this.reloadListener=null,this.uid=t,this.auth=r,this.stsTokenManager=i,this.accessToken=i.accessToken,this.displayName=s.displayName||null,this.email=s.email||null,this.emailVerified=s.emailVerified||!1,this.phoneNumber=s.phoneNumber||null,this.photoURL=s.photoURL||null,this.isAnonymous=s.isAnonymous||!1,this.tenantId=s.tenantId||null,this.providerData=s.providerData?[...s.providerData]:[],this.metadata=new Q(s.createdAt||void 0,s.lastLoginAt||void 0)}async getIdToken(e){let t=await X(this,this.stsTokenManager.getToken(this.auth,e));return l(t,this.auth,"internal-error"),this.accessToken!==t&&(this.accessToken=t,await this.auth._persistUserIfCurrent(this),this.auth._notifyListenersIfCurrent(this)),t}getIdTokenResult(e){return En(this,e)}reload(){return wn(this)}_assign(e){this!==e&&(l(this.uid===e.uid,this.auth,"internal-error"),this.displayName=e.displayName,this.photoURL=e.photoURL,this.email=e.email,this.emailVerified=e.emailVerified,this.phoneNumber=e.phoneNumber,this.isAnonymous=e.isAnonymous,this.tenantId=e.tenantId,this.providerData=e.providerData.map(t=>Object.assign({},t)),this.metadata._copy(e.metadata),this.stsTokenManager._assign(e.stsTokenManager))}_clone(e){let t=new n(Object.assign(Object.assign({},this),{auth:e,stsTokenManager:this.stsTokenManager._clone()}));return t.metadata._copy(this.metadata),t}_onReload(e){l(!this.reloadListener,this.auth,"internal-error"),this.reloadListener=e,this.reloadUserInfo&&(this._notifyReloadListener(this.reloadUserInfo),this.reloadUserInfo=null)}_notifyReloadListener(e){this.reloadListener?this.reloadListener(e):this.reloadUserInfo=e}_startProactiveRefresh(){this.proactiveRefresh._start()}_stopProactiveRefresh(){this.proactiveRefresh._stop()}async _updateTokensIfNecessary(e,t=!1){let r=!1;e.idToken&&e.idToken!==this.stsTokenManager.accessToken&&(this.stsTokenManager.updateFromServerResponse(e),r=!0),t&&await _e(this),await this.auth._persistUserIfCurrent(this),r&&this.auth._notifyListenersIfCurrent(this)}async delete(){if(A(this.auth.app))return Promise.reject(W(this.auth));let e=await this.getIdToken();return await X(this,ii(this.auth,{idToken:e})),this.stsTokenManager.clearRefreshToken(),this.auth.signOut()}toJSON(){return Object.assign(Object.assign({uid:this.uid,email:this.email||void 0,emailVerified:this.emailVerified,displayName:this.displayName||void 0,isAnonymous:this.isAnonymous,photoURL:this.photoURL||void 0,phoneNumber:this.phoneNumber||void 0,tenantId:this.tenantId||void 0,providerData:this.providerData.map(e=>Object.assign({},e)),stsTokenManager:this.stsTokenManager.toJSON(),_redirectEventId:this._redirectEventId},this.metadata.toJSON()),{apiKey:this.auth.config.apiKey,appName:this.auth.name})}get refreshToken(){return this.stsTokenManager.refreshToken||""}static _fromJSON(e,t){var r,i,s,o,c,a,u,h;let p=(r=t.displayName)!==null&&r!==void 0?r:void 0,_=(i=t.email)!==null&&i!==void 0?i:void 0,O=(s=t.phoneNumber)!==null&&s!==void 0?s:void 0,ce=(o=t.photoURL)!==null&&o!==void 0?o:void 0,Tt=(c=t.tenantId)!==null&&c!==void 0?c:void 0,Ne=(a=t._redirectEventId)!==null&&a!==void 0?a:void 0,bt=(u=t.createdAt)!==null&&u!==void 0?u:void 0,At=(h=t.lastLoginAt)!==null&&h!==void 0?h:void 0,{uid:De,emailVerified:St,isAnonymous:Rt,providerData:Le,stsTokenManager:Ot}=t;l(De&&Ot,e,"internal-error");let Mn=Y.fromJSON(this.name,Ot);l(typeof De=="string",e,"internal-error"),S(p,e.name),S(_,e.name),l(typeof St=="boolean",e,"internal-error"),l(typeof Rt=="boolean",e,"internal-error"),S(O,e.name),S(ce,e.name),S(Tt,e.name),S(Ne,e.name),S(bt,e.name),S(At,e.name);let Me=new n({uid:De,auth:e,email:_,emailVerified:St,displayName:p,isAnonymous:Rt,photoURL:ce,phoneNumber:O,tenantId:Tt,stsTokenManager:Mn,createdAt:bt,lastLoginAt:At});return Le&&Array.isArray(Le)&&(Me.providerData=Le.map(Un=>Object.assign({},Un))),Ne&&(Me._redirectEventId=Ne),Me}static async _fromIdTokenResponse(e,t,r=!1){let i=new Y;i.updateFromServerResponse(t);let s=new n({uid:t.localId,auth:e,stsTokenManager:i,isAnonymous:r});return await _e(s),s}static async _fromGetAccountInfoResponse(e,t,r){let i=t.users[0];l(i.localId!==void 0,"internal-error");let s=i.providerUserInfo!==void 0?Tn(i.providerUserInfo):[],o=!(i.email&&i.passwordHash)&&!s?.length,c=new Y;c.updateFromIdToken(r);let a=new n({uid:i.localId,auth:e,stsTokenManager:c,isAnonymous:o}),u={uid:i.localId,displayName:i.displayName||null,photoURL:i.photoUrl||null,email:i.email||null,emailVerified:i.emailVerified||!1,phoneNumber:i.phoneNumber||null,tenantId:i.tenantId||null,providerData:s,metadata:new Q(i.createdAt,i.lastLoginAt),isAnonymous:!(i.email&&i.passwordHash)&&!s?.length};return Object.assign(a,u),a}};var rn=new Map;function P(n){k(n instanceof Function,"Expected a class definition");let e=rn.get(n);return e?(k(e instanceof n,"Instance stored in cache mismatched with class"),e):(e=new n,rn.set(n,e),e)}var Ie=class{constructor(){this.type="NONE",this.storage={}}async _isAvailable(){return!0}async _set(e,t){this.storage[e]=t}async _get(e){let t=this.storage[e];return t===void 0?null:t}async _remove(e){delete this.storage[e]}_addListener(e,t){}_removeListener(e,t){}};Ie.type="NONE";var rt=Ie;function Ze(n,e,t){return`firebase:${n}:${e}:${t}`}var ve=class n{constructor(e,t,r){this.persistence=e,this.auth=t,this.userKey=r;let{config:i,name:s}=this.auth;this.fullUserKey=Ze(this.userKey,i.apiKey,s),this.fullPersistenceKey=Ze("persistence",i.apiKey,s),this.boundEventHandler=t._onStorageEvent.bind(t),this.persistence._addListener(this.fullUserKey,this.boundEventHandler)}setCurrentUser(e){return this.persistence._set(this.fullUserKey,e.toJSON())}async getCurrentUser(){let e=await this.persistence._get(this.fullUserKey);return e?z._fromJSON(this.auth,e):null}removeCurrentUser(){return this.persistence._remove(this.fullUserKey)}savePersistenceForRedirect(){return this.persistence._set(this.fullPersistenceKey,this.persistence.type)}async setPersistence(e){if(this.persistence===e)return;let t=await this.getCurrentUser();if(await this.removeCurrentUser(),this.persistence=e,t)return this.setCurrentUser(t)}delete(){this.persistence._removeListener(this.fullUserKey,this.boundEventHandler)}static async create(e,t,r="authUser"){if(!t.length)return new n(P(rt),e,r);let i=(await Promise.all(t.map(async u=>{if(await u._isAvailable())return u}))).filter(u=>u),s=i[0]||P(rt),o=Ze(r,e.config.apiKey,e.name),c=null;for(let u of t)try{let h=await u._get(o);if(h){let p=z._fromJSON(e,h);u!==s&&(c=p),s=u;break}}catch{}let a=i.filter(u=>u._shouldAllowMigration);return!s._shouldAllowMigration||!a.length?new n(s,e,r):(s=a[0],c&&await s._set(o,c.toJSON()),await Promise.all(t.map(async u=>{if(u!==s)try{await u._remove(o)}catch{}})),new n(s,e,r))}};function sn(n){let e=n.toLowerCase();if(e.includes("opera/")||e.includes("opr/")||e.includes("opios/"))return"Opera";if(bn(e))return"IEMobile";if(e.includes("msie")||e.includes("trident/"))return"IE";if(e.includes("edge/"))return"Edge";if(ui(e))return"Firefox";if(e.includes("silk/"))return"Silk";if(Sn(e))return"Blackberry";if(Rn(e))return"Webos";if(li(e))return"Safari";if((e.includes("chrome/")||di(e))&&!e.includes("edge/"))return"Chrome";if(An(e))return"Android";{let t=/([a-zA-Z\d\.]+)\/[a-zA-Z\d\.]*$/,r=n.match(t);if(r?.length===2)return r[1]}return"Other"}function ui(n=g()){return/firefox\//i.test(n)}function li(n=g()){let e=n.toLowerCase();return e.includes("safari/")&&!e.includes("chrome/")&&!e.includes("crios/")&&!e.includes("android")}function di(n=g()){return/crios\//i.test(n)}function bn(n=g()){return/iemobile/i.test(n)}function An(n=g()){return/android/i.test(n)}function Sn(n=g()){return/blackberry/i.test(n)}function Rn(n=g()){return/webos/i.test(n)}function hi(n=g()){return/iphone|ipad|ipod/i.test(n)||/macintosh/i.test(n)&&/mobile/i.test(n)}function fi(){return Mt()&&document.documentMode===10}function pi(n=g()){return hi(n)||An(n)||Rn(n)||Sn(n)||/windows phone/i.test(n)||bn(n)}function On(n,e=[]){let t;switch(n){case"Browser":t=sn(g());break;case"Worker":t=`${sn(g())}-${n}`;break;default:t=n}let r=e.length?e.join(","):"FirebaseCore-web";return`${t}/JsCore/${de}/${r}`}var it=class{constructor(e){this.auth=e,this.queue=[]}pushCallback(e,t){let r=s=>new Promise((o,c)=>{try{let a=e(s);o(a)}catch(a){c(a)}});r.onAbort=t,this.queue.push(r);let i=this.queue.length-1;return()=>{this.queue[i]=()=>Promise.resolve()}}async runMiddleware(e){if(this.auth.currentUser===e)return;let t=[];try{for(let r of this.queue)await r(e),r.onAbort&&t.push(r.onAbort)}catch(r){t.reverse();for(let i of t)try{i()}catch{}throw this.auth._errorFactory.create("login-blocked",{originalMessage:r?.message})}}};async function mi(n,e={}){return m(n,"GET","/v2/passwordPolicy",f(n,e))}var gi=6,st=class{constructor(e){var t,r,i,s;let o=e.customStrengthOptions;this.customStrengthOptions={},this.customStrengthOptions.minPasswordLength=(t=o.minPasswordLength)!==null&&t!==void 0?t:gi,o.maxPasswordLength&&(this.customStrengthOptions.maxPasswordLength=o.maxPasswordLength),o.containsLowercaseCharacter!==void 0&&(this.customStrengthOptions.containsLowercaseLetter=o.containsLowercaseCharacter),o.containsUppercaseCharacter!==void 0&&(this.customStrengthOptions.containsUppercaseLetter=o.containsUppercaseCharacter),o.containsNumericCharacter!==void 0&&(this.customStrengthOptions.containsNumericCharacter=o.containsNumericCharacter),o.containsNonAlphanumericCharacter!==void 0&&(this.customStrengthOptions.containsNonAlphanumericCharacter=o.containsNonAlphanumericCharacter),this.enforcementState=e.enforcementState,this.enforcementState==="ENFORCEMENT_STATE_UNSPECIFIED"&&(this.enforcementState="OFF"),this.allowedNonAlphanumericCharacters=(i=(r=e.allowedNonAlphanumericCharacters)===null||r===void 0?void 0:r.join(""))!==null&&i!==void 0?i:"",this.forceUpgradeOnSignin=(s=e.forceUpgradeOnSignin)!==null&&s!==void 0?s:!1,this.schemaVersion=e.schemaVersion}validatePassword(e){var t,r,i,s,o,c;let a={isValid:!0,passwordPolicy:this};return this.validatePasswordLengthOptions(e,a),this.validatePasswordCharacterOptions(e,a),a.isValid&&(a.isValid=(t=a.meetsMinPasswordLength)!==null&&t!==void 0?t:!0),a.isValid&&(a.isValid=(r=a.meetsMaxPasswordLength)!==null&&r!==void 0?r:!0),a.isValid&&(a.isValid=(i=a.containsLowercaseLetter)!==null&&i!==void 0?i:!0),a.isValid&&(a.isValid=(s=a.containsUppercaseLetter)!==null&&s!==void 0?s:!0),a.isValid&&(a.isValid=(o=a.containsNumericCharacter)!==null&&o!==void 0?o:!0),a.isValid&&(a.isValid=(c=a.containsNonAlphanumericCharacter)!==null&&c!==void 0?c:!0),a}validatePasswordLengthOptions(e,t){let r=this.customStrengthOptions.minPasswordLength,i=this.customStrengthOptions.maxPasswordLength;r&&(t.meetsMinPasswordLength=e.length>=r),i&&(t.meetsMaxPasswordLength=e.length<=i)}validatePasswordCharacterOptions(e,t){this.updatePasswordCharacterOptionsStatuses(t,!1,!1,!1,!1);let r;for(let i=0;i="a"&&r<="z",r>="A"&&r<="Z",r>="0"&&r<="9",this.allowedNonAlphanumericCharacters.includes(r))}updatePasswordCharacterOptionsStatuses(e,t,r,i,s){this.customStrengthOptions.containsLowercaseLetter&&(e.containsLowercaseLetter||(e.containsLowercaseLetter=t)),this.customStrengthOptions.containsUppercaseLetter&&(e.containsUppercaseLetter||(e.containsUppercaseLetter=r)),this.customStrengthOptions.containsNumericCharacter&&(e.containsNumericCharacter||(e.containsNumericCharacter=i)),this.customStrengthOptions.containsNonAlphanumericCharacter&&(e.containsNonAlphanumericCharacter||(e.containsNonAlphanumericCharacter=s))}};var ot=class{constructor(e,t,r,i){this.app=e,this.heartbeatServiceProvider=t,this.appCheckServiceProvider=r,this.config=i,this.currentUser=null,this.emulatorConfig=null,this.operations=Promise.resolve(),this.authStateSubscription=new ye(this),this.idTokenSubscription=new ye(this),this.beforeStateQueue=new it(this),this.redirectUser=null,this.isProactiveRefreshEnabled=!1,this.EXPECTED_PASSWORD_POLICY_SCHEMA_VERSION=1,this._canInitEmulator=!0,this._isInitialized=!1,this._deleted=!1,this._initializationPromise=null,this._popupRedirectResolver=null,this._errorFactory=gn,this._agentRecaptchaConfig=null,this._tenantRecaptchaConfigs={},this._projectPasswordPolicy=null,this._tenantPasswordPolicies={},this.lastNotifiedUid=void 0,this.languageCode=null,this.tenantId=null,this.settings={appVerificationDisabledForTesting:!1},this.frameworks=[],this.name=e.name,this.clientVersion=i.sdkClientVersion}_initializeWithPersistence(e,t){return t&&(this._popupRedirectResolver=P(t)),this._initializationPromise=this.queue(async()=>{var r,i;if(!this._deleted&&(this.persistenceManager=await ve.create(this,e),!this._deleted)){if(!((r=this._popupRedirectResolver)===null||r===void 0)&&r._shouldInitProactively)try{await this._popupRedirectResolver._initialize(this)}catch{}await this.initializeCurrentUser(t),this.lastNotifiedUid=((i=this.currentUser)===null||i===void 0?void 0:i.uid)||null,!this._deleted&&(this._isInitialized=!0)}}),this._initializationPromise}async _onStorageEvent(){if(this._deleted)return;let e=await this.assertedPersistence.getCurrentUser();if(!(!this.currentUser&&!e)){if(this.currentUser&&e&&this.currentUser.uid===e.uid){this._currentUser._assign(e),await this.currentUser.getIdToken();return}await this._updateCurrentUser(e,!0)}}async initializeCurrentUserFromIdToken(e){try{let t=await yn(this,{idToken:e}),r=await z._fromGetAccountInfoResponse(this,t,e);await this.directlySetCurrentUser(r)}catch(t){console.warn("FirebaseServerApp could not login user with provided authIdToken: ",t),await this.directlySetCurrentUser(null)}}async initializeCurrentUser(e){var t;if(A(this.app)){let o=this.app.settings.authIdToken;return o?new Promise(c=>{setTimeout(()=>this.initializeCurrentUserFromIdToken(o).then(c,c))}):this.directlySetCurrentUser(null)}let r=await this.assertedPersistence.getCurrentUser(),i=r,s=!1;if(e&&this.config.authDomain){await this.getOrInitRedirectPersistenceManager();let o=(t=this.redirectUser)===null||t===void 0?void 0:t._redirectEventId,c=i?._redirectEventId,a=await this.tryRedirectSignIn(e);(!o||o===c)&&a?.user&&(i=a.user,s=!0)}if(!i)return this.directlySetCurrentUser(null);if(!i._redirectEventId){if(s)try{await this.beforeStateQueue.runMiddleware(i)}catch(o){i=r,this._popupRedirectResolver._overrideRedirectResult(this,()=>Promise.reject(o))}return i?this.reloadAndSetCurrentUserOrClear(i):this.directlySetCurrentUser(null)}return l(this._popupRedirectResolver,this,"argument-error"),await this.getOrInitRedirectPersistenceManager(),this.redirectUser&&this.redirectUser._redirectEventId===i._redirectEventId?this.directlySetCurrentUser(i):this.reloadAndSetCurrentUserOrClear(i)}async tryRedirectSignIn(e){let t=null;try{t=await this._popupRedirectResolver._completeRedirectFn(this,e,!0)}catch{await this._setRedirectUser(null)}return t}async reloadAndSetCurrentUserOrClear(e){try{await _e(e)}catch(t){if(t?.code!=="auth/network-request-failed")return this.directlySetCurrentUser(null)}return this.directlySetCurrentUser(e)}useDeviceLanguage(){this.languageCode=Qr()}async _delete(){this._deleted=!0}async updateCurrentUser(e){if(A(this.app))return Promise.reject(W(this));let t=e?F(e):null;return t&&l(t.auth.config.apiKey===this.config.apiKey,this,"invalid-user-token"),this._updateCurrentUser(t&&t._clone(this))}async _updateCurrentUser(e,t=!1){if(!this._deleted)return e&&l(this.tenantId===e.tenantId,this,"tenant-id-mismatch"),t||await this.beforeStateQueue.runMiddleware(e),this.queue(async()=>{await this.directlySetCurrentUser(e),this.notifyAuthListeners()})}async signOut(){return A(this.app)?Promise.reject(W(this)):(await this.beforeStateQueue.runMiddleware(null),(this.redirectPersistenceManager||this._popupRedirectResolver)&&await this._setRedirectUser(null),this._updateCurrentUser(null,!0))}setPersistence(e){return A(this.app)?Promise.reject(W(this)):this.queue(async()=>{await this.assertedPersistence.setPersistence(P(e))})}_getRecaptchaConfig(){return this.tenantId==null?this._agentRecaptchaConfig:this._tenantRecaptchaConfigs[this.tenantId]}async validatePassword(e){this._getPasswordPolicyInternal()||await this._updatePasswordPolicy();let t=this._getPasswordPolicyInternal();return t.schemaVersion!==this.EXPECTED_PASSWORD_POLICY_SCHEMA_VERSION?Promise.reject(this._errorFactory.create("unsupported-password-policy-schema-version",{})):t.validatePassword(e)}_getPasswordPolicyInternal(){return this.tenantId===null?this._projectPasswordPolicy:this._tenantPasswordPolicies[this.tenantId]}async _updatePasswordPolicy(){let e=await mi(this),t=new st(e);this.tenantId===null?this._projectPasswordPolicy=t:this._tenantPasswordPolicies[this.tenantId]=t}_getPersistence(){return this.assertedPersistence.persistence.type}_updateErrorMap(e){this._errorFactory=new E("auth","Firebase",e())}onAuthStateChanged(e,t,r){return this.registerStateListener(this.authStateSubscription,e,t,r)}beforeAuthStateChanged(e,t){return this.beforeStateQueue.pushCallback(e,t)}onIdTokenChanged(e,t,r){return this.registerStateListener(this.idTokenSubscription,e,t,r)}authStateReady(){return new Promise((e,t)=>{if(this.currentUser)e();else{let r=this.onAuthStateChanged(()=>{r(),e()},t)}})}async revokeAccessToken(e){if(this.currentUser){let t=await this.currentUser.getIdToken(),r={providerId:"apple.com",tokenType:"ACCESS_TOKEN",token:e,idToken:t};this.tenantId!=null&&(r.tenantId=this.tenantId),await ci(this,r)}}toJSON(){var e;return{apiKey:this.config.apiKey,authDomain:this.config.authDomain,appName:this.name,currentUser:(e=this._currentUser)===null||e===void 0?void 0:e.toJSON()}}async _setRedirectUser(e,t){let r=await this.getOrInitRedirectPersistenceManager(t);return e===null?r.removeCurrentUser():r.setCurrentUser(e)}async getOrInitRedirectPersistenceManager(e){if(!this.redirectPersistenceManager){let t=e&&P(e)||this._popupRedirectResolver;l(t,this,"argument-error"),this.redirectPersistenceManager=await ve.create(this,[P(t._redirectPersistence)],"redirectUser"),this.redirectUser=await this.redirectPersistenceManager.getCurrentUser()}return this.redirectPersistenceManager}async _redirectUserForId(e){var t,r;return this._isInitialized&&await this.queue(async()=>{}),((t=this._currentUser)===null||t===void 0?void 0:t._redirectEventId)===e?this._currentUser:((r=this.redirectUser)===null||r===void 0?void 0:r._redirectEventId)===e?this.redirectUser:null}async _persistUserIfCurrent(e){if(e===this.currentUser)return this.queue(async()=>this.directlySetCurrentUser(e))}_notifyListenersIfCurrent(e){e===this.currentUser&&this.notifyAuthListeners()}_key(){return`${this.config.authDomain}:${this.config.apiKey}:${this.name}`}_startProactiveRefresh(){this.isProactiveRefreshEnabled=!0,this.currentUser&&this._currentUser._startProactiveRefresh()}_stopProactiveRefresh(){this.isProactiveRefreshEnabled=!1,this.currentUser&&this._currentUser._stopProactiveRefresh()}get _currentUser(){return this.currentUser}notifyAuthListeners(){var e,t;if(!this._isInitialized)return;this.idTokenSubscription.next(this.currentUser);let r=(t=(e=this.currentUser)===null||e===void 0?void 0:e.uid)!==null&&t!==void 0?t:null;this.lastNotifiedUid!==r&&(this.lastNotifiedUid=r,this.authStateSubscription.next(this.currentUser))}registerStateListener(e,t,r,i){if(this._deleted)return()=>{};let s=typeof t=="function"?t:t.next.bind(t),o=!1,c=this._isInitialized?Promise.resolve():this._initializationPromise;if(l(c,this,"internal-error"),c.then(()=>{o||s(this.currentUser)}),typeof t=="function"){let a=e.addObserver(t,r,i);return()=>{o=!0,a()}}else{let a=e.addObserver(t);return()=>{o=!0,a()}}}async directlySetCurrentUser(e){this.currentUser&&this.currentUser!==e&&this._currentUser._stopProactiveRefresh(),e&&this.isProactiveRefreshEnabled&&e._startProactiveRefresh(),this.currentUser=e,e?await this.assertedPersistence.setCurrentUser(e):await this.assertedPersistence.removeCurrentUser()}queue(e){return this.operations=this.operations.then(e,e),this.operations}get assertedPersistence(){return l(this.persistenceManager,this,"internal-error"),this.persistenceManager}_logFramework(e){!e||this.frameworks.includes(e)||(this.frameworks.push(e),this.frameworks.sort(),this.clientVersion=On(this.config.clientPlatform,this._getFrameworks()))}_getFrameworks(){return this.frameworks}async _getAdditionalHeaders(){var e;let t={"X-Client-Version":this.clientVersion};this.app.options.appId&&(t["X-Firebase-gmpid"]=this.app.options.appId);let r=await((e=this.heartbeatServiceProvider.getImmediate({optional:!0}))===null||e===void 0?void 0:e.getHeartbeatsHeader());r&&(t["X-Firebase-Client"]=r);let i=await this._getAppCheckToken();return i&&(t["X-Firebase-AppCheck"]=i),t}async _getAppCheckToken(){var e;let t=await((e=this.appCheckServiceProvider.getImmediate({optional:!0}))===null||e===void 0?void 0:e.getToken());return t?.error&&Jr(`Error while retrieving App Check token: ${t.error}`),t?.token}};function Et(n){return F(n)}var ye=class{constructor(e){this.auth=e,this.observer=null,this.addObserver=Ft(t=>this.observer=t)}get next(){return l(this.observer,this.auth,"internal-error"),this.observer.next.bind(this.observer)}};var wt={async loadJS(){throw new Error("Unable to load external scripts")},recaptchaV2Script:"",recaptchaEnterpriseScript:"",gapiScript:""};function _i(n){wt=n}function Ii(n){return wt.loadJS(n)}function vi(){return wt.recaptchaEnterpriseScript}function yi(n){return`__${n}${Math.floor(Math.random()*1e6)}`}var Ei="recaptcha-enterprise",wi="NO_RECAPTCHA",at=class{constructor(e){this.type=Ei,this.auth=Et(e)}async verify(e="verify",t=!1){async function r(s){if(!t){if(s.tenantId==null&&s._agentRecaptchaConfig!=null)return s._agentRecaptchaConfig.siteKey;if(s.tenantId!=null&&s._tenantRecaptchaConfigs[s.tenantId]!==void 0)return s._tenantRecaptchaConfigs[s.tenantId].siteKey}return new Promise(async(o,c)=>{ri(s,{clientType:"CLIENT_TYPE_WEB",version:"RECAPTCHA_ENTERPRISE"}).then(a=>{if(a.recaptchaKey===void 0)c(new Error("recaptcha Enterprise site key undefined"));else{let u=new tt(a);return s.tenantId==null?s._agentRecaptchaConfig=u:s._tenantRecaptchaConfigs[s.tenantId]=u,o(u.siteKey)}}).catch(a=>{c(a)})})}function i(s,o,c){let a=window.grecaptcha;tn(a)?a.enterprise.ready(()=>{a.enterprise.execute(s,{action:e}).then(u=>{o(u)}).catch(()=>{o(wi)})}):c(Error("No reCAPTCHA enterprise script loaded."))}return new Promise((s,o)=>{r(this.auth).then(c=>{if(!t&&tn(window.grecaptcha))i(c,s,o);else{if(typeof window>"u"){o(new Error("RecaptchaVerifier is only supported in browser"));return}let a=vi();a.length!==0&&(a+=c),Ii(a).then(()=>{i(c,s,o)}).catch(u=>{o(u)})}}).catch(c=>{o(c)})})}};async function on(n,e,t,r=!1){let i=new at(n),s;try{s=await i.verify(t)}catch{s=await i.verify(t,!0)}let o=Object.assign({},e);return r?Object.assign(o,{captchaResp:s}):Object.assign(o,{captchaResponse:s}),Object.assign(o,{clientType:"CLIENT_TYPE_WEB"}),Object.assign(o,{recaptchaVersion:"RECAPTCHA_ENTERPRISE"}),o}async function an(n,e,t,r){var i;if(!((i=n._getRecaptchaConfig())===null||i===void 0)&&i.isProviderEnabled("EMAIL_PASSWORD_PROVIDER")){let s=await on(n,e,t,t==="getOobCode");return r(n,s)}else return r(n,e).catch(async s=>{if(s.code==="auth/missing-recaptcha-token"){console.log(`${t} is protected by reCAPTCHA Enterprise for this project. Automatically triggering the reCAPTCHA flow and restarting the flow.`);let o=await on(n,e,t,t==="getOobCode");return r(n,o)}else return Promise.reject(s)})}function Ti(n,e){let t=e?.persistence||[],r=(Array.isArray(t)?t:[t]).map(P);e?.errorMap&&n._updateErrorMap(e.errorMap),n._initializeWithPersistence(r,e?.popupRedirectResolver)}var N=class{constructor(e,t){this.providerId=e,this.signInMethod=t}toJSON(){return y("not implemented")}_getIdTokenResponse(e){return y("not implemented")}_linkToIdToken(e,t){return y("not implemented")}_getReauthenticationResolver(e){return y("not implemented")}};async function bi(n,e){return m(n,"POST","/v1/accounts:signUp",e)}async function Ai(n,e){return M(n,"POST","/v1/accounts:signInWithPassword",f(n,e))}async function Si(n,e){return M(n,"POST","/v1/accounts:signInWithEmailLink",f(n,e))}async function Ri(n,e){return M(n,"POST","/v1/accounts:signInWithEmailLink",f(n,e))}var Z=class n extends N{constructor(e,t,r,i=null){super("password",r),this._email=e,this._password=t,this._tenantId=i}static _fromEmailAndPassword(e,t){return new n(e,t,"password")}static _fromEmailAndCode(e,t,r=null){return new n(e,t,"emailLink",r)}toJSON(){return{email:this._email,password:this._password,signInMethod:this.signInMethod,tenantId:this._tenantId}}static fromJSON(e){let t=typeof e=="string"?JSON.parse(e):e;if(t?.email&&t?.password){if(t.signInMethod==="password")return this._fromEmailAndPassword(t.email,t.password);if(t.signInMethod==="emailLink")return this._fromEmailAndCode(t.email,t.password,t.tenantId)}return null}async _getIdTokenResponse(e){switch(this.signInMethod){case"password":let t={returnSecureToken:!0,email:this._email,password:this._password,clientType:"CLIENT_TYPE_WEB"};return an(e,t,"signInWithPassword",Ai);case"emailLink":return Si(e,{email:this._email,oobCode:this._password});default:R(e,"internal-error")}}async _linkToIdToken(e,t){switch(this.signInMethod){case"password":let r={idToken:t,returnSecureToken:!0,email:this._email,password:this._password,clientType:"CLIENT_TYPE_WEB"};return an(e,r,"signUpPassword",bi);case"emailLink":return Ri(e,{idToken:t,email:this._email,oobCode:this._password});default:R(e,"internal-error")}}_getReauthenticationResolver(e){return this._getIdTokenResponse(e)}};async function B(n,e){return M(n,"POST","/v1/accounts:signInWithIdp",f(n,e))}var Oi="http://localhost",D=class n extends N{constructor(){super(...arguments),this.pendingToken=null}static _fromParams(e){let t=new n(e.providerId,e.signInMethod);return e.idToken||e.accessToken?(e.idToken&&(t.idToken=e.idToken),e.accessToken&&(t.accessToken=e.accessToken),e.nonce&&!e.pendingToken&&(t.nonce=e.nonce),e.pendingToken&&(t.pendingToken=e.pendingToken)):e.oauthToken&&e.oauthTokenSecret?(t.accessToken=e.oauthToken,t.secret=e.oauthTokenSecret):R("argument-error"),t}toJSON(){return{idToken:this.idToken,accessToken:this.accessToken,secret:this.secret,nonce:this.nonce,pendingToken:this.pendingToken,providerId:this.providerId,signInMethod:this.signInMethod}}static fromJSON(e){let t=typeof e=="string"?JSON.parse(e):e,{providerId:r,signInMethod:i}=t,s=he(t,["providerId","signInMethod"]);if(!r||!i)return null;let o=new n(r,i);return o.idToken=s.idToken||void 0,o.accessToken=s.accessToken||void 0,o.secret=s.secret,o.nonce=s.nonce,o.pendingToken=s.pendingToken||null,o}_getIdTokenResponse(e){let t=this.buildRequest();return B(e,t)}_linkToIdToken(e,t){let r=this.buildRequest();return r.idToken=t,B(e,r)}_getReauthenticationResolver(e){let t=this.buildRequest();return t.autoCreate=!1,B(e,t)}buildRequest(){let e={requestUri:Oi,returnSecureToken:!0};if(this.pendingToken)e.pendingToken=this.pendingToken;else{let t={};this.idToken&&(t.id_token=this.idToken),this.accessToken&&(t.access_token=this.accessToken),this.secret&&(t.oauth_token_secret=this.secret),t.providerId=this.providerId,this.nonce&&!this.pendingToken&&(t.nonce=this.nonce),e.postBody=ue(t)}return e}};async function Pi(n,e){return m(n,"POST","/v1/accounts:sendVerificationCode",f(n,e))}async function ki(n,e){return M(n,"POST","/v1/accounts:signInWithPhoneNumber",f(n,e))}async function Ci(n,e){let t=await M(n,"POST","/v1/accounts:signInWithPhoneNumber",f(n,e));if(t.temporaryProof)throw K(n,"account-exists-with-different-credential",t);return t}var Ni={USER_NOT_FOUND:"user-not-found"};async function Di(n,e){let t=Object.assign(Object.assign({},e),{operation:"REAUTH"});return M(n,"POST","/v1/accounts:signInWithPhoneNumber",f(n,t),Ni)}var ee=class n extends N{constructor(e){super("phone","phone"),this.params=e}static _fromVerification(e,t){return new n({verificationId:e,verificationCode:t})}static _fromTokenResponse(e,t){return new n({phoneNumber:e,temporaryProof:t})}_getIdTokenResponse(e){return ki(e,this._makeVerificationRequest())}_linkToIdToken(e,t){return Ci(e,Object.assign({idToken:t},this._makeVerificationRequest()))}_getReauthenticationResolver(e){return Di(e,this._makeVerificationRequest())}_makeVerificationRequest(){let{temporaryProof:e,phoneNumber:t,verificationId:r,verificationCode:i}=this.params;return e&&t?{temporaryProof:e,phoneNumber:t}:{sessionInfo:r,code:i}}toJSON(){let e={providerId:this.providerId};return this.params.phoneNumber&&(e.phoneNumber=this.params.phoneNumber),this.params.temporaryProof&&(e.temporaryProof=this.params.temporaryProof),this.params.verificationCode&&(e.verificationCode=this.params.verificationCode),this.params.verificationId&&(e.verificationId=this.params.verificationId),e}static fromJSON(e){typeof e=="string"&&(e=JSON.parse(e));let{verificationId:t,verificationCode:r,phoneNumber:i,temporaryProof:s}=e;return!r&&!t&&!i&&!s?null:new n({verificationId:t,verificationCode:r,phoneNumber:i,temporaryProof:s})}};function Li(n){switch(n){case"recoverEmail":return"RECOVER_EMAIL";case"resetPassword":return"PASSWORD_RESET";case"signIn":return"EMAIL_SIGNIN";case"verifyEmail":return"VERIFY_EMAIL";case"verifyAndChangeEmail":return"VERIFY_AND_CHANGE_EMAIL";case"revertSecondFactorAddition":return"REVERT_SECOND_FACTOR_ADDITION";default:return null}}function Mi(n){let e=U(x(n)).link,t=e?U(x(e)).deep_link_id:null,r=U(x(n)).deep_link_id;return(r?U(x(r)).link:null)||r||t||e||n}var Ee=class n{constructor(e){var t,r,i,s,o,c;let a=U(x(e)),u=(t=a.apiKey)!==null&&t!==void 0?t:null,h=(r=a.oobCode)!==null&&r!==void 0?r:null,p=Li((i=a.mode)!==null&&i!==void 0?i:null);l(u&&h&&p,"argument-error"),this.apiKey=u,this.operation=p,this.code=h,this.continueUrl=(s=a.continueUrl)!==null&&s!==void 0?s:null,this.languageCode=(o=a.languageCode)!==null&&o!==void 0?o:null,this.tenantId=(c=a.tenantId)!==null&&c!==void 0?c:null}static parseLink(e){let t=Mi(e);try{return new n(t)}catch{return null}}};var $=class n{constructor(){this.providerId=n.PROVIDER_ID}static credential(e,t){return Z._fromEmailAndPassword(e,t)}static credentialWithLink(e,t){let r=Ee.parseLink(t);return l(r,"argument-error"),Z._fromEmailAndCode(e,r.code,r.tenantId)}};$.PROVIDER_ID="password";$.EMAIL_PASSWORD_SIGN_IN_METHOD="password";$.EMAIL_LINK_SIGN_IN_METHOD="emailLink";var ct=class{constructor(e){this.providerId=e,this.defaultLanguageCode=null,this.customParameters={}}setDefaultLanguage(e){this.defaultLanguageCode=e}setCustomParameters(e){return this.customParameters=e,this}getCustomParameters(){return this.customParameters}};var q=class extends ct{constructor(){super(...arguments),this.scopes=[]}addScope(e){return this.scopes.includes(e)||this.scopes.push(e),this}getScopes(){return[...this.scopes]}};var te=class n extends q{constructor(){super("facebook.com")}static credential(e){return D._fromParams({providerId:n.PROVIDER_ID,signInMethod:n.FACEBOOK_SIGN_IN_METHOD,accessToken:e})}static credentialFromResult(e){return n.credentialFromTaggedObject(e)}static credentialFromError(e){return n.credentialFromTaggedObject(e.customData||{})}static credentialFromTaggedObject({_tokenResponse:e}){if(!e||!("oauthAccessToken"in e)||!e.oauthAccessToken)return null;try{return n.credential(e.oauthAccessToken)}catch{return null}}};te.FACEBOOK_SIGN_IN_METHOD="facebook.com";te.PROVIDER_ID="facebook.com";var ne=class n extends q{constructor(){super("google.com"),this.addScope("profile")}static credential(e,t){return D._fromParams({providerId:n.PROVIDER_ID,signInMethod:n.GOOGLE_SIGN_IN_METHOD,idToken:e,accessToken:t})}static credentialFromResult(e){return n.credentialFromTaggedObject(e)}static credentialFromError(e){return n.credentialFromTaggedObject(e.customData||{})}static credentialFromTaggedObject({_tokenResponse:e}){if(!e)return null;let{oauthIdToken:t,oauthAccessToken:r}=e;if(!t&&!r)return null;try{return n.credential(t,r)}catch{return null}}};ne.GOOGLE_SIGN_IN_METHOD="google.com";ne.PROVIDER_ID="google.com";var re=class n extends q{constructor(){super("github.com")}static credential(e){return D._fromParams({providerId:n.PROVIDER_ID,signInMethod:n.GITHUB_SIGN_IN_METHOD,accessToken:e})}static credentialFromResult(e){return n.credentialFromTaggedObject(e)}static credentialFromError(e){return n.credentialFromTaggedObject(e.customData||{})}static credentialFromTaggedObject({_tokenResponse:e}){if(!e||!("oauthAccessToken"in e)||!e.oauthAccessToken)return null;try{return n.credential(e.oauthAccessToken)}catch{return null}}};re.GITHUB_SIGN_IN_METHOD="github.com";re.PROVIDER_ID="github.com";var ie=class n extends q{constructor(){super("twitter.com")}static credential(e,t){return D._fromParams({providerId:n.PROVIDER_ID,signInMethod:n.TWITTER_SIGN_IN_METHOD,oauthToken:e,oauthTokenSecret:t})}static credentialFromResult(e){return n.credentialFromTaggedObject(e)}static credentialFromError(e){return n.credentialFromTaggedObject(e.customData||{})}static credentialFromTaggedObject({_tokenResponse:e}){if(!e)return null;let{oauthAccessToken:t,oauthTokenSecret:r}=e;if(!t||!r)return null;try{return n.credential(t,r)}catch{return null}}};ie.TWITTER_SIGN_IN_METHOD="twitter.com";ie.PROVIDER_ID="twitter.com";var se=class n{constructor(e){this.user=e.user,this.providerId=e.providerId,this._tokenResponse=e._tokenResponse,this.operationType=e.operationType}static async _fromIdTokenResponse(e,t,r,i=!1){let s=await z._fromIdTokenResponse(e,r,i),o=cn(r);return new n({user:s,providerId:o,_tokenResponse:r,operationType:t})}static async _forOperation(e,t,r){await e._updateTokensIfNecessary(r,!0);let i=cn(r);return new n({user:e,providerId:i,_tokenResponse:r,operationType:t})}};function cn(n){return n.providerId?n.providerId:"phoneNumber"in n?"phone":null}var ut=class n extends I{constructor(e,t,r,i){var s;super(t.code,t.message),this.operationType=r,this.user=i,Object.setPrototypeOf(this,n.prototype),this.customData={appName:e.name,tenantId:(s=e.tenantId)!==null&&s!==void 0?s:void 0,_serverResponse:t.customData._serverResponse,operationType:r}}static _fromErrorAndOperation(e,t,r,i){return new n(e,t,r,i)}};function Pn(n,e,t,r){return(e==="reauthenticate"?t._getReauthenticationResolver(n):t._getIdTokenResponse(n)).catch(s=>{throw s.code==="auth/multi-factor-auth-required"?ut._fromErrorAndOperation(n,s,e,r):s})}async function Ui(n,e,t=!1){let r=await X(n,e._linkToIdToken(n.auth,await n.getIdToken()),t);return se._forOperation(n,"link",r)}async function xi(n,e,t=!1){let{auth:r}=n;if(A(r.app))return Promise.reject(W(r));let i="reauthenticate";try{let s=await X(n,Pn(r,i,e,n),t);l(s.idToken,r,"internal-error");let o=yt(s.idToken);l(o,r,"internal-error");let{sub:c}=o;return l(n.uid===c,r,"user-mismatch"),se._forOperation(n,i,s)}catch(s){throw s?.code==="auth/user-not-found"&&R(r,"user-mismatch"),s}}async function Fi(n,e,t=!1){if(A(n.app))return Promise.reject(W(n));let r="signIn",i=await Pn(n,r,e),s=await se._fromIdTokenResponse(n,r,i);return t||await n._updateCurrentUser(s.user),s}function Vi(n,e){return m(n,"POST","/v2/accounts/mfaEnrollment:start",f(n,e))}function ji(n,e){return m(n,"POST","/v2/accounts/mfaEnrollment:finalize",f(n,e))}function Hi(n,e){return m(n,"POST","/v2/accounts/mfaEnrollment:start",f(n,e))}function Wi(n,e){return m(n,"POST","/v2/accounts/mfaEnrollment:finalize",f(n,e))}var we="__sak";var Te=class{constructor(e,t){this.storageRetriever=e,this.type=t}_isAvailable(){try{return this.storage?(this.storage.setItem(we,"1"),this.storage.removeItem(we),Promise.resolve(!0)):Promise.resolve(!1)}catch{return Promise.resolve(!1)}}_set(e,t){return this.storage.setItem(e,JSON.stringify(t)),Promise.resolve()}_get(e){let t=this.storage.getItem(e);return Promise.resolve(t?JSON.parse(t):null)}_remove(e){return this.storage.removeItem(e),Promise.resolve()}get storage(){return this.storageRetriever()}};var Bi=1e3,zi=10,lt=class extends Te{constructor(){super(()=>window.localStorage,"LOCAL"),this.boundEventHandler=(e,t)=>this.onStorageEvent(e,t),this.listeners={},this.localCache={},this.pollTimer=null,this.fallbackToPolling=pi(),this._shouldAllowMigration=!0}forAllChangedKeys(e){for(let t of Object.keys(this.listeners)){let r=this.storage.getItem(t),i=this.localCache[t];r!==i&&e(t,i,r)}}onStorageEvent(e,t=!1){if(!e.key){this.forAllChangedKeys((o,c,a)=>{this.notifyListeners(o,a)});return}let r=e.key;t?this.detachListener():this.stopPolling();let i=()=>{let o=this.storage.getItem(r);!t&&this.localCache[r]===o||this.notifyListeners(r,o)},s=this.storage.getItem(r);fi()&&s!==e.newValue&&e.newValue!==e.oldValue?setTimeout(i,zi):i()}notifyListeners(e,t){this.localCache[e]=t;let r=this.listeners[e];if(r)for(let i of Array.from(r))i(t&&JSON.parse(t))}startPolling(){this.stopPolling(),this.pollTimer=setInterval(()=>{this.forAllChangedKeys((e,t,r)=>{this.onStorageEvent(new StorageEvent("storage",{key:e,oldValue:t,newValue:r}),!0)})},Bi)}stopPolling(){this.pollTimer&&(clearInterval(this.pollTimer),this.pollTimer=null)}attachListener(){window.addEventListener("storage",this.boundEventHandler)}detachListener(){window.removeEventListener("storage",this.boundEventHandler)}_addListener(e,t){Object.keys(this.listeners).length===0&&(this.fallbackToPolling?this.startPolling():this.attachListener()),this.listeners[e]||(this.listeners[e]=new Set,this.localCache[e]=this.storage.getItem(e)),this.listeners[e].add(t)}_removeListener(e,t){this.listeners[e]&&(this.listeners[e].delete(t),this.listeners[e].size===0&&delete this.listeners[e]),Object.keys(this.listeners).length===0&&(this.detachListener(),this.stopPolling())}async _set(e,t){await super._set(e,t),this.localCache[e]=JSON.stringify(t)}async _get(e){let t=await super._get(e);return this.localCache[e]=JSON.stringify(t),t}async _remove(e){await super._remove(e),delete this.localCache[e]}};lt.type="LOCAL";var dt=class extends Te{constructor(){super(()=>window.sessionStorage,"SESSION")}_addListener(e,t){}_removeListener(e,t){}};dt.type="SESSION";function $i(n){return Promise.all(n.map(async e=>{try{return{fulfilled:!0,value:await e}}catch(t){return{fulfilled:!1,reason:t}}}))}var be=class n{constructor(e){this.eventTarget=e,this.handlersMap={},this.boundEventHandler=this.handleEvent.bind(this)}static _getInstance(e){let t=this.receivers.find(i=>i.isListeningto(e));if(t)return t;let r=new n(e);return this.receivers.push(r),r}isListeningto(e){return this.eventTarget===e}async handleEvent(e){let t=e,{eventId:r,eventType:i,data:s}=t.data,o=this.handlersMap[i];if(!o?.size)return;t.ports[0].postMessage({status:"ack",eventId:r,eventType:i});let c=Array.from(o).map(async u=>u(t.origin,s)),a=await $i(c);t.ports[0].postMessage({status:"done",eventId:r,eventType:i,response:a})}_subscribe(e,t){Object.keys(this.handlersMap).length===0&&this.eventTarget.addEventListener("message",this.boundEventHandler),this.handlersMap[e]||(this.handlersMap[e]=new Set),this.handlersMap[e].add(t)}_unsubscribe(e,t){this.handlersMap[e]&&t&&this.handlersMap[e].delete(t),(!t||this.handlersMap[e].size===0)&&delete this.handlersMap[e],Object.keys(this.handlersMap).length===0&&this.eventTarget.removeEventListener("message",this.boundEventHandler)}};be.receivers=[];function kn(n="",e=10){let t="";for(let r=0;r{let u=kn("",20);i.port1.start();let h=setTimeout(()=>{a(new Error("unsupported_event"))},r);o={messageChannel:i,onMessage(p){let _=p;if(_.data.eventId===u)switch(_.data.status){case"ack":clearTimeout(h),s=setTimeout(()=>{a(new Error("timeout"))},3e3);break;case"done":clearTimeout(s),c(_.data.response);break;default:clearTimeout(h),clearTimeout(s),a(new Error("invalid_response"));break}}},this.handlers.add(o),i.port1.addEventListener("message",o.onMessage),this.target.postMessage({eventType:e,eventId:u,data:t},[i.port2])}).finally(()=>{o&&this.removeMessageHandler(o)})}};function un(){return window}function Cn(){return typeof un().WorkerGlobalScope<"u"&&typeof un().importScripts=="function"}async function qi(){if(!navigator?.serviceWorker)return null;try{return(await navigator.serviceWorker.ready).active}catch{return null}}function Gi(){var n;return((n=navigator?.serviceWorker)===null||n===void 0?void 0:n.controller)||null}function Ki(){return Cn()?self:null}var Nn="firebaseLocalStorageDb",Ji=1,Ae="firebaseLocalStorage",Dn="fbase_key",L=class{constructor(e){this.request=e}toPromise(){return new Promise((e,t)=>{this.request.addEventListener("success",()=>{e(this.request.result)}),this.request.addEventListener("error",()=>{t(this.request.error)})})}};function Ce(n,e){return n.transaction([Ae],e?"readwrite":"readonly").objectStore(Ae)}function Yi(){let n=indexedDB.deleteDatabase(Nn);return new L(n).toPromise()}function ft(){let n=indexedDB.open(Nn,Ji);return new Promise((e,t)=>{n.addEventListener("error",()=>{t(n.error)}),n.addEventListener("upgradeneeded",()=>{let r=n.result;try{r.createObjectStore(Ae,{keyPath:Dn})}catch(i){t(i)}}),n.addEventListener("success",async()=>{let r=n.result;r.objectStoreNames.contains(Ae)?e(r):(r.close(),await Yi(),e(await ft()))})})}async function ln(n,e,t){let r=Ce(n,!0).put({[Dn]:e,value:t});return new L(r).toPromise()}async function Xi(n,e){let t=Ce(n,!1).get(e),r=await new L(t).toPromise();return r===void 0?null:r.value}function dn(n,e){let t=Ce(n,!0).delete(e);return new L(t).toPromise()}var Qi=800,Zi=3,pt=class{constructor(){this.type="LOCAL",this._shouldAllowMigration=!0,this.listeners={},this.localCache={},this.pollTimer=null,this.pendingWrites=0,this.receiver=null,this.sender=null,this.serviceWorkerReceiverAvailable=!1,this.activeServiceWorker=null,this._workerInitializationPromise=this.initializeServiceWorkerMessaging().then(()=>{},()=>{})}async _openDb(){return this.db?this.db:(this.db=await ft(),this.db)}async _withRetries(e){let t=0;for(;;)try{let r=await this._openDb();return await e(r)}catch(r){if(t++>Zi)throw r;this.db&&(this.db.close(),this.db=void 0)}}async initializeServiceWorkerMessaging(){return Cn()?this.initializeReceiver():this.initializeSender()}async initializeReceiver(){this.receiver=be._getInstance(Ki()),this.receiver._subscribe("keyChanged",async(e,t)=>({keyProcessed:(await this._poll()).includes(t.key)})),this.receiver._subscribe("ping",async(e,t)=>["keyChanged"])}async initializeSender(){var e,t;if(this.activeServiceWorker=await qi(),!this.activeServiceWorker)return;this.sender=new ht(this.activeServiceWorker);let r=await this.sender._send("ping",{},800);r&&!((e=r[0])===null||e===void 0)&&e.fulfilled&&!((t=r[0])===null||t===void 0)&&t.value.includes("keyChanged")&&(this.serviceWorkerReceiverAvailable=!0)}async notifyServiceWorker(e){if(!(!this.sender||!this.activeServiceWorker||Gi()!==this.activeServiceWorker))try{await this.sender._send("keyChanged",{key:e},this.serviceWorkerReceiverAvailable?800:50)}catch{}}async _isAvailable(){try{if(!indexedDB)return!1;let e=await ft();return await ln(e,we,"1"),await dn(e,we),!0}catch{}return!1}async _withPendingWrite(e){this.pendingWrites++;try{await e()}finally{this.pendingWrites--}}async _set(e,t){return this._withPendingWrite(async()=>(await this._withRetries(r=>ln(r,e,t)),this.localCache[e]=t,this.notifyServiceWorker(e)))}async _get(e){let t=await this._withRetries(r=>Xi(r,e));return this.localCache[e]=t,t}async _remove(e){return this._withPendingWrite(async()=>(await this._withRetries(t=>dn(t,e)),delete this.localCache[e],this.notifyServiceWorker(e)))}async _poll(){let e=await this._withRetries(i=>{let s=Ce(i,!1).getAll();return new L(s).toPromise()});if(!e)return[];if(this.pendingWrites!==0)return[];let t=[],r=new Set;if(e.length!==0)for(let{fbase_key:i,value:s}of e)r.add(i),JSON.stringify(this.localCache[i])!==JSON.stringify(s)&&(this.notifyListeners(i,s),t.push(i));for(let i of Object.keys(this.localCache))this.localCache[i]&&!r.has(i)&&(this.notifyListeners(i,null),t.push(i));return t}notifyListeners(e,t){this.localCache[e]=t;let r=this.listeners[e];if(r)for(let i of Array.from(r))i(t)}startPolling(){this.stopPolling(),this.pollTimer=setInterval(async()=>this._poll(),Qi)}stopPolling(){this.pollTimer&&(clearInterval(this.pollTimer),this.pollTimer=null)}_addListener(e,t){Object.keys(this.listeners).length===0&&this.startPolling(),this.listeners[e]||(this.listeners[e]=new Set,this._get(e)),this.listeners[e].add(t)}_removeListener(e,t){this.listeners[e]&&(this.listeners[e].delete(t),this.listeners[e].size===0&&delete this.listeners[e]),Object.keys(this.listeners).length===0&&this.stopPolling()}};pt.type="LOCAL";function es(n,e){return m(n,"POST","/v2/accounts/mfaSignIn:start",f(n,e))}function ts(n,e){return m(n,"POST","/v2/accounts/mfaSignIn:finalize",f(n,e))}function ns(n,e){return m(n,"POST","/v2/accounts/mfaSignIn:finalize",f(n,e))}var Ys=yi("rcb"),Xs=new C(3e4,6e4);var rs="recaptcha";async function is(n,e,t){var r;let i=await t.verify();try{l(typeof i=="string",n,"argument-error"),l(t.type===rs,n,"argument-error");let s;if(typeof e=="string"?s={phoneNumber:e}:s=e,"session"in s){let o=s.session;if("phoneNumber"in s)return l(o.type==="enroll",n,"internal-error"),(await Vi(n,{idToken:o.credential,phoneEnrollmentInfo:{phoneNumber:s.phoneNumber,recaptchaToken:i}})).phoneSessionInfo.sessionInfo;{l(o.type==="signin",n,"internal-error");let c=((r=s.multiFactorHint)===null||r===void 0?void 0:r.uid)||s.multiFactorUid;return l(c,n,"missing-multi-factor-info"),(await es(n,{mfaPendingCredential:o.credential,mfaEnrollmentId:c,phoneSignInInfo:{recaptchaToken:i}})).phoneResponseInfo.sessionInfo}}else{let{sessionInfo:o}=await Pi(n,{phoneNumber:s.phoneNumber,recaptchaToken:i});return o}}finally{t._reset()}}var oe=class n{constructor(e){this.providerId=n.PROVIDER_ID,this.auth=Et(e)}verifyPhoneNumber(e,t){return is(this.auth,e,F(t))}static credential(e,t){return ee._fromVerification(e,t)}static credentialFromResult(e){let t=e;return n.credentialFromTaggedObject(t)}static credentialFromError(e){return n.credentialFromTaggedObject(e.customData||{})}static credentialFromTaggedObject({_tokenResponse:e}){if(!e)return null;let{phoneNumber:t,temporaryProof:r}=e;return t&&r?ee._fromTokenResponse(t,r):null}};oe.PROVIDER_ID="phone";oe.PHONE_SIGN_IN_METHOD="phone";var ae=class extends N{constructor(e){super("custom","custom"),this.params=e}_getIdTokenResponse(e){return B(e,this._buildIdpRequest())}_linkToIdToken(e,t){return B(e,this._buildIdpRequest(t))}_getReauthenticationResolver(e){return B(e,this._buildIdpRequest())}_buildIdpRequest(e){let t={requestUri:this.params.requestUri,sessionId:this.params.sessionId,postBody:this.params.postBody,tenantId:this.params.tenantId,pendingToken:this.params.pendingToken,returnSecureToken:!0,returnIdpCredential:!0};return e&&(t.idToken=e),t}};function ss(n){return Fi(n.auth,new ae(n),n.bypassAuthState)}function os(n){let{auth:e,user:t}=n;return l(t,e,"internal-error"),xi(t,new ae(n),n.bypassAuthState)}async function as(n){let{auth:e,user:t}=n;return l(t,e,"internal-error"),Ui(t,new ae(n),n.bypassAuthState)}var mt=class{constructor(e,t,r,i,s=!1){this.auth=e,this.resolver=r,this.user=i,this.bypassAuthState=s,this.pendingPromise=null,this.eventManager=null,this.filter=Array.isArray(t)?t:[t]}execute(){return new Promise(async(e,t)=>{this.pendingPromise={resolve:e,reject:t};try{this.eventManager=await this.resolver._initialize(this.auth),await this.onExecution(),this.eventManager.registerConsumer(this)}catch(r){this.reject(r)}})}async onAuthEvent(e){let{urlResponse:t,sessionId:r,postBody:i,tenantId:s,error:o,type:c}=e;if(o){this.reject(o);return}let a={auth:this.auth,requestUri:t,sessionId:r,tenantId:s||void 0,postBody:i||void 0,user:this.user,bypassAuthState:this.bypassAuthState};try{this.resolve(await this.getIdpTask(c)(a))}catch(u){this.reject(u)}}onError(e){this.reject(e)}getIdpTask(e){switch(e){case"signInViaPopup":case"signInViaRedirect":return ss;case"linkViaPopup":case"linkViaRedirect":return as;case"reauthViaPopup":case"reauthViaRedirect":return os;default:R(this.auth,"internal-error")}}resolve(e){k(this.pendingPromise,"Pending promise was never set"),this.pendingPromise.resolve(e),this.unregisterAndCleanUp()}reject(e){k(this.pendingPromise,"Pending promise was never set"),this.pendingPromise.reject(e),this.unregisterAndCleanUp()}unregisterAndCleanUp(){this.eventManager&&this.eventManager.unregisterConsumer(this),this.pendingPromise=null,this.cleanUp()}};var cs=new C(2e3,1e4);var gt=class n extends mt{constructor(e,t,r,i,s){super(e,t,i,s),this.provider=r,this.authWindow=null,this.pollId=null,n.currentPopupAction&&n.currentPopupAction.cancel(),n.currentPopupAction=this}async executeNotNull(){let e=await this.execute();return l(e,this.auth,"internal-error"),e}async onExecution(){k(this.filter.length===1,"Popup operations only handle one event");let e=kn();this.authWindow=await this.resolver._openPopup(this.auth,this.provider,this.filter[0],e),this.authWindow.associatedEvent=e,this.resolver._originValidation(this.auth).catch(t=>{this.reject(t)}),this.resolver._isIframeWebStorageSupported(this.auth,t=>{t||this.reject(H(this.auth,"web-storage-unsupported"))}),this.pollUserCancellation()}get eventId(){var e;return((e=this.authWindow)===null||e===void 0?void 0:e.associatedEvent)||null}cancel(){this.reject(H(this.auth,"cancelled-popup-request"))}cleanUp(){this.authWindow&&this.authWindow.close(),this.pollId&&window.clearTimeout(this.pollId),this.authWindow=null,this.pollId=null,n.currentPopupAction=null}pollUserCancellation(){let e=()=>{var t,r;if(!((r=(t=this.authWindow)===null||t===void 0?void 0:t.window)===null||r===void 0)&&r.closed){this.pollId=window.setTimeout(()=>{this.pollId=null,this.reject(H(this.auth,"popup-closed-by-user"))},8e3);return}this.pollId=window.setTimeout(e,cs.get())};e()}};gt.currentPopupAction=null;var Qs=10*60*1e3;var Zs=new C(3e4,6e4);var eo=new C(5e3,15e3);var to=encodeURIComponent("fac");var Se=class{constructor(e){this.factorId=e}_process(e,t,r){switch(t.type){case"enroll":return this._finalizeEnroll(e,t.credential,r);case"signin":return this._finalizeSignIn(e,t.credential);default:return y("unexpected MultiFactorSessionType")}}},_t=class n extends Se{constructor(e){super("phone"),this.credential=e}static _fromCredential(e){return new n(e)}_finalizeEnroll(e,t,r){return ji(e,{idToken:t,displayName:r,phoneVerificationInfo:this.credential._makeVerificationRequest()})}_finalizeSignIn(e,t){return ts(e,{mfaPendingCredential:t,phoneVerificationInfo:this.credential._makeVerificationRequest()})}},Re=class{constructor(){}static assertion(e){return _t._fromCredential(e)}};Re.FACTOR_ID="phone";var Oe=class{static assertionForEnrollment(e,t){return Pe._fromSecret(e,t)}static assertionForSignIn(e,t){return Pe._fromEnrollmentId(e,t)}static async generateSecret(e){var t;let r=e;l(typeof((t=r.user)===null||t===void 0?void 0:t.auth)<"u","internal-error");let i=await Hi(r.user.auth,{idToken:r.credential,totpEnrollmentInfo:{}});return ke._fromStartTotpMfaEnrollmentResponse(i,r.user.auth)}};Oe.FACTOR_ID="totp";var Pe=class n extends Se{constructor(e,t,r){super("totp"),this.otp=e,this.enrollmentId=t,this.secret=r}static _fromSecret(e,t){return new n(t,void 0,e)}static _fromEnrollmentId(e,t){return new n(t,e)}async _finalizeEnroll(e,t,r){return l(typeof this.secret<"u",e,"argument-error"),Wi(e,{idToken:t,displayName:r,totpVerificationInfo:this.secret._makeTotpVerificationInfo(this.otp)})}async _finalizeSignIn(e,t){l(this.enrollmentId!==void 0&&this.otp!==void 0,e,"argument-error");let r={verificationCode:this.otp};return ns(e,{mfaPendingCredential:t,mfaEnrollmentId:this.enrollmentId,totpVerificationInfo:r})}},ke=class n{constructor(e,t,r,i,s,o,c){this.sessionInfo=o,this.auth=c,this.secretKey=e,this.hashingAlgorithm=t,this.codeLength=r,this.codeIntervalSeconds=i,this.enrollmentCompletionDeadline=s}static _fromStartTotpMfaEnrollmentResponse(e,t){return new n(e.totpSessionInfo.sharedSecretKey,e.totpSessionInfo.hashingAlgorithm,e.totpSessionInfo.verificationCodeLength,e.totpSessionInfo.periodSec,new Date(e.totpSessionInfo.finalizeEnrollmentTime).toUTCString(),e.totpSessionInfo.sessionInfo,t)}_makeTotpVerificationInfo(e){return{sessionInfo:this.sessionInfo,verificationCode:e}}generateQrCodeUrl(e,t){var r;let i=!1;return(fe(e)||fe(t))&&(i=!0),i&&(fe(e)&&(e=((r=this.auth.currentUser)===null||r===void 0?void 0:r.email)||"unknownuser"),fe(t)&&(t=this.auth.name)),`otpauth://totp/${t}:${e}?secret=${this.secretKey}&issuer=${t}&algorithm=${this.hashingAlgorithm}&digits=${this.codeLength}`}};function fe(n){return typeof n>"u"||n?.length===0}var hn="@firebase/auth",fn="1.7.8";var It=class{constructor(e){this.auth=e,this.internalListeners=new Map}getUid(){var e;return this.assertAuthConfigured(),((e=this.auth.currentUser)===null||e===void 0?void 0:e.uid)||null}async getToken(e){return this.assertAuthConfigured(),await this.auth._initializationPromise,this.auth.currentUser?{accessToken:await this.auth.currentUser.getIdToken(e)}:null}addAuthTokenListener(e){if(this.assertAuthConfigured(),this.internalListeners.has(e))return;let t=this.auth.onIdTokenChanged(r=>{e(r?.stsTokenManager.accessToken||null)});this.internalListeners.set(e,t),this.updateProactiveRefresh()}removeAuthTokenListener(e){this.assertAuthConfigured();let t=this.internalListeners.get(e);t&&(this.internalListeners.delete(e),t(),this.updateProactiveRefresh())}assertAuthConfigured(){l(this.auth._initializationPromise,"dependent-sdk-initialized-before-auth")}updateProactiveRefresh(){this.internalListeners.size>0?this.auth._startProactiveRefresh():this.auth._stopProactiveRefresh()}};function us(n){switch(n){case"Node":return"node";case"ReactNative":return"rn";case"Worker":return"webworker";case"Cordova":return"cordova";case"WebExtension":return"web-extension";default:return}}function ls(n){j(new w("auth",(e,{options:t})=>{let r=e.getProvider("app").getImmediate(),i=e.getProvider("heartbeat"),s=e.getProvider("app-check-internal"),{apiKey:o,authDomain:c}=r.options;l(o&&!o.includes(":"),"invalid-api-key",{appName:r.name});let a={apiKey:o,authDomain:c,clientPlatform:n,apiHost:"identitytoolkit.googleapis.com",tokenApiHost:"securetoken.googleapis.com",apiScheme:"https",sdkClientVersion:On(n)},u=new ot(r,i,s,a);return Ti(u,t),u},"PUBLIC").setInstantiationMode("EXPLICIT").setInstanceCreatedCallback((e,t,r)=>{e.getProvider("auth-internal").initialize()})),j(new w("auth-internal",e=>{let t=Et(e.getProvider("auth").getImmediate());return(r=>new It(r))(t)},"PRIVATE").setInstantiationMode("EXPLICIT")),b(hn,fn,us(n)),b(hn,fn,"esm2017")}var ds=5*60,no=Ct("authIdTokenMaxAge")||ds;function hs(){var n,e;return(e=(n=document.getElementsByTagName("head"))===null||n===void 0?void 0:n[0])!==null&&e!==void 0?e:document}_i({loadJS(n){return new Promise((e,t)=>{let r=document.createElement("script");r.setAttribute("src",n),r.onload=e,r.onerror=i=>{let s=H("internal-error");s.customData=i,t(s)},r.type="text/javascript",r.charset="UTF-8",hs().appendChild(r)})},gapiScript:"https://apis.google.com/js/api.js",recaptchaV2Script:"https://www.google.com/recaptcha/api.js",recaptchaEnterpriseScript:"https://www.google.com/recaptcha/enterprise.js?render="});ls("Browser");var Ln=new URLSearchParams(self.location.search).get("firebaseConfig");if(!Ln)throw new Error("Firebase Config object not found in service worker query string.");var fs=JSON.parse(Ln);self.addEventListener("install",()=>{console.log("Service worker installed with Firebase config",fs),self.skipWaiting()});self.addEventListener("activate",()=>{self.clients.claim()});self.addEventListener("fetch",n=>{let{origin:e}=new URL(n.request.url);e===self.location.origin&&n.respondWith(ps(n.request))});async function ps(n){return await fetch(n)}})(); +/*! Bundled license information: + +@firebase/util/dist/index.esm2017.js: + (** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/util/dist/index.esm2017.js: + (** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/util/dist/index.esm2017.js: + (** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/util/dist/index.esm2017.js: + (** + * @license + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/util/dist/index.esm2017.js: + (** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/util/dist/index.esm2017.js: + (** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/util/dist/index.esm2017.js: + (** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/util/dist/index.esm2017.js: + (** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/util/dist/index.esm2017.js: + (** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/util/dist/index.esm2017.js: + (** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/util/dist/index.esm2017.js: + (** + * @license + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/util/dist/index.esm2017.js: + (** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/util/dist/index.esm2017.js: + (** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/util/dist/index.esm2017.js: + (** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/util/dist/index.esm2017.js: + (** + * @license + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/util/dist/index.esm2017.js: + (** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/util/dist/index.esm2017.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/util/dist/index.esm2017.js: + (** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/component/dist/esm/index.esm2017.js: + (** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/component/dist/esm/index.esm2017.js: + (** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/component/dist/esm/index.esm2017.js: + (** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/logger/dist/esm/index.esm2017.js: + (** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/app/dist/esm/index.esm2017.js: + (** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/app/dist/esm/index.esm2017.js: + (** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/app/dist/esm/index.esm2017.js: + (** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/app/dist/esm/index.esm2017.js: + (** + * @license + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/app/dist/esm/index.esm2017.js: + (** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/app/dist/esm/index.esm2017.js: + (** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + (** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +firebase/app/dist/esm/index.esm.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + (** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + (** + * @license + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + (** + * @license + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + (** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + (** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + (** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + +@firebase/auth/dist/esm2017/index-2788dcb0.js: + (** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) + (** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *) +*/ diff --git a/nextjs-start/src/components/Header.jsx b/nextjs-start/src/components/Header.jsx index 5020ae4e..da1b1bdf 100644 --- a/nextjs-start/src/components/Header.jsx +++ b/nextjs-start/src/components/Header.jsx @@ -1,4 +1,4 @@ -'use client' +'use client'; import React, { useState, useEffect } from "react"; import Link from "next/link"; import { diff --git a/nextjs-start/src/components/Restaurant.jsx b/nextjs-start/src/components/Restaurant.jsx index 85848ab6..9f584299 100644 --- a/nextjs-start/src/components/Restaurant.jsx +++ b/nextjs-start/src/components/Restaurant.jsx @@ -4,11 +4,11 @@ // It receives data from src/app/restaurant/[id]/page.jsx import { React, useState, useEffect, Suspense } from "react"; -import dynamic from 'next/dynamic' +import dynamic from 'next/dynamic'; import { getRestaurantSnapshotById, } from "@/src/lib/firebase/firestore.js"; -import {useUser} from '@/src/lib/getUser' +import {useUser} from '@/src/lib/getUser'; import RestaurantDetails from "@/src/components/RestaurantDetails.jsx"; import { updateRestaurantImage } from "@/src/lib/firebase/storage.js"; @@ -41,7 +41,7 @@ export default function Restaurant({ } const imageURL = await updateRestaurantImage(id, image); - setRestaurantDetails({ ...restaurant, photo: imageURL }); + setRestaurantDetails({ ...restaurantDetails, photo: imageURL }); } const handleClose = () => { @@ -50,14 +50,10 @@ export default function Restaurant({ }; useEffect(() => { - const unsubscribeFromRestaurant = getRestaurantSnapshotById(id, (data) => { + return getRestaurantSnapshotById(id, (data) => { setRestaurantDetails(data); }); - - return () => { - unsubscribeFromRestaurant(); - }; - }, []); + }, [id]); return ( <> diff --git a/nextjs-start/src/components/RestaurantDetails.jsx b/nextjs-start/src/components/RestaurantDetails.jsx index 6f65ad8e..7dadf5ce 100644 --- a/nextjs-start/src/components/RestaurantDetails.jsx +++ b/nextjs-start/src/components/RestaurantDetails.jsx @@ -18,6 +18,7 @@ const RestaurantDetails = ({
{userId && ( review { setIsOpen(!isOpen); diff --git a/nextjs-start/src/components/RestaurantListings.jsx b/nextjs-start/src/components/RestaurantListings.jsx index 4252640e..e173e6be 100644 --- a/nextjs-start/src/components/RestaurantListings.jsx +++ b/nextjs-start/src/components/RestaurantListings.jsx @@ -74,16 +74,12 @@ export default function RestaurantListings({ useEffect(() => { routerWithFilters(router, filters); - }, [filters]); + }, [router, filters]); useEffect(() => { - const unsubscribe = getRestaurantsSnapshot(data => { + return getRestaurantsSnapshot(data => { setRestaurants(data); }, filters); - - return () => { - unsubscribe(); - }; }, [filters]); return ( diff --git a/nextjs-start/src/components/ReviewDialog.jsx b/nextjs-start/src/components/ReviewDialog.jsx index 5ab19c60..113c43c3 100644 --- a/nextjs-start/src/components/ReviewDialog.jsx +++ b/nextjs-start/src/components/ReviewDialog.jsx @@ -24,14 +24,14 @@ const ReviewDialog = ({ dialog.current.close(); } - }, [isOpen, dialog.current]); + }, [isOpen, dialog]); const handleClick = (e) => { // close if clicked outside the modal if (e.target === dialog.current) { handleClose(); } - } + }; return ( diff --git a/nextjs-start/src/lib/firebase/firestore.js b/nextjs-start/src/lib/firebase/firestore.js index 690baa0f..d8941f4c 100644 --- a/nextjs-start/src/lib/firebase/firestore.js +++ b/nextjs-start/src/lib/firebase/firestore.js @@ -102,7 +102,7 @@ export function getReviewsSnapshotByRestaurantId(restaurantId, cb) { collection(db, "restaurants", restaurantId, "ratings"), orderBy("timestamp", "desc") ); - const unsubscribe = onSnapshot(q, querySnapshot => { + return onSnapshot(q, querySnapshot => { const results = querySnapshot.docs.map(doc => { return { id: doc.id, @@ -113,7 +113,6 @@ export function getReviewsSnapshotByRestaurantId(restaurantId, cb) { }); cb(results); }); - return unsubscribe; } export async function addFakeRestaurantsAndReviews() { diff --git a/nextjs-start/src/lib/getUser.js b/nextjs-start/src/lib/getUser.js index ecba2318..07fb0ed3 100644 --- a/nextjs-start/src/lib/getUser.js +++ b/nextjs-start/src/lib/getUser.js @@ -10,12 +10,9 @@ export function useUser() { const [user, setUser] = useState(); useEffect(() => { - const unsubscribe = onAuthStateChanged(auth, (authUser) => { + return onAuthStateChanged(auth, (authUser) => { setUser(authUser); }); - - return () => unsubscribe(); - // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return user; From 0486af097f66b048411e9eb2a9f1dc5cddf35378 Mon Sep 17 00:00:00 2001 From: James Daniels Date: Tue, 10 Sep 2024 14:28:58 -0400 Subject: [PATCH 13/38] Sync up with -end --- nextjs-start/.gitignore | 1 + nextjs-start/auth-service-worker.js | 30 +- nextjs-start/public/auth-service-worker.js | 1761 -------------------- nextjs-start/src/components/Header.jsx | 2 - nextjs-start/src/lib/firebase/clientApp.js | 7 +- nextjs-start/src/lib/firebase/serverApp.js | 2 + 6 files changed, 32 insertions(+), 1771 deletions(-) delete mode 100644 nextjs-start/public/auth-service-worker.js diff --git a/nextjs-start/.gitignore b/nextjs-start/.gitignore index 8fb25514..aaaab22a 100644 --- a/nextjs-start/.gitignore +++ b/nextjs-start/.gitignore @@ -1,4 +1,5 @@ lib/firebase/config.js +public/auth-service-worker.js .next/ .firebase/ node_modules/ \ No newline at end of file diff --git a/nextjs-start/auth-service-worker.js b/nextjs-start/auth-service-worker.js index 9fe0cf74..d6bdc347 100644 --- a/nextjs-start/auth-service-worker.js +++ b/nextjs-start/auth-service-worker.js @@ -1,5 +1,5 @@ import { initializeApp } from "firebase/app"; -import { getAuth, getIdToken } from "firebase/auth"; +import { getAuth, getIdToken, onAuthStateChanged } from "firebase/auth"; // extract firebase config from query string const serializedFirebaseConfig = new URLSearchParams(self.location.search).get('firebaseConfig'); @@ -9,18 +9,32 @@ if (!serializedFirebaseConfig) { const firebaseConfig = JSON.parse(serializedFirebaseConfig); +const app = initializeApp(firebaseConfig); +const auth = getAuth(app); + self.addEventListener("install", () => { console.log("Service worker installed with Firebase config", firebaseConfig); self.skipWaiting(); }); -self.addEventListener("activate", () => { - self.clients.claim(); +self.addEventListener("activate", (event) => { + event.waitUntil(self.clients.claim()); }); self.addEventListener("fetch", (event) => { - const { origin } = new URL(event.request.url); + const { origin, pathname } = new URL(event.request.url); if (origin !== self.location.origin) return; + // Use a magic url to ensure that auth state is in sync between + // the client and the sw, this helps with actions such as router.refresh(); + if (pathname.startsWith('/__/auth/wait/')) { + const uid = pathname.split('/').at(-1); + event.respondWith(waitForMatchingUid(uid)); + return; + } + if (pathname.startsWith('/_next/')) return; + // Don't add headers to non-get requests or those with an extension—this + // helps with css, images, fonts, json, etc. + if (event.request.method === "GET" && pathname.includes(".")) return; event.respondWith(fetchWithFirebaseHeaders(event.request)); }); @@ -29,7 +43,13 @@ async function fetchWithFirebaseHeaders(request) { return await fetch(request); } +async function waitForMatchingUid(_uid) { + const uid = _uid === "undefined" ? undefined : _uid; + // TODO: wait for the expected user to deal with race conditions + return new Response(undefined, { status: 200, headers: { "cache-control": "no-store" } }); +} + // TODO: get user token -async function getAuthIdToken(auth) { +async function getAuthIdToken() { throw new Error('not implemented'); } diff --git a/nextjs-start/public/auth-service-worker.js b/nextjs-start/public/auth-service-worker.js deleted file mode 100644 index b31ed67c..00000000 --- a/nextjs-start/public/auth-service-worker.js +++ /dev/null @@ -1,1761 +0,0 @@ -(()=>{var Pt=function(n){let e=[],t=0;for(let r=0;r>6|192,e[t++]=i&63|128):(i&64512)===55296&&r+1>18|240,e[t++]=i>>12&63|128,e[t++]=i>>6&63|128,e[t++]=i&63|128):(e[t++]=i>>12|224,e[t++]=i>>6&63|128,e[t++]=i&63|128)}return e},xn=function(n){let e=[],t=0,r=0;for(;t191&&i<224){let s=n[t++];e[r++]=String.fromCharCode((i&31)<<6|s&63)}else if(i>239&&i<365){let s=n[t++],o=n[t++],c=n[t++],a=((i&7)<<18|(s&63)<<12|(o&63)<<6|c&63)-65536;e[r++]=String.fromCharCode(55296+(a>>10)),e[r++]=String.fromCharCode(56320+(a&1023))}else{let s=n[t++],o=n[t++];e[r++]=String.fromCharCode((i&15)<<12|(s&63)<<6|o&63)}}return e.join("")},kt={byteToCharMap_:null,charToByteMap_:null,byteToCharMapWebSafe_:null,charToByteMapWebSafe_:null,ENCODED_VALS_BASE:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",get ENCODED_VALS(){return this.ENCODED_VALS_BASE+"+/="},get ENCODED_VALS_WEBSAFE(){return this.ENCODED_VALS_BASE+"-_."},HAS_NATIVE_SUPPORT:typeof atob=="function",encodeByteArray(n,e){if(!Array.isArray(n))throw Error("encodeByteArray takes an array as a parameter");this.init_();let t=e?this.byteToCharMapWebSafe_:this.byteToCharMap_,r=[];for(let i=0;i>2,p=(s&3)<<4|c>>4,_=(c&15)<<2|u>>6,O=u&63;a||(O=64,o||(_=64)),r.push(t[h],t[p],t[_],t[O])}return r.join("")},encodeString(n,e){return this.HAS_NATIVE_SUPPORT&&!e?btoa(n):this.encodeByteArray(Pt(n),e)},decodeString(n,e){return this.HAS_NATIVE_SUPPORT&&!e?atob(n):xn(this.decodeStringToByteArray(n,e))},decodeStringToByteArray(n,e){this.init_();let t=e?this.charToByteMapWebSafe_:this.charToByteMap_,r=[];for(let i=0;i>4;if(r.push(_),u!==64){let O=c<<4&240|u>>2;if(r.push(O),p!==64){let ce=u<<6&192|p;r.push(ce)}}}return r},init_(){if(!this.byteToCharMap_){this.byteToCharMap_={},this.charToByteMap_={},this.byteToCharMapWebSafe_={},this.charToByteMapWebSafe_={};for(let n=0;n=this.ENCODED_VALS_BASE.length&&(this.charToByteMap_[this.ENCODED_VALS_WEBSAFE.charAt(n)]=n,this.charToByteMapWebSafe_[this.ENCODED_VALS.charAt(n)]=n)}}},xe=class extends Error{constructor(){super(...arguments),this.name="DecodeBase64StringError"}},Fn=function(n){let e=Pt(n);return kt.encodeByteArray(e,!0)},Ve=function(n){return Fn(n).replace(/\./g,"")},je=function(n){try{return kt.decodeString(n,!0)}catch(e){console.error("base64Decode failed: ",e)}return null};function Vn(){if(typeof self<"u")return self;if(typeof window<"u")return window;if(typeof global<"u")return global;throw new Error("Unable to locate global object.")}var jn=()=>Vn().__FIREBASE_DEFAULTS__,Hn=()=>{if(typeof process>"u"||typeof process.env>"u")return;let n=process.env.__FIREBASE_DEFAULTS__;if(n)return JSON.parse(n)},Wn=()=>{if(typeof document>"u")return;let n;try{n=document.cookie.match(/__FIREBASE_DEFAULTS__=([^;]+)/)}catch{return}let e=n&&je(n[1]);return e&&JSON.parse(e)},Bn=()=>{try{return jn()||Hn()||Wn()}catch(n){console.info(`Unable to get __FIREBASE_DEFAULTS__ due to: ${n}`);return}};var Ct=n=>{var e;return(e=Bn())===null||e===void 0?void 0:e[`_${n}`]};function g(){return typeof navigator<"u"&&typeof navigator.userAgent=="string"?navigator.userAgent:""}function Nt(){return typeof window<"u"&&!!(window.cordova||window.phonegap||window.PhoneGap)&&/ios|iphone|ipod|ipad|android|blackberry|iemobile/i.test(g())}function Dt(){let n=typeof chrome=="object"?chrome.runtime:typeof browser=="object"?browser.runtime:void 0;return typeof n=="object"&&n.id!==void 0}function Lt(){return typeof navigator=="object"&&navigator.product==="ReactNative"}function Mt(){let n=g();return n.indexOf("MSIE ")>=0||n.indexOf("Trident/")>=0}function Ut(){try{return typeof indexedDB=="object"}catch{return!1}}function xt(){return new Promise((n,e)=>{try{let t=!0,r="validate-browser-context-for-indexeddb-analytics-module",i=self.indexedDB.open(r);i.onsuccess=()=>{i.result.close(),t||self.indexedDB.deleteDatabase(r),n(!0)},i.onupgradeneeded=()=>{t=!1},i.onerror=()=>{var s;e(((s=i.error)===null||s===void 0?void 0:s.message)||"")}}catch(t){e(t)}})}var zn="FirebaseError",I=class n extends Error{constructor(e,t,r){super(t),this.code=e,this.customData=r,this.name=zn,Object.setPrototypeOf(this,n.prototype),Error.captureStackTrace&&Error.captureStackTrace(this,E.prototype.create)}},E=class{constructor(e,t,r){this.service=e,this.serviceName=t,this.errors=r}create(e,...t){let r=t[0]||{},i=`${this.service}/${e}`,s=this.errors[e],o=s?$n(s,r):"Error",c=`${this.serviceName}: ${o} (${i}).`;return new I(i,c,r)}};function $n(n,e){return n.replace(qn,(t,r)=>{let i=e[r];return i!=null?String(i):`<${r}?>`})}var qn=/\{\$([^}]+)}/g;function ue(n){let e=[];for(let[t,r]of Object.entries(n))Array.isArray(r)?r.forEach(i=>{e.push(encodeURIComponent(t)+"="+encodeURIComponent(i))}):e.push(encodeURIComponent(t)+"="+encodeURIComponent(r));return e.length?"&"+e.join("&"):""}function U(n){let e={};return n.replace(/^\?/,"").split("&").forEach(r=>{if(r){let[i,s]=r.split("=");e[decodeURIComponent(i)]=decodeURIComponent(s)}}),e}function x(n){let e=n.indexOf("?");if(!e)return"";let t=n.indexOf("#",e);return n.substring(e,t>0?t:void 0)}function Ft(n,e){let t=new Fe(n,e);return t.subscribe.bind(t)}var Fe=class{constructor(e,t){this.observers=[],this.unsubscribes=[],this.observerCount=0,this.task=Promise.resolve(),this.finalized=!1,this.onNoObservers=t,this.task.then(()=>{e(this)}).catch(r=>{this.error(r)})}next(e){this.forEachObserver(t=>{t.next(e)})}error(e){this.forEachObserver(t=>{t.error(e)}),this.close(e)}complete(){this.forEachObserver(e=>{e.complete()}),this.close()}subscribe(e,t,r){let i;if(e===void 0&&t===void 0&&r===void 0)throw new Error("Missing Observer.");Gn(e,["next","error","complete"])?i=e:i={next:e,error:t,complete:r},i.next===void 0&&(i.next=Ue),i.error===void 0&&(i.error=Ue),i.complete===void 0&&(i.complete=Ue);let s=this.unsubscribeOne.bind(this,this.observers.length);return this.finalized&&this.task.then(()=>{try{this.finalError?i.error(this.finalError):i.complete()}catch{}}),this.observers.push(i),s}unsubscribeOne(e){this.observers===void 0||this.observers[e]===void 0||(delete this.observers[e],this.observerCount-=1,this.observerCount===0&&this.onNoObservers!==void 0&&this.onNoObservers(this))}forEachObserver(e){if(!this.finalized)for(let t=0;t{if(this.observers!==void 0&&this.observers[e]!==void 0)try{t(this.observers[e])}catch(r){typeof console<"u"&&console.error&&console.error(r)}})}close(e){this.finalized||(this.finalized=!0,e!==void 0&&(this.finalError=e),this.task.then(()=>{this.observers=void 0,this.onNoObservers=void 0}))}};function Gn(n,e){if(typeof n!="object"||n===null)return!1;for(let t of e)if(t in n&&typeof n[t]=="function")return!0;return!1}function Ue(){}var ms=4*60*60*1e3;function F(n){return n&&n._delegate?n._delegate:n}var w=class{constructor(e,t,r){this.name=e,this.instanceFactory=t,this.type=r,this.multipleInstances=!1,this.serviceProps={},this.instantiationMode="LAZY",this.onInstanceCreated=null}setInstantiationMode(e){return this.instantiationMode=e,this}setMultipleInstances(e){return this.multipleInstances=e,this}setServiceProps(e){return this.serviceProps=e,this}setInstanceCreatedCallback(e){return this.onInstanceCreated=e,this}};var Kn=[],d;(function(n){n[n.DEBUG=0]="DEBUG",n[n.VERBOSE=1]="VERBOSE",n[n.INFO=2]="INFO",n[n.WARN=3]="WARN",n[n.ERROR=4]="ERROR",n[n.SILENT=5]="SILENT"})(d||(d={}));var Jn={debug:d.DEBUG,verbose:d.VERBOSE,info:d.INFO,warn:d.WARN,error:d.ERROR,silent:d.SILENT},Yn=d.INFO,Xn={[d.DEBUG]:"log",[d.VERBOSE]:"log",[d.INFO]:"info",[d.WARN]:"warn",[d.ERROR]:"error"},Qn=(n,e,...t)=>{if(ee.some(t=>n instanceof t),Vt,jt;function er(){return Vt||(Vt=[IDBDatabase,IDBObjectStore,IDBIndex,IDBCursor,IDBTransaction])}function tr(){return jt||(jt=[IDBCursor.prototype.advance,IDBCursor.prototype.continue,IDBCursor.prototype.continuePrimaryKey])}var Ht=new WeakMap,We=new WeakMap,Wt=new WeakMap,He=new WeakMap,ze=new WeakMap;function nr(n){let e=new Promise((t,r)=>{let i=()=>{n.removeEventListener("success",s),n.removeEventListener("error",o)},s=()=>{t(v(n.result)),i()},o=()=>{r(n.error),i()};n.addEventListener("success",s),n.addEventListener("error",o)});return e.then(t=>{t instanceof IDBCursor&&Ht.set(t,n)}).catch(()=>{}),ze.set(e,n),e}function rr(n){if(We.has(n))return;let e=new Promise((t,r)=>{let i=()=>{n.removeEventListener("complete",s),n.removeEventListener("error",o),n.removeEventListener("abort",o)},s=()=>{t(),i()},o=()=>{r(n.error||new DOMException("AbortError","AbortError")),i()};n.addEventListener("complete",s),n.addEventListener("error",o),n.addEventListener("abort",o)});We.set(n,e)}var Be={get(n,e,t){if(n instanceof IDBTransaction){if(e==="done")return We.get(n);if(e==="objectStoreNames")return n.objectStoreNames||Wt.get(n);if(e==="store")return t.objectStoreNames[1]?void 0:t.objectStore(t.objectStoreNames[0])}return v(n[e])},set(n,e,t){return n[e]=t,!0},has(n,e){return n instanceof IDBTransaction&&(e==="done"||e==="store")?!0:e in n}};function Bt(n){Be=n(Be)}function ir(n){return n===IDBDatabase.prototype.transaction&&!("objectStoreNames"in IDBTransaction.prototype)?function(e,...t){let r=n.call(le(this),e,...t);return Wt.set(r,e.sort?e.sort():[e]),v(r)}:tr().includes(n)?function(...e){return n.apply(le(this),e),v(Ht.get(this))}:function(...e){return v(n.apply(le(this),e))}}function sr(n){return typeof n=="function"?ir(n):(n instanceof IDBTransaction&&rr(n),Zn(n,er())?new Proxy(n,Be):n)}function v(n){if(n instanceof IDBRequest)return nr(n);if(He.has(n))return He.get(n);let e=sr(n);return e!==n&&(He.set(n,e),ze.set(e,n)),e}var le=n=>ze.get(n);function $t(n,e,{blocked:t,upgrade:r,blocking:i,terminated:s}={}){let o=indexedDB.open(n,e),c=v(o);return r&&o.addEventListener("upgradeneeded",a=>{r(v(o.result),a.oldVersion,a.newVersion,v(o.transaction),a)}),t&&o.addEventListener("blocked",a=>t(a.oldVersion,a.newVersion,a)),c.then(a=>{s&&a.addEventListener("close",()=>s()),i&&a.addEventListener("versionchange",u=>i(u.oldVersion,u.newVersion,u))}).catch(()=>{}),c}var or=["get","getKey","getAll","getAllKeys","count"],ar=["put","add","delete","clear"],$e=new Map;function zt(n,e){if(!(n instanceof IDBDatabase&&!(e in n)&&typeof e=="string"))return;if($e.get(e))return $e.get(e);let t=e.replace(/FromIndex$/,""),r=e!==t,i=ar.includes(t);if(!(t in(r?IDBIndex:IDBObjectStore).prototype)||!(i||or.includes(t)))return;let s=async function(o,...c){let a=this.transaction(o,i?"readwrite":"readonly"),u=a.store;return r&&(u=u.index(c.shift())),(await Promise.all([u[t](...c),i&&a.done]))[0]};return $e.set(e,s),s}Bt(n=>({...n,get:(e,t,r)=>zt(e,t)||n.get(e,t,r),has:(e,t)=>!!zt(e,t)||n.has(e,t)}));var Ge=class{constructor(e){this.container=e}getPlatformInfoString(){return this.container.getProviders().map(t=>{if(ur(t)){let r=t.getImmediate();return`${r.library}/${r.version}`}else return null}).filter(t=>t).join(" ")}};function ur(n){let e=n.getComponent();return e?.type==="VERSION"}var Ke="@firebase/app",qt="0.10.10";var T=new V("@firebase/app"),lr="@firebase/app-compat",dr="@firebase/analytics-compat",hr="@firebase/analytics",fr="@firebase/app-check-compat",pr="@firebase/app-check",mr="@firebase/auth",gr="@firebase/auth-compat",_r="@firebase/database",Ir="@firebase/database-compat",vr="@firebase/functions",yr="@firebase/functions-compat",Er="@firebase/installations",wr="@firebase/installations-compat",Tr="@firebase/messaging",br="@firebase/messaging-compat",Ar="@firebase/performance",Sr="@firebase/performance-compat",Rr="@firebase/remote-config",Or="@firebase/remote-config-compat",Pr="@firebase/storage",kr="@firebase/storage-compat",Cr="@firebase/firestore",Nr="@firebase/vertexai-preview",Dr="@firebase/firestore-compat",Lr="firebase",Mr="10.13.1";var Ur={[Ke]:"fire-core",[lr]:"fire-core-compat",[hr]:"fire-analytics",[dr]:"fire-analytics-compat",[pr]:"fire-app-check",[fr]:"fire-app-check-compat",[mr]:"fire-auth",[gr]:"fire-auth-compat",[_r]:"fire-rtdb",[Ir]:"fire-rtdb-compat",[vr]:"fire-fn",[yr]:"fire-fn-compat",[Er]:"fire-iid",[wr]:"fire-iid-compat",[Tr]:"fire-fcm",[br]:"fire-fcm-compat",[Ar]:"fire-perf",[Sr]:"fire-perf-compat",[Rr]:"fire-rc",[Or]:"fire-rc-compat",[Pr]:"fire-gcs",[kr]:"fire-gcs-compat",[Cr]:"fire-fst",[Dr]:"fire-fst-compat",[Nr]:"fire-vertex","fire-js":"fire-js",[Lr]:"fire-js-all"};var xr=new Map,Fr=new Map,Gt=new Map;function Kt(n,e){try{n.container.addComponent(e)}catch(t){T.debug(`Component ${e.name} failed to register with FirebaseApp ${n.name}`,t)}}function j(n){let e=n.name;if(Gt.has(e))return T.debug(`There were multiple attempts to register component ${e}.`),!1;Gt.set(e,n);for(let t of xr.values())Kt(t,n);for(let t of Fr.values())Kt(t,n);return!0}function A(n){return n.settings!==void 0}var Vr={"no-app":"No Firebase App '{$appName}' has been created - call initializeApp() first","bad-app-name":"Illegal App name: '{$appName}'","duplicate-app":"Firebase App named '{$appName}' already exists with different options or config","app-deleted":"Firebase App named '{$appName}' already deleted","server-app-deleted":"Firebase Server App has been deleted","no-options":"Need to provide options, when not being deployed to hosting via source.","invalid-app-argument":"firebase.{$appName}() takes either no argument or a Firebase App instance.","invalid-log-argument":"First argument to `onLog` must be null or a function.","idb-open":"Error thrown when opening IndexedDB. Original error: {$originalErrorMessage}.","idb-get":"Error thrown when reading from IndexedDB. Original error: {$originalErrorMessage}.","idb-set":"Error thrown when writing to IndexedDB. Original error: {$originalErrorMessage}.","idb-delete":"Error thrown when deleting from IndexedDB. Original error: {$originalErrorMessage}.","finalization-registry-not-supported":"FirebaseServerApp deleteOnDeref field defined but the JS runtime does not support FinalizationRegistry.","invalid-server-app-environment":"FirebaseServerApp is not for use in browser environments."},Xe=new E("app","Firebase",Vr);var de=Mr;function b(n,e,t){var r;let i=(r=Ur[n])!==null&&r!==void 0?r:n;t&&(i+=`-${t}`);let s=i.match(/\s|\//),o=e.match(/\s|\//);if(s||o){let c=[`Unable to register library "${i}" with version "${e}":`];s&&c.push(`library name "${i}" contains illegal characters (whitespace or "/")`),s&&o&&c.push("and"),o&&c.push(`version name "${e}" contains illegal characters (whitespace or "/")`),T.warn(c.join(" "));return}j(new w(`${i}-version`,()=>({library:i,version:e}),"VERSION"))}var jr="firebase-heartbeat-database",Hr=1,G="firebase-heartbeat-store",qe=null;function Qt(){return qe||(qe=$t(jr,Hr,{upgrade:(n,e)=>{switch(e){case 0:try{n.createObjectStore(G)}catch(t){console.warn(t)}}}}).catch(n=>{throw Xe.create("idb-open",{originalErrorMessage:n.message})})),qe}async function Wr(n){try{let t=(await Qt()).transaction(G),r=await t.objectStore(G).get(Zt(n));return await t.done,r}catch(e){if(e instanceof I)T.warn(e.message);else{let t=Xe.create("idb-get",{originalErrorMessage:e?.message});T.warn(t.message)}}}async function Jt(n,e){try{let r=(await Qt()).transaction(G,"readwrite");await r.objectStore(G).put(e,Zt(n)),await r.done}catch(t){if(t instanceof I)T.warn(t.message);else{let r=Xe.create("idb-set",{originalErrorMessage:t?.message});T.warn(r.message)}}}function Zt(n){return`${n.name}!${n.options.appId}`}var Br=1024,zr=30*24*60*60*1e3,Je=class{constructor(e){this.container=e,this._heartbeatsCache=null;let t=this.container.getProvider("app").getImmediate();this._storage=new Ye(t),this._heartbeatsCachePromise=this._storage.read().then(r=>(this._heartbeatsCache=r,r))}async triggerHeartbeat(){var e,t;try{let i=this.container.getProvider("platform-logger").getImmediate().getPlatformInfoString(),s=Yt();return((e=this._heartbeatsCache)===null||e===void 0?void 0:e.heartbeats)==null&&(this._heartbeatsCache=await this._heartbeatsCachePromise,((t=this._heartbeatsCache)===null||t===void 0?void 0:t.heartbeats)==null)||this._heartbeatsCache.lastSentHeartbeatDate===s||this._heartbeatsCache.heartbeats.some(o=>o.date===s)?void 0:(this._heartbeatsCache.heartbeats.push({date:s,agent:i}),this._heartbeatsCache.heartbeats=this._heartbeatsCache.heartbeats.filter(o=>{let c=new Date(o.date).valueOf();return Date.now()-c<=zr}),this._storage.overwrite(this._heartbeatsCache))}catch(r){T.warn(r)}}async getHeartbeatsHeader(){var e;try{if(this._heartbeatsCache===null&&await this._heartbeatsCachePromise,((e=this._heartbeatsCache)===null||e===void 0?void 0:e.heartbeats)==null||this._heartbeatsCache.heartbeats.length===0)return"";let t=Yt(),{heartbeatsToSend:r,unsentEntries:i}=$r(this._heartbeatsCache.heartbeats),s=Ve(JSON.stringify({version:2,heartbeats:r}));return this._heartbeatsCache.lastSentHeartbeatDate=t,i.length>0?(this._heartbeatsCache.heartbeats=i,await this._storage.overwrite(this._heartbeatsCache)):(this._heartbeatsCache.heartbeats=[],this._storage.overwrite(this._heartbeatsCache)),s}catch(t){return T.warn(t),""}}};function Yt(){return new Date().toISOString().substring(0,10)}function $r(n,e=Br){let t=[],r=n.slice();for(let i of n){let s=t.find(o=>o.agent===i.agent);if(s){if(s.dates.push(i.date),Xt(t)>e){s.dates.pop();break}}else if(t.push({agent:i.agent,dates:[i.date]}),Xt(t)>e){t.pop();break}r=r.slice(1)}return{heartbeatsToSend:t,unsentEntries:r}}var Ye=class{constructor(e){this.app=e,this._canUseIndexedDBPromise=this.runIndexedDBEnvironmentCheck()}async runIndexedDBEnvironmentCheck(){return Ut()?xt().then(()=>!0).catch(()=>!1):!1}async read(){if(await this._canUseIndexedDBPromise){let t=await Wr(this.app);return t?.heartbeats?t:{heartbeats:[]}}else return{heartbeats:[]}}async overwrite(e){var t;if(await this._canUseIndexedDBPromise){let i=await this.read();return Jt(this.app,{lastSentHeartbeatDate:(t=e.lastSentHeartbeatDate)!==null&&t!==void 0?t:i.lastSentHeartbeatDate,heartbeats:e.heartbeats})}else return}async add(e){var t;if(await this._canUseIndexedDBPromise){let i=await this.read();return Jt(this.app,{lastSentHeartbeatDate:(t=e.lastSentHeartbeatDate)!==null&&t!==void 0?t:i.lastSentHeartbeatDate,heartbeats:[...i.heartbeats,...e.heartbeats]})}else return}};function Xt(n){return Ve(JSON.stringify({version:2,heartbeats:n})).length}function qr(n){j(new w("platform-logger",e=>new Ge(e),"PRIVATE")),j(new w("heartbeat",e=>new Je(e),"PRIVATE")),b(Ke,qt,n),b(Ke,qt,"esm2017"),b("fire-js","")}qr("");var Gr="firebase",Kr="10.13.1";b(Gr,Kr,"app");function he(n,e){var t={};for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&e.indexOf(r)<0&&(t[r]=n[r]);if(n!=null&&typeof Object.getOwnPropertySymbols=="function")for(var i=0,r=Object.getOwnPropertySymbols(n);i"u")return null;let n=navigator;return n.languages&&n.languages[0]||n.language||null}var C=class{constructor(e,t){this.shortDelay=e,this.longDelay=t,k(t>e,"Short delay should be less than long delay!"),this.isMobile=Nt()||Lt()}get(){return Xr()?this.isMobile?this.longDelay:this.shortDelay:Math.min(5e3,this.shortDelay)}};function Zr(n,e){k(n.emulator,"Emulator should always be set here");let{url:t}=n.emulator;return e?`${t}${e.startsWith("/")?e.slice(1):e}`:t}var ge=class{static initialize(e,t,r){this.fetchImpl=e,t&&(this.headersImpl=t),r&&(this.responseImpl=r)}static fetch(){if(this.fetchImpl)return this.fetchImpl;if(typeof self<"u"&&"fetch"in self)return self.fetch;if(typeof globalThis<"u"&&globalThis.fetch)return globalThis.fetch;if(typeof fetch<"u")return fetch;y("Could not find fetch implementation, make sure you call FetchProvider.initialize() with an appropriate polyfill")}static headers(){if(this.headersImpl)return this.headersImpl;if(typeof self<"u"&&"Headers"in self)return self.Headers;if(typeof globalThis<"u"&&globalThis.Headers)return globalThis.Headers;if(typeof Headers<"u")return Headers;y("Could not find Headers implementation, make sure you call FetchProvider.initialize() with an appropriate polyfill")}static response(){if(this.responseImpl)return this.responseImpl;if(typeof self<"u"&&"Response"in self)return self.Response;if(typeof globalThis<"u"&&globalThis.Response)return globalThis.Response;if(typeof Response<"u")return Response;y("Could not find Response implementation, make sure you call FetchProvider.initialize() with an appropriate polyfill")}};var ei={CREDENTIAL_MISMATCH:"custom-token-mismatch",MISSING_CUSTOM_TOKEN:"internal-error",INVALID_IDENTIFIER:"invalid-email",MISSING_CONTINUE_URI:"internal-error",INVALID_PASSWORD:"wrong-password",MISSING_PASSWORD:"missing-password",INVALID_LOGIN_CREDENTIALS:"invalid-credential",EMAIL_EXISTS:"email-already-in-use",PASSWORD_LOGIN_DISABLED:"operation-not-allowed",INVALID_IDP_RESPONSE:"invalid-credential",INVALID_PENDING_TOKEN:"invalid-credential",FEDERATED_USER_ID_ALREADY_LINKED:"credential-already-in-use",MISSING_REQ_TYPE:"internal-error",EMAIL_NOT_FOUND:"user-not-found",RESET_PASSWORD_EXCEED_LIMIT:"too-many-requests",EXPIRED_OOB_CODE:"expired-action-code",INVALID_OOB_CODE:"invalid-action-code",MISSING_OOB_CODE:"internal-error",CREDENTIAL_TOO_OLD_LOGIN_AGAIN:"requires-recent-login",INVALID_ID_TOKEN:"invalid-user-token",TOKEN_EXPIRED:"user-token-expired",USER_NOT_FOUND:"user-token-expired",TOO_MANY_ATTEMPTS_TRY_LATER:"too-many-requests",PASSWORD_DOES_NOT_MEET_REQUIREMENTS:"password-does-not-meet-requirements",INVALID_CODE:"invalid-verification-code",INVALID_SESSION_INFO:"invalid-verification-id",INVALID_TEMPORARY_PROOF:"invalid-credential",MISSING_SESSION_INFO:"missing-verification-id",SESSION_EXPIRED:"code-expired",MISSING_ANDROID_PACKAGE_NAME:"missing-android-pkg-name",UNAUTHORIZED_DOMAIN:"unauthorized-continue-uri",INVALID_OAUTH_CLIENT_ID:"invalid-oauth-client-id",ADMIN_ONLY_OPERATION:"admin-restricted-operation",INVALID_MFA_PENDING_CREDENTIAL:"invalid-multi-factor-session",MFA_ENROLLMENT_NOT_FOUND:"multi-factor-info-not-found",MISSING_MFA_ENROLLMENT_ID:"missing-multi-factor-info",MISSING_MFA_PENDING_CREDENTIAL:"missing-multi-factor-session",SECOND_FACTOR_EXISTS:"second-factor-already-in-use",SECOND_FACTOR_LIMIT_EXCEEDED:"maximum-second-factor-count-exceeded",BLOCKING_FUNCTION_ERROR_RESPONSE:"internal-error",RECAPTCHA_NOT_ENABLED:"recaptcha-not-enabled",MISSING_RECAPTCHA_TOKEN:"missing-recaptcha-token",INVALID_RECAPTCHA_TOKEN:"invalid-recaptcha-token",INVALID_RECAPTCHA_ACTION:"invalid-recaptcha-action",MISSING_CLIENT_TYPE:"missing-client-type",MISSING_RECAPTCHA_VERSION:"missing-recaptcha-version",INVALID_RECAPTCHA_VERSION:"invalid-recaptcha-version",INVALID_REQ_TYPE:"invalid-req-type"};var ti=new C(3e4,6e4);function f(n,e){return n.tenantId&&!e.tenantId?Object.assign(Object.assign({},e),{tenantId:n.tenantId}):e}async function m(n,e,t,r,i={}){return In(n,i,async()=>{let s={},o={};r&&(e==="GET"?o=r:s={body:JSON.stringify(r)});let c=ue(Object.assign({key:n.config.apiKey},o)).slice(1),a=await n._getAdditionalHeaders();return a["Content-Type"]="application/json",n.languageCode&&(a["X-Firebase-Locale"]=n.languageCode),ge.fetch()(vn(n,n.config.apiHost,t,c),Object.assign({method:e,headers:a,referrerPolicy:"no-referrer"},s))})}async function In(n,e,t){n._canInitEmulator=!1;let r=Object.assign(Object.assign({},ei),e);try{let i=new et(n),s=await Promise.race([t(),i.promise]);i.clearNetworkTimeout();let o=await s.json();if("needConfirmation"in o)throw K(n,"account-exists-with-different-credential",o);if(s.ok&&!("errorMessage"in o))return o;{let c=s.ok?o.errorMessage:o.error.message,[a,u]=c.split(" : ");if(a==="FEDERATED_USER_ID_ALREADY_LINKED")throw K(n,"credential-already-in-use",o);if(a==="EMAIL_EXISTS")throw K(n,"email-already-in-use",o);if(a==="USER_DISABLED")throw K(n,"user-disabled",o);let h=r[a]||a.toLowerCase().replace(/[_\s]+/g,"-");if(u)throw _n(n,h,u);R(n,h)}}catch(i){if(i instanceof I)throw i;R(n,"network-request-failed",{message:String(i)})}}async function M(n,e,t,r,i={}){let s=await m(n,e,t,r,i);return"mfaPendingCredential"in s&&R(n,"multi-factor-auth-required",{_serverResponse:s}),s}function vn(n,e,t,r){let i=`${e}${t}?${r}`;return n.config.emulator?Zr(n.config,i):`${n.config.apiScheme}://${i}`}function ni(n){switch(n){case"ENFORCE":return"ENFORCE";case"AUDIT":return"AUDIT";case"OFF":return"OFF";default:return"ENFORCEMENT_STATE_UNSPECIFIED"}}var et=class{constructor(e){this.auth=e,this.timer=null,this.promise=new Promise((t,r)=>{this.timer=setTimeout(()=>r(H(this.auth,"network-request-failed")),ti.get())})}clearNetworkTimeout(){clearTimeout(this.timer)}};function K(n,e,t){let r={appName:n.name};t.email&&(r.email=t.email),t.phoneNumber&&(r.phoneNumber=t.phoneNumber);let i=H(n,e,r);return i.customData._tokenResponse=t,i}function tn(n){return n!==void 0&&n.enterprise!==void 0}var tt=class{constructor(e){if(this.siteKey="",this.recaptchaEnforcementState=[],e.recaptchaKey===void 0)throw new Error("recaptchaKey undefined");this.siteKey=e.recaptchaKey.split("/")[3],this.recaptchaEnforcementState=e.recaptchaEnforcementState}getProviderEnforcementState(e){if(!this.recaptchaEnforcementState||this.recaptchaEnforcementState.length===0)return null;for(let t of this.recaptchaEnforcementState)if(t.provider&&t.provider===e)return ni(t.enforcementState);return null}isProviderEnabled(e){return this.getProviderEnforcementState(e)==="ENFORCE"||this.getProviderEnforcementState(e)==="AUDIT"}};async function ri(n,e){return m(n,"GET","/v2/recaptchaConfig",f(n,e))}async function ii(n,e){return m(n,"POST","/v1/accounts:delete",e)}async function yn(n,e){return m(n,"POST","/v1/accounts:lookup",e)}function J(n){if(n)try{let e=new Date(Number(n));if(!isNaN(e.getTime()))return e.toUTCString()}catch{}}async function En(n,e=!1){let t=F(n),r=await t.getIdToken(e),i=yt(r);l(i&&i.exp&&i.auth_time&&i.iat,t.auth,"internal-error");let s=typeof i.firebase=="object"?i.firebase:void 0,o=s?.sign_in_provider;return{claims:i,token:r,authTime:J(Qe(i.auth_time)),issuedAtTime:J(Qe(i.iat)),expirationTime:J(Qe(i.exp)),signInProvider:o||null,signInSecondFactor:s?.sign_in_second_factor||null}}function Qe(n){return Number(n)*1e3}function yt(n){let[e,t,r]=n.split(".");if(e===void 0||t===void 0||r===void 0)return pe("JWT malformed, contained fewer than 3 sections"),null;try{let i=je(t);return i?JSON.parse(i):(pe("Failed to decode base64 JWT payload"),null)}catch(i){return pe("Caught error parsing JWT payload as JSON",i?.toString()),null}}function nn(n){let e=yt(n);return l(e,"internal-error"),l(typeof e.exp<"u","internal-error"),l(typeof e.iat<"u","internal-error"),Number(e.exp)-Number(e.iat)}async function X(n,e,t=!1){if(t)return e;try{return await e}catch(r){throw r instanceof I&&si(r)&&n.auth.currentUser===n&&await n.auth.signOut(),r}}function si({code:n}){return n==="auth/user-disabled"||n==="auth/user-token-expired"}var nt=class{constructor(e){this.user=e,this.isRunning=!1,this.timerId=null,this.errorBackoff=3e4}_start(){this.isRunning||(this.isRunning=!0,this.schedule())}_stop(){this.isRunning&&(this.isRunning=!1,this.timerId!==null&&clearTimeout(this.timerId))}getInterval(e){var t;if(e){let r=this.errorBackoff;return this.errorBackoff=Math.min(this.errorBackoff*2,96e4),r}else{this.errorBackoff=3e4;let i=((t=this.user.stsTokenManager.expirationTime)!==null&&t!==void 0?t:0)-Date.now()-3e5;return Math.max(0,i)}}schedule(e=!1){if(!this.isRunning)return;let t=this.getInterval(e);this.timerId=setTimeout(async()=>{await this.iteration()},t)}async iteration(){try{await this.user.getIdToken(!0)}catch(e){e?.code==="auth/network-request-failed"&&this.schedule(!0);return}this.schedule()}};var Q=class{constructor(e,t){this.createdAt=e,this.lastLoginAt=t,this._initializeTime()}_initializeTime(){this.lastSignInTime=J(this.lastLoginAt),this.creationTime=J(this.createdAt)}_copy(e){this.createdAt=e.createdAt,this.lastLoginAt=e.lastLoginAt,this._initializeTime()}toJSON(){return{createdAt:this.createdAt,lastLoginAt:this.lastLoginAt}}};async function _e(n){var e;let t=n.auth,r=await n.getIdToken(),i=await X(n,yn(t,{idToken:r}));l(i?.users.length,t,"internal-error");let s=i.users[0];n._notifyReloadListener(s);let o=!((e=s.providerUserInfo)===null||e===void 0)&&e.length?Tn(s.providerUserInfo):[],c=oi(n.providerData,o),a=n.isAnonymous,u=!(n.email&&s.passwordHash)&&!c?.length,h=a?u:!1,p={uid:s.localId,displayName:s.displayName||null,photoURL:s.photoUrl||null,email:s.email||null,emailVerified:s.emailVerified||!1,phoneNumber:s.phoneNumber||null,tenantId:s.tenantId||null,providerData:c,metadata:new Q(s.createdAt,s.lastLoginAt),isAnonymous:h};Object.assign(n,p)}async function wn(n){let e=F(n);await _e(e),await e.auth._persistUserIfCurrent(e),e.auth._notifyListenersIfCurrent(e)}function oi(n,e){return[...n.filter(r=>!e.some(i=>i.providerId===r.providerId)),...e]}function Tn(n){return n.map(e=>{var{providerId:t}=e,r=he(e,["providerId"]);return{providerId:t,uid:r.rawId||"",displayName:r.displayName||null,email:r.email||null,phoneNumber:r.phoneNumber||null,photoURL:r.photoUrl||null}})}async function ai(n,e){let t=await In(n,{},async()=>{let r=ue({grant_type:"refresh_token",refresh_token:e}).slice(1),{tokenApiHost:i,apiKey:s}=n.config,o=vn(n,i,"/v1/token",`key=${s}`),c=await n._getAdditionalHeaders();return c["Content-Type"]="application/x-www-form-urlencoded",ge.fetch()(o,{method:"POST",headers:c,body:r})});return{accessToken:t.access_token,expiresIn:t.expires_in,refreshToken:t.refresh_token}}async function ci(n,e){return m(n,"POST","/v2/accounts:revokeToken",f(n,e))}var Y=class n{constructor(){this.refreshToken=null,this.accessToken=null,this.expirationTime=null}get isExpired(){return!this.expirationTime||Date.now()>this.expirationTime-3e4}updateFromServerResponse(e){l(e.idToken,"internal-error"),l(typeof e.idToken<"u","internal-error"),l(typeof e.refreshToken<"u","internal-error");let t="expiresIn"in e&&typeof e.expiresIn<"u"?Number(e.expiresIn):nn(e.idToken);this.updateTokensAndExpiration(e.idToken,e.refreshToken,t)}updateFromIdToken(e){l(e.length!==0,"internal-error");let t=nn(e);this.updateTokensAndExpiration(e,null,t)}async getToken(e,t=!1){return!t&&this.accessToken&&!this.isExpired?this.accessToken:(l(this.refreshToken,e,"user-token-expired"),this.refreshToken?(await this.refresh(e,this.refreshToken),this.accessToken):null)}clearRefreshToken(){this.refreshToken=null}async refresh(e,t){let{accessToken:r,refreshToken:i,expiresIn:s}=await ai(e,t);this.updateTokensAndExpiration(r,i,Number(s))}updateTokensAndExpiration(e,t,r){this.refreshToken=t||null,this.accessToken=e||null,this.expirationTime=Date.now()+r*1e3}static fromJSON(e,t){let{refreshToken:r,accessToken:i,expirationTime:s}=t,o=new n;return r&&(l(typeof r=="string","internal-error",{appName:e}),o.refreshToken=r),i&&(l(typeof i=="string","internal-error",{appName:e}),o.accessToken=i),s&&(l(typeof s=="number","internal-error",{appName:e}),o.expirationTime=s),o}toJSON(){return{refreshToken:this.refreshToken,accessToken:this.accessToken,expirationTime:this.expirationTime}}_assign(e){this.accessToken=e.accessToken,this.refreshToken=e.refreshToken,this.expirationTime=e.expirationTime}_clone(){return Object.assign(new n,this.toJSON())}_performRefresh(){return y("not implemented")}};function S(n,e){l(typeof n=="string"||typeof n>"u","internal-error",{appName:e})}var z=class n{constructor(e){var{uid:t,auth:r,stsTokenManager:i}=e,s=he(e,["uid","auth","stsTokenManager"]);this.providerId="firebase",this.proactiveRefresh=new nt(this),this.reloadUserInfo=null,this.reloadListener=null,this.uid=t,this.auth=r,this.stsTokenManager=i,this.accessToken=i.accessToken,this.displayName=s.displayName||null,this.email=s.email||null,this.emailVerified=s.emailVerified||!1,this.phoneNumber=s.phoneNumber||null,this.photoURL=s.photoURL||null,this.isAnonymous=s.isAnonymous||!1,this.tenantId=s.tenantId||null,this.providerData=s.providerData?[...s.providerData]:[],this.metadata=new Q(s.createdAt||void 0,s.lastLoginAt||void 0)}async getIdToken(e){let t=await X(this,this.stsTokenManager.getToken(this.auth,e));return l(t,this.auth,"internal-error"),this.accessToken!==t&&(this.accessToken=t,await this.auth._persistUserIfCurrent(this),this.auth._notifyListenersIfCurrent(this)),t}getIdTokenResult(e){return En(this,e)}reload(){return wn(this)}_assign(e){this!==e&&(l(this.uid===e.uid,this.auth,"internal-error"),this.displayName=e.displayName,this.photoURL=e.photoURL,this.email=e.email,this.emailVerified=e.emailVerified,this.phoneNumber=e.phoneNumber,this.isAnonymous=e.isAnonymous,this.tenantId=e.tenantId,this.providerData=e.providerData.map(t=>Object.assign({},t)),this.metadata._copy(e.metadata),this.stsTokenManager._assign(e.stsTokenManager))}_clone(e){let t=new n(Object.assign(Object.assign({},this),{auth:e,stsTokenManager:this.stsTokenManager._clone()}));return t.metadata._copy(this.metadata),t}_onReload(e){l(!this.reloadListener,this.auth,"internal-error"),this.reloadListener=e,this.reloadUserInfo&&(this._notifyReloadListener(this.reloadUserInfo),this.reloadUserInfo=null)}_notifyReloadListener(e){this.reloadListener?this.reloadListener(e):this.reloadUserInfo=e}_startProactiveRefresh(){this.proactiveRefresh._start()}_stopProactiveRefresh(){this.proactiveRefresh._stop()}async _updateTokensIfNecessary(e,t=!1){let r=!1;e.idToken&&e.idToken!==this.stsTokenManager.accessToken&&(this.stsTokenManager.updateFromServerResponse(e),r=!0),t&&await _e(this),await this.auth._persistUserIfCurrent(this),r&&this.auth._notifyListenersIfCurrent(this)}async delete(){if(A(this.auth.app))return Promise.reject(W(this.auth));let e=await this.getIdToken();return await X(this,ii(this.auth,{idToken:e})),this.stsTokenManager.clearRefreshToken(),this.auth.signOut()}toJSON(){return Object.assign(Object.assign({uid:this.uid,email:this.email||void 0,emailVerified:this.emailVerified,displayName:this.displayName||void 0,isAnonymous:this.isAnonymous,photoURL:this.photoURL||void 0,phoneNumber:this.phoneNumber||void 0,tenantId:this.tenantId||void 0,providerData:this.providerData.map(e=>Object.assign({},e)),stsTokenManager:this.stsTokenManager.toJSON(),_redirectEventId:this._redirectEventId},this.metadata.toJSON()),{apiKey:this.auth.config.apiKey,appName:this.auth.name})}get refreshToken(){return this.stsTokenManager.refreshToken||""}static _fromJSON(e,t){var r,i,s,o,c,a,u,h;let p=(r=t.displayName)!==null&&r!==void 0?r:void 0,_=(i=t.email)!==null&&i!==void 0?i:void 0,O=(s=t.phoneNumber)!==null&&s!==void 0?s:void 0,ce=(o=t.photoURL)!==null&&o!==void 0?o:void 0,Tt=(c=t.tenantId)!==null&&c!==void 0?c:void 0,Ne=(a=t._redirectEventId)!==null&&a!==void 0?a:void 0,bt=(u=t.createdAt)!==null&&u!==void 0?u:void 0,At=(h=t.lastLoginAt)!==null&&h!==void 0?h:void 0,{uid:De,emailVerified:St,isAnonymous:Rt,providerData:Le,stsTokenManager:Ot}=t;l(De&&Ot,e,"internal-error");let Mn=Y.fromJSON(this.name,Ot);l(typeof De=="string",e,"internal-error"),S(p,e.name),S(_,e.name),l(typeof St=="boolean",e,"internal-error"),l(typeof Rt=="boolean",e,"internal-error"),S(O,e.name),S(ce,e.name),S(Tt,e.name),S(Ne,e.name),S(bt,e.name),S(At,e.name);let Me=new n({uid:De,auth:e,email:_,emailVerified:St,displayName:p,isAnonymous:Rt,photoURL:ce,phoneNumber:O,tenantId:Tt,stsTokenManager:Mn,createdAt:bt,lastLoginAt:At});return Le&&Array.isArray(Le)&&(Me.providerData=Le.map(Un=>Object.assign({},Un))),Ne&&(Me._redirectEventId=Ne),Me}static async _fromIdTokenResponse(e,t,r=!1){let i=new Y;i.updateFromServerResponse(t);let s=new n({uid:t.localId,auth:e,stsTokenManager:i,isAnonymous:r});return await _e(s),s}static async _fromGetAccountInfoResponse(e,t,r){let i=t.users[0];l(i.localId!==void 0,"internal-error");let s=i.providerUserInfo!==void 0?Tn(i.providerUserInfo):[],o=!(i.email&&i.passwordHash)&&!s?.length,c=new Y;c.updateFromIdToken(r);let a=new n({uid:i.localId,auth:e,stsTokenManager:c,isAnonymous:o}),u={uid:i.localId,displayName:i.displayName||null,photoURL:i.photoUrl||null,email:i.email||null,emailVerified:i.emailVerified||!1,phoneNumber:i.phoneNumber||null,tenantId:i.tenantId||null,providerData:s,metadata:new Q(i.createdAt,i.lastLoginAt),isAnonymous:!(i.email&&i.passwordHash)&&!s?.length};return Object.assign(a,u),a}};var rn=new Map;function P(n){k(n instanceof Function,"Expected a class definition");let e=rn.get(n);return e?(k(e instanceof n,"Instance stored in cache mismatched with class"),e):(e=new n,rn.set(n,e),e)}var Ie=class{constructor(){this.type="NONE",this.storage={}}async _isAvailable(){return!0}async _set(e,t){this.storage[e]=t}async _get(e){let t=this.storage[e];return t===void 0?null:t}async _remove(e){delete this.storage[e]}_addListener(e,t){}_removeListener(e,t){}};Ie.type="NONE";var rt=Ie;function Ze(n,e,t){return`firebase:${n}:${e}:${t}`}var ve=class n{constructor(e,t,r){this.persistence=e,this.auth=t,this.userKey=r;let{config:i,name:s}=this.auth;this.fullUserKey=Ze(this.userKey,i.apiKey,s),this.fullPersistenceKey=Ze("persistence",i.apiKey,s),this.boundEventHandler=t._onStorageEvent.bind(t),this.persistence._addListener(this.fullUserKey,this.boundEventHandler)}setCurrentUser(e){return this.persistence._set(this.fullUserKey,e.toJSON())}async getCurrentUser(){let e=await this.persistence._get(this.fullUserKey);return e?z._fromJSON(this.auth,e):null}removeCurrentUser(){return this.persistence._remove(this.fullUserKey)}savePersistenceForRedirect(){return this.persistence._set(this.fullPersistenceKey,this.persistence.type)}async setPersistence(e){if(this.persistence===e)return;let t=await this.getCurrentUser();if(await this.removeCurrentUser(),this.persistence=e,t)return this.setCurrentUser(t)}delete(){this.persistence._removeListener(this.fullUserKey,this.boundEventHandler)}static async create(e,t,r="authUser"){if(!t.length)return new n(P(rt),e,r);let i=(await Promise.all(t.map(async u=>{if(await u._isAvailable())return u}))).filter(u=>u),s=i[0]||P(rt),o=Ze(r,e.config.apiKey,e.name),c=null;for(let u of t)try{let h=await u._get(o);if(h){let p=z._fromJSON(e,h);u!==s&&(c=p),s=u;break}}catch{}let a=i.filter(u=>u._shouldAllowMigration);return!s._shouldAllowMigration||!a.length?new n(s,e,r):(s=a[0],c&&await s._set(o,c.toJSON()),await Promise.all(t.map(async u=>{if(u!==s)try{await u._remove(o)}catch{}})),new n(s,e,r))}};function sn(n){let e=n.toLowerCase();if(e.includes("opera/")||e.includes("opr/")||e.includes("opios/"))return"Opera";if(bn(e))return"IEMobile";if(e.includes("msie")||e.includes("trident/"))return"IE";if(e.includes("edge/"))return"Edge";if(ui(e))return"Firefox";if(e.includes("silk/"))return"Silk";if(Sn(e))return"Blackberry";if(Rn(e))return"Webos";if(li(e))return"Safari";if((e.includes("chrome/")||di(e))&&!e.includes("edge/"))return"Chrome";if(An(e))return"Android";{let t=/([a-zA-Z\d\.]+)\/[a-zA-Z\d\.]*$/,r=n.match(t);if(r?.length===2)return r[1]}return"Other"}function ui(n=g()){return/firefox\//i.test(n)}function li(n=g()){let e=n.toLowerCase();return e.includes("safari/")&&!e.includes("chrome/")&&!e.includes("crios/")&&!e.includes("android")}function di(n=g()){return/crios\//i.test(n)}function bn(n=g()){return/iemobile/i.test(n)}function An(n=g()){return/android/i.test(n)}function Sn(n=g()){return/blackberry/i.test(n)}function Rn(n=g()){return/webos/i.test(n)}function hi(n=g()){return/iphone|ipad|ipod/i.test(n)||/macintosh/i.test(n)&&/mobile/i.test(n)}function fi(){return Mt()&&document.documentMode===10}function pi(n=g()){return hi(n)||An(n)||Rn(n)||Sn(n)||/windows phone/i.test(n)||bn(n)}function On(n,e=[]){let t;switch(n){case"Browser":t=sn(g());break;case"Worker":t=`${sn(g())}-${n}`;break;default:t=n}let r=e.length?e.join(","):"FirebaseCore-web";return`${t}/JsCore/${de}/${r}`}var it=class{constructor(e){this.auth=e,this.queue=[]}pushCallback(e,t){let r=s=>new Promise((o,c)=>{try{let a=e(s);o(a)}catch(a){c(a)}});r.onAbort=t,this.queue.push(r);let i=this.queue.length-1;return()=>{this.queue[i]=()=>Promise.resolve()}}async runMiddleware(e){if(this.auth.currentUser===e)return;let t=[];try{for(let r of this.queue)await r(e),r.onAbort&&t.push(r.onAbort)}catch(r){t.reverse();for(let i of t)try{i()}catch{}throw this.auth._errorFactory.create("login-blocked",{originalMessage:r?.message})}}};async function mi(n,e={}){return m(n,"GET","/v2/passwordPolicy",f(n,e))}var gi=6,st=class{constructor(e){var t,r,i,s;let o=e.customStrengthOptions;this.customStrengthOptions={},this.customStrengthOptions.minPasswordLength=(t=o.minPasswordLength)!==null&&t!==void 0?t:gi,o.maxPasswordLength&&(this.customStrengthOptions.maxPasswordLength=o.maxPasswordLength),o.containsLowercaseCharacter!==void 0&&(this.customStrengthOptions.containsLowercaseLetter=o.containsLowercaseCharacter),o.containsUppercaseCharacter!==void 0&&(this.customStrengthOptions.containsUppercaseLetter=o.containsUppercaseCharacter),o.containsNumericCharacter!==void 0&&(this.customStrengthOptions.containsNumericCharacter=o.containsNumericCharacter),o.containsNonAlphanumericCharacter!==void 0&&(this.customStrengthOptions.containsNonAlphanumericCharacter=o.containsNonAlphanumericCharacter),this.enforcementState=e.enforcementState,this.enforcementState==="ENFORCEMENT_STATE_UNSPECIFIED"&&(this.enforcementState="OFF"),this.allowedNonAlphanumericCharacters=(i=(r=e.allowedNonAlphanumericCharacters)===null||r===void 0?void 0:r.join(""))!==null&&i!==void 0?i:"",this.forceUpgradeOnSignin=(s=e.forceUpgradeOnSignin)!==null&&s!==void 0?s:!1,this.schemaVersion=e.schemaVersion}validatePassword(e){var t,r,i,s,o,c;let a={isValid:!0,passwordPolicy:this};return this.validatePasswordLengthOptions(e,a),this.validatePasswordCharacterOptions(e,a),a.isValid&&(a.isValid=(t=a.meetsMinPasswordLength)!==null&&t!==void 0?t:!0),a.isValid&&(a.isValid=(r=a.meetsMaxPasswordLength)!==null&&r!==void 0?r:!0),a.isValid&&(a.isValid=(i=a.containsLowercaseLetter)!==null&&i!==void 0?i:!0),a.isValid&&(a.isValid=(s=a.containsUppercaseLetter)!==null&&s!==void 0?s:!0),a.isValid&&(a.isValid=(o=a.containsNumericCharacter)!==null&&o!==void 0?o:!0),a.isValid&&(a.isValid=(c=a.containsNonAlphanumericCharacter)!==null&&c!==void 0?c:!0),a}validatePasswordLengthOptions(e,t){let r=this.customStrengthOptions.minPasswordLength,i=this.customStrengthOptions.maxPasswordLength;r&&(t.meetsMinPasswordLength=e.length>=r),i&&(t.meetsMaxPasswordLength=e.length<=i)}validatePasswordCharacterOptions(e,t){this.updatePasswordCharacterOptionsStatuses(t,!1,!1,!1,!1);let r;for(let i=0;i="a"&&r<="z",r>="A"&&r<="Z",r>="0"&&r<="9",this.allowedNonAlphanumericCharacters.includes(r))}updatePasswordCharacterOptionsStatuses(e,t,r,i,s){this.customStrengthOptions.containsLowercaseLetter&&(e.containsLowercaseLetter||(e.containsLowercaseLetter=t)),this.customStrengthOptions.containsUppercaseLetter&&(e.containsUppercaseLetter||(e.containsUppercaseLetter=r)),this.customStrengthOptions.containsNumericCharacter&&(e.containsNumericCharacter||(e.containsNumericCharacter=i)),this.customStrengthOptions.containsNonAlphanumericCharacter&&(e.containsNonAlphanumericCharacter||(e.containsNonAlphanumericCharacter=s))}};var ot=class{constructor(e,t,r,i){this.app=e,this.heartbeatServiceProvider=t,this.appCheckServiceProvider=r,this.config=i,this.currentUser=null,this.emulatorConfig=null,this.operations=Promise.resolve(),this.authStateSubscription=new ye(this),this.idTokenSubscription=new ye(this),this.beforeStateQueue=new it(this),this.redirectUser=null,this.isProactiveRefreshEnabled=!1,this.EXPECTED_PASSWORD_POLICY_SCHEMA_VERSION=1,this._canInitEmulator=!0,this._isInitialized=!1,this._deleted=!1,this._initializationPromise=null,this._popupRedirectResolver=null,this._errorFactory=gn,this._agentRecaptchaConfig=null,this._tenantRecaptchaConfigs={},this._projectPasswordPolicy=null,this._tenantPasswordPolicies={},this.lastNotifiedUid=void 0,this.languageCode=null,this.tenantId=null,this.settings={appVerificationDisabledForTesting:!1},this.frameworks=[],this.name=e.name,this.clientVersion=i.sdkClientVersion}_initializeWithPersistence(e,t){return t&&(this._popupRedirectResolver=P(t)),this._initializationPromise=this.queue(async()=>{var r,i;if(!this._deleted&&(this.persistenceManager=await ve.create(this,e),!this._deleted)){if(!((r=this._popupRedirectResolver)===null||r===void 0)&&r._shouldInitProactively)try{await this._popupRedirectResolver._initialize(this)}catch{}await this.initializeCurrentUser(t),this.lastNotifiedUid=((i=this.currentUser)===null||i===void 0?void 0:i.uid)||null,!this._deleted&&(this._isInitialized=!0)}}),this._initializationPromise}async _onStorageEvent(){if(this._deleted)return;let e=await this.assertedPersistence.getCurrentUser();if(!(!this.currentUser&&!e)){if(this.currentUser&&e&&this.currentUser.uid===e.uid){this._currentUser._assign(e),await this.currentUser.getIdToken();return}await this._updateCurrentUser(e,!0)}}async initializeCurrentUserFromIdToken(e){try{let t=await yn(this,{idToken:e}),r=await z._fromGetAccountInfoResponse(this,t,e);await this.directlySetCurrentUser(r)}catch(t){console.warn("FirebaseServerApp could not login user with provided authIdToken: ",t),await this.directlySetCurrentUser(null)}}async initializeCurrentUser(e){var t;if(A(this.app)){let o=this.app.settings.authIdToken;return o?new Promise(c=>{setTimeout(()=>this.initializeCurrentUserFromIdToken(o).then(c,c))}):this.directlySetCurrentUser(null)}let r=await this.assertedPersistence.getCurrentUser(),i=r,s=!1;if(e&&this.config.authDomain){await this.getOrInitRedirectPersistenceManager();let o=(t=this.redirectUser)===null||t===void 0?void 0:t._redirectEventId,c=i?._redirectEventId,a=await this.tryRedirectSignIn(e);(!o||o===c)&&a?.user&&(i=a.user,s=!0)}if(!i)return this.directlySetCurrentUser(null);if(!i._redirectEventId){if(s)try{await this.beforeStateQueue.runMiddleware(i)}catch(o){i=r,this._popupRedirectResolver._overrideRedirectResult(this,()=>Promise.reject(o))}return i?this.reloadAndSetCurrentUserOrClear(i):this.directlySetCurrentUser(null)}return l(this._popupRedirectResolver,this,"argument-error"),await this.getOrInitRedirectPersistenceManager(),this.redirectUser&&this.redirectUser._redirectEventId===i._redirectEventId?this.directlySetCurrentUser(i):this.reloadAndSetCurrentUserOrClear(i)}async tryRedirectSignIn(e){let t=null;try{t=await this._popupRedirectResolver._completeRedirectFn(this,e,!0)}catch{await this._setRedirectUser(null)}return t}async reloadAndSetCurrentUserOrClear(e){try{await _e(e)}catch(t){if(t?.code!=="auth/network-request-failed")return this.directlySetCurrentUser(null)}return this.directlySetCurrentUser(e)}useDeviceLanguage(){this.languageCode=Qr()}async _delete(){this._deleted=!0}async updateCurrentUser(e){if(A(this.app))return Promise.reject(W(this));let t=e?F(e):null;return t&&l(t.auth.config.apiKey===this.config.apiKey,this,"invalid-user-token"),this._updateCurrentUser(t&&t._clone(this))}async _updateCurrentUser(e,t=!1){if(!this._deleted)return e&&l(this.tenantId===e.tenantId,this,"tenant-id-mismatch"),t||await this.beforeStateQueue.runMiddleware(e),this.queue(async()=>{await this.directlySetCurrentUser(e),this.notifyAuthListeners()})}async signOut(){return A(this.app)?Promise.reject(W(this)):(await this.beforeStateQueue.runMiddleware(null),(this.redirectPersistenceManager||this._popupRedirectResolver)&&await this._setRedirectUser(null),this._updateCurrentUser(null,!0))}setPersistence(e){return A(this.app)?Promise.reject(W(this)):this.queue(async()=>{await this.assertedPersistence.setPersistence(P(e))})}_getRecaptchaConfig(){return this.tenantId==null?this._agentRecaptchaConfig:this._tenantRecaptchaConfigs[this.tenantId]}async validatePassword(e){this._getPasswordPolicyInternal()||await this._updatePasswordPolicy();let t=this._getPasswordPolicyInternal();return t.schemaVersion!==this.EXPECTED_PASSWORD_POLICY_SCHEMA_VERSION?Promise.reject(this._errorFactory.create("unsupported-password-policy-schema-version",{})):t.validatePassword(e)}_getPasswordPolicyInternal(){return this.tenantId===null?this._projectPasswordPolicy:this._tenantPasswordPolicies[this.tenantId]}async _updatePasswordPolicy(){let e=await mi(this),t=new st(e);this.tenantId===null?this._projectPasswordPolicy=t:this._tenantPasswordPolicies[this.tenantId]=t}_getPersistence(){return this.assertedPersistence.persistence.type}_updateErrorMap(e){this._errorFactory=new E("auth","Firebase",e())}onAuthStateChanged(e,t,r){return this.registerStateListener(this.authStateSubscription,e,t,r)}beforeAuthStateChanged(e,t){return this.beforeStateQueue.pushCallback(e,t)}onIdTokenChanged(e,t,r){return this.registerStateListener(this.idTokenSubscription,e,t,r)}authStateReady(){return new Promise((e,t)=>{if(this.currentUser)e();else{let r=this.onAuthStateChanged(()=>{r(),e()},t)}})}async revokeAccessToken(e){if(this.currentUser){let t=await this.currentUser.getIdToken(),r={providerId:"apple.com",tokenType:"ACCESS_TOKEN",token:e,idToken:t};this.tenantId!=null&&(r.tenantId=this.tenantId),await ci(this,r)}}toJSON(){var e;return{apiKey:this.config.apiKey,authDomain:this.config.authDomain,appName:this.name,currentUser:(e=this._currentUser)===null||e===void 0?void 0:e.toJSON()}}async _setRedirectUser(e,t){let r=await this.getOrInitRedirectPersistenceManager(t);return e===null?r.removeCurrentUser():r.setCurrentUser(e)}async getOrInitRedirectPersistenceManager(e){if(!this.redirectPersistenceManager){let t=e&&P(e)||this._popupRedirectResolver;l(t,this,"argument-error"),this.redirectPersistenceManager=await ve.create(this,[P(t._redirectPersistence)],"redirectUser"),this.redirectUser=await this.redirectPersistenceManager.getCurrentUser()}return this.redirectPersistenceManager}async _redirectUserForId(e){var t,r;return this._isInitialized&&await this.queue(async()=>{}),((t=this._currentUser)===null||t===void 0?void 0:t._redirectEventId)===e?this._currentUser:((r=this.redirectUser)===null||r===void 0?void 0:r._redirectEventId)===e?this.redirectUser:null}async _persistUserIfCurrent(e){if(e===this.currentUser)return this.queue(async()=>this.directlySetCurrentUser(e))}_notifyListenersIfCurrent(e){e===this.currentUser&&this.notifyAuthListeners()}_key(){return`${this.config.authDomain}:${this.config.apiKey}:${this.name}`}_startProactiveRefresh(){this.isProactiveRefreshEnabled=!0,this.currentUser&&this._currentUser._startProactiveRefresh()}_stopProactiveRefresh(){this.isProactiveRefreshEnabled=!1,this.currentUser&&this._currentUser._stopProactiveRefresh()}get _currentUser(){return this.currentUser}notifyAuthListeners(){var e,t;if(!this._isInitialized)return;this.idTokenSubscription.next(this.currentUser);let r=(t=(e=this.currentUser)===null||e===void 0?void 0:e.uid)!==null&&t!==void 0?t:null;this.lastNotifiedUid!==r&&(this.lastNotifiedUid=r,this.authStateSubscription.next(this.currentUser))}registerStateListener(e,t,r,i){if(this._deleted)return()=>{};let s=typeof t=="function"?t:t.next.bind(t),o=!1,c=this._isInitialized?Promise.resolve():this._initializationPromise;if(l(c,this,"internal-error"),c.then(()=>{o||s(this.currentUser)}),typeof t=="function"){let a=e.addObserver(t,r,i);return()=>{o=!0,a()}}else{let a=e.addObserver(t);return()=>{o=!0,a()}}}async directlySetCurrentUser(e){this.currentUser&&this.currentUser!==e&&this._currentUser._stopProactiveRefresh(),e&&this.isProactiveRefreshEnabled&&e._startProactiveRefresh(),this.currentUser=e,e?await this.assertedPersistence.setCurrentUser(e):await this.assertedPersistence.removeCurrentUser()}queue(e){return this.operations=this.operations.then(e,e),this.operations}get assertedPersistence(){return l(this.persistenceManager,this,"internal-error"),this.persistenceManager}_logFramework(e){!e||this.frameworks.includes(e)||(this.frameworks.push(e),this.frameworks.sort(),this.clientVersion=On(this.config.clientPlatform,this._getFrameworks()))}_getFrameworks(){return this.frameworks}async _getAdditionalHeaders(){var e;let t={"X-Client-Version":this.clientVersion};this.app.options.appId&&(t["X-Firebase-gmpid"]=this.app.options.appId);let r=await((e=this.heartbeatServiceProvider.getImmediate({optional:!0}))===null||e===void 0?void 0:e.getHeartbeatsHeader());r&&(t["X-Firebase-Client"]=r);let i=await this._getAppCheckToken();return i&&(t["X-Firebase-AppCheck"]=i),t}async _getAppCheckToken(){var e;let t=await((e=this.appCheckServiceProvider.getImmediate({optional:!0}))===null||e===void 0?void 0:e.getToken());return t?.error&&Jr(`Error while retrieving App Check token: ${t.error}`),t?.token}};function Et(n){return F(n)}var ye=class{constructor(e){this.auth=e,this.observer=null,this.addObserver=Ft(t=>this.observer=t)}get next(){return l(this.observer,this.auth,"internal-error"),this.observer.next.bind(this.observer)}};var wt={async loadJS(){throw new Error("Unable to load external scripts")},recaptchaV2Script:"",recaptchaEnterpriseScript:"",gapiScript:""};function _i(n){wt=n}function Ii(n){return wt.loadJS(n)}function vi(){return wt.recaptchaEnterpriseScript}function yi(n){return`__${n}${Math.floor(Math.random()*1e6)}`}var Ei="recaptcha-enterprise",wi="NO_RECAPTCHA",at=class{constructor(e){this.type=Ei,this.auth=Et(e)}async verify(e="verify",t=!1){async function r(s){if(!t){if(s.tenantId==null&&s._agentRecaptchaConfig!=null)return s._agentRecaptchaConfig.siteKey;if(s.tenantId!=null&&s._tenantRecaptchaConfigs[s.tenantId]!==void 0)return s._tenantRecaptchaConfigs[s.tenantId].siteKey}return new Promise(async(o,c)=>{ri(s,{clientType:"CLIENT_TYPE_WEB",version:"RECAPTCHA_ENTERPRISE"}).then(a=>{if(a.recaptchaKey===void 0)c(new Error("recaptcha Enterprise site key undefined"));else{let u=new tt(a);return s.tenantId==null?s._agentRecaptchaConfig=u:s._tenantRecaptchaConfigs[s.tenantId]=u,o(u.siteKey)}}).catch(a=>{c(a)})})}function i(s,o,c){let a=window.grecaptcha;tn(a)?a.enterprise.ready(()=>{a.enterprise.execute(s,{action:e}).then(u=>{o(u)}).catch(()=>{o(wi)})}):c(Error("No reCAPTCHA enterprise script loaded."))}return new Promise((s,o)=>{r(this.auth).then(c=>{if(!t&&tn(window.grecaptcha))i(c,s,o);else{if(typeof window>"u"){o(new Error("RecaptchaVerifier is only supported in browser"));return}let a=vi();a.length!==0&&(a+=c),Ii(a).then(()=>{i(c,s,o)}).catch(u=>{o(u)})}}).catch(c=>{o(c)})})}};async function on(n,e,t,r=!1){let i=new at(n),s;try{s=await i.verify(t)}catch{s=await i.verify(t,!0)}let o=Object.assign({},e);return r?Object.assign(o,{captchaResp:s}):Object.assign(o,{captchaResponse:s}),Object.assign(o,{clientType:"CLIENT_TYPE_WEB"}),Object.assign(o,{recaptchaVersion:"RECAPTCHA_ENTERPRISE"}),o}async function an(n,e,t,r){var i;if(!((i=n._getRecaptchaConfig())===null||i===void 0)&&i.isProviderEnabled("EMAIL_PASSWORD_PROVIDER")){let s=await on(n,e,t,t==="getOobCode");return r(n,s)}else return r(n,e).catch(async s=>{if(s.code==="auth/missing-recaptcha-token"){console.log(`${t} is protected by reCAPTCHA Enterprise for this project. Automatically triggering the reCAPTCHA flow and restarting the flow.`);let o=await on(n,e,t,t==="getOobCode");return r(n,o)}else return Promise.reject(s)})}function Ti(n,e){let t=e?.persistence||[],r=(Array.isArray(t)?t:[t]).map(P);e?.errorMap&&n._updateErrorMap(e.errorMap),n._initializeWithPersistence(r,e?.popupRedirectResolver)}var N=class{constructor(e,t){this.providerId=e,this.signInMethod=t}toJSON(){return y("not implemented")}_getIdTokenResponse(e){return y("not implemented")}_linkToIdToken(e,t){return y("not implemented")}_getReauthenticationResolver(e){return y("not implemented")}};async function bi(n,e){return m(n,"POST","/v1/accounts:signUp",e)}async function Ai(n,e){return M(n,"POST","/v1/accounts:signInWithPassword",f(n,e))}async function Si(n,e){return M(n,"POST","/v1/accounts:signInWithEmailLink",f(n,e))}async function Ri(n,e){return M(n,"POST","/v1/accounts:signInWithEmailLink",f(n,e))}var Z=class n extends N{constructor(e,t,r,i=null){super("password",r),this._email=e,this._password=t,this._tenantId=i}static _fromEmailAndPassword(e,t){return new n(e,t,"password")}static _fromEmailAndCode(e,t,r=null){return new n(e,t,"emailLink",r)}toJSON(){return{email:this._email,password:this._password,signInMethod:this.signInMethod,tenantId:this._tenantId}}static fromJSON(e){let t=typeof e=="string"?JSON.parse(e):e;if(t?.email&&t?.password){if(t.signInMethod==="password")return this._fromEmailAndPassword(t.email,t.password);if(t.signInMethod==="emailLink")return this._fromEmailAndCode(t.email,t.password,t.tenantId)}return null}async _getIdTokenResponse(e){switch(this.signInMethod){case"password":let t={returnSecureToken:!0,email:this._email,password:this._password,clientType:"CLIENT_TYPE_WEB"};return an(e,t,"signInWithPassword",Ai);case"emailLink":return Si(e,{email:this._email,oobCode:this._password});default:R(e,"internal-error")}}async _linkToIdToken(e,t){switch(this.signInMethod){case"password":let r={idToken:t,returnSecureToken:!0,email:this._email,password:this._password,clientType:"CLIENT_TYPE_WEB"};return an(e,r,"signUpPassword",bi);case"emailLink":return Ri(e,{idToken:t,email:this._email,oobCode:this._password});default:R(e,"internal-error")}}_getReauthenticationResolver(e){return this._getIdTokenResponse(e)}};async function B(n,e){return M(n,"POST","/v1/accounts:signInWithIdp",f(n,e))}var Oi="http://localhost",D=class n extends N{constructor(){super(...arguments),this.pendingToken=null}static _fromParams(e){let t=new n(e.providerId,e.signInMethod);return e.idToken||e.accessToken?(e.idToken&&(t.idToken=e.idToken),e.accessToken&&(t.accessToken=e.accessToken),e.nonce&&!e.pendingToken&&(t.nonce=e.nonce),e.pendingToken&&(t.pendingToken=e.pendingToken)):e.oauthToken&&e.oauthTokenSecret?(t.accessToken=e.oauthToken,t.secret=e.oauthTokenSecret):R("argument-error"),t}toJSON(){return{idToken:this.idToken,accessToken:this.accessToken,secret:this.secret,nonce:this.nonce,pendingToken:this.pendingToken,providerId:this.providerId,signInMethod:this.signInMethod}}static fromJSON(e){let t=typeof e=="string"?JSON.parse(e):e,{providerId:r,signInMethod:i}=t,s=he(t,["providerId","signInMethod"]);if(!r||!i)return null;let o=new n(r,i);return o.idToken=s.idToken||void 0,o.accessToken=s.accessToken||void 0,o.secret=s.secret,o.nonce=s.nonce,o.pendingToken=s.pendingToken||null,o}_getIdTokenResponse(e){let t=this.buildRequest();return B(e,t)}_linkToIdToken(e,t){let r=this.buildRequest();return r.idToken=t,B(e,r)}_getReauthenticationResolver(e){let t=this.buildRequest();return t.autoCreate=!1,B(e,t)}buildRequest(){let e={requestUri:Oi,returnSecureToken:!0};if(this.pendingToken)e.pendingToken=this.pendingToken;else{let t={};this.idToken&&(t.id_token=this.idToken),this.accessToken&&(t.access_token=this.accessToken),this.secret&&(t.oauth_token_secret=this.secret),t.providerId=this.providerId,this.nonce&&!this.pendingToken&&(t.nonce=this.nonce),e.postBody=ue(t)}return e}};async function Pi(n,e){return m(n,"POST","/v1/accounts:sendVerificationCode",f(n,e))}async function ki(n,e){return M(n,"POST","/v1/accounts:signInWithPhoneNumber",f(n,e))}async function Ci(n,e){let t=await M(n,"POST","/v1/accounts:signInWithPhoneNumber",f(n,e));if(t.temporaryProof)throw K(n,"account-exists-with-different-credential",t);return t}var Ni={USER_NOT_FOUND:"user-not-found"};async function Di(n,e){let t=Object.assign(Object.assign({},e),{operation:"REAUTH"});return M(n,"POST","/v1/accounts:signInWithPhoneNumber",f(n,t),Ni)}var ee=class n extends N{constructor(e){super("phone","phone"),this.params=e}static _fromVerification(e,t){return new n({verificationId:e,verificationCode:t})}static _fromTokenResponse(e,t){return new n({phoneNumber:e,temporaryProof:t})}_getIdTokenResponse(e){return ki(e,this._makeVerificationRequest())}_linkToIdToken(e,t){return Ci(e,Object.assign({idToken:t},this._makeVerificationRequest()))}_getReauthenticationResolver(e){return Di(e,this._makeVerificationRequest())}_makeVerificationRequest(){let{temporaryProof:e,phoneNumber:t,verificationId:r,verificationCode:i}=this.params;return e&&t?{temporaryProof:e,phoneNumber:t}:{sessionInfo:r,code:i}}toJSON(){let e={providerId:this.providerId};return this.params.phoneNumber&&(e.phoneNumber=this.params.phoneNumber),this.params.temporaryProof&&(e.temporaryProof=this.params.temporaryProof),this.params.verificationCode&&(e.verificationCode=this.params.verificationCode),this.params.verificationId&&(e.verificationId=this.params.verificationId),e}static fromJSON(e){typeof e=="string"&&(e=JSON.parse(e));let{verificationId:t,verificationCode:r,phoneNumber:i,temporaryProof:s}=e;return!r&&!t&&!i&&!s?null:new n({verificationId:t,verificationCode:r,phoneNumber:i,temporaryProof:s})}};function Li(n){switch(n){case"recoverEmail":return"RECOVER_EMAIL";case"resetPassword":return"PASSWORD_RESET";case"signIn":return"EMAIL_SIGNIN";case"verifyEmail":return"VERIFY_EMAIL";case"verifyAndChangeEmail":return"VERIFY_AND_CHANGE_EMAIL";case"revertSecondFactorAddition":return"REVERT_SECOND_FACTOR_ADDITION";default:return null}}function Mi(n){let e=U(x(n)).link,t=e?U(x(e)).deep_link_id:null,r=U(x(n)).deep_link_id;return(r?U(x(r)).link:null)||r||t||e||n}var Ee=class n{constructor(e){var t,r,i,s,o,c;let a=U(x(e)),u=(t=a.apiKey)!==null&&t!==void 0?t:null,h=(r=a.oobCode)!==null&&r!==void 0?r:null,p=Li((i=a.mode)!==null&&i!==void 0?i:null);l(u&&h&&p,"argument-error"),this.apiKey=u,this.operation=p,this.code=h,this.continueUrl=(s=a.continueUrl)!==null&&s!==void 0?s:null,this.languageCode=(o=a.languageCode)!==null&&o!==void 0?o:null,this.tenantId=(c=a.tenantId)!==null&&c!==void 0?c:null}static parseLink(e){let t=Mi(e);try{return new n(t)}catch{return null}}};var $=class n{constructor(){this.providerId=n.PROVIDER_ID}static credential(e,t){return Z._fromEmailAndPassword(e,t)}static credentialWithLink(e,t){let r=Ee.parseLink(t);return l(r,"argument-error"),Z._fromEmailAndCode(e,r.code,r.tenantId)}};$.PROVIDER_ID="password";$.EMAIL_PASSWORD_SIGN_IN_METHOD="password";$.EMAIL_LINK_SIGN_IN_METHOD="emailLink";var ct=class{constructor(e){this.providerId=e,this.defaultLanguageCode=null,this.customParameters={}}setDefaultLanguage(e){this.defaultLanguageCode=e}setCustomParameters(e){return this.customParameters=e,this}getCustomParameters(){return this.customParameters}};var q=class extends ct{constructor(){super(...arguments),this.scopes=[]}addScope(e){return this.scopes.includes(e)||this.scopes.push(e),this}getScopes(){return[...this.scopes]}};var te=class n extends q{constructor(){super("facebook.com")}static credential(e){return D._fromParams({providerId:n.PROVIDER_ID,signInMethod:n.FACEBOOK_SIGN_IN_METHOD,accessToken:e})}static credentialFromResult(e){return n.credentialFromTaggedObject(e)}static credentialFromError(e){return n.credentialFromTaggedObject(e.customData||{})}static credentialFromTaggedObject({_tokenResponse:e}){if(!e||!("oauthAccessToken"in e)||!e.oauthAccessToken)return null;try{return n.credential(e.oauthAccessToken)}catch{return null}}};te.FACEBOOK_SIGN_IN_METHOD="facebook.com";te.PROVIDER_ID="facebook.com";var ne=class n extends q{constructor(){super("google.com"),this.addScope("profile")}static credential(e,t){return D._fromParams({providerId:n.PROVIDER_ID,signInMethod:n.GOOGLE_SIGN_IN_METHOD,idToken:e,accessToken:t})}static credentialFromResult(e){return n.credentialFromTaggedObject(e)}static credentialFromError(e){return n.credentialFromTaggedObject(e.customData||{})}static credentialFromTaggedObject({_tokenResponse:e}){if(!e)return null;let{oauthIdToken:t,oauthAccessToken:r}=e;if(!t&&!r)return null;try{return n.credential(t,r)}catch{return null}}};ne.GOOGLE_SIGN_IN_METHOD="google.com";ne.PROVIDER_ID="google.com";var re=class n extends q{constructor(){super("github.com")}static credential(e){return D._fromParams({providerId:n.PROVIDER_ID,signInMethod:n.GITHUB_SIGN_IN_METHOD,accessToken:e})}static credentialFromResult(e){return n.credentialFromTaggedObject(e)}static credentialFromError(e){return n.credentialFromTaggedObject(e.customData||{})}static credentialFromTaggedObject({_tokenResponse:e}){if(!e||!("oauthAccessToken"in e)||!e.oauthAccessToken)return null;try{return n.credential(e.oauthAccessToken)}catch{return null}}};re.GITHUB_SIGN_IN_METHOD="github.com";re.PROVIDER_ID="github.com";var ie=class n extends q{constructor(){super("twitter.com")}static credential(e,t){return D._fromParams({providerId:n.PROVIDER_ID,signInMethod:n.TWITTER_SIGN_IN_METHOD,oauthToken:e,oauthTokenSecret:t})}static credentialFromResult(e){return n.credentialFromTaggedObject(e)}static credentialFromError(e){return n.credentialFromTaggedObject(e.customData||{})}static credentialFromTaggedObject({_tokenResponse:e}){if(!e)return null;let{oauthAccessToken:t,oauthTokenSecret:r}=e;if(!t||!r)return null;try{return n.credential(t,r)}catch{return null}}};ie.TWITTER_SIGN_IN_METHOD="twitter.com";ie.PROVIDER_ID="twitter.com";var se=class n{constructor(e){this.user=e.user,this.providerId=e.providerId,this._tokenResponse=e._tokenResponse,this.operationType=e.operationType}static async _fromIdTokenResponse(e,t,r,i=!1){let s=await z._fromIdTokenResponse(e,r,i),o=cn(r);return new n({user:s,providerId:o,_tokenResponse:r,operationType:t})}static async _forOperation(e,t,r){await e._updateTokensIfNecessary(r,!0);let i=cn(r);return new n({user:e,providerId:i,_tokenResponse:r,operationType:t})}};function cn(n){return n.providerId?n.providerId:"phoneNumber"in n?"phone":null}var ut=class n extends I{constructor(e,t,r,i){var s;super(t.code,t.message),this.operationType=r,this.user=i,Object.setPrototypeOf(this,n.prototype),this.customData={appName:e.name,tenantId:(s=e.tenantId)!==null&&s!==void 0?s:void 0,_serverResponse:t.customData._serverResponse,operationType:r}}static _fromErrorAndOperation(e,t,r,i){return new n(e,t,r,i)}};function Pn(n,e,t,r){return(e==="reauthenticate"?t._getReauthenticationResolver(n):t._getIdTokenResponse(n)).catch(s=>{throw s.code==="auth/multi-factor-auth-required"?ut._fromErrorAndOperation(n,s,e,r):s})}async function Ui(n,e,t=!1){let r=await X(n,e._linkToIdToken(n.auth,await n.getIdToken()),t);return se._forOperation(n,"link",r)}async function xi(n,e,t=!1){let{auth:r}=n;if(A(r.app))return Promise.reject(W(r));let i="reauthenticate";try{let s=await X(n,Pn(r,i,e,n),t);l(s.idToken,r,"internal-error");let o=yt(s.idToken);l(o,r,"internal-error");let{sub:c}=o;return l(n.uid===c,r,"user-mismatch"),se._forOperation(n,i,s)}catch(s){throw s?.code==="auth/user-not-found"&&R(r,"user-mismatch"),s}}async function Fi(n,e,t=!1){if(A(n.app))return Promise.reject(W(n));let r="signIn",i=await Pn(n,r,e),s=await se._fromIdTokenResponse(n,r,i);return t||await n._updateCurrentUser(s.user),s}function Vi(n,e){return m(n,"POST","/v2/accounts/mfaEnrollment:start",f(n,e))}function ji(n,e){return m(n,"POST","/v2/accounts/mfaEnrollment:finalize",f(n,e))}function Hi(n,e){return m(n,"POST","/v2/accounts/mfaEnrollment:start",f(n,e))}function Wi(n,e){return m(n,"POST","/v2/accounts/mfaEnrollment:finalize",f(n,e))}var we="__sak";var Te=class{constructor(e,t){this.storageRetriever=e,this.type=t}_isAvailable(){try{return this.storage?(this.storage.setItem(we,"1"),this.storage.removeItem(we),Promise.resolve(!0)):Promise.resolve(!1)}catch{return Promise.resolve(!1)}}_set(e,t){return this.storage.setItem(e,JSON.stringify(t)),Promise.resolve()}_get(e){let t=this.storage.getItem(e);return Promise.resolve(t?JSON.parse(t):null)}_remove(e){return this.storage.removeItem(e),Promise.resolve()}get storage(){return this.storageRetriever()}};var Bi=1e3,zi=10,lt=class extends Te{constructor(){super(()=>window.localStorage,"LOCAL"),this.boundEventHandler=(e,t)=>this.onStorageEvent(e,t),this.listeners={},this.localCache={},this.pollTimer=null,this.fallbackToPolling=pi(),this._shouldAllowMigration=!0}forAllChangedKeys(e){for(let t of Object.keys(this.listeners)){let r=this.storage.getItem(t),i=this.localCache[t];r!==i&&e(t,i,r)}}onStorageEvent(e,t=!1){if(!e.key){this.forAllChangedKeys((o,c,a)=>{this.notifyListeners(o,a)});return}let r=e.key;t?this.detachListener():this.stopPolling();let i=()=>{let o=this.storage.getItem(r);!t&&this.localCache[r]===o||this.notifyListeners(r,o)},s=this.storage.getItem(r);fi()&&s!==e.newValue&&e.newValue!==e.oldValue?setTimeout(i,zi):i()}notifyListeners(e,t){this.localCache[e]=t;let r=this.listeners[e];if(r)for(let i of Array.from(r))i(t&&JSON.parse(t))}startPolling(){this.stopPolling(),this.pollTimer=setInterval(()=>{this.forAllChangedKeys((e,t,r)=>{this.onStorageEvent(new StorageEvent("storage",{key:e,oldValue:t,newValue:r}),!0)})},Bi)}stopPolling(){this.pollTimer&&(clearInterval(this.pollTimer),this.pollTimer=null)}attachListener(){window.addEventListener("storage",this.boundEventHandler)}detachListener(){window.removeEventListener("storage",this.boundEventHandler)}_addListener(e,t){Object.keys(this.listeners).length===0&&(this.fallbackToPolling?this.startPolling():this.attachListener()),this.listeners[e]||(this.listeners[e]=new Set,this.localCache[e]=this.storage.getItem(e)),this.listeners[e].add(t)}_removeListener(e,t){this.listeners[e]&&(this.listeners[e].delete(t),this.listeners[e].size===0&&delete this.listeners[e]),Object.keys(this.listeners).length===0&&(this.detachListener(),this.stopPolling())}async _set(e,t){await super._set(e,t),this.localCache[e]=JSON.stringify(t)}async _get(e){let t=await super._get(e);return this.localCache[e]=JSON.stringify(t),t}async _remove(e){await super._remove(e),delete this.localCache[e]}};lt.type="LOCAL";var dt=class extends Te{constructor(){super(()=>window.sessionStorage,"SESSION")}_addListener(e,t){}_removeListener(e,t){}};dt.type="SESSION";function $i(n){return Promise.all(n.map(async e=>{try{return{fulfilled:!0,value:await e}}catch(t){return{fulfilled:!1,reason:t}}}))}var be=class n{constructor(e){this.eventTarget=e,this.handlersMap={},this.boundEventHandler=this.handleEvent.bind(this)}static _getInstance(e){let t=this.receivers.find(i=>i.isListeningto(e));if(t)return t;let r=new n(e);return this.receivers.push(r),r}isListeningto(e){return this.eventTarget===e}async handleEvent(e){let t=e,{eventId:r,eventType:i,data:s}=t.data,o=this.handlersMap[i];if(!o?.size)return;t.ports[0].postMessage({status:"ack",eventId:r,eventType:i});let c=Array.from(o).map(async u=>u(t.origin,s)),a=await $i(c);t.ports[0].postMessage({status:"done",eventId:r,eventType:i,response:a})}_subscribe(e,t){Object.keys(this.handlersMap).length===0&&this.eventTarget.addEventListener("message",this.boundEventHandler),this.handlersMap[e]||(this.handlersMap[e]=new Set),this.handlersMap[e].add(t)}_unsubscribe(e,t){this.handlersMap[e]&&t&&this.handlersMap[e].delete(t),(!t||this.handlersMap[e].size===0)&&delete this.handlersMap[e],Object.keys(this.handlersMap).length===0&&this.eventTarget.removeEventListener("message",this.boundEventHandler)}};be.receivers=[];function kn(n="",e=10){let t="";for(let r=0;r{let u=kn("",20);i.port1.start();let h=setTimeout(()=>{a(new Error("unsupported_event"))},r);o={messageChannel:i,onMessage(p){let _=p;if(_.data.eventId===u)switch(_.data.status){case"ack":clearTimeout(h),s=setTimeout(()=>{a(new Error("timeout"))},3e3);break;case"done":clearTimeout(s),c(_.data.response);break;default:clearTimeout(h),clearTimeout(s),a(new Error("invalid_response"));break}}},this.handlers.add(o),i.port1.addEventListener("message",o.onMessage),this.target.postMessage({eventType:e,eventId:u,data:t},[i.port2])}).finally(()=>{o&&this.removeMessageHandler(o)})}};function un(){return window}function Cn(){return typeof un().WorkerGlobalScope<"u"&&typeof un().importScripts=="function"}async function qi(){if(!navigator?.serviceWorker)return null;try{return(await navigator.serviceWorker.ready).active}catch{return null}}function Gi(){var n;return((n=navigator?.serviceWorker)===null||n===void 0?void 0:n.controller)||null}function Ki(){return Cn()?self:null}var Nn="firebaseLocalStorageDb",Ji=1,Ae="firebaseLocalStorage",Dn="fbase_key",L=class{constructor(e){this.request=e}toPromise(){return new Promise((e,t)=>{this.request.addEventListener("success",()=>{e(this.request.result)}),this.request.addEventListener("error",()=>{t(this.request.error)})})}};function Ce(n,e){return n.transaction([Ae],e?"readwrite":"readonly").objectStore(Ae)}function Yi(){let n=indexedDB.deleteDatabase(Nn);return new L(n).toPromise()}function ft(){let n=indexedDB.open(Nn,Ji);return new Promise((e,t)=>{n.addEventListener("error",()=>{t(n.error)}),n.addEventListener("upgradeneeded",()=>{let r=n.result;try{r.createObjectStore(Ae,{keyPath:Dn})}catch(i){t(i)}}),n.addEventListener("success",async()=>{let r=n.result;r.objectStoreNames.contains(Ae)?e(r):(r.close(),await Yi(),e(await ft()))})})}async function ln(n,e,t){let r=Ce(n,!0).put({[Dn]:e,value:t});return new L(r).toPromise()}async function Xi(n,e){let t=Ce(n,!1).get(e),r=await new L(t).toPromise();return r===void 0?null:r.value}function dn(n,e){let t=Ce(n,!0).delete(e);return new L(t).toPromise()}var Qi=800,Zi=3,pt=class{constructor(){this.type="LOCAL",this._shouldAllowMigration=!0,this.listeners={},this.localCache={},this.pollTimer=null,this.pendingWrites=0,this.receiver=null,this.sender=null,this.serviceWorkerReceiverAvailable=!1,this.activeServiceWorker=null,this._workerInitializationPromise=this.initializeServiceWorkerMessaging().then(()=>{},()=>{})}async _openDb(){return this.db?this.db:(this.db=await ft(),this.db)}async _withRetries(e){let t=0;for(;;)try{let r=await this._openDb();return await e(r)}catch(r){if(t++>Zi)throw r;this.db&&(this.db.close(),this.db=void 0)}}async initializeServiceWorkerMessaging(){return Cn()?this.initializeReceiver():this.initializeSender()}async initializeReceiver(){this.receiver=be._getInstance(Ki()),this.receiver._subscribe("keyChanged",async(e,t)=>({keyProcessed:(await this._poll()).includes(t.key)})),this.receiver._subscribe("ping",async(e,t)=>["keyChanged"])}async initializeSender(){var e,t;if(this.activeServiceWorker=await qi(),!this.activeServiceWorker)return;this.sender=new ht(this.activeServiceWorker);let r=await this.sender._send("ping",{},800);r&&!((e=r[0])===null||e===void 0)&&e.fulfilled&&!((t=r[0])===null||t===void 0)&&t.value.includes("keyChanged")&&(this.serviceWorkerReceiverAvailable=!0)}async notifyServiceWorker(e){if(!(!this.sender||!this.activeServiceWorker||Gi()!==this.activeServiceWorker))try{await this.sender._send("keyChanged",{key:e},this.serviceWorkerReceiverAvailable?800:50)}catch{}}async _isAvailable(){try{if(!indexedDB)return!1;let e=await ft();return await ln(e,we,"1"),await dn(e,we),!0}catch{}return!1}async _withPendingWrite(e){this.pendingWrites++;try{await e()}finally{this.pendingWrites--}}async _set(e,t){return this._withPendingWrite(async()=>(await this._withRetries(r=>ln(r,e,t)),this.localCache[e]=t,this.notifyServiceWorker(e)))}async _get(e){let t=await this._withRetries(r=>Xi(r,e));return this.localCache[e]=t,t}async _remove(e){return this._withPendingWrite(async()=>(await this._withRetries(t=>dn(t,e)),delete this.localCache[e],this.notifyServiceWorker(e)))}async _poll(){let e=await this._withRetries(i=>{let s=Ce(i,!1).getAll();return new L(s).toPromise()});if(!e)return[];if(this.pendingWrites!==0)return[];let t=[],r=new Set;if(e.length!==0)for(let{fbase_key:i,value:s}of e)r.add(i),JSON.stringify(this.localCache[i])!==JSON.stringify(s)&&(this.notifyListeners(i,s),t.push(i));for(let i of Object.keys(this.localCache))this.localCache[i]&&!r.has(i)&&(this.notifyListeners(i,null),t.push(i));return t}notifyListeners(e,t){this.localCache[e]=t;let r=this.listeners[e];if(r)for(let i of Array.from(r))i(t)}startPolling(){this.stopPolling(),this.pollTimer=setInterval(async()=>this._poll(),Qi)}stopPolling(){this.pollTimer&&(clearInterval(this.pollTimer),this.pollTimer=null)}_addListener(e,t){Object.keys(this.listeners).length===0&&this.startPolling(),this.listeners[e]||(this.listeners[e]=new Set,this._get(e)),this.listeners[e].add(t)}_removeListener(e,t){this.listeners[e]&&(this.listeners[e].delete(t),this.listeners[e].size===0&&delete this.listeners[e]),Object.keys(this.listeners).length===0&&this.stopPolling()}};pt.type="LOCAL";function es(n,e){return m(n,"POST","/v2/accounts/mfaSignIn:start",f(n,e))}function ts(n,e){return m(n,"POST","/v2/accounts/mfaSignIn:finalize",f(n,e))}function ns(n,e){return m(n,"POST","/v2/accounts/mfaSignIn:finalize",f(n,e))}var Ys=yi("rcb"),Xs=new C(3e4,6e4);var rs="recaptcha";async function is(n,e,t){var r;let i=await t.verify();try{l(typeof i=="string",n,"argument-error"),l(t.type===rs,n,"argument-error");let s;if(typeof e=="string"?s={phoneNumber:e}:s=e,"session"in s){let o=s.session;if("phoneNumber"in s)return l(o.type==="enroll",n,"internal-error"),(await Vi(n,{idToken:o.credential,phoneEnrollmentInfo:{phoneNumber:s.phoneNumber,recaptchaToken:i}})).phoneSessionInfo.sessionInfo;{l(o.type==="signin",n,"internal-error");let c=((r=s.multiFactorHint)===null||r===void 0?void 0:r.uid)||s.multiFactorUid;return l(c,n,"missing-multi-factor-info"),(await es(n,{mfaPendingCredential:o.credential,mfaEnrollmentId:c,phoneSignInInfo:{recaptchaToken:i}})).phoneResponseInfo.sessionInfo}}else{let{sessionInfo:o}=await Pi(n,{phoneNumber:s.phoneNumber,recaptchaToken:i});return o}}finally{t._reset()}}var oe=class n{constructor(e){this.providerId=n.PROVIDER_ID,this.auth=Et(e)}verifyPhoneNumber(e,t){return is(this.auth,e,F(t))}static credential(e,t){return ee._fromVerification(e,t)}static credentialFromResult(e){let t=e;return n.credentialFromTaggedObject(t)}static credentialFromError(e){return n.credentialFromTaggedObject(e.customData||{})}static credentialFromTaggedObject({_tokenResponse:e}){if(!e)return null;let{phoneNumber:t,temporaryProof:r}=e;return t&&r?ee._fromTokenResponse(t,r):null}};oe.PROVIDER_ID="phone";oe.PHONE_SIGN_IN_METHOD="phone";var ae=class extends N{constructor(e){super("custom","custom"),this.params=e}_getIdTokenResponse(e){return B(e,this._buildIdpRequest())}_linkToIdToken(e,t){return B(e,this._buildIdpRequest(t))}_getReauthenticationResolver(e){return B(e,this._buildIdpRequest())}_buildIdpRequest(e){let t={requestUri:this.params.requestUri,sessionId:this.params.sessionId,postBody:this.params.postBody,tenantId:this.params.tenantId,pendingToken:this.params.pendingToken,returnSecureToken:!0,returnIdpCredential:!0};return e&&(t.idToken=e),t}};function ss(n){return Fi(n.auth,new ae(n),n.bypassAuthState)}function os(n){let{auth:e,user:t}=n;return l(t,e,"internal-error"),xi(t,new ae(n),n.bypassAuthState)}async function as(n){let{auth:e,user:t}=n;return l(t,e,"internal-error"),Ui(t,new ae(n),n.bypassAuthState)}var mt=class{constructor(e,t,r,i,s=!1){this.auth=e,this.resolver=r,this.user=i,this.bypassAuthState=s,this.pendingPromise=null,this.eventManager=null,this.filter=Array.isArray(t)?t:[t]}execute(){return new Promise(async(e,t)=>{this.pendingPromise={resolve:e,reject:t};try{this.eventManager=await this.resolver._initialize(this.auth),await this.onExecution(),this.eventManager.registerConsumer(this)}catch(r){this.reject(r)}})}async onAuthEvent(e){let{urlResponse:t,sessionId:r,postBody:i,tenantId:s,error:o,type:c}=e;if(o){this.reject(o);return}let a={auth:this.auth,requestUri:t,sessionId:r,tenantId:s||void 0,postBody:i||void 0,user:this.user,bypassAuthState:this.bypassAuthState};try{this.resolve(await this.getIdpTask(c)(a))}catch(u){this.reject(u)}}onError(e){this.reject(e)}getIdpTask(e){switch(e){case"signInViaPopup":case"signInViaRedirect":return ss;case"linkViaPopup":case"linkViaRedirect":return as;case"reauthViaPopup":case"reauthViaRedirect":return os;default:R(this.auth,"internal-error")}}resolve(e){k(this.pendingPromise,"Pending promise was never set"),this.pendingPromise.resolve(e),this.unregisterAndCleanUp()}reject(e){k(this.pendingPromise,"Pending promise was never set"),this.pendingPromise.reject(e),this.unregisterAndCleanUp()}unregisterAndCleanUp(){this.eventManager&&this.eventManager.unregisterConsumer(this),this.pendingPromise=null,this.cleanUp()}};var cs=new C(2e3,1e4);var gt=class n extends mt{constructor(e,t,r,i,s){super(e,t,i,s),this.provider=r,this.authWindow=null,this.pollId=null,n.currentPopupAction&&n.currentPopupAction.cancel(),n.currentPopupAction=this}async executeNotNull(){let e=await this.execute();return l(e,this.auth,"internal-error"),e}async onExecution(){k(this.filter.length===1,"Popup operations only handle one event");let e=kn();this.authWindow=await this.resolver._openPopup(this.auth,this.provider,this.filter[0],e),this.authWindow.associatedEvent=e,this.resolver._originValidation(this.auth).catch(t=>{this.reject(t)}),this.resolver._isIframeWebStorageSupported(this.auth,t=>{t||this.reject(H(this.auth,"web-storage-unsupported"))}),this.pollUserCancellation()}get eventId(){var e;return((e=this.authWindow)===null||e===void 0?void 0:e.associatedEvent)||null}cancel(){this.reject(H(this.auth,"cancelled-popup-request"))}cleanUp(){this.authWindow&&this.authWindow.close(),this.pollId&&window.clearTimeout(this.pollId),this.authWindow=null,this.pollId=null,n.currentPopupAction=null}pollUserCancellation(){let e=()=>{var t,r;if(!((r=(t=this.authWindow)===null||t===void 0?void 0:t.window)===null||r===void 0)&&r.closed){this.pollId=window.setTimeout(()=>{this.pollId=null,this.reject(H(this.auth,"popup-closed-by-user"))},8e3);return}this.pollId=window.setTimeout(e,cs.get())};e()}};gt.currentPopupAction=null;var Qs=10*60*1e3;var Zs=new C(3e4,6e4);var eo=new C(5e3,15e3);var to=encodeURIComponent("fac");var Se=class{constructor(e){this.factorId=e}_process(e,t,r){switch(t.type){case"enroll":return this._finalizeEnroll(e,t.credential,r);case"signin":return this._finalizeSignIn(e,t.credential);default:return y("unexpected MultiFactorSessionType")}}},_t=class n extends Se{constructor(e){super("phone"),this.credential=e}static _fromCredential(e){return new n(e)}_finalizeEnroll(e,t,r){return ji(e,{idToken:t,displayName:r,phoneVerificationInfo:this.credential._makeVerificationRequest()})}_finalizeSignIn(e,t){return ts(e,{mfaPendingCredential:t,phoneVerificationInfo:this.credential._makeVerificationRequest()})}},Re=class{constructor(){}static assertion(e){return _t._fromCredential(e)}};Re.FACTOR_ID="phone";var Oe=class{static assertionForEnrollment(e,t){return Pe._fromSecret(e,t)}static assertionForSignIn(e,t){return Pe._fromEnrollmentId(e,t)}static async generateSecret(e){var t;let r=e;l(typeof((t=r.user)===null||t===void 0?void 0:t.auth)<"u","internal-error");let i=await Hi(r.user.auth,{idToken:r.credential,totpEnrollmentInfo:{}});return ke._fromStartTotpMfaEnrollmentResponse(i,r.user.auth)}};Oe.FACTOR_ID="totp";var Pe=class n extends Se{constructor(e,t,r){super("totp"),this.otp=e,this.enrollmentId=t,this.secret=r}static _fromSecret(e,t){return new n(t,void 0,e)}static _fromEnrollmentId(e,t){return new n(t,e)}async _finalizeEnroll(e,t,r){return l(typeof this.secret<"u",e,"argument-error"),Wi(e,{idToken:t,displayName:r,totpVerificationInfo:this.secret._makeTotpVerificationInfo(this.otp)})}async _finalizeSignIn(e,t){l(this.enrollmentId!==void 0&&this.otp!==void 0,e,"argument-error");let r={verificationCode:this.otp};return ns(e,{mfaPendingCredential:t,mfaEnrollmentId:this.enrollmentId,totpVerificationInfo:r})}},ke=class n{constructor(e,t,r,i,s,o,c){this.sessionInfo=o,this.auth=c,this.secretKey=e,this.hashingAlgorithm=t,this.codeLength=r,this.codeIntervalSeconds=i,this.enrollmentCompletionDeadline=s}static _fromStartTotpMfaEnrollmentResponse(e,t){return new n(e.totpSessionInfo.sharedSecretKey,e.totpSessionInfo.hashingAlgorithm,e.totpSessionInfo.verificationCodeLength,e.totpSessionInfo.periodSec,new Date(e.totpSessionInfo.finalizeEnrollmentTime).toUTCString(),e.totpSessionInfo.sessionInfo,t)}_makeTotpVerificationInfo(e){return{sessionInfo:this.sessionInfo,verificationCode:e}}generateQrCodeUrl(e,t){var r;let i=!1;return(fe(e)||fe(t))&&(i=!0),i&&(fe(e)&&(e=((r=this.auth.currentUser)===null||r===void 0?void 0:r.email)||"unknownuser"),fe(t)&&(t=this.auth.name)),`otpauth://totp/${t}:${e}?secret=${this.secretKey}&issuer=${t}&algorithm=${this.hashingAlgorithm}&digits=${this.codeLength}`}};function fe(n){return typeof n>"u"||n?.length===0}var hn="@firebase/auth",fn="1.7.8";var It=class{constructor(e){this.auth=e,this.internalListeners=new Map}getUid(){var e;return this.assertAuthConfigured(),((e=this.auth.currentUser)===null||e===void 0?void 0:e.uid)||null}async getToken(e){return this.assertAuthConfigured(),await this.auth._initializationPromise,this.auth.currentUser?{accessToken:await this.auth.currentUser.getIdToken(e)}:null}addAuthTokenListener(e){if(this.assertAuthConfigured(),this.internalListeners.has(e))return;let t=this.auth.onIdTokenChanged(r=>{e(r?.stsTokenManager.accessToken||null)});this.internalListeners.set(e,t),this.updateProactiveRefresh()}removeAuthTokenListener(e){this.assertAuthConfigured();let t=this.internalListeners.get(e);t&&(this.internalListeners.delete(e),t(),this.updateProactiveRefresh())}assertAuthConfigured(){l(this.auth._initializationPromise,"dependent-sdk-initialized-before-auth")}updateProactiveRefresh(){this.internalListeners.size>0?this.auth._startProactiveRefresh():this.auth._stopProactiveRefresh()}};function us(n){switch(n){case"Node":return"node";case"ReactNative":return"rn";case"Worker":return"webworker";case"Cordova":return"cordova";case"WebExtension":return"web-extension";default:return}}function ls(n){j(new w("auth",(e,{options:t})=>{let r=e.getProvider("app").getImmediate(),i=e.getProvider("heartbeat"),s=e.getProvider("app-check-internal"),{apiKey:o,authDomain:c}=r.options;l(o&&!o.includes(":"),"invalid-api-key",{appName:r.name});let a={apiKey:o,authDomain:c,clientPlatform:n,apiHost:"identitytoolkit.googleapis.com",tokenApiHost:"securetoken.googleapis.com",apiScheme:"https",sdkClientVersion:On(n)},u=new ot(r,i,s,a);return Ti(u,t),u},"PUBLIC").setInstantiationMode("EXPLICIT").setInstanceCreatedCallback((e,t,r)=>{e.getProvider("auth-internal").initialize()})),j(new w("auth-internal",e=>{let t=Et(e.getProvider("auth").getImmediate());return(r=>new It(r))(t)},"PRIVATE").setInstantiationMode("EXPLICIT")),b(hn,fn,us(n)),b(hn,fn,"esm2017")}var ds=5*60,no=Ct("authIdTokenMaxAge")||ds;function hs(){var n,e;return(e=(n=document.getElementsByTagName("head"))===null||n===void 0?void 0:n[0])!==null&&e!==void 0?e:document}_i({loadJS(n){return new Promise((e,t)=>{let r=document.createElement("script");r.setAttribute("src",n),r.onload=e,r.onerror=i=>{let s=H("internal-error");s.customData=i,t(s)},r.type="text/javascript",r.charset="UTF-8",hs().appendChild(r)})},gapiScript:"https://apis.google.com/js/api.js",recaptchaV2Script:"https://www.google.com/recaptcha/api.js",recaptchaEnterpriseScript:"https://www.google.com/recaptcha/enterprise.js?render="});ls("Browser");var Ln=new URLSearchParams(self.location.search).get("firebaseConfig");if(!Ln)throw new Error("Firebase Config object not found in service worker query string.");var fs=JSON.parse(Ln);self.addEventListener("install",()=>{console.log("Service worker installed with Firebase config",fs),self.skipWaiting()});self.addEventListener("activate",()=>{self.clients.claim()});self.addEventListener("fetch",n=>{let{origin:e}=new URL(n.request.url);e===self.location.origin&&n.respondWith(ps(n.request))});async function ps(n){return await fetch(n)}})(); -/*! Bundled license information: - -@firebase/util/dist/index.esm2017.js: - (** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/util/dist/index.esm2017.js: - (** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/util/dist/index.esm2017.js: - (** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/util/dist/index.esm2017.js: - (** - * @license - * Copyright 2022 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/util/dist/index.esm2017.js: - (** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/util/dist/index.esm2017.js: - (** - * @license - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/util/dist/index.esm2017.js: - (** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/util/dist/index.esm2017.js: - (** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/util/dist/index.esm2017.js: - (** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/util/dist/index.esm2017.js: - (** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/util/dist/index.esm2017.js: - (** - * @license - * Copyright 2022 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/util/dist/index.esm2017.js: - (** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/util/dist/index.esm2017.js: - (** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/util/dist/index.esm2017.js: - (** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/util/dist/index.esm2017.js: - (** - * @license - * Copyright 2022 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/util/dist/index.esm2017.js: - (** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/util/dist/index.esm2017.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/util/dist/index.esm2017.js: - (** - * @license - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/component/dist/esm/index.esm2017.js: - (** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/component/dist/esm/index.esm2017.js: - (** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/component/dist/esm/index.esm2017.js: - (** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/logger/dist/esm/index.esm2017.js: - (** - * @license - * Copyright 2017 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/app/dist/esm/index.esm2017.js: - (** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/app/dist/esm/index.esm2017.js: - (** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/app/dist/esm/index.esm2017.js: - (** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/app/dist/esm/index.esm2017.js: - (** - * @license - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/app/dist/esm/index.esm2017.js: - (** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/app/dist/esm/index.esm2017.js: - (** - * @license - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - (** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -firebase/app/dist/esm/index.esm.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - (** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - (** - * @license - * Copyright 2022 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - (** - * @license - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - (** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - (** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - (** - * @license - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - -@firebase/auth/dist/esm2017/index-2788dcb0.js: - (** - * @license - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) - (** - * @license - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *) -*/ diff --git a/nextjs-start/src/components/Header.jsx b/nextjs-start/src/components/Header.jsx index da1b1bdf..3f9a7e20 100644 --- a/nextjs-start/src/components/Header.jsx +++ b/nextjs-start/src/components/Header.jsx @@ -5,12 +5,10 @@ import { signInWithGoogle, signOut, onAuthStateChanged, - onIdTokenChanged, } from "@/src/lib/firebase/auth.js"; import { addFakeRestaurantsAndReviews } from "@/src/lib/firebase/firestore.js"; import { useRouter } from "next/navigation"; import { firebaseConfig } from "@/src/lib/firebase/config"; -import { getIdToken } from "firebase/auth"; function useUserSession(initialUser) { return; diff --git a/nextjs-start/src/lib/firebase/clientApp.js b/nextjs-start/src/lib/firebase/clientApp.js index e988ef6e..d05ec760 100644 --- a/nextjs-start/src/lib/firebase/clientApp.js +++ b/nextjs-start/src/lib/firebase/clientApp.js @@ -1,12 +1,13 @@ 'use client'; -import { initializeApp, getApps } from "firebase/app"; +import { initializeApp } from "firebase/app"; import { firebaseConfig } from "./config"; import { getAuth } from "firebase/auth"; import { getFirestore } from "firebase/firestore"; import { getStorage } from "firebase/storage"; -export const firebaseApp = - getApps().length === 0 ? initializeApp(firebaseConfig) : getApps()[0]; + +export const firebaseApp = initializeApp(firebaseConfig); + export const auth = getAuth(firebaseApp); export const db = getFirestore(firebaseApp); export const storage = getStorage(firebaseApp); diff --git a/nextjs-start/src/lib/firebase/serverApp.js b/nextjs-start/src/lib/firebase/serverApp.js index 5515b12a..6a66b85c 100644 --- a/nextjs-start/src/lib/firebase/serverApp.js +++ b/nextjs-start/src/lib/firebase/serverApp.js @@ -8,6 +8,8 @@ import { initializeServerApp } from "firebase/app"; import { firebaseConfig } from "./config"; import { getAuth } from "firebase/auth"; +// Returns an authenticated client SDK instance for use in Server Side Rendering +// and Static Site Generation export async function getAuthenticatedAppForUser() { throw new Error('not implemented'); } \ No newline at end of file From b0698698d252d6bb22a21e65860b94117bfa947d Mon Sep 17 00:00:00 2001 From: James Daniels Date: Tue, 10 Sep 2024 14:29:08 -0400 Subject: [PATCH 14/38] more cleanup --- nextjs-end/auth-service-worker.js | 36 ++++++++++++------------ nextjs-end/src/lib/firebase/auth.js | 7 ----- nextjs-end/src/lib/firebase/clientApp.js | 7 +++-- nextjs-end/src/lib/firebase/storage.js | 10 ++++--- 4 files changed, 28 insertions(+), 32 deletions(-) diff --git a/nextjs-end/auth-service-worker.js b/nextjs-end/auth-service-worker.js index fd8cd538..f5822ff2 100644 --- a/nextjs-end/auth-service-worker.js +++ b/nextjs-end/auth-service-worker.js @@ -21,12 +21,6 @@ self.addEventListener("activate", (event) => { event.waitUntil(self.clients.claim()); }); -async function getAuthIdToken() { - await auth.authStateReady(); - if (!auth.currentUser) return; - return await getIdToken(auth.currentUser); -}; - self.addEventListener("fetch", (event) => { const { origin, pathname } = new URL(event.request.url); if (origin !== self.location.origin) return; @@ -44,6 +38,19 @@ self.addEventListener("fetch", (event) => { event.respondWith(fetchWithFirebaseHeaders(event.request)); }); +async function fetchWithFirebaseHeaders(request) { + const authIdToken = await getAuthIdToken(); + if (authIdToken) { + const headers = new Headers(request.headers); + headers.append("Authorization", `Bearer ${authIdToken}`); + request = new Request(request, { headers }); + } + return await fetch(request).catch((reason) => { + console.error(reason); + return new Response("Fail.", { status: 500, headers: { "content-type": "text/html" } }); + }); +} + async function waitForMatchingUid(_uid) { const uid = _uid === "undefined" ? undefined : _uid; await auth.authStateReady(); @@ -58,15 +65,8 @@ async function waitForMatchingUid(_uid) { return new Response(undefined, { status: 200, headers: { "cache-control": "no-store" } }); } -async function fetchWithFirebaseHeaders(request) { - const authIdToken = await getAuthIdToken(); - if (authIdToken) { - const headers = new Headers(request.headers); - headers.append("Authorization", `Bearer ${authIdToken}`); - request = new Request(request, { headers }); - } - return await fetch(request).catch((reason) => { - console.error(reason); - return new Response("Fail.", { status: 500, headers: { "content-type": "text/html" } }); - }); -} +async function getAuthIdToken() { + await auth.authStateReady(); + if (!auth.currentUser) return; + return await getIdToken(auth.currentUser); +}; diff --git a/nextjs-end/src/lib/firebase/auth.js b/nextjs-end/src/lib/firebase/auth.js index 95e08709..362984b3 100644 --- a/nextjs-end/src/lib/firebase/auth.js +++ b/nextjs-end/src/lib/firebase/auth.js @@ -2,17 +2,10 @@ import { GoogleAuthProvider, signInWithPopup, onAuthStateChanged as _onAuthStateChanged, - onIdTokenChanged as _onIdTokenChanged, } from "firebase/auth"; import { auth } from "@/src/lib/firebase/clientApp"; -export { getIdToken } from "firebase/auth"; - -export function onIdTokenChanged(cb) { - return _onIdTokenChanged(auth, cb); -} - export function onAuthStateChanged(cb) { return _onAuthStateChanged(auth, cb); } diff --git a/nextjs-end/src/lib/firebase/clientApp.js b/nextjs-end/src/lib/firebase/clientApp.js index e988ef6e..d05ec760 100644 --- a/nextjs-end/src/lib/firebase/clientApp.js +++ b/nextjs-end/src/lib/firebase/clientApp.js @@ -1,12 +1,13 @@ 'use client'; -import { initializeApp, getApps } from "firebase/app"; +import { initializeApp } from "firebase/app"; import { firebaseConfig } from "./config"; import { getAuth } from "firebase/auth"; import { getFirestore } from "firebase/firestore"; import { getStorage } from "firebase/storage"; -export const firebaseApp = - getApps().length === 0 ? initializeApp(firebaseConfig) : getApps()[0]; + +export const firebaseApp = initializeApp(firebaseConfig); + export const auth = getAuth(firebaseApp); export const db = getFirestore(firebaseApp); export const storage = getStorage(firebaseApp); diff --git a/nextjs-end/src/lib/firebase/storage.js b/nextjs-end/src/lib/firebase/storage.js index 99417d08..9ea3e31a 100644 --- a/nextjs-end/src/lib/firebase/storage.js +++ b/nextjs-end/src/lib/firebase/storage.js @@ -6,11 +6,13 @@ import { updateRestaurantImageReference } from "@/src/lib/firebase/firestore"; export async function updateRestaurantImage(restaurantId, image) { try { - if (!restaurantId) - {throw new Error("No restaurant ID has been provided.");} + if (!restaurantId) { + throw new Error("No restaurant ID has been provided."); + } - if (!image || !image.name) - {throw new Error("A valid image has not been provided.");} + if (!image || !image.name) { + throw new Error("A valid image has not been provided."); + } const publicImageUrl = await uploadImage(restaurantId, image); await updateRestaurantImageReference(restaurantId, publicImageUrl); From 27171b6455d4d9ca791f899b205d0785855a953b Mon Sep 17 00:00:00 2001 From: James Daniels Date: Tue, 10 Sep 2024 15:12:19 -0400 Subject: [PATCH 15/38] Use prettier --- nextjs-end/.eslintrc | 5 +- nextjs-end/.prettierrc.json | 8 + nextjs-end/apphosting.yaml | 2 +- nextjs-end/auth-service-worker.js | 50 +- nextjs-end/firebase.json | 6 +- nextjs-end/jsconfig.json | 10 +- nextjs-end/next.config.js | 6 +- nextjs-end/package.json | 79 +- nextjs-end/src/app/actions.js | 24 +- nextjs-end/src/app/layout.js | 19 +- nextjs-end/src/app/page.js | 37 +- nextjs-end/src/app/restaurant/[id]/error.jsx | 36 +- nextjs-end/src/app/restaurant/[id]/page.jsx | 30 +- nextjs-end/src/app/styles.css | 851 ++++++++--------- nextjs-end/src/components/Filters.jsx | 294 +++--- nextjs-end/src/components/Header.jsx | 193 ++-- nextjs-end/src/components/RatingPicker.jsx | 110 +-- nextjs-end/src/components/Restaurant.jsx | 44 +- .../src/components/RestaurantDetails.jsx | 104 +-- .../src/components/RestaurantListings.jsx | 157 ++-- nextjs-end/src/components/ReviewDialog.jsx | 143 ++- nextjs-end/src/components/Reviews/Review.jsx | 49 +- .../src/components/Reviews/ReviewSummary.jsx | 26 +- .../src/components/Reviews/ReviewsList.jsx | 23 +- .../components/Reviews/ReviewsListClient.jsx | 23 +- nextjs-end/src/components/Stars.jsx | 82 +- nextjs-end/src/components/Tag.jsx | 24 +- nextjs-end/src/lib/fakeRestaurants.js | 140 ++- nextjs-end/src/lib/firebase/auth.js | 10 +- nextjs-end/src/lib/firebase/clientApp.js | 10 +- nextjs-end/src/lib/firebase/config.js | 2 +- nextjs-end/src/lib/firebase/firestore.js | 398 ++++---- nextjs-end/src/lib/firebase/serverApp.js | 16 +- nextjs-end/src/lib/firebase/storage.js | 46 +- nextjs-end/src/lib/getUser.js | 8 +- nextjs-end/src/lib/randomData.js | 284 +++--- nextjs-end/src/lib/utils.js | 22 +- nextjs-start/.eslintrc | 5 +- nextjs-start/apphosting.yaml | 2 +- nextjs-start/auth-service-worker.js | 21 +- nextjs-start/jsconfig.json | 10 +- nextjs-start/next.config.js | 6 +- nextjs-start/package.json | 79 +- nextjs-start/src/app/layout.js | 5 +- nextjs-start/src/app/page.js | 27 +- .../src/app/restaurant/[id]/error.jsx | 32 +- nextjs-start/src/app/restaurant/[id]/page.jsx | 16 +- nextjs-start/src/app/styles.css | 853 +++++++++--------- nextjs-start/src/components/Filters.jsx | 292 +++--- nextjs-start/src/components/Header.jsx | 118 +-- nextjs-start/src/components/RatingPicker.jsx | 108 +-- nextjs-start/src/components/Restaurant.jsx | 36 +- .../src/components/RestaurantDetails.jsx | 100 +- .../src/components/RestaurantListings.jsx | 143 ++- nextjs-start/src/components/ReviewDialog.jsx | 141 ++- .../src/components/Reviews/Review.jsx | 47 +- .../src/components/Reviews/ReviewsList.jsx | 9 +- .../components/Reviews/ReviewsListClient.jsx | 9 +- nextjs-start/src/components/Stars.jsx | 82 +- nextjs-start/src/components/Tag.jsx | 24 +- nextjs-start/src/lib/fakeRestaurants.js | 134 ++- nextjs-start/src/lib/firebase/clientApp.js | 2 +- nextjs-start/src/lib/firebase/firestore.js | 194 ++-- nextjs-start/src/lib/firebase/serverApp.js | 4 +- nextjs-start/src/lib/randomData.js | 284 +++--- nextjs-start/src/lib/utils.js | 22 +- 66 files changed, 3113 insertions(+), 3063 deletions(-) create mode 100644 nextjs-end/.prettierrc.json diff --git a/nextjs-end/.eslintrc b/nextjs-end/.eslintrc index c8027ead..26ba79a7 100644 --- a/nextjs-end/.eslintrc +++ b/nextjs-end/.eslintrc @@ -1,9 +1,6 @@ { - "extends": ["eslint:recommended", "next"], + "extends": ["eslint:recommended", "next", "prettier"], "rules": { - "indent": "off", - "quotes": "off", - "no-mixed-spaces-and-tabs": "off", "@next/next/no-img-element": "off" } } \ No newline at end of file diff --git a/nextjs-end/.prettierrc.json b/nextjs-end/.prettierrc.json new file mode 100644 index 00000000..48c545c5 --- /dev/null +++ b/nextjs-end/.prettierrc.json @@ -0,0 +1,8 @@ +{ + "trailingComma": "all", + "semi": true, + "tabWidth": 2, + "singleQuote": true, + "jsxSingleQuote": true, + "plugins": [] +} diff --git a/nextjs-end/apphosting.yaml b/nextjs-end/apphosting.yaml index 12017a25..8c75763d 100644 --- a/nextjs-end/apphosting.yaml +++ b/nextjs-end/apphosting.yaml @@ -14,4 +14,4 @@ env: - variable: NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID value: TODO - variable: NEXT_PUBLIC_FIREBASE_APP_ID - value: TODO \ No newline at end of file + value: TODO diff --git a/nextjs-end/auth-service-worker.js b/nextjs-end/auth-service-worker.js index f5822ff2..b24d82cc 100644 --- a/nextjs-end/auth-service-worker.js +++ b/nextjs-end/auth-service-worker.js @@ -1,10 +1,14 @@ -import { initializeApp } from "firebase/app"; -import { getAuth, getIdToken, onAuthStateChanged } from "firebase/auth"; +import { initializeApp } from 'firebase/app'; +import { getAuth, getIdToken, onAuthStateChanged } from 'firebase/auth'; // extract firebase config from query string -const serializedFirebaseConfig = new URLSearchParams(self.location.search).get('firebaseConfig'); +const serializedFirebaseConfig = new URLSearchParams(self.location.search).get( + 'firebaseConfig', +); if (!serializedFirebaseConfig) { - throw new Error('Firebase Config object not found in service worker query string.'); + throw new Error( + 'Firebase Config object not found in service worker query string.', + ); } const firebaseConfig = JSON.parse(serializedFirebaseConfig); @@ -12,16 +16,16 @@ const firebaseConfig = JSON.parse(serializedFirebaseConfig); const app = initializeApp(firebaseConfig); const auth = getAuth(app); -self.addEventListener("install", () => { - console.log("Service worker installed with Firebase config", firebaseConfig); +self.addEventListener('install', () => { + console.log('Service worker installed with Firebase config', firebaseConfig); self.skipWaiting(); }); -self.addEventListener("activate", (event) => { +self.addEventListener('activate', (event) => { event.waitUntil(self.clients.claim()); }); -self.addEventListener("fetch", (event) => { +self.addEventListener('fetch', (event) => { const { origin, pathname } = new URL(event.request.url); if (origin !== self.location.origin) return; // Use a magic url to ensure that auth state is in sync between @@ -34,7 +38,7 @@ self.addEventListener("fetch", (event) => { if (pathname.startsWith('/_next/')) return; // Don't add headers to non-get requests or those with an extension—this // helps with css, images, fonts, json, etc. - if (event.request.method === "GET" && pathname.includes(".")) return; + if (event.request.method === 'GET' && pathname.includes('.')) return; event.respondWith(fetchWithFirebaseHeaders(event.request)); }); @@ -42,31 +46,37 @@ async function fetchWithFirebaseHeaders(request) { const authIdToken = await getAuthIdToken(); if (authIdToken) { const headers = new Headers(request.headers); - headers.append("Authorization", `Bearer ${authIdToken}`); + headers.append('Authorization', `Bearer ${authIdToken}`); request = new Request(request, { headers }); } return await fetch(request).catch((reason) => { console.error(reason); - return new Response("Fail.", { status: 500, headers: { "content-type": "text/html" } }); + return new Response('Fail.', { + status: 500, + headers: { 'content-type': 'text/html' }, + }); }); } async function waitForMatchingUid(_uid) { - const uid = _uid === "undefined" ? undefined : _uid; + const uid = _uid === 'undefined' ? undefined : _uid; await auth.authStateReady(); await new Promise((resolve) => { - const unsubscribe = onAuthStateChanged(auth, (user) => { - if (user?.uid === uid) { - unsubscribe(); - resolve(); - } - }); + const unsubscribe = onAuthStateChanged(auth, (user) => { + if (user?.uid === uid) { + unsubscribe(); + resolve(); + } + }); + }); + return new Response(undefined, { + status: 200, + headers: { 'cache-control': 'no-store' }, }); - return new Response(undefined, { status: 200, headers: { "cache-control": "no-store" } }); } async function getAuthIdToken() { await auth.authStateReady(); if (!auth.currentUser) return; return await getIdToken(auth.currentUser); -}; +} diff --git a/nextjs-end/firebase.json b/nextjs-end/firebase.json index 069dcbed..93d8f0a3 100644 --- a/nextjs-end/firebase.json +++ b/nextjs-end/firebase.json @@ -41,11 +41,7 @@ }, "hosting": { "source": ".", - "ignore": [ - "firebase.json", - "**/.*", - "**/node_modules/**" - ], + "ignore": ["firebase.json", "**/.*", "**/node_modules/**"], "frameworksBackend": { "region": "us-central1" } diff --git a/nextjs-end/jsconfig.json b/nextjs-end/jsconfig.json index 5c64b067..2a2e4b3b 100644 --- a/nextjs-end/jsconfig.json +++ b/nextjs-end/jsconfig.json @@ -1,7 +1,7 @@ { - "compilerOptions": { - "paths": { - "@/*": ["./*"] - } - } + "compilerOptions": { + "paths": { + "@/*": ["./*"] + } + } } diff --git a/nextjs-end/next.config.js b/nextjs-end/next.config.js index 4a171357..954fac0d 100644 --- a/nextjs-end/next.config.js +++ b/nextjs-end/next.config.js @@ -1,8 +1,8 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - eslint: { - ignoreDuringBuilds: true, - } + eslint: { + ignoreDuringBuilds: true, + }, }; module.exports = nextConfig; diff --git a/nextjs-end/package.json b/nextjs-end/package.json index 973266e2..ccabdfe4 100644 --- a/nextjs-end/package.json +++ b/nextjs-end/package.json @@ -1,39 +1,44 @@ { - "name": "my-app", - "version": "0.1.0", - "private": true, - "scripts": { - "dev": "concurrently -r -k \"npm:dev:next\" \"npm:dev:sw\"", - "dev:next": "next dev", - "dev:sw": "npm run build:sw -- --watch", - "build": "next build", - "build:sw": "esbuild auth-service-worker.js --bundle --minify --main-fields=webworker,browser,module,main --outfile=public/auth-service-worker.js", - "prebuild": "npm run build:sw", - "start": "next start", - "lint": "next lint" - }, - "dependencies": { - "@google/generative-ai": "^0.10.0", - "firebase": "^10.11.1", - "firebase-admin": "^12.1.0", - "next": "^14.2.3", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "server-only": "^0.0.1" - }, - "browser": { - "fs": false, - "os": false, - "path": false, - "child_process": false, - "net": false, - "tls": false - }, - "devDependencies": { - "@next/eslint-plugin-next": "^14.2.9", - "concurrently": "^9.0.0", - "esbuild": "^0.20.2", - "eslint": "^8.0.0", - "eslint-config-next": "^14.2.9" - } + "name": "my-app", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "concurrently -r -k \"npm:dev:next\" \"npm:dev:sw\"", + "dev:next": "next dev", + "dev:sw": "npm run build:sw -- --watch", + "build": "next build", + "build:sw": "esbuild auth-service-worker.js --bundle --minify --main-fields=webworker,browser,module,main --outfile=public/auth-service-worker.js", + "prebuild": "npm run build:sw", + "start": "next start", + "lint": "concurrently -r \"npm:lint:next\" \"npm:lint:prettier\"", + "lint:next": "next lint", + "lint:prettier": "prettier --check --ignore-path .gitignore .", + "lint:fix": "concurrently -r \"npm:lint:next -- --fix\" \"npm:lint:prettier -- --write\"" + }, + "dependencies": { + "@google/generative-ai": "^0.10.0", + "firebase": "^10.11.1", + "firebase-admin": "^12.1.0", + "next": "^14.2.3", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "server-only": "^0.0.1" + }, + "browser": { + "fs": false, + "os": false, + "path": false, + "child_process": false, + "net": false, + "tls": false + }, + "devDependencies": { + "@next/eslint-plugin-next": "^14.2.9", + "concurrently": "^9.0.0", + "esbuild": "^0.20.2", + "eslint": "^8.0.0", + "eslint-config-next": "^14.2.9", + "eslint-config-prettier": "^9.1.0", + "prettier": "^3.3.3" + } } diff --git a/nextjs-end/src/app/actions.js b/nextjs-end/src/app/actions.js index 3bba9192..dee55aee 100644 --- a/nextjs-end/src/app/actions.js +++ b/nextjs-end/src/app/actions.js @@ -1,20 +1,20 @@ -"use server"; +'use server'; -import { addReviewToRestaurant } from "@/src/lib/firebase/firestore.js"; -import { getAuthenticatedAppForUser } from "@/src/lib/firebase/serverApp.js"; -import { getFirestore } from "firebase/firestore"; +import { addReviewToRestaurant } from '@/src/lib/firebase/firestore.js'; +import { getAuthenticatedAppForUser } from '@/src/lib/firebase/serverApp.js'; +import { getFirestore } from 'firebase/firestore'; // This is a Server Action // https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions export async function handleReviewFormSubmission(data) { - const { firebaseServerApp } = await getAuthenticatedAppForUser(); - const db = getFirestore(firebaseServerApp); + const { firebaseServerApp } = await getAuthenticatedAppForUser(); + const db = getFirestore(firebaseServerApp); - await addReviewToRestaurant(db, data.get("restaurantId"), { - text: data.get("text"), - rating: data.get("rating"), + await addReviewToRestaurant(db, data.get('restaurantId'), { + text: data.get('text'), + rating: data.get('rating'), - // This came from a hidden form field - userId: data.get("userId"), - }); + // This came from a hidden form field + userId: data.get('userId'), + }); } diff --git a/nextjs-end/src/app/layout.js b/nextjs-end/src/app/layout.js index c75b8fc1..6604e904 100644 --- a/nextjs-end/src/app/layout.js +++ b/nextjs-end/src/app/layout.js @@ -1,28 +1,25 @@ -import "@/src/app/styles.css"; -import Header from "@/src/components/Header.jsx"; -import { getAuthenticatedAppForUser } from "@/src/lib/firebase/serverApp"; +import '@/src/app/styles.css'; +import Header from '@/src/components/Header.jsx'; +import { getAuthenticatedAppForUser } from '@/src/lib/firebase/serverApp'; // Force next.js to treat this route as server-side rendered // Without this line, during the build process, next.js will treat this route as static and build a static HTML file for it -export const dynamic = "force-dynamic"; +export const dynamic = 'force-dynamic'; export const metadata = { - title: "FriendlyEats", + title: 'FriendlyEats', description: - "FriendlyEats is a restaurant review website built with Next.js and Firebase.", + 'FriendlyEats is a restaurant review website built with Next.js and Firebase.', }; - export default async function RootLayout({ children }) { const { currentUser } = await getAuthenticatedAppForUser(); return ( - - + -
+
{children}
- ); } diff --git a/nextjs-end/src/app/page.js b/nextjs-end/src/app/page.js index c52769cc..f092e885 100644 --- a/nextjs-end/src/app/page.js +++ b/nextjs-end/src/app/page.js @@ -1,27 +1,30 @@ -import RestaurantListings from "@/src/components/RestaurantListings.jsx"; -import { getRestaurants } from "@/src/lib/firebase/firestore.js"; -import { getAuthenticatedAppForUser } from "@/src/lib/firebase/serverApp.js"; -import { getFirestore } from "firebase/firestore"; +import RestaurantListings from '@/src/components/RestaurantListings.jsx'; +import { getRestaurants } from '@/src/lib/firebase/firestore.js'; +import { getAuthenticatedAppForUser } from '@/src/lib/firebase/serverApp.js'; +import { getFirestore } from 'firebase/firestore'; // Force next.js to treat this route as server-side rendered // Without this line, during the build process, next.js will treat this route as static and build a static HTML file for it -export const dynamic = "force-dynamic"; +export const dynamic = 'force-dynamic'; // This line also forces this route to be server-side rendered // export const revalidate = 0; export default async function Home({ searchParams }) { - // Using seachParams which Next.js provides, allows the filtering to happen on the server-side, for example: - // ?city=London&category=Indian&sort=Review - const {firebaseServerApp} = await getAuthenticatedAppForUser(); - const restaurants = await getRestaurants(getFirestore(firebaseServerApp), searchParams); - return ( -
- -
- ); + // Using seachParams which Next.js provides, allows the filtering to happen on the server-side, for example: + // ?city=London&category=Indian&sort=Review + const { firebaseServerApp } = await getAuthenticatedAppForUser(); + const restaurants = await getRestaurants( + getFirestore(firebaseServerApp), + searchParams, + ); + return ( +
+ +
+ ); } diff --git a/nextjs-end/src/app/restaurant/[id]/error.jsx b/nextjs-end/src/app/restaurant/[id]/error.jsx index 8ce09e05..35c57145 100644 --- a/nextjs-end/src/app/restaurant/[id]/error.jsx +++ b/nextjs-end/src/app/restaurant/[id]/error.jsx @@ -1,23 +1,23 @@ -"use client"; // Error components must be Client Components +'use client'; // Error components must be Client Components -import { useEffect } from "react"; +import { useEffect } from 'react'; export default function Error({ error, reset }) { - useEffect(() => { - console.error(error); - }, [error]); + useEffect(() => { + console.error(error); + }, [error]); - return ( -
-

Something went wrong!

- -
- ); + return ( +
+

Something went wrong!

+ +
+ ); } diff --git a/nextjs-end/src/app/restaurant/[id]/page.jsx b/nextjs-end/src/app/restaurant/[id]/page.jsx index 6f14da9d..0ab46dfc 100644 --- a/nextjs-end/src/app/restaurant/[id]/page.jsx +++ b/nextjs-end/src/app/restaurant/[id]/page.jsx @@ -1,27 +1,33 @@ -import Restaurant from "@/src/components/Restaurant.jsx"; -import { Suspense } from "react"; -import { getRestaurantById } from "@/src/lib/firebase/firestore.js"; -import { getAuthenticatedAppForUser, getAuthenticatedAppForUser as getUser } from "@/src/lib/firebase/serverApp.js"; +import Restaurant from '@/src/components/Restaurant.jsx'; +import { Suspense } from 'react'; +import { getRestaurantById } from '@/src/lib/firebase/firestore.js'; +import { + getAuthenticatedAppForUser, + getAuthenticatedAppForUser as getUser, +} from '@/src/lib/firebase/serverApp.js'; import ReviewsList, { ReviewsListSkeleton, -} from "@/src/components/Reviews/ReviewsList"; +} from '@/src/components/Reviews/ReviewsList'; import { GeminiSummary, GeminiSummarySkeleton, -} from "@/src/components/Reviews/ReviewSummary"; -import { getFirestore } from "firebase/firestore"; +} from '@/src/components/Reviews/ReviewSummary'; +import { getFirestore } from 'firebase/firestore'; export default async function Home({ params }) { const { currentUser } = await getUser(); - const {firebaseServerApp} = await getAuthenticatedAppForUser(); - const restaurant = await getRestaurantById(getFirestore(firebaseServerApp), params.id); + const { firebaseServerApp } = await getAuthenticatedAppForUser(); + const restaurant = await getRestaurantById( + getFirestore(firebaseServerApp), + params.id, + ); return ( -
+
}> @@ -30,7 +36,7 @@ export default async function Home({ params }) { } > - +
); diff --git a/nextjs-end/src/app/styles.css b/nextjs-end/src/app/styles.css index e8fd5c6e..d4036677 100644 --- a/nextjs-end/src/app/styles.css +++ b/nextjs-end/src/app/styles.css @@ -1,434 +1,437 @@ @import url('https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,500;0,700;1,800&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap'); * { - box-sizing: border-box; - padding: 0; - margin: 0; + box-sizing: border-box; + padding: 0; + margin: 0; } body { - font-family: "Roboto", ui-sans-serif, system-ui, -apple-system; + font-family: + 'Roboto', + ui-sans-serif, + system-ui, + -apple-system; } ul { - list-style-type: none; + list-style-type: none; } h2 { - font-weight: normal; + font-weight: normal; } dialog { - &[open] { - position: fixed; - width: 80vw; - height: 50vh; - min-height: 270px; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - z-index: 999; - - border-width: 0px; - border-radius: 0.75rem; - box-shadow: -7px 12px 14px 6px rgb(0 0 0 / 0.2); - - & article { - background-color: unset; - } - } - - & form { - display: flex; - flex-direction: column; - justify-content: space-between; - height: 100%; - } - - & footer { - padding-right: 20px; - padding-right: 20px; - } - - &::backdrop { - background-color: #F6F7F9; - opacity: 0.8; - } + &[open] { + position: fixed; + width: 80vw; + height: 50vh; + min-height: 270px; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 999; + + border-width: 0px; + border-radius: 0.75rem; + box-shadow: -7px 12px 14px 6px rgb(0 0 0 / 0.2); + + & article { + background-color: unset; + } + } + + & form { + display: flex; + flex-direction: column; + justify-content: space-between; + height: 100%; + } + + & footer { + padding-right: 20px; + padding-right: 20px; + } + + &::backdrop { + background-color: #f6f7f9; + opacity: 0.8; + } } footer { - & button { - --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), - 0 1px 2px -1px rgb(0 0 0 / 0.1); - --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), - 0 1px 2px -1px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), - var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); - text-transform: uppercase; - font-size: 1rem; - outline: 0; - border: 0; - padding: 10px; - cursor: pointer; - } - - & .button--cancel { - color: rgb(178, 193, 212); - background-color: white; - border-radius: 3px; - } - - & .button--confirm { - background-color: rgb(255 111 0); - color: white; - border-radius: 3px; - } - - & menu { - display: flex; - justify-content: flex-end; - padding: 20px 0; - gap: 20px; - } + & button { + --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), + 0 1px 2px -1px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), + var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + text-transform: uppercase; + font-size: 1rem; + outline: 0; + border: 0; + padding: 10px; + cursor: pointer; + } + + & .button--cancel { + color: rgb(178, 193, 212); + background-color: white; + border-radius: 3px; + } + + & .button--confirm { + background-color: rgb(255 111 0); + color: white; + border-radius: 3px; + } + + & menu { + display: flex; + justify-content: flex-end; + padding: 20px 0; + gap: 20px; + } } header { - background-color: rgb(56 85 116); - color: white; - display: flex; - justify-content: space-between; - padding: 0.8rem; - align-items: center; - & img { - height: 2rem; - } - - & ul { - display: none; - position: absolute; - width: 220px; - z-index: 99; - } - & a { - text-decoration: none; - color: white; - } - .profileImage { - border-radius: 100%; - border-color: white; - border-width: 2px; - border-style: solid; - margin-right: 10px; - } + background-color: rgb(56 85 116); + color: white; + display: flex; + justify-content: space-between; + padding: 0.8rem; + align-items: center; + & img { + height: 2rem; + } + + & ul { + display: none; + position: absolute; + width: 220px; + z-index: 99; + } + & a { + text-decoration: none; + color: white; + } + .profileImage { + border-radius: 100%; + border-color: white; + border-width: 2px; + border-style: solid; + margin-right: 10px; + } } .logo { - display: flex; - align-items: center; + display: flex; + align-items: center; - & img { - margin-inline-end: 10px; - } + & img { + margin-inline-end: 10px; + } - color: white; - text-decoration: none; - font-size: 1.25rem; - font-weight: 500; + color: white; + text-decoration: none; + font-size: 1.25rem; + font-weight: 500; } .menu { - display: inline-block; - position: relative; - padding: 15px 20px; - align-self: stretch; + display: inline-block; + position: relative; + padding: 15px 20px; + align-self: stretch; } .menu ul { - /* display: block; */ - left: calc(-220px * 0.9); - color: rgb(42 72 101); - background-color: white; - box-shadow: 0 0 10px 0 rgb(0 0 0 / 50%); + /* display: block; */ + left: calc(-220px * 0.9); + color: rgb(42 72 101); + background-color: white; + box-shadow: 0 0 10px 0 rgb(0 0 0 / 50%); - & li { - padding: 10px; - border-bottom: 1px solid rgb(42 72 101 / 0.25); - } + & li { + padding: 10px; + border-bottom: 1px solid rgb(42 72 101 / 0.25); + } - & a { - font-weight: bold; - color: unset; - } + & a { + font-weight: bold; + color: unset; + } - & li:has(a):hover { - background-color: rgb(42 72 101 / 0.05); - } + & li:has(a):hover { + background-color: rgb(42 72 101 / 0.05); + } - & a:visited { - color: unset; - } + & a:visited { + color: unset; + } } .menu:hover ul { - display: block; + display: block; } .profile { - display: flex; - /* align-items: center; + display: flex; + /* align-items: center; justify-content: center; */ - & p { - display: flex; - align-items: center; - } + & p { + display: flex; + align-items: center; + } - & a { - display: flex; - align-items: center; - } + & a { + display: flex; + align-items: center; + } } .main__home { - background-color: rgb(178 193 212); - min-height: 100vh; + background-color: rgb(178 193 212); + min-height: 100vh; } .main__restaurant { - background-color: rgb(229 234 240); - min-height: 90vh; + background-color: rgb(229 234 240); + min-height: 90vh; } article { - margin: 0 auto; - background-color: rgb(229 234 240); - padding: 20px 40px; + margin: 0 auto; + background-color: rgb(229 234 240); + padding: 20px 40px; } article { - width: 75%; + width: 75%; } .restaurants { - display: grid; - margin-top: 20px; - grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + display: grid; + margin-top: 20px; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); - gap: 40px; + gap: 40px; - & li { - background: white; - max-width: 300px; - } + & li { + background: white; + max-width: 300px; + } - & a { - color: black; - display: flex; - flex-direction: column; - flex: 2 1 100%; - } + & a { + color: black; + display: flex; + flex-direction: column; + flex: 2 1 100%; + } - & h2 { - font-weight: normal; - } + & h2 { + font-weight: normal; + } } .image-cover { - width: 100%; - height: 100%; - object-fit: cover; - max-height: 300px; - min-height: 300px; - position: relative; + width: 100%; + height: 100%; + object-fit: cover; + max-height: 300px; + min-height: 300px; + position: relative; - & img { - width: 100%; - height: 100%; - object-fit: cover; - position: absolute; - } + & img { + width: 100%; + height: 100%; + object-fit: cover; + position: absolute; + } } a { - text-decoration: none; + text-decoration: none; } .restaurant__meta { - display: flex; - font-weight: 500; - justify-content: space-between; - align-items: center; - margin-bottom: 10px; + display: flex; + font-weight: 500; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; } .restaurant__details { - padding: 20px; + padding: 20px; } .restaurant__rating { - padding: 5px 0; - display: flex; - align-items: center; + padding: 5px 0; + display: flex; + align-items: center; - & ul { - display: flex; - } + & ul { + display: flex; + } - & svg { - width: 2rem; - height: 2rem; - color: rgb(255 202 40); - } + & svg { + width: 2rem; + height: 2rem; + color: rgb(255 202 40); + } - & span { - color: rgb(156 163 175); - } + & span { + color: rgb(156 163 175); + } } .restaurant__review_summary { - max-width: "50vw"; - height: "75px"; - padding-top: "10px"; + max-width: '50vw'; + height: '75px'; + padding-top: '10px'; } .img__section { - width: 100%; - height: 400px; - position: relative; - > img { - width: 100%; - height: 100%; - object-fit: cover; - position: absolute; - max-width: unset; - } + width: 100%; + height: 400px; + position: relative; + > img { + width: 100%; + height: 100%; + object-fit: cover; + position: absolute; + max-width: unset; + } } .details { - position: absolute; - bottom: 0; - padding: 20px; - color: white; + position: absolute; + bottom: 0; + padding: 20px; + color: white; - & span { - color: inherit; - } + & span { + color: inherit; + } } .details__container { - --tw-gradient-from: #c60094 var(--tw-gradient-from-position); - --tw-gradient-to: rgb(56 85 116 / 0) var(--tw-gradient-to-position); - --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); - background-image: linear-gradient(to top right, var(--tw-gradient-stops)); - position: absolute; - right: 0; - bottom: 0; - left: 0; - background: rgb(24 25 26 / 50%); - width: 100%; - height: 100%; + --tw-gradient-from: #c60094 var(--tw-gradient-from-position); + --tw-gradient-to: rgb(56 85 116 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); + background-image: linear-gradient(to top right, var(--tw-gradient-stops)); + position: absolute; + right: 0; + bottom: 0; + left: 0; + background: rgb(24 25 26 / 50%); + width: 100%; + height: 100%; } .reviews { - & .review__item { - padding: 40px; - border-bottom: 1px solid rgb(156 163 175 / 0.25); - } + & .review__item { + padding: 40px; + border-bottom: 1px solid rgb(156 163 175 / 0.25); + } - & time { - font-size: 0.8rem; - color: darkgrey; - } + & time { + font-size: 0.8rem; + color: darkgrey; + } } .actions { - position: absolute; - z-index: 1; - bottom: -30px; - - right: 0; - display: flex; - justify-content: flex-end; - & img { - height: 4rem; - } - - .review { - --tw-ring-offset-shadow: 0 0 #0000; - --tw-ring-shadow: 0 0 #0000; - --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), - 0 4px 6px -4px rgb(0 0 0 / 0.1); - cursor: pointer; - background-color: rgb(255 202 40); - border-radius: 0.75rem; - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), - var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); - } - - .add { - --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), - 0 4px 6px -4px rgb(0 0 0 / 0.1); - --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), - 0 4px 6px -4px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), - var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); - background-color: rgb(255 143 0); - border-radius: 9999px; - cursor: pointer; - height: 4rem; - } - - .add input { - display: none; - } - - :where(.review, .add) { - margin: 0 30px; - } + position: absolute; + z-index: 1; + bottom: -30px; + + right: 0; + display: flex; + justify-content: flex-end; + & img { + height: 4rem; + } + + .review { + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), + 0 4px 6px -4px rgb(0 0 0 / 0.1); + cursor: pointer; + background-color: rgb(255 202 40); + border-radius: 0.75rem; + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), + var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + } + + .add { + --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), + 0 4px 6px -4px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), + 0 4px 6px -4px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), + var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + background-color: rgb(255 143 0); + border-radius: 9999px; + cursor: pointer; + height: 4rem; + } + + .add input { + display: none; + } + + :where(.review, .add) { + margin: 0 30px; + } } #review { - padding: 20px; - font-size: 17px; - border: none; - border-bottom: 2px solid rgb(255 111 0); - width: 100%; + padding: 20px; + font-size: 17px; + border: none; + border-bottom: 2px solid rgb(255 111 0); + width: 100%; } /* Thanks to: https://codepen.io/chris22smith/pen/MJzLJN */ .star-rating { - display: flex; - flex-direction: row-reverse; - justify-content: flex-end; + display: flex; + flex-direction: row-reverse; + justify-content: flex-end; } .radio-input { - position: fixed; - opacity: 0; - pointer-events: none; + position: fixed; + opacity: 0; + pointer-events: none; } .radio-label { - cursor: pointer; - font-size: 0; - color: rgba(0, 0, 0, 0.2); - transition: color 0.1s ease-in-out; + cursor: pointer; + font-size: 0; + color: rgba(0, 0, 0, 0.2); + transition: color 0.1s ease-in-out; } .radio-label:before { - content: "★"; - display: inline-block; - font-size: 32px; + content: '★'; + display: inline-block; + font-size: 32px; } .radio-input:checked ~ .radio-label { - color: #ffc700; - color: gold; + color: #ffc700; + color: gold; } .radio-label:hover, .radio-label:hover ~ .radio-label { - color: goldenrod; + color: goldenrod; } .radio-input:checked + .radio-label:hover, @@ -436,141 +439,141 @@ a { .radio-input:checked ~ .radio-label:hover, .radio-input:checked ~ .radio-label:hover ~ .radio-label, .radio-label:hover ~ .radio-input:checked ~ .radio-label { - color: darkgoldenrod; + color: darkgoldenrod; } .average-rating { - position: relative; - appearance: none; - color: transparent; - width: auto; - display: inline-block; - vertical-align: baseline; - font-size: 25px; + position: relative; + appearance: none; + color: transparent; + width: auto; + display: inline-block; + vertical-align: baseline; + font-size: 25px; } .average-rating::before { - --percent: calc(4.3 / 5 * 100%); - content: "★★★★★"; - position: absolute; - top: 0; - left: 0; - color: rgba(0, 0, 0, 0.2); - background: linear-gradient( - 90deg, - gold var(--percent), - rgba(0, 0, 0, 0.2) var(--percent) - ); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; + --percent: calc(4.3 / 5 * 100%); + content: '★★★★★'; + position: absolute; + top: 0; + left: 0; + color: rgba(0, 0, 0, 0.2); + background: linear-gradient( + 90deg, + gold var(--percent), + rgba(0, 0, 0, 0.2) var(--percent) + ); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; } .rating-picker { - display: flex; - flex-direction: row-reverse; - justify-content: center; + display: flex; + flex-direction: row-reverse; + justify-content: center; } .filter-menu { - background-color: white; - border-radius: 3px; - border-bottom: 1px solid rgb(27 58 87); - - & summary { - font-weight: bold; - cursor: pointer; - display: flex; - align-items: center; - } - - & form { - display: flex; - flex-direction: column; - padding: 20px; - padding-bottom: 0; - } - - & label { - padding: 10px 0; - display: flex; - flex-direction: column; - flex-grow: 1; - - color: rgb(75 85 99); - font-size: 0.75rem; - line-height: 1rem; - } - - & img { - height: 4rem; - max-width: 100%; - } - - & form div { - display: flex; - gap: 10px; - } - - & select { - color: rgb(17 24 39); - font-size: 0.875rem; - line-height: 1.25rem; - padding-top: 1rem; - padding-bottom: 0.5rem; - padding-left: 0.625rem; - padding-right: 0.625rem; - border: 0; - border-bottom-width: 2px; - border-style: solid; - border-color: #e5e7eb; - } - - & p:first-child { - font-weight: 300; - font-size: 1.25rem; - line-height: 1.75rem; - margin-bottom: 2px; - } - - & p:last-child { - color: rgb(42 72 101); - font-weight: 600; - font-size: 0.875rem; - line-height: 1.25rem; - } + background-color: white; + border-radius: 3px; + border-bottom: 1px solid rgb(27 58 87); + + & summary { + font-weight: bold; + cursor: pointer; + display: flex; + align-items: center; + } + + & form { + display: flex; + flex-direction: column; + padding: 20px; + padding-bottom: 0; + } + + & label { + padding: 10px 0; + display: flex; + flex-direction: column; + flex-grow: 1; + + color: rgb(75 85 99); + font-size: 0.75rem; + line-height: 1rem; + } + + & img { + height: 4rem; + max-width: 100%; + } + + & form div { + display: flex; + gap: 10px; + } + + & select { + color: rgb(17 24 39); + font-size: 0.875rem; + line-height: 1.25rem; + padding-top: 1rem; + padding-bottom: 0.5rem; + padding-left: 0.625rem; + padding-right: 0.625rem; + border: 0; + border-bottom-width: 2px; + border-style: solid; + border-color: #e5e7eb; + } + + & p:first-child { + font-weight: 300; + font-size: 1.25rem; + line-height: 1.75rem; + margin-bottom: 2px; + } + + & p:last-child { + color: rgb(42 72 101); + font-weight: 600; + font-size: 0.875rem; + line-height: 1.25rem; + } } .filter { - margin: 0 auto; + margin: 0 auto; } .tags { - display: flex; - flex-wrap: wrap; - gap: 10px; - margin: 30px 0px; - - & span { - font-weight: 500; - line-height: 1.25rem; - padding-top: 0.25rem; - padding-bottom: 0.25rem; - padding-left: 0.5rem; - padding-right: 0.5rem; - background-color: rgb(71 98 130); - border-radius: 9999px; - color: white; - font-size: 0.95rem; - } - - & button { - cursor: pointer; - margin-left: 5px; - padding: 2px 10px; - color: white; - background-color: transparent; - outline: none; - border: none; - font-size: 0.8rem; - } + display: flex; + flex-wrap: wrap; + gap: 10px; + margin: 30px 0px; + + & span { + font-weight: 500; + line-height: 1.25rem; + padding-top: 0.25rem; + padding-bottom: 0.25rem; + padding-left: 0.5rem; + padding-right: 0.5rem; + background-color: rgb(71 98 130); + border-radius: 9999px; + color: white; + font-size: 0.95rem; + } + + & button { + cursor: pointer; + margin-left: 5px; + padding: 2px 10px; + color: white; + background-color: transparent; + outline: none; + border: none; + font-size: 0.8rem; + } } diff --git a/nextjs-end/src/components/Filters.jsx b/nextjs-end/src/components/Filters.jsx index 4e547fa0..985dcca2 100644 --- a/nextjs-end/src/components/Filters.jsx +++ b/nextjs-end/src/components/Filters.jsx @@ -1,165 +1,161 @@ // The filters shown on the restaurant listings page -import Tag from "@/src/components/Tag.jsx"; +import Tag from '@/src/components/Tag.jsx'; function FilterSelect({ label, options, value, onChange, name, icon }) { - return ( -
- {label} - -
- ); + return ( +
+ {label} + +
+ ); } export default function Filters({ filters, setFilters }) { - const handleSelectionChange = (event, name) => { - setFilters(prevFilters => ({ - ...prevFilters, - [name]: event.target.value, - })); - }; + const handleSelectionChange = (event, name) => { + setFilters((prevFilters) => ({ + ...prevFilters, + [name]: event.target.value, + })); + }; - const updateField = (type, value) => { - setFilters({ ...filters, [type]: value }); - }; + const updateField = (type, value) => { + setFilters({ ...filters, [type]: value }); + }; - return ( -
-
- - filter -
-

Restaurants

-

Sorted by {filters.sort || "Rating"}

-
-
+ return ( +
+
+ + filter +
+

Restaurants

+

Sorted by {filters.sort || 'Rating'}

+
+
-
{ - event.preventDefault(); - event.target.parentNode.removeAttribute("open"); - }} - > - - handleSelectionChange(event, "category") - } - name="category" - icon="/food.svg" - /> + { + event.preventDefault(); + event.target.parentNode.removeAttribute('open'); + }} + > + handleSelectionChange(event, 'category')} + name='category' + icon='/food.svg' + /> - handleSelectionChange(event, "city")} - name="city" - icon="/location.svg" - /> + handleSelectionChange(event, 'city')} + name='city' + icon='/location.svg' + /> - - handleSelectionChange(event, "price") - } - name="price" - icon="/price.svg" - /> + handleSelectionChange(event, 'price')} + name='price' + icon='/price.svg' + /> - handleSelectionChange(event, "sort")} - name="sort" - icon="/sortBy.svg" - /> + handleSelectionChange(event, 'sort')} + name='sort' + icon='/sortBy.svg' + /> -
- - - - -
- -
+
+ + + + +
+ +
-
- {Object.entries(filters).map(([type, value]) => { - // The main filter bar already specifies what - // sorting is being used. So skip showing the - // sorting as a 'tag' - if (type == "sort" || value == "") { - return null; - } - return ( - - ); - })} -
-
- ); +
+ {Object.entries(filters).map(([type, value]) => { + // The main filter bar already specifies what + // sorting is being used. So skip showing the + // sorting as a 'tag' + if (type == 'sort' || value == '') { + return null; + } + return ( + + ); + })} +
+ + ); } diff --git a/nextjs-end/src/components/Header.jsx b/nextjs-end/src/components/Header.jsx index 06d1a14e..880f414f 100644 --- a/nextjs-end/src/components/Header.jsx +++ b/nextjs-end/src/components/Header.jsx @@ -1,109 +1,116 @@ 'use client'; -import React, { useState, useEffect } from "react"; -import Link from "next/link"; +import React, { useState, useEffect } from 'react'; +import Link from 'next/link'; import { - signInWithGoogle, - signOut, - onAuthStateChanged, -} from "@/src/lib/firebase/auth.js"; -import { addFakeRestaurantsAndReviews } from "@/src/lib/firebase/firestore.js"; -import { useRouter } from "next/navigation"; -import { firebaseConfig } from "@/src/lib/firebase/config"; + signInWithGoogle, + signOut, + onAuthStateChanged, +} from '@/src/lib/firebase/auth.js'; +import { addFakeRestaurantsAndReviews } from '@/src/lib/firebase/firestore.js'; +import { useRouter } from 'next/navigation'; +import { firebaseConfig } from '@/src/lib/firebase/config'; function useUserSession(initialUser) { - // The initialUser comes from the server via a server component - const [user, setUser] = useState(initialUser); - const router = useRouter(); + // The initialUser comes from the server via a server component + const [user, setUser] = useState(initialUser); + const router = useRouter(); - // Register the service worker that sends auth state back to server - // The service worker is built with npm run build-service-worker - useEffect(() => { - if ("serviceWorker" in navigator) { - const serializedFirebaseConfig = encodeURIComponent(JSON.stringify(firebaseConfig)); - const serviceWorkerUrl = `/auth-service-worker.js?firebaseConfig=${serializedFirebaseConfig}`; - - navigator - .serviceWorker - .register(serviceWorkerUrl, { scope: "/", updateViaCache: "none" }) - .then((registration) => { - console.log("scope is: ", registration.scope); - registration.update(); - }); - } - }, []); + // Register the service worker that sends auth state back to server + // The service worker is built with npm run build-service-worker + useEffect(() => { + if ('serviceWorker' in navigator) { + const serializedFirebaseConfig = encodeURIComponent( + JSON.stringify(firebaseConfig), + ); + const serviceWorkerUrl = `/auth-service-worker.js?firebaseConfig=${serializedFirebaseConfig}`; - useEffect(() => { - return onAuthStateChanged(async (authUser) => { - if (user?.uid === authUser?.uid) {return;} - if ("serviceWorker" in navigator) { - await navigator.serviceWorker.ready; - await fetch(`/__/auth/wait/${authUser?.uid}`, { method: "HEAD" }).catch(() => undefined); - } - setUser(authUser); - router.refresh(); - }); - }, [user, router]); + navigator.serviceWorker + .register(serviceWorkerUrl, { scope: '/', updateViaCache: 'none' }) + .then((registration) => { + console.log('scope is: ', registration.scope); + registration.update(); + }); + } + }, []); - return user; -} + useEffect(() => { + return onAuthStateChanged(async (authUser) => { + if (user?.uid === authUser?.uid) { + return; + } + if ('serviceWorker' in navigator) { + await navigator.serviceWorker.ready; + await fetch(`/__/auth/wait/${authUser?.uid}`, { method: 'HEAD' }).catch( + () => undefined, + ); + } + setUser(authUser); + router.refresh(); + }); + }, [user, router]); -export default function Header({initialUser}) { + return user; +} - const user = useUserSession(initialUser) ; +export default function Header({ initialUser }) { + const user = useUserSession(initialUser); - const handleSignOut = event => { - event.preventDefault(); - signOut(); - }; + const handleSignOut = (event) => { + event.preventDefault(); + signOut(); + }; - const handleSignIn = event => { - event.preventDefault(); - signInWithGoogle(); - }; + const handleSignIn = (event) => { + event.preventDefault(); + signInWithGoogle(); + }; - return ( -
- - FriendlyEats - Friendly Eats - - {user ? ( - <> -
-

- {user.email} - {user.displayName} -

+ return ( +
+ + FriendlyEats + Friendly Eats + + {user ? ( + <> +
+

+ {user.email} + {user.displayName} +

-
- ... -
- - ) : ( - - )} -
- ); +
  • + + Sign Out + +
  • + +
    +
    + + ) : ( + + )} + + ); } diff --git a/nextjs-end/src/components/RatingPicker.jsx b/nextjs-end/src/components/RatingPicker.jsx index 1b2ce114..f69aa72b 100644 --- a/nextjs-end/src/components/RatingPicker.jsx +++ b/nextjs-end/src/components/RatingPicker.jsx @@ -1,66 +1,66 @@ -import React from "react"; +import React from 'react'; // A HTML and CSS only rating picker thanks to: https://codepen.io/chris22smith/pen/MJzLJN const RatingPicker = () => { - return ( -

    - - + return ( +

    + + - - + + - - + + - - + + - - -

    - ); + + +

    + ); }; export default RatingPicker; diff --git a/nextjs-end/src/components/Restaurant.jsx b/nextjs-end/src/components/Restaurant.jsx index 9f584299..11d915fa 100644 --- a/nextjs-end/src/components/Restaurant.jsx +++ b/nextjs-end/src/components/Restaurant.jsx @@ -1,16 +1,14 @@ -"use client"; +'use client'; // This components shows one individual restaurant // It receives data from src/app/restaurant/[id]/page.jsx -import { React, useState, useEffect, Suspense } from "react"; +import { React, useState, useEffect, Suspense } from 'react'; import dynamic from 'next/dynamic'; -import { - getRestaurantSnapshotById, -} from "@/src/lib/firebase/firestore.js"; -import {useUser} from '@/src/lib/getUser'; -import RestaurantDetails from "@/src/components/RestaurantDetails.jsx"; -import { updateRestaurantImage } from "@/src/lib/firebase/storage.js"; +import { getRestaurantSnapshotById } from '@/src/lib/firebase/firestore.js'; +import { useUser } from '@/src/lib/getUser'; +import RestaurantDetails from '@/src/components/RestaurantDetails.jsx'; +import { updateRestaurantImage } from '@/src/lib/firebase/storage.js'; const ReviewDialog = dynamic(() => import('@/src/components/ReviewDialog.jsx')); @@ -18,7 +16,7 @@ export default function Restaurant({ id, initialRestaurant, initialUserId, - children + children, }) { const [restaurantDetails, setRestaurantDetails] = useState(initialRestaurant); const [isOpen, setIsOpen] = useState(false); @@ -27,7 +25,7 @@ export default function Restaurant({ const userId = useUser()?.uid || initialUserId; const [review, setReview] = useState({ rating: 0, - text: "", + text: '', }); const onChange = (value, name) => { @@ -46,7 +44,7 @@ export default function Restaurant({ const handleClose = () => { setIsOpen(false); - setReview({ rating: 0, text: "" }); + setReview({ rating: 0, text: '' }); }; useEffect(() => { @@ -63,15 +61,21 @@ export default function Restaurant({ handleRestaurantImage={handleRestaurantImage} setIsOpen={setIsOpen} isOpen={isOpen} - >{children} - {userId && Loading...

    }>
    } + > + {children} + + {userId && ( + Loading...

    }> + +
    + )} ); } diff --git a/nextjs-end/src/components/RestaurantDetails.jsx b/nextjs-end/src/components/RestaurantDetails.jsx index 7dadf5ce..64e08e15 100644 --- a/nextjs-end/src/components/RestaurantDetails.jsx +++ b/nextjs-end/src/components/RestaurantDetails.jsx @@ -1,66 +1,66 @@ // This component shows restaurant metadata, and offers some actions to the user like uploading a new restaurant image, and adding a review. -import React from "react"; -import renderStars from "@/src/components/Stars.jsx"; +import React from 'react'; +import renderStars from '@/src/components/Stars.jsx'; const RestaurantDetails = ({ - restaurant, - userId, - handleRestaurantImage, - setIsOpen, - isOpen, - children + restaurant, + userId, + handleRestaurantImage, + setIsOpen, + isOpen, + children, }) => { - return ( -
    - {restaurant.name} + return ( +
    + {restaurant.name} -
    - {userId && ( - review { - setIsOpen(!isOpen); - }} - src="/review.svg" - /> - )} - +
    -
    -
    -

    {restaurant.name}

    +
    +
    +

    {restaurant.name}

    -
    -
      {renderStars(restaurant.avgRating)}
    +
    +
      {renderStars(restaurant.avgRating)}
    - ({restaurant.numRatings}) -
    + ({restaurant.numRatings}) +
    -

    - {restaurant.category} | {restaurant.city} -

    -

    {"$".repeat(restaurant.price)}

    - {children} -
    -
    -
    - ); +

    + {restaurant.category} | {restaurant.city} +

    +

    {'$'.repeat(restaurant.price)}

    + {children} +
    + + + ); }; export default RestaurantDetails; diff --git a/nextjs-end/src/components/RestaurantListings.jsx b/nextjs-end/src/components/RestaurantListings.jsx index e173e6be..270d721b 100644 --- a/nextjs-end/src/components/RestaurantListings.jsx +++ b/nextjs-end/src/components/RestaurantListings.jsx @@ -1,111 +1,108 @@ -"use client"; +'use client'; // This components handles the restaurant listings page // It receives data from src/app/page.jsx, such as the initial restaurants and search params from the URL -import Link from "next/link"; -import { React, useState, useEffect } from "react"; -import { useRouter } from "next/navigation"; -import renderStars from "@/src/components/Stars.jsx"; -import { getRestaurantsSnapshot } from "@/src/lib/firebase/firestore.js"; -import Filters from "@/src/components/Filters.jsx"; +import Link from 'next/link'; +import { React, useState, useEffect } from 'react'; +import { useRouter } from 'next/navigation'; +import renderStars from '@/src/components/Stars.jsx'; +import { getRestaurantsSnapshot } from '@/src/lib/firebase/firestore.js'; +import Filters from '@/src/components/Filters.jsx'; const RestaurantItem = ({ restaurant }) => ( -
  • - - - -
  • +
  • + + + +
  • ); const ActiveResturant = ({ restaurant }) => ( -
    - - -
    +
    + + +
    ); const ImageCover = ({ photo, name }) => ( -
    - {name} -
    +
    + {name} +
    ); const ResturantDetails = ({ restaurant }) => ( -
    -

    {restaurant.name}

    - - -
    +
    +

    {restaurant.name}

    + + +
    ); const RestaurantRating = ({ restaurant }) => ( -
    -
      {renderStars(restaurant.avgRating)}
    - ({restaurant.numRatings}) -
    +
    +
      {renderStars(restaurant.avgRating)}
    + ({restaurant.numRatings}) +
    ); const RestaurantMetadata = ({ restaurant }) => ( -
    -

    - {restaurant.category} | {restaurant.city} -

    -

    {"$".repeat(restaurant.price)}

    -
    +
    +

    + {restaurant.category} | {restaurant.city} +

    +

    {'$'.repeat(restaurant.price)}

    +
    ); export default function RestaurantListings({ - initialRestaurants, - searchParams, + initialRestaurants, + searchParams, }) { - const router = useRouter(); - - // The initial filters are the search params from the URL, useful for when the user refreshes the page - const initialFilters = { - city: searchParams.city || "", - category: searchParams.category || "", - price: searchParams.price || "", - sort: searchParams.sort || "", - }; - - const [restaurants, setRestaurants] = useState(initialRestaurants); - const [filters, setFilters] = useState(initialFilters); - - useEffect(() => { - routerWithFilters(router, filters); - }, [router, filters]); - - useEffect(() => { - return getRestaurantsSnapshot(data => { - setRestaurants(data); - }, filters); - }, [filters]); - - return ( -
    - -
      - {restaurants.map(restaurant => ( - - ))} -
    -
    - ); + const router = useRouter(); + + // The initial filters are the search params from the URL, useful for when the user refreshes the page + const initialFilters = { + city: searchParams.city || '', + category: searchParams.category || '', + price: searchParams.price || '', + sort: searchParams.sort || '', + }; + + const [restaurants, setRestaurants] = useState(initialRestaurants); + const [filters, setFilters] = useState(initialFilters); + + useEffect(() => { + routerWithFilters(router, filters); + }, [router, filters]); + + useEffect(() => { + return getRestaurantsSnapshot((data) => { + setRestaurants(data); + }, filters); + }, [filters]); + + return ( +
    + +
      + {restaurants.map((restaurant) => ( + + ))} +
    +
    + ); } function routerWithFilters(router, filters) { - const queryParams = new URLSearchParams(); + const queryParams = new URLSearchParams(); - for (const [key, value] of Object.entries(filters)) { - if (value !== undefined && value !== "") { - queryParams.append(key, value); - } - } + for (const [key, value] of Object.entries(filters)) { + if (value !== undefined && value !== '') { + queryParams.append(key, value); + } + } - const queryString = queryParams.toString(); - router.push(`?${queryString}`); + const queryString = queryParams.toString(); + router.push(`?${queryString}`); } diff --git a/nextjs-end/src/components/ReviewDialog.jsx b/nextjs-end/src/components/ReviewDialog.jsx index a7279626..6b69b63d 100644 --- a/nextjs-end/src/components/ReviewDialog.jsx +++ b/nextjs-end/src/components/ReviewDialog.jsx @@ -2,88 +2,83 @@ // This components handles the review dialog and uses a next.js feature known as Server Actions to handle the form submission -import { useLayoutEffect, useRef } from "react"; -import RatingPicker from "@/src/components/RatingPicker.jsx"; -import { handleReviewFormSubmission } from "@/src/app/actions.js"; +import { useLayoutEffect, useRef } from 'react'; +import RatingPicker from '@/src/components/RatingPicker.jsx'; +import { handleReviewFormSubmission } from '@/src/app/actions.js'; const ReviewDialog = ({ - isOpen, - handleClose, - review, - onChange, - userId, - id, + isOpen, + handleClose, + review, + onChange, + userId, + id, }) => { - const dialog = useRef(); + const dialog = useRef(); - // dialogs only render their backdrop when called with `showModal` - useLayoutEffect(() => { - if (isOpen) { - dialog.current.showModal(); - } else { - dialog.current.close(); - } - - }, [isOpen, dialog]); + // dialogs only render their backdrop when called with `showModal` + useLayoutEffect(() => { + if (isOpen) { + dialog.current.showModal(); + } else { + dialog.current.close(); + } + }, [isOpen, dialog]); - const handleClick = (e) => { - // close if clicked outside the modal - if (e.target === dialog.current) { - handleClose(); - } - }; + const handleClick = (e) => { + // close if clicked outside the modal + if (e.target === dialog.current) { + handleClose(); + } + }; - return ( - -
    { - handleClose(); - }} - > -
    -

    Add your review

    -
    -
    - + return ( + + { + handleClose(); + }} + > +
    +

    Add your review

    +
    +
    + -

    - onChange(e.target.value, "text")} - /> -

    +

    + onChange(e.target.value, 'text')} + /> +

    - - -
    -
    - - - - -
    - -
    - ); + + +
    +
    + + + + +
    + +
    + ); }; export default ReviewDialog; diff --git a/nextjs-end/src/components/Reviews/Review.jsx b/nextjs-end/src/components/Reviews/Review.jsx index d52294b2..87aa40ca 100644 --- a/nextjs-end/src/components/Reviews/Review.jsx +++ b/nextjs-end/src/components/Reviews/Review.jsx @@ -1,23 +1,40 @@ -import renderStars from "@/src/components/Stars.jsx"; - +import renderStars from '@/src/components/Stars.jsx'; export function Review({ rating, text, timestamp }) { - return (
  • -
      {renderStars(rating)}
    -

    {text}

    + return ( +
  • +
      {renderStars(rating)}
    +

    {text}

    - -
  • ); + + + ); } export function ReviewSkeleton() { - return (
  • -
    -
    -

    {' '}

    -
  • ); + return ( +
  • +
    +
    +
    +
    +

    {' '}

    +
  • + ); } diff --git a/nextjs-end/src/components/Reviews/ReviewSummary.jsx b/nextjs-end/src/components/Reviews/ReviewSummary.jsx index a11f35c8..1c1c1c34 100644 --- a/nextjs-end/src/components/Reviews/ReviewSummary.jsx +++ b/nextjs-end/src/components/Reviews/ReviewSummary.jsx @@ -1,25 +1,25 @@ -const { GoogleGenerativeAI } = require("@google/generative-ai"); -import { getReviewsByRestaurantId } from "@/src/lib/firebase/firestore.js"; -import { getAuthenticatedAppForUser } from "@/src/lib/firebase/serverApp"; -import { getFirestore } from "firebase/firestore"; +const { GoogleGenerativeAI } = require('@google/generative-ai'); +import { getReviewsByRestaurantId } from '@/src/lib/firebase/firestore.js'; +import { getAuthenticatedAppForUser } from '@/src/lib/firebase/serverApp'; +import { getFirestore } from 'firebase/firestore'; export async function GeminiSummary({ restaurantId }) { const { firebaseServerApp } = await getAuthenticatedAppForUser(); const reviews = await getReviewsByRestaurantId( getFirestore(firebaseServerApp), - restaurantId + restaurantId, ); const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY); - const model = genAI.getGenerativeModel({ model: "gemini-pro"}); + const model = genAI.getGenerativeModel({ model: 'gemini-pro' }); - const reviewSeparator = "@"; + const reviewSeparator = '@'; const prompt = ` Based on the following restaurant reviews, where each review is separated by a '${reviewSeparator}' character, create a one-sentence summary of what people think of the restaurant. - Here are the reviews: ${reviews.map(review => review.text).join(reviewSeparator)} + Here are the reviews: ${reviews.map((review) => review.text).join(reviewSeparator)} `; try { @@ -28,18 +28,18 @@ export async function GeminiSummary({ restaurantId }) { const text = response.text(); return ( -
    +

    {text}

    ✨ Summarized with Gemini

    ); } catch (e) { console.error(e); - if (e.message.includes("403 Forbidden")) { + if (e.message.includes('403 Forbidden')) { return (

    - This service account doesn't have permission to talk to Gemini via - Vertex + This service account doesn't have permission to talk to Gemini + via Vertex

    ); } else { @@ -50,7 +50,7 @@ export async function GeminiSummary({ restaurantId }) { export function GeminiSummarySkeleton() { return ( -
    +

    ✨ Summarizing reviews with Gemini...

    ); diff --git a/nextjs-end/src/components/Reviews/ReviewsList.jsx b/nextjs-end/src/components/Reviews/ReviewsList.jsx index 939e5b97..84d6f023 100644 --- a/nextjs-end/src/components/Reviews/ReviewsList.jsx +++ b/nextjs-end/src/components/Reviews/ReviewsList.jsx @@ -1,16 +1,19 @@ // This component handles the list of reviews for a given restaurant -import React from "react"; -import { getReviewsByRestaurantId } from "@/src/lib/firebase/firestore.js"; -import ReviewsListClient from "@/src/components/Reviews/ReviewsListClient"; -import { ReviewSkeleton } from "@/src/components/Reviews/Review"; -import { getFirestore } from "firebase/firestore"; -import { getAuthenticatedAppForUser } from "@/src/lib/firebase/serverApp"; +import React from 'react'; +import { getReviewsByRestaurantId } from '@/src/lib/firebase/firestore.js'; +import ReviewsListClient from '@/src/components/Reviews/ReviewsListClient'; +import { ReviewSkeleton } from '@/src/components/Reviews/Review'; +import { getFirestore } from 'firebase/firestore'; +import { getAuthenticatedAppForUser } from '@/src/lib/firebase/serverApp'; export default async function ReviewsList({ restaurantId, userId }) { - const {firebaseServerApp} = await getAuthenticatedAppForUser(); - const reviews = await getReviewsByRestaurantId(getFirestore(firebaseServerApp), restaurantId); - + const { firebaseServerApp } = await getAuthenticatedAppForUser(); + const reviews = await getReviewsByRestaurantId( + getFirestore(firebaseServerApp), + restaurantId, + ); + return ( -
      +
          {Array(numReviews) .fill(0) diff --git a/nextjs-end/src/components/Reviews/ReviewsListClient.jsx b/nextjs-end/src/components/Reviews/ReviewsListClient.jsx index ce509540..c5b2d8d5 100644 --- a/nextjs-end/src/components/Reviews/ReviewsListClient.jsx +++ b/nextjs-end/src/components/Reviews/ReviewsListClient.jsx @@ -1,8 +1,8 @@ -"use client"; +'use client'; -import React, { useState, useEffect } from "react"; -import { getReviewsSnapshotByRestaurantId } from "@/src/lib/firebase/firestore.js"; -import { Review } from "@/src/components/Reviews/Review"; +import React, { useState, useEffect } from 'react'; +import { getReviewsSnapshotByRestaurantId } from '@/src/lib/firebase/firestore.js'; +import { Review } from '@/src/components/Reviews/Review'; export default function ReviewsListClient({ initialReviews, @@ -12,16 +12,13 @@ export default function ReviewsListClient({ const [reviews, setReviews] = useState(initialReviews); useEffect(() => { - return getReviewsSnapshotByRestaurantId( - restaurantId, - (data) => { - setReviews(data); - } - ); + return getReviewsSnapshotByRestaurantId(restaurantId, (data) => { + setReviews(data); + }); }, [restaurantId]); return (
          -
            +
              {reviews.length > 0 ? (
                {reviews.map((review) => ( @@ -35,8 +32,8 @@ export default function ReviewsListClient({
              ) : (

              - This restaurant has not been reviewed yet,{" "} - {!userId ? "first login and then" : ""} add your own review! + This restaurant has not been reviewed yet,{' '} + {!userId ? 'first login and then' : ''} add your own review!

              )}
            diff --git a/nextjs-end/src/components/Stars.jsx b/nextjs-end/src/components/Stars.jsx index 2ddc2e0a..49665d58 100644 --- a/nextjs-end/src/components/Stars.jsx +++ b/nextjs-end/src/components/Stars.jsx @@ -1,45 +1,45 @@ // This component displays star ratings export default function renderStars(avgRating) { - const arr = []; - for (let i = 0; i < 5; i++) { - if (i < Math.floor(avgRating)) { - arr.push( -
          • - - - -
          • - ); - } else { - arr.push( -
          • - - - -
          • - ); - } - } - return arr; + const arr = []; + for (let i = 0; i < 5; i++) { + if (i < Math.floor(avgRating)) { + arr.push( +
          • + + + +
          • , + ); + } else { + arr.push( +
          • + + + +
          • , + ); + } + } + return arr; } diff --git a/nextjs-end/src/components/Tag.jsx b/nextjs-end/src/components/Tag.jsx index 986bc24c..9883fd02 100644 --- a/nextjs-end/src/components/Tag.jsx +++ b/nextjs-end/src/components/Tag.jsx @@ -3,16 +3,16 @@ // On click, the tag is removed and the filter is reset export default function Tag({ type, value, updateField }) { - return ( - - {value} - - - ); + return ( + + {value} + + + ); } diff --git a/nextjs-end/src/lib/fakeRestaurants.js b/nextjs-end/src/lib/fakeRestaurants.js index 621516fd..1ce64c51 100644 --- a/nextjs-end/src/lib/fakeRestaurants.js +++ b/nextjs-end/src/lib/fakeRestaurants.js @@ -1,88 +1,78 @@ import { - randomNumberBetween, - getRandomDateAfter, - getRandomDateBefore, -} from "@/src/lib/utils.js"; -import { randomData } from "@/src/lib/randomData.js"; + randomNumberBetween, + getRandomDateAfter, + getRandomDateBefore, +} from '@/src/lib/utils.js'; +import { randomData } from '@/src/lib/randomData.js'; -import { Timestamp } from "firebase/firestore"; +import { Timestamp } from 'firebase/firestore'; export async function generateFakeRestaurantsAndReviews() { - const restaurantsToAdd = 5; - const data = []; + const restaurantsToAdd = 5; + const data = []; - for (let i = 0; i < restaurantsToAdd; i++) { - const restaurantTimestamp = Timestamp.fromDate(getRandomDateBefore()); + for (let i = 0; i < restaurantsToAdd; i++) { + const restaurantTimestamp = Timestamp.fromDate(getRandomDateBefore()); - const ratingsData = []; + const ratingsData = []; - // Generate a random number of ratings/reviews for this restaurant - for (let j = 0; j < randomNumberBetween(0, 5); j++) { - const ratingTimestamp = Timestamp.fromDate( - getRandomDateAfter(restaurantTimestamp.toDate()) - ); + // Generate a random number of ratings/reviews for this restaurant + for (let j = 0; j < randomNumberBetween(0, 5); j++) { + const ratingTimestamp = Timestamp.fromDate( + getRandomDateAfter(restaurantTimestamp.toDate()), + ); - const ratingData = { - rating: randomData.restaurantReviews[ - randomNumberBetween( - 0, - randomData.restaurantReviews.length - 1 - ) - ].rating, - text: randomData.restaurantReviews[ - randomNumberBetween( - 0, - randomData.restaurantReviews.length - 1 - ) - ].text, - userId: `User #${randomNumberBetween()}`, - timestamp: ratingTimestamp, - }; + const ratingData = { + rating: + randomData.restaurantReviews[ + randomNumberBetween(0, randomData.restaurantReviews.length - 1) + ].rating, + text: randomData.restaurantReviews[ + randomNumberBetween(0, randomData.restaurantReviews.length - 1) + ].text, + userId: `User #${randomNumberBetween()}`, + timestamp: ratingTimestamp, + }; - ratingsData.push(ratingData); - } + ratingsData.push(ratingData); + } - const avgRating = ratingsData.length - ? ratingsData.reduce( - (accumulator, currentValue) => - accumulator + currentValue.rating, - 0 - ) / ratingsData.length - : 0; + const avgRating = ratingsData.length + ? ratingsData.reduce( + (accumulator, currentValue) => accumulator + currentValue.rating, + 0, + ) / ratingsData.length + : 0; - const restaurantData = { - category: - randomData.restaurantCategories[ - randomNumberBetween( - 0, - randomData.restaurantCategories.length - 1 - ) - ], - name: randomData.restaurantNames[ - randomNumberBetween(0, randomData.restaurantNames.length - 1) - ], - avgRating, - city: randomData.restaurantCities[ - randomNumberBetween(0, randomData.restaurantCities.length - 1) - ], - numRatings: ratingsData.length, - sumRating: ratingsData.reduce( - (accumulator, currentValue) => - accumulator + currentValue.rating, - 0 - ), - price: randomNumberBetween(1, 4), - photo: `https://storage.googleapis.com/firestorequickstarts.appspot.com/food_${randomNumberBetween( - 1, - 22 - )}.png`, - timestamp: restaurantTimestamp, - }; + const restaurantData = { + category: + randomData.restaurantCategories[ + randomNumberBetween(0, randomData.restaurantCategories.length - 1) + ], + name: randomData.restaurantNames[ + randomNumberBetween(0, randomData.restaurantNames.length - 1) + ], + avgRating, + city: randomData.restaurantCities[ + randomNumberBetween(0, randomData.restaurantCities.length - 1) + ], + numRatings: ratingsData.length, + sumRating: ratingsData.reduce( + (accumulator, currentValue) => accumulator + currentValue.rating, + 0, + ), + price: randomNumberBetween(1, 4), + photo: `https://storage.googleapis.com/firestorequickstarts.appspot.com/food_${randomNumberBetween( + 1, + 22, + )}.png`, + timestamp: restaurantTimestamp, + }; - data.push({ - restaurantData, - ratingsData, - }); - } - return data; + data.push({ + restaurantData, + ratingsData, + }); + } + return data; } diff --git a/nextjs-end/src/lib/firebase/auth.js b/nextjs-end/src/lib/firebase/auth.js index 362984b3..8878d80e 100644 --- a/nextjs-end/src/lib/firebase/auth.js +++ b/nextjs-end/src/lib/firebase/auth.js @@ -2,12 +2,12 @@ import { GoogleAuthProvider, signInWithPopup, onAuthStateChanged as _onAuthStateChanged, -} from "firebase/auth"; +} from 'firebase/auth'; -import { auth } from "@/src/lib/firebase/clientApp"; +import { auth } from '@/src/lib/firebase/clientApp'; export function onAuthStateChanged(cb) { - return _onAuthStateChanged(auth, cb); + return _onAuthStateChanged(auth, cb); } export async function signInWithGoogle() { @@ -16,7 +16,7 @@ export async function signInWithGoogle() { try { await signInWithPopup(auth, provider); } catch (error) { - console.error("Error signing in with Google", error); + console.error('Error signing in with Google', error); } } @@ -24,6 +24,6 @@ export async function signOut() { try { return auth.signOut(); } catch (error) { - console.error("Error signing out with Google", error); + console.error('Error signing out with Google', error); } } diff --git a/nextjs-end/src/lib/firebase/clientApp.js b/nextjs-end/src/lib/firebase/clientApp.js index d05ec760..c00cf5dc 100644 --- a/nextjs-end/src/lib/firebase/clientApp.js +++ b/nextjs-end/src/lib/firebase/clientApp.js @@ -1,10 +1,10 @@ 'use client'; -import { initializeApp } from "firebase/app"; -import { firebaseConfig } from "./config"; -import { getAuth } from "firebase/auth"; -import { getFirestore } from "firebase/firestore"; -import { getStorage } from "firebase/storage"; +import { initializeApp } from 'firebase/app'; +import { firebaseConfig } from './config'; +import { getAuth } from 'firebase/auth'; +import { getFirestore } from 'firebase/firestore'; +import { getStorage } from 'firebase/storage'; export const firebaseApp = initializeApp(firebaseConfig); diff --git a/nextjs-end/src/lib/firebase/config.js b/nextjs-end/src/lib/firebase/config.js index 00d23aef..cbfc082a 100644 --- a/nextjs-end/src/lib/firebase/config.js +++ b/nextjs-end/src/lib/firebase/config.js @@ -9,7 +9,7 @@ const config = { // When deployed, there are quotes that need to be stripped Object.keys(config).forEach((key) => { - const configValue = config[key] + ""; + const configValue = config[key] + ''; if (configValue.charAt(0) === '"') { config[key] = configValue.substring(1, configValue.length - 1); } diff --git a/nextjs-end/src/lib/firebase/firestore.js b/nextjs-end/src/lib/firebase/firestore.js index 5880dc72..98039ecd 100644 --- a/nextjs-end/src/lib/firebase/firestore.js +++ b/nextjs-end/src/lib/firebase/firestore.js @@ -1,236 +1,236 @@ -import { generateFakeRestaurantsAndReviews } from "@/src/lib/fakeRestaurants.js"; +import { generateFakeRestaurantsAndReviews } from '@/src/lib/fakeRestaurants.js'; import { - collection, - onSnapshot, - query, - getDocs, - doc, - getDoc, - updateDoc, - orderBy, - Timestamp, - runTransaction, - where, - addDoc, -} from "firebase/firestore"; - -import { db } from "@/src/lib/firebase/clientApp"; + collection, + onSnapshot, + query, + getDocs, + doc, + getDoc, + updateDoc, + orderBy, + Timestamp, + runTransaction, + where, + addDoc, +} from 'firebase/firestore'; + +import { db } from '@/src/lib/firebase/clientApp'; export async function updateRestaurantImageReference( - restaurantId, - publicImageUrl + restaurantId, + publicImageUrl, ) { - const restaurantRef = doc(collection(db, "restaurants"), restaurantId); - if (restaurantRef) { - await updateDoc(restaurantRef, { photo: publicImageUrl }); - } + const restaurantRef = doc(collection(db, 'restaurants'), restaurantId); + if (restaurantRef) { + await updateDoc(restaurantRef, { photo: publicImageUrl }); + } } const updateWithRating = async ( - transaction, - docRef, - newRatingDocument, - review + transaction, + docRef, + newRatingDocument, + review, ) => { - const restaurant = await transaction.get(docRef); - const data = restaurant.data(); - const newNumRatings = data?.numRatings ? data.numRatings + 1 : 1; - const newSumRating = (data?.sumRating || 0) + Number(review.rating); - const newAverage = newSumRating / newNumRatings; - - transaction.update(docRef, { - numRatings: newNumRatings, - sumRating: newSumRating, - avgRating: newAverage, - }); - - transaction.set(newRatingDocument, { - ...review, - timestamp: Timestamp.fromDate(new Date()), - }); + const restaurant = await transaction.get(docRef); + const data = restaurant.data(); + const newNumRatings = data?.numRatings ? data.numRatings + 1 : 1; + const newSumRating = (data?.sumRating || 0) + Number(review.rating); + const newAverage = newSumRating / newNumRatings; + + transaction.update(docRef, { + numRatings: newNumRatings, + sumRating: newSumRating, + avgRating: newAverage, + }); + + transaction.set(newRatingDocument, { + ...review, + timestamp: Timestamp.fromDate(new Date()), + }); }; export async function addReviewToRestaurant(db, restaurantId, review) { - if (!restaurantId) { - throw new Error("No restaurant ID has been provided."); - } - - if (!review) { - throw new Error("A valid review has not been provided."); - } - - try { - const docRef = doc(collection(db, "restaurants"), restaurantId); - const newRatingDocument = doc( - collection(db, `restaurants/${restaurantId}/ratings`) - ); - - // corrected line - await runTransaction(db, transaction => - updateWithRating(transaction, docRef, newRatingDocument, review) - ); - } catch (error) { - console.error( - "There was an error adding the rating to the restaurant", - error - ); - throw error; - } + if (!restaurantId) { + throw new Error('No restaurant ID has been provided.'); + } + + if (!review) { + throw new Error('A valid review has not been provided.'); + } + + try { + const docRef = doc(collection(db, 'restaurants'), restaurantId); + const newRatingDocument = doc( + collection(db, `restaurants/${restaurantId}/ratings`), + ); + + // corrected line + await runTransaction(db, (transaction) => + updateWithRating(transaction, docRef, newRatingDocument, review), + ); + } catch (error) { + console.error( + 'There was an error adding the rating to the restaurant', + error, + ); + throw error; + } } function applyQueryFilters(q, { category, city, price, sort }) { - if (category) { - q = query(q, where("category", "==", category)); - } - if (city) { - q = query(q, where("city", "==", city)); - } - if (price) { - q = query(q, where("price", "==", price.length)); - } - if (sort === "Rating" || !sort) { - q = query(q, orderBy("avgRating", "desc")); - } else if (sort === "Review") { - q = query(q, orderBy("numRatings", "desc")); - } - return q; + if (category) { + q = query(q, where('category', '==', category)); + } + if (city) { + q = query(q, where('city', '==', city)); + } + if (price) { + q = query(q, where('price', '==', price.length)); + } + if (sort === 'Rating' || !sort) { + q = query(q, orderBy('avgRating', 'desc')); + } else if (sort === 'Review') { + q = query(q, orderBy('numRatings', 'desc')); + } + return q; } export async function getRestaurants(db = db, filters = {}) { - let q = query(collection(db, "restaurants")); - - q = applyQueryFilters(q, filters); - const results = await getDocs(q); - return results.docs.map(doc => { - return { - id: doc.id, - ...doc.data(), - // Only plain objects can be passed to Client Components from Server Components - timestamp: doc.data().timestamp.toDate(), - }; - }); + let q = query(collection(db, 'restaurants')); + + q = applyQueryFilters(q, filters); + const results = await getDocs(q); + return results.docs.map((doc) => { + return { + id: doc.id, + ...doc.data(), + // Only plain objects can be passed to Client Components from Server Components + timestamp: doc.data().timestamp.toDate(), + }; + }); } export function getRestaurantsSnapshot(cb, filters = {}) { - if (typeof cb !== "function") { - console.log("Error: The callback parameter is not a function"); - return; - } - - let q = query(collection(db, "restaurants")); - q = applyQueryFilters(q, filters); - - return onSnapshot(q, querySnapshot => { - const results = querySnapshot.docs.map(doc => { - return { - id: doc.id, - ...doc.data(), - // Only plain objects can be passed to Client Components from Server Components - timestamp: doc.data().timestamp.toDate(), - }; - }); - - cb(results); - }); + if (typeof cb !== 'function') { + console.log('Error: The callback parameter is not a function'); + return; + } + + let q = query(collection(db, 'restaurants')); + q = applyQueryFilters(q, filters); + + return onSnapshot(q, (querySnapshot) => { + const results = querySnapshot.docs.map((doc) => { + return { + id: doc.id, + ...doc.data(), + // Only plain objects can be passed to Client Components from Server Components + timestamp: doc.data().timestamp.toDate(), + }; + }); + + cb(results); + }); } export async function getRestaurantById(db, restaurantId) { - if (!restaurantId) { - console.log("Error: Invalid ID received: ", restaurantId); - return; - } - const docRef = doc(db, "restaurants", restaurantId); - const docSnap = await getDoc(docRef); - return { - ...docSnap.data(), - timestamp: docSnap.data().timestamp.toDate(), - }; + if (!restaurantId) { + console.log('Error: Invalid ID received: ', restaurantId); + return; + } + const docRef = doc(db, 'restaurants', restaurantId); + const docSnap = await getDoc(docRef); + return { + ...docSnap.data(), + timestamp: docSnap.data().timestamp.toDate(), + }; } export function getRestaurantSnapshotById(restaurantId, cb) { - if (!restaurantId) { - console.log("Error: Invalid ID received: ", restaurantId); - return; - } - - if (typeof cb !== "function") { - console.log("Error: The callback parameter is not a function"); - return; - } - - const docRef = doc(db, "restaurants", restaurantId); - return onSnapshot(docRef, docSnap => { - cb({ - ...docSnap.data(), - timestamp: docSnap.data().timestamp.toDate(), - }); - }); + if (!restaurantId) { + console.log('Error: Invalid ID received: ', restaurantId); + return; + } + + if (typeof cb !== 'function') { + console.log('Error: The callback parameter is not a function'); + return; + } + + const docRef = doc(db, 'restaurants', restaurantId); + return onSnapshot(docRef, (docSnap) => { + cb({ + ...docSnap.data(), + timestamp: docSnap.data().timestamp.toDate(), + }); + }); } export async function getReviewsByRestaurantId(db, restaurantId) { - if (!restaurantId) { - console.log("Error: Invalid restaurantId received: ", restaurantId); - return; - } - - const q = query( - collection(db, "restaurants", restaurantId, "ratings"), - orderBy("timestamp", "desc") - ); - - const results = await getDocs(q); - return results.docs.map(doc => { - return { - id: doc.id, - ...doc.data(), - // Only plain objects can be passed to Client Components from Server Components - timestamp: doc.data().timestamp.toDate(), - }; - }); + if (!restaurantId) { + console.log('Error: Invalid restaurantId received: ', restaurantId); + return; + } + + const q = query( + collection(db, 'restaurants', restaurantId, 'ratings'), + orderBy('timestamp', 'desc'), + ); + + const results = await getDocs(q); + return results.docs.map((doc) => { + return { + id: doc.id, + ...doc.data(), + // Only plain objects can be passed to Client Components from Server Components + timestamp: doc.data().timestamp.toDate(), + }; + }); } export function getReviewsSnapshotByRestaurantId(restaurantId, cb) { - if (!restaurantId) { - console.log("Error: Invalid restaurantId received: ", restaurantId); - return; - } - - const q = query( - collection(db, "restaurants", restaurantId, "ratings"), - orderBy("timestamp", "desc") - ); - return onSnapshot(q, querySnapshot => { - const results = querySnapshot.docs.map(doc => { - return { - id: doc.id, - ...doc.data(), - // Only plain objects can be passed to Client Components from Server Components - timestamp: doc.data().timestamp.toDate(), - }; - }); - cb(results); - }); + if (!restaurantId) { + console.log('Error: Invalid restaurantId received: ', restaurantId); + return; + } + + const q = query( + collection(db, 'restaurants', restaurantId, 'ratings'), + orderBy('timestamp', 'desc'), + ); + return onSnapshot(q, (querySnapshot) => { + const results = querySnapshot.docs.map((doc) => { + return { + id: doc.id, + ...doc.data(), + // Only plain objects can be passed to Client Components from Server Components + timestamp: doc.data().timestamp.toDate(), + }; + }); + cb(results); + }); } export async function addFakeRestaurantsAndReviews() { - const data = await generateFakeRestaurantsAndReviews(); - for (const { restaurantData, ratingsData } of data) { - try { - const docRef = await addDoc( - collection(db, "restaurants"), - restaurantData - ); - - for (const ratingData of ratingsData) { - await addDoc( - collection(db, "restaurants", docRef.id, "ratings"), - ratingData - ); - } - } catch (e) { - console.log("There was an error adding the document"); - console.error("Error adding document: ", e); - } - } + const data = await generateFakeRestaurantsAndReviews(); + for (const { restaurantData, ratingsData } of data) { + try { + const docRef = await addDoc( + collection(db, 'restaurants'), + restaurantData, + ); + + for (const ratingData of ratingsData) { + await addDoc( + collection(db, 'restaurants', docRef.id, 'ratings'), + ratingData, + ); + } + } catch (e) { + console.log('There was an error adding the document'); + console.error('Error adding document: ', e); + } + } } diff --git a/nextjs-end/src/lib/firebase/serverApp.js b/nextjs-end/src/lib/firebase/serverApp.js index 635b9302..5ce33523 100644 --- a/nextjs-end/src/lib/firebase/serverApp.js +++ b/nextjs-end/src/lib/firebase/serverApp.js @@ -1,29 +1,29 @@ // enforces that this code can only be called on the server // https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns#keeping-server-only-code-out-of-the-client-environment -import "server-only"; +import 'server-only'; -import { headers } from "next/headers"; -import { initializeServerApp } from "firebase/app"; +import { headers } from 'next/headers'; +import { initializeServerApp } from 'firebase/app'; -import { firebaseConfig } from "./config"; -import { getAuth } from "firebase/auth"; +import { firebaseConfig } from './config'; +import { getAuth } from 'firebase/auth'; // Returns an authenticated client SDK instance for use in Server Side Rendering // and Static Site Generation export async function getAuthenticatedAppForUser() { // Firebase App Hosting currently does not support cookies, so we transmit // the client idToken via an Authorization header with Service Workers - const authIdToken = headers().get("Authorization")?.split("Bearer ")[1]; + const authIdToken = headers().get('Authorization')?.split('Bearer ')[1]; // Firebase Server App is a new feature in the JS SDK that allows you to // instantiate the SDK with credentials retrieved from the client & has // other afforances for use in server environments. const firebaseServerApp = initializeServerApp(firebaseConfig, { - authIdToken + authIdToken, }); const auth = getAuth(firebaseServerApp); await auth.authStateReady(); return { firebaseServerApp, currentUser: auth.currentUser }; -} \ No newline at end of file +} diff --git a/nextjs-end/src/lib/firebase/storage.js b/nextjs-end/src/lib/firebase/storage.js index 9ea3e31a..93739481 100644 --- a/nextjs-end/src/lib/firebase/storage.js +++ b/nextjs-end/src/lib/firebase/storage.js @@ -1,32 +1,32 @@ -import { ref, uploadBytesResumable, getDownloadURL } from "firebase/storage"; +import { ref, uploadBytesResumable, getDownloadURL } from 'firebase/storage'; -import { storage } from "@/src/lib/firebase/clientApp"; +import { storage } from '@/src/lib/firebase/clientApp'; -import { updateRestaurantImageReference } from "@/src/lib/firebase/firestore"; +import { updateRestaurantImageReference } from '@/src/lib/firebase/firestore'; export async function updateRestaurantImage(restaurantId, image) { - try { - if (!restaurantId) { - throw new Error("No restaurant ID has been provided."); - } - - if (!image || !image.name) { - throw new Error("A valid image has not been provided."); - } - - const publicImageUrl = await uploadImage(restaurantId, image); - await updateRestaurantImageReference(restaurantId, publicImageUrl); - - return publicImageUrl; - } catch (error) { - console.error("Error processing request:", error); - } + try { + if (!restaurantId) { + throw new Error('No restaurant ID has been provided.'); + } + + if (!image || !image.name) { + throw new Error('A valid image has not been provided.'); + } + + const publicImageUrl = await uploadImage(restaurantId, image); + await updateRestaurantImageReference(restaurantId, publicImageUrl); + + return publicImageUrl; + } catch (error) { + console.error('Error processing request:', error); + } } async function uploadImage(restaurantId, image) { - const filePath = `images/${restaurantId}/${image.name}`; - const newImageRef = ref(storage, filePath); - await uploadBytesResumable(newImageRef, image); + const filePath = `images/${restaurantId}/${image.name}`; + const newImageRef = ref(storage, filePath); + await uploadBytesResumable(newImageRef, image); - return await getDownloadURL(newImageRef); + return await getDownloadURL(newImageRef); } diff --git a/nextjs-end/src/lib/getUser.js b/nextjs-end/src/lib/getUser.js index 35659d93..096e2d4c 100644 --- a/nextjs-end/src/lib/getUser.js +++ b/nextjs-end/src/lib/getUser.js @@ -1,9 +1,9 @@ -"use client"; +'use client'; -import { onAuthStateChanged } from "firebase/auth"; -import { useEffect, useState } from "react"; +import { onAuthStateChanged } from 'firebase/auth'; +import { useEffect, useState } from 'react'; -import { auth } from "@/src/lib/firebase/clientApp.js"; +import { auth } from '@/src/lib/firebase/clientApp.js'; export function useUser() { const [user, setUser] = useState(); diff --git a/nextjs-end/src/lib/randomData.js b/nextjs-end/src/lib/randomData.js index 41045dc8..638b11f3 100644 --- a/nextjs-end/src/lib/randomData.js +++ b/nextjs-end/src/lib/randomData.js @@ -3,146 +3,146 @@ // menu in the top right corner and select "Add random data" export const randomData = { - restaurantNames: [ - "Savory Bites", - "Gourmet Delight", - "Wholesome Kitchen", - "Cheesy Cravings", - "Spice Fusion", - "Burger Bonanza", - "Pasta Paradise", - "Taco Tango", - "Sushi Sensation", - "Pizza Pizzazz", - "Cafe Mocha", - "Mouthwatering BBQ", - "Thai Temptations", - "Sizzling Steaks", - "Veggie Heaven", - "Seafood Symphony", - "Wok & Roll", - "French Flavors", - "Tandoori Nights", - "Mediterranean Magic", - "Enchilada Express", - "Ramen Ramble", - "Deli Delights", - "Crepes & Co.", - "Hot Pot Haven", - "Tasty Tandoor", - "Bistro Bliss", - "Curry Corner", - "Pancake Paradise", - "Pita Panache", - "Biryani Bliss", - "Garden Grub", - "Dim Sum Delish", - "Cajun Craze", - "Fondue Fantasy", - "Bagel Bar", - "Tapas Talk", - "Pho Fusion", - "Brunch Bunch", - "Steaming Samosas", - "Falafel Frenzy", - ], - restaurantCities: [ - "New York", - "Los Angeles", - "London", - "Paris", - "Tokyo", - "Mumbai", - "Dubai", - "Amsterdam", - "Seoul", - "Singapore", - "Istanbul", - ], - restaurantCategories: [ - "Italian", - "Chinese", - "Japanese", - "Mexican", - "Indian", - "Mediterranean", - "Caribbean", - "Cajun", - "German", - "Russian", - "Cuban", - "Organic", - "Tapas", - ], - restaurantReviews: [ - { text: "The food was exceptional, absolutely loved it!", rating: 5 }, - { text: "Delicious dishes and excellent service!", rating: 5 }, - { text: "The flavors were so rich and satisfying.", rating: 5 }, - { text: "Great ambiance and friendly staff.", rating: 5 }, - { text: "A delightful culinary experience!", rating: 5 }, - { - text: "Mouthwatering dishes that left me wanting more.", - rating: 5, - }, - { text: "Perfectly cooked and seasoned meals.", rating: 5 }, - { text: "Incredible presentation and top-notch taste.", rating: 5 }, - { text: "Good food and cozy atmosphere.", rating: 4 }, - { text: "Enjoyed the meal, would come back again.", rating: 4 }, - { text: "Tasty options and reasonable prices.", rating: 4 }, - { text: "Friendly staff, but the food was just okay.", rating: 3 }, - { text: "Decent experience, but nothing extraordinary.", rating: 3 }, - { text: "The food was average, could be better.", rating: 3 }, - { - text: "Service was slow, and the dishes were disappointing.", - rating: 2, - }, - { text: "Expected more, but left unsatisfied.", rating: 2 }, - { text: "Underwhelming taste and presentation.", rating: 2 }, - { text: "Disappointing experience overall.", rating: 2 }, - { - text: "The food was terrible, will never go back again.", - rating: 1, - }, - { - text: "Worst restaurant experience I've had so far.", - rating: 1, - }, - { text: "Avoid this place, the food was inedible.", rating: 1 }, - { text: "Unpleasant service and tasteless dishes.", rating: 1 }, - { text: "Simply outstanding! A culinary masterpiece.", rating: 5 }, - { text: "Couldn't get enough of the amazing flavors.", rating: 5 }, - { text: "Top-notch quality, worth every penny.", rating: 5 }, - { text: "Highly recommended for food enthusiasts.", rating: 5 }, - { text: "Exquisite dishes that pleased my taste buds.", rating: 5 }, - { text: "A gem of a place, impeccable in every aspect.", rating: 5 }, - { text: "Excellent selection of dishes on the menu.", rating: 5 }, - { text: "A fantastic dining experience overall.", rating: 5 }, - { - text: "The atmosphere was lovely, perfect for a date night.", - rating: 4, - }, - { text: "Good food, but the service could be improved.", rating: 4 }, - { - text: "Pleasantly surprised by the variety of flavors.", - rating: 4, - }, - { text: "Well-prepared dishes and friendly staff.", rating: 4 }, - { text: "Satisfying meals, suitable for a quick bite.", rating: 4 }, - { text: "The food was okay, nothing special.", rating: 3 }, - { - text: "Service could be better, but the taste was fine.", - rating: 3, - }, - { - text: "An average experience, didn't leave a lasting impression.", - rating: 3, - }, - { text: "Expected more value for the price.", rating: 2 }, - { text: "Mediocre taste and lackluster presentation.", rating: 2 }, - { - text: "Regret spending money on such a disappointing meal.", - rating: 2, - }, - { text: "Not up to par with my expectations.", rating: 2 }, - ], + restaurantNames: [ + 'Savory Bites', + 'Gourmet Delight', + 'Wholesome Kitchen', + 'Cheesy Cravings', + 'Spice Fusion', + 'Burger Bonanza', + 'Pasta Paradise', + 'Taco Tango', + 'Sushi Sensation', + 'Pizza Pizzazz', + 'Cafe Mocha', + 'Mouthwatering BBQ', + 'Thai Temptations', + 'Sizzling Steaks', + 'Veggie Heaven', + 'Seafood Symphony', + 'Wok & Roll', + 'French Flavors', + 'Tandoori Nights', + 'Mediterranean Magic', + 'Enchilada Express', + 'Ramen Ramble', + 'Deli Delights', + 'Crepes & Co.', + 'Hot Pot Haven', + 'Tasty Tandoor', + 'Bistro Bliss', + 'Curry Corner', + 'Pancake Paradise', + 'Pita Panache', + 'Biryani Bliss', + 'Garden Grub', + 'Dim Sum Delish', + 'Cajun Craze', + 'Fondue Fantasy', + 'Bagel Bar', + 'Tapas Talk', + 'Pho Fusion', + 'Brunch Bunch', + 'Steaming Samosas', + 'Falafel Frenzy', + ], + restaurantCities: [ + 'New York', + 'Los Angeles', + 'London', + 'Paris', + 'Tokyo', + 'Mumbai', + 'Dubai', + 'Amsterdam', + 'Seoul', + 'Singapore', + 'Istanbul', + ], + restaurantCategories: [ + 'Italian', + 'Chinese', + 'Japanese', + 'Mexican', + 'Indian', + 'Mediterranean', + 'Caribbean', + 'Cajun', + 'German', + 'Russian', + 'Cuban', + 'Organic', + 'Tapas', + ], + restaurantReviews: [ + { text: 'The food was exceptional, absolutely loved it!', rating: 5 }, + { text: 'Delicious dishes and excellent service!', rating: 5 }, + { text: 'The flavors were so rich and satisfying.', rating: 5 }, + { text: 'Great ambiance and friendly staff.', rating: 5 }, + { text: 'A delightful culinary experience!', rating: 5 }, + { + text: 'Mouthwatering dishes that left me wanting more.', + rating: 5, + }, + { text: 'Perfectly cooked and seasoned meals.', rating: 5 }, + { text: 'Incredible presentation and top-notch taste.', rating: 5 }, + { text: 'Good food and cozy atmosphere.', rating: 4 }, + { text: 'Enjoyed the meal, would come back again.', rating: 4 }, + { text: 'Tasty options and reasonable prices.', rating: 4 }, + { text: 'Friendly staff, but the food was just okay.', rating: 3 }, + { text: 'Decent experience, but nothing extraordinary.', rating: 3 }, + { text: 'The food was average, could be better.', rating: 3 }, + { + text: 'Service was slow, and the dishes were disappointing.', + rating: 2, + }, + { text: 'Expected more, but left unsatisfied.', rating: 2 }, + { text: 'Underwhelming taste and presentation.', rating: 2 }, + { text: 'Disappointing experience overall.', rating: 2 }, + { + text: 'The food was terrible, will never go back again.', + rating: 1, + }, + { + text: "Worst restaurant experience I've had so far.", + rating: 1, + }, + { text: 'Avoid this place, the food was inedible.', rating: 1 }, + { text: 'Unpleasant service and tasteless dishes.', rating: 1 }, + { text: 'Simply outstanding! A culinary masterpiece.', rating: 5 }, + { text: "Couldn't get enough of the amazing flavors.", rating: 5 }, + { text: 'Top-notch quality, worth every penny.', rating: 5 }, + { text: 'Highly recommended for food enthusiasts.', rating: 5 }, + { text: 'Exquisite dishes that pleased my taste buds.', rating: 5 }, + { text: 'A gem of a place, impeccable in every aspect.', rating: 5 }, + { text: 'Excellent selection of dishes on the menu.', rating: 5 }, + { text: 'A fantastic dining experience overall.', rating: 5 }, + { + text: 'The atmosphere was lovely, perfect for a date night.', + rating: 4, + }, + { text: 'Good food, but the service could be improved.', rating: 4 }, + { + text: 'Pleasantly surprised by the variety of flavors.', + rating: 4, + }, + { text: 'Well-prepared dishes and friendly staff.', rating: 4 }, + { text: 'Satisfying meals, suitable for a quick bite.', rating: 4 }, + { text: 'The food was okay, nothing special.', rating: 3 }, + { + text: 'Service could be better, but the taste was fine.', + rating: 3, + }, + { + text: "An average experience, didn't leave a lasting impression.", + rating: 3, + }, + { text: 'Expected more value for the price.', rating: 2 }, + { text: 'Mediocre taste and lackluster presentation.', rating: 2 }, + { + text: 'Regret spending money on such a disappointing meal.', + rating: 2, + }, + { text: 'Not up to par with my expectations.', rating: 2 }, + ], }; diff --git a/nextjs-end/src/lib/utils.js b/nextjs-end/src/lib/utils.js index 22abdc85..bdd5d27d 100644 --- a/nextjs-end/src/lib/utils.js +++ b/nextjs-end/src/lib/utils.js @@ -1,19 +1,19 @@ export function randomNumberBetween(min = 0, max = 1000) { - return Math.floor(Math.random() * (max - min + 1) + min); + return Math.floor(Math.random() * (max - min + 1) + min); } export function getRandomDateBefore(startingDate = new Date()) { - const randomNumberOfDays = randomNumberBetween(20, 80); - const randomDate = new Date( - startingDate - randomNumberOfDays * 24 * 60 * 60 * 1000 - ); - return randomDate; + const randomNumberOfDays = randomNumberBetween(20, 80); + const randomDate = new Date( + startingDate - randomNumberOfDays * 24 * 60 * 60 * 1000, + ); + return randomDate; } export function getRandomDateAfter(startingDate = new Date()) { - const randomNumberOfDays = randomNumberBetween(1, 19); - const randomDate = new Date( - startingDate.getTime() + randomNumberOfDays * 24 * 60 * 60 * 1000 - ); - return randomDate; + const randomNumberOfDays = randomNumberBetween(1, 19); + const randomDate = new Date( + startingDate.getTime() + randomNumberOfDays * 24 * 60 * 60 * 1000, + ); + return randomDate; } diff --git a/nextjs-start/.eslintrc b/nextjs-start/.eslintrc index bb088a83..597ffeb8 100644 --- a/nextjs-start/.eslintrc +++ b/nextjs-start/.eslintrc @@ -1,9 +1,6 @@ { - "extends": ["eslint:recommended", "next"], + "extends": ["eslint:recommended", "next", "prettier"], "rules": { - "indent": "off", - "quotes": "off", - "no-mixed-spaces-and-tabs": "off", "@next/next/no-img-element": "off", "no-unused-vars": "off" } diff --git a/nextjs-start/apphosting.yaml b/nextjs-start/apphosting.yaml index f9a39181..33ab8557 100644 --- a/nextjs-start/apphosting.yaml +++ b/nextjs-start/apphosting.yaml @@ -11,4 +11,4 @@ env: - variable: NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID value: TODO - variable: NEXT_PUBLIC_FIREBASE_APP_ID - value: TODO \ No newline at end of file + value: TODO diff --git a/nextjs-start/auth-service-worker.js b/nextjs-start/auth-service-worker.js index d6bdc347..ee04249b 100644 --- a/nextjs-start/auth-service-worker.js +++ b/nextjs-start/auth-service-worker.js @@ -2,9 +2,13 @@ import { initializeApp } from "firebase/app"; import { getAuth, getIdToken, onAuthStateChanged } from "firebase/auth"; // extract firebase config from query string -const serializedFirebaseConfig = new URLSearchParams(self.location.search).get('firebaseConfig'); +const serializedFirebaseConfig = new URLSearchParams(self.location.search).get( + "firebaseConfig", +); if (!serializedFirebaseConfig) { - throw new Error('Firebase Config object not found in service worker query string.'); + throw new Error( + "Firebase Config object not found in service worker query string.", + ); } const firebaseConfig = JSON.parse(serializedFirebaseConfig); @@ -26,12 +30,12 @@ self.addEventListener("fetch", (event) => { if (origin !== self.location.origin) return; // Use a magic url to ensure that auth state is in sync between // the client and the sw, this helps with actions such as router.refresh(); - if (pathname.startsWith('/__/auth/wait/')) { - const uid = pathname.split('/').at(-1); + if (pathname.startsWith("/__/auth/wait/")) { + const uid = pathname.split("/").at(-1); event.respondWith(waitForMatchingUid(uid)); return; } - if (pathname.startsWith('/_next/')) return; + if (pathname.startsWith("/_next/")) return; // Don't add headers to non-get requests or those with an extension—this // helps with css, images, fonts, json, etc. if (event.request.method === "GET" && pathname.includes(".")) return; @@ -46,10 +50,13 @@ async function fetchWithFirebaseHeaders(request) { async function waitForMatchingUid(_uid) { const uid = _uid === "undefined" ? undefined : _uid; // TODO: wait for the expected user to deal with race conditions - return new Response(undefined, { status: 200, headers: { "cache-control": "no-store" } }); + return new Response(undefined, { + status: 200, + headers: { "cache-control": "no-store" }, + }); } // TODO: get user token async function getAuthIdToken() { - throw new Error('not implemented'); + throw new Error("not implemented"); } diff --git a/nextjs-start/jsconfig.json b/nextjs-start/jsconfig.json index 5c64b067..2a2e4b3b 100644 --- a/nextjs-start/jsconfig.json +++ b/nextjs-start/jsconfig.json @@ -1,7 +1,7 @@ { - "compilerOptions": { - "paths": { - "@/*": ["./*"] - } - } + "compilerOptions": { + "paths": { + "@/*": ["./*"] + } + } } diff --git a/nextjs-start/next.config.js b/nextjs-start/next.config.js index 2d747871..954fac0d 100644 --- a/nextjs-start/next.config.js +++ b/nextjs-start/next.config.js @@ -1,8 +1,8 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - eslint: { - ignoreDuringBuilds: true, - } + eslint: { + ignoreDuringBuilds: true, + }, }; module.exports = nextConfig; diff --git a/nextjs-start/package.json b/nextjs-start/package.json index 973266e2..ccabdfe4 100644 --- a/nextjs-start/package.json +++ b/nextjs-start/package.json @@ -1,39 +1,44 @@ { - "name": "my-app", - "version": "0.1.0", - "private": true, - "scripts": { - "dev": "concurrently -r -k \"npm:dev:next\" \"npm:dev:sw\"", - "dev:next": "next dev", - "dev:sw": "npm run build:sw -- --watch", - "build": "next build", - "build:sw": "esbuild auth-service-worker.js --bundle --minify --main-fields=webworker,browser,module,main --outfile=public/auth-service-worker.js", - "prebuild": "npm run build:sw", - "start": "next start", - "lint": "next lint" - }, - "dependencies": { - "@google/generative-ai": "^0.10.0", - "firebase": "^10.11.1", - "firebase-admin": "^12.1.0", - "next": "^14.2.3", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "server-only": "^0.0.1" - }, - "browser": { - "fs": false, - "os": false, - "path": false, - "child_process": false, - "net": false, - "tls": false - }, - "devDependencies": { - "@next/eslint-plugin-next": "^14.2.9", - "concurrently": "^9.0.0", - "esbuild": "^0.20.2", - "eslint": "^8.0.0", - "eslint-config-next": "^14.2.9" - } + "name": "my-app", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "concurrently -r -k \"npm:dev:next\" \"npm:dev:sw\"", + "dev:next": "next dev", + "dev:sw": "npm run build:sw -- --watch", + "build": "next build", + "build:sw": "esbuild auth-service-worker.js --bundle --minify --main-fields=webworker,browser,module,main --outfile=public/auth-service-worker.js", + "prebuild": "npm run build:sw", + "start": "next start", + "lint": "concurrently -r \"npm:lint:next\" \"npm:lint:prettier\"", + "lint:next": "next lint", + "lint:prettier": "prettier --check --ignore-path .gitignore .", + "lint:fix": "concurrently -r \"npm:lint:next -- --fix\" \"npm:lint:prettier -- --write\"" + }, + "dependencies": { + "@google/generative-ai": "^0.10.0", + "firebase": "^10.11.1", + "firebase-admin": "^12.1.0", + "next": "^14.2.3", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "server-only": "^0.0.1" + }, + "browser": { + "fs": false, + "os": false, + "path": false, + "child_process": false, + "net": false, + "tls": false + }, + "devDependencies": { + "@next/eslint-plugin-next": "^14.2.9", + "concurrently": "^9.0.0", + "esbuild": "^0.20.2", + "eslint": "^8.0.0", + "eslint-config-next": "^14.2.9", + "eslint-config-prettier": "^9.1.0", + "prettier": "^3.3.3" + } } diff --git a/nextjs-start/src/app/layout.js b/nextjs-start/src/app/layout.js index c75b8fc1..dad6ea92 100644 --- a/nextjs-start/src/app/layout.js +++ b/nextjs-start/src/app/layout.js @@ -11,18 +11,15 @@ export const metadata = { "FriendlyEats is a restaurant review website built with Next.js and Firebase.", }; - export default async function RootLayout({ children }) { const { currentUser } = await getAuthenticatedAppForUser(); return ( - -
            +
            {children}
            - ); } diff --git a/nextjs-start/src/app/page.js b/nextjs-start/src/app/page.js index c52769cc..e07647f3 100644 --- a/nextjs-start/src/app/page.js +++ b/nextjs-start/src/app/page.js @@ -12,16 +12,19 @@ export const dynamic = "force-dynamic"; // export const revalidate = 0; export default async function Home({ searchParams }) { - // Using seachParams which Next.js provides, allows the filtering to happen on the server-side, for example: - // ?city=London&category=Indian&sort=Review - const {firebaseServerApp} = await getAuthenticatedAppForUser(); - const restaurants = await getRestaurants(getFirestore(firebaseServerApp), searchParams); - return ( -
            - -
            - ); + // Using seachParams which Next.js provides, allows the filtering to happen on the server-side, for example: + // ?city=London&category=Indian&sort=Review + const { firebaseServerApp } = await getAuthenticatedAppForUser(); + const restaurants = await getRestaurants( + getFirestore(firebaseServerApp), + searchParams, + ); + return ( +
            + +
            + ); } diff --git a/nextjs-start/src/app/restaurant/[id]/error.jsx b/nextjs-start/src/app/restaurant/[id]/error.jsx index 8ce09e05..90c8af65 100644 --- a/nextjs-start/src/app/restaurant/[id]/error.jsx +++ b/nextjs-start/src/app/restaurant/[id]/error.jsx @@ -3,21 +3,21 @@ import { useEffect } from "react"; export default function Error({ error, reset }) { - useEffect(() => { - console.error(error); - }, [error]); + useEffect(() => { + console.error(error); + }, [error]); - return ( -
            -

            Something went wrong!

            - -
            - ); + return ( +
            +

            Something went wrong!

            + +
            + ); } diff --git a/nextjs-start/src/app/restaurant/[id]/page.jsx b/nextjs-start/src/app/restaurant/[id]/page.jsx index af4e05b9..b61108e9 100644 --- a/nextjs-start/src/app/restaurant/[id]/page.jsx +++ b/nextjs-start/src/app/restaurant/[id]/page.jsx @@ -1,7 +1,10 @@ import Restaurant from "@/src/components/Restaurant.jsx"; import { Suspense } from "react"; import { getRestaurantById } from "@/src/lib/firebase/firestore.js"; -import { getAuthenticatedAppForUser, getAuthenticatedAppForUser as getUser } from "@/src/lib/firebase/serverApp.js"; +import { + getAuthenticatedAppForUser, + getAuthenticatedAppForUser as getUser, +} from "@/src/lib/firebase/serverApp.js"; import ReviewsList, { ReviewsListSkeleton, } from "@/src/components/Reviews/ReviewsList"; @@ -13,11 +16,14 @@ import { getFirestore } from "firebase/firestore"; export default async function Home({ params }) { // This is a server component, we can access URL - // parameters via Next.js and download the data - // we need for this page + // parameters via Next.js and download the data + // we need for this page const { currentUser } = await getUser(); - const {firebaseServerApp} = await getAuthenticatedAppForUser(); - const restaurant = await getRestaurantById(getFirestore(firebaseServerApp), params.id); + const { firebaseServerApp } = await getAuthenticatedAppForUser(); + const restaurant = await getRestaurantById( + getFirestore(firebaseServerApp), + params.id, + ); return (
            diff --git a/nextjs-start/src/app/styles.css b/nextjs-start/src/app/styles.css index e8fd5c6e..b9584b99 100644 --- a/nextjs-start/src/app/styles.css +++ b/nextjs-start/src/app/styles.css @@ -1,434 +1,437 @@ -@import url('https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,500;0,700;1,800&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap'); +@import url("https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,500;0,700;1,800&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"); * { - box-sizing: border-box; - padding: 0; - margin: 0; + box-sizing: border-box; + padding: 0; + margin: 0; } body { - font-family: "Roboto", ui-sans-serif, system-ui, -apple-system; + font-family: + "Roboto", + ui-sans-serif, + system-ui, + -apple-system; } ul { - list-style-type: none; + list-style-type: none; } h2 { - font-weight: normal; + font-weight: normal; } dialog { - &[open] { - position: fixed; - width: 80vw; - height: 50vh; - min-height: 270px; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - z-index: 999; - - border-width: 0px; - border-radius: 0.75rem; - box-shadow: -7px 12px 14px 6px rgb(0 0 0 / 0.2); - - & article { - background-color: unset; - } - } - - & form { - display: flex; - flex-direction: column; - justify-content: space-between; - height: 100%; - } - - & footer { - padding-right: 20px; - padding-right: 20px; - } - - &::backdrop { - background-color: #F6F7F9; - opacity: 0.8; - } + &[open] { + position: fixed; + width: 80vw; + height: 50vh; + min-height: 270px; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 999; + + border-width: 0px; + border-radius: 0.75rem; + box-shadow: -7px 12px 14px 6px rgb(0 0 0 / 0.2); + + & article { + background-color: unset; + } + } + + & form { + display: flex; + flex-direction: column; + justify-content: space-between; + height: 100%; + } + + & footer { + padding-right: 20px; + padding-right: 20px; + } + + &::backdrop { + background-color: #f6f7f9; + opacity: 0.8; + } } footer { - & button { - --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), - 0 1px 2px -1px rgb(0 0 0 / 0.1); - --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), - 0 1px 2px -1px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), - var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); - text-transform: uppercase; - font-size: 1rem; - outline: 0; - border: 0; - padding: 10px; - cursor: pointer; - } - - & .button--cancel { - color: rgb(178, 193, 212); - background-color: white; - border-radius: 3px; - } - - & .button--confirm { - background-color: rgb(255 111 0); - color: white; - border-radius: 3px; - } - - & menu { - display: flex; - justify-content: flex-end; - padding: 20px 0; - gap: 20px; - } + & button { + --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), + 0 1px 2px -1px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), + var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + text-transform: uppercase; + font-size: 1rem; + outline: 0; + border: 0; + padding: 10px; + cursor: pointer; + } + + & .button--cancel { + color: rgb(178, 193, 212); + background-color: white; + border-radius: 3px; + } + + & .button--confirm { + background-color: rgb(255 111 0); + color: white; + border-radius: 3px; + } + + & menu { + display: flex; + justify-content: flex-end; + padding: 20px 0; + gap: 20px; + } } header { - background-color: rgb(56 85 116); - color: white; - display: flex; - justify-content: space-between; - padding: 0.8rem; - align-items: center; - & img { - height: 2rem; - } - - & ul { - display: none; - position: absolute; - width: 220px; - z-index: 99; - } - & a { - text-decoration: none; - color: white; - } - .profileImage { - border-radius: 100%; - border-color: white; - border-width: 2px; - border-style: solid; - margin-right: 10px; - } + background-color: rgb(56 85 116); + color: white; + display: flex; + justify-content: space-between; + padding: 0.8rem; + align-items: center; + & img { + height: 2rem; + } + + & ul { + display: none; + position: absolute; + width: 220px; + z-index: 99; + } + & a { + text-decoration: none; + color: white; + } + .profileImage { + border-radius: 100%; + border-color: white; + border-width: 2px; + border-style: solid; + margin-right: 10px; + } } .logo { - display: flex; - align-items: center; + display: flex; + align-items: center; - & img { - margin-inline-end: 10px; - } + & img { + margin-inline-end: 10px; + } - color: white; - text-decoration: none; - font-size: 1.25rem; - font-weight: 500; + color: white; + text-decoration: none; + font-size: 1.25rem; + font-weight: 500; } .menu { - display: inline-block; - position: relative; - padding: 15px 20px; - align-self: stretch; + display: inline-block; + position: relative; + padding: 15px 20px; + align-self: stretch; } .menu ul { - /* display: block; */ - left: calc(-220px * 0.9); - color: rgb(42 72 101); - background-color: white; - box-shadow: 0 0 10px 0 rgb(0 0 0 / 50%); + /* display: block; */ + left: calc(-220px * 0.9); + color: rgb(42 72 101); + background-color: white; + box-shadow: 0 0 10px 0 rgb(0 0 0 / 50%); - & li { - padding: 10px; - border-bottom: 1px solid rgb(42 72 101 / 0.25); - } + & li { + padding: 10px; + border-bottom: 1px solid rgb(42 72 101 / 0.25); + } - & a { - font-weight: bold; - color: unset; - } + & a { + font-weight: bold; + color: unset; + } - & li:has(a):hover { - background-color: rgb(42 72 101 / 0.05); - } + & li:has(a):hover { + background-color: rgb(42 72 101 / 0.05); + } - & a:visited { - color: unset; - } + & a:visited { + color: unset; + } } .menu:hover ul { - display: block; + display: block; } .profile { - display: flex; - /* align-items: center; + display: flex; + /* align-items: center; justify-content: center; */ - & p { - display: flex; - align-items: center; - } + & p { + display: flex; + align-items: center; + } - & a { - display: flex; - align-items: center; - } + & a { + display: flex; + align-items: center; + } } .main__home { - background-color: rgb(178 193 212); - min-height: 100vh; + background-color: rgb(178 193 212); + min-height: 100vh; } .main__restaurant { - background-color: rgb(229 234 240); - min-height: 90vh; + background-color: rgb(229 234 240); + min-height: 90vh; } article { - margin: 0 auto; - background-color: rgb(229 234 240); - padding: 20px 40px; + margin: 0 auto; + background-color: rgb(229 234 240); + padding: 20px 40px; } article { - width: 75%; + width: 75%; } .restaurants { - display: grid; - margin-top: 20px; - grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + display: grid; + margin-top: 20px; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); - gap: 40px; + gap: 40px; - & li { - background: white; - max-width: 300px; - } + & li { + background: white; + max-width: 300px; + } - & a { - color: black; - display: flex; - flex-direction: column; - flex: 2 1 100%; - } + & a { + color: black; + display: flex; + flex-direction: column; + flex: 2 1 100%; + } - & h2 { - font-weight: normal; - } + & h2 { + font-weight: normal; + } } .image-cover { - width: 100%; - height: 100%; - object-fit: cover; - max-height: 300px; - min-height: 300px; - position: relative; + width: 100%; + height: 100%; + object-fit: cover; + max-height: 300px; + min-height: 300px; + position: relative; - & img { - width: 100%; - height: 100%; - object-fit: cover; - position: absolute; - } + & img { + width: 100%; + height: 100%; + object-fit: cover; + position: absolute; + } } a { - text-decoration: none; + text-decoration: none; } .restaurant__meta { - display: flex; - font-weight: 500; - justify-content: space-between; - align-items: center; - margin-bottom: 10px; + display: flex; + font-weight: 500; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; } .restaurant__details { - padding: 20px; + padding: 20px; } .restaurant__rating { - padding: 5px 0; - display: flex; - align-items: center; + padding: 5px 0; + display: flex; + align-items: center; - & ul { - display: flex; - } + & ul { + display: flex; + } - & svg { - width: 2rem; - height: 2rem; - color: rgb(255 202 40); - } + & svg { + width: 2rem; + height: 2rem; + color: rgb(255 202 40); + } - & span { - color: rgb(156 163 175); - } + & span { + color: rgb(156 163 175); + } } .restaurant__review_summary { - max-width: "50vw"; - height: "75px"; - padding-top: "10px"; + max-width: "50vw"; + height: "75px"; + padding-top: "10px"; } .img__section { - width: 100%; - height: 400px; - position: relative; - > img { - width: 100%; - height: 100%; - object-fit: cover; - position: absolute; - max-width: unset; - } + width: 100%; + height: 400px; + position: relative; + > img { + width: 100%; + height: 100%; + object-fit: cover; + position: absolute; + max-width: unset; + } } .details { - position: absolute; - bottom: 0; - padding: 20px; - color: white; + position: absolute; + bottom: 0; + padding: 20px; + color: white; - & span { - color: inherit; - } + & span { + color: inherit; + } } .details__container { - --tw-gradient-from: #c60094 var(--tw-gradient-from-position); - --tw-gradient-to: rgb(56 85 116 / 0) var(--tw-gradient-to-position); - --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); - background-image: linear-gradient(to top right, var(--tw-gradient-stops)); - position: absolute; - right: 0; - bottom: 0; - left: 0; - background: rgb(24 25 26 / 50%); - width: 100%; - height: 100%; + --tw-gradient-from: #c60094 var(--tw-gradient-from-position); + --tw-gradient-to: rgb(56 85 116 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); + background-image: linear-gradient(to top right, var(--tw-gradient-stops)); + position: absolute; + right: 0; + bottom: 0; + left: 0; + background: rgb(24 25 26 / 50%); + width: 100%; + height: 100%; } .reviews { - & .review__item { - padding: 40px; - border-bottom: 1px solid rgb(156 163 175 / 0.25); - } + & .review__item { + padding: 40px; + border-bottom: 1px solid rgb(156 163 175 / 0.25); + } - & time { - font-size: 0.8rem; - color: darkgrey; - } + & time { + font-size: 0.8rem; + color: darkgrey; + } } .actions { - position: absolute; - z-index: 1; - bottom: -30px; - - right: 0; - display: flex; - justify-content: flex-end; - & img { - height: 4rem; - } - - .review { - --tw-ring-offset-shadow: 0 0 #0000; - --tw-ring-shadow: 0 0 #0000; - --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), - 0 4px 6px -4px rgb(0 0 0 / 0.1); - cursor: pointer; - background-color: rgb(255 202 40); - border-radius: 0.75rem; - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), - var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); - } - - .add { - --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), - 0 4px 6px -4px rgb(0 0 0 / 0.1); - --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), - 0 4px 6px -4px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), - var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); - background-color: rgb(255 143 0); - border-radius: 9999px; - cursor: pointer; - height: 4rem; - } - - .add input { - display: none; - } - - :where(.review, .add) { - margin: 0 30px; - } + position: absolute; + z-index: 1; + bottom: -30px; + + right: 0; + display: flex; + justify-content: flex-end; + & img { + height: 4rem; + } + + .review { + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), + 0 4px 6px -4px rgb(0 0 0 / 0.1); + cursor: pointer; + background-color: rgb(255 202 40); + border-radius: 0.75rem; + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), + var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + } + + .add { + --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), + 0 4px 6px -4px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), + 0 4px 6px -4px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), + var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + background-color: rgb(255 143 0); + border-radius: 9999px; + cursor: pointer; + height: 4rem; + } + + .add input { + display: none; + } + + :where(.review, .add) { + margin: 0 30px; + } } #review { - padding: 20px; - font-size: 17px; - border: none; - border-bottom: 2px solid rgb(255 111 0); - width: 100%; + padding: 20px; + font-size: 17px; + border: none; + border-bottom: 2px solid rgb(255 111 0); + width: 100%; } /* Thanks to: https://codepen.io/chris22smith/pen/MJzLJN */ .star-rating { - display: flex; - flex-direction: row-reverse; - justify-content: flex-end; + display: flex; + flex-direction: row-reverse; + justify-content: flex-end; } .radio-input { - position: fixed; - opacity: 0; - pointer-events: none; + position: fixed; + opacity: 0; + pointer-events: none; } .radio-label { - cursor: pointer; - font-size: 0; - color: rgba(0, 0, 0, 0.2); - transition: color 0.1s ease-in-out; + cursor: pointer; + font-size: 0; + color: rgba(0, 0, 0, 0.2); + transition: color 0.1s ease-in-out; } .radio-label:before { - content: "★"; - display: inline-block; - font-size: 32px; + content: "★"; + display: inline-block; + font-size: 32px; } .radio-input:checked ~ .radio-label { - color: #ffc700; - color: gold; + color: #ffc700; + color: gold; } .radio-label:hover, .radio-label:hover ~ .radio-label { - color: goldenrod; + color: goldenrod; } .radio-input:checked + .radio-label:hover, @@ -436,141 +439,141 @@ a { .radio-input:checked ~ .radio-label:hover, .radio-input:checked ~ .radio-label:hover ~ .radio-label, .radio-label:hover ~ .radio-input:checked ~ .radio-label { - color: darkgoldenrod; + color: darkgoldenrod; } .average-rating { - position: relative; - appearance: none; - color: transparent; - width: auto; - display: inline-block; - vertical-align: baseline; - font-size: 25px; + position: relative; + appearance: none; + color: transparent; + width: auto; + display: inline-block; + vertical-align: baseline; + font-size: 25px; } .average-rating::before { - --percent: calc(4.3 / 5 * 100%); - content: "★★★★★"; - position: absolute; - top: 0; - left: 0; - color: rgba(0, 0, 0, 0.2); - background: linear-gradient( - 90deg, - gold var(--percent), - rgba(0, 0, 0, 0.2) var(--percent) - ); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; + --percent: calc(4.3 / 5 * 100%); + content: "★★★★★"; + position: absolute; + top: 0; + left: 0; + color: rgba(0, 0, 0, 0.2); + background: linear-gradient( + 90deg, + gold var(--percent), + rgba(0, 0, 0, 0.2) var(--percent) + ); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; } .rating-picker { - display: flex; - flex-direction: row-reverse; - justify-content: center; + display: flex; + flex-direction: row-reverse; + justify-content: center; } .filter-menu { - background-color: white; - border-radius: 3px; - border-bottom: 1px solid rgb(27 58 87); - - & summary { - font-weight: bold; - cursor: pointer; - display: flex; - align-items: center; - } - - & form { - display: flex; - flex-direction: column; - padding: 20px; - padding-bottom: 0; - } - - & label { - padding: 10px 0; - display: flex; - flex-direction: column; - flex-grow: 1; - - color: rgb(75 85 99); - font-size: 0.75rem; - line-height: 1rem; - } - - & img { - height: 4rem; - max-width: 100%; - } - - & form div { - display: flex; - gap: 10px; - } - - & select { - color: rgb(17 24 39); - font-size: 0.875rem; - line-height: 1.25rem; - padding-top: 1rem; - padding-bottom: 0.5rem; - padding-left: 0.625rem; - padding-right: 0.625rem; - border: 0; - border-bottom-width: 2px; - border-style: solid; - border-color: #e5e7eb; - } - - & p:first-child { - font-weight: 300; - font-size: 1.25rem; - line-height: 1.75rem; - margin-bottom: 2px; - } - - & p:last-child { - color: rgb(42 72 101); - font-weight: 600; - font-size: 0.875rem; - line-height: 1.25rem; - } + background-color: white; + border-radius: 3px; + border-bottom: 1px solid rgb(27 58 87); + + & summary { + font-weight: bold; + cursor: pointer; + display: flex; + align-items: center; + } + + & form { + display: flex; + flex-direction: column; + padding: 20px; + padding-bottom: 0; + } + + & label { + padding: 10px 0; + display: flex; + flex-direction: column; + flex-grow: 1; + + color: rgb(75 85 99); + font-size: 0.75rem; + line-height: 1rem; + } + + & img { + height: 4rem; + max-width: 100%; + } + + & form div { + display: flex; + gap: 10px; + } + + & select { + color: rgb(17 24 39); + font-size: 0.875rem; + line-height: 1.25rem; + padding-top: 1rem; + padding-bottom: 0.5rem; + padding-left: 0.625rem; + padding-right: 0.625rem; + border: 0; + border-bottom-width: 2px; + border-style: solid; + border-color: #e5e7eb; + } + + & p:first-child { + font-weight: 300; + font-size: 1.25rem; + line-height: 1.75rem; + margin-bottom: 2px; + } + + & p:last-child { + color: rgb(42 72 101); + font-weight: 600; + font-size: 0.875rem; + line-height: 1.25rem; + } } .filter { - margin: 0 auto; + margin: 0 auto; } .tags { - display: flex; - flex-wrap: wrap; - gap: 10px; - margin: 30px 0px; - - & span { - font-weight: 500; - line-height: 1.25rem; - padding-top: 0.25rem; - padding-bottom: 0.25rem; - padding-left: 0.5rem; - padding-right: 0.5rem; - background-color: rgb(71 98 130); - border-radius: 9999px; - color: white; - font-size: 0.95rem; - } - - & button { - cursor: pointer; - margin-left: 5px; - padding: 2px 10px; - color: white; - background-color: transparent; - outline: none; - border: none; - font-size: 0.8rem; - } + display: flex; + flex-wrap: wrap; + gap: 10px; + margin: 30px 0px; + + & span { + font-weight: 500; + line-height: 1.25rem; + padding-top: 0.25rem; + padding-bottom: 0.25rem; + padding-left: 0.5rem; + padding-right: 0.5rem; + background-color: rgb(71 98 130); + border-radius: 9999px; + color: white; + font-size: 0.95rem; + } + + & button { + cursor: pointer; + margin-left: 5px; + padding: 2px 10px; + color: white; + background-color: transparent; + outline: none; + border: none; + font-size: 0.8rem; + } } diff --git a/nextjs-start/src/components/Filters.jsx b/nextjs-start/src/components/Filters.jsx index 4e547fa0..515e7961 100644 --- a/nextjs-start/src/components/Filters.jsx +++ b/nextjs-start/src/components/Filters.jsx @@ -3,163 +3,159 @@ import Tag from "@/src/components/Tag.jsx"; function FilterSelect({ label, options, value, onChange, name, icon }) { - return ( -
            - {label} - -
            - ); + return ( +
            + {label} + +
            + ); } export default function Filters({ filters, setFilters }) { - const handleSelectionChange = (event, name) => { - setFilters(prevFilters => ({ - ...prevFilters, - [name]: event.target.value, - })); - }; + const handleSelectionChange = (event, name) => { + setFilters((prevFilters) => ({ + ...prevFilters, + [name]: event.target.value, + })); + }; - const updateField = (type, value) => { - setFilters({ ...filters, [type]: value }); - }; + const updateField = (type, value) => { + setFilters({ ...filters, [type]: value }); + }; - return ( -
            -
            - - filter -
            -

            Restaurants

            -

            Sorted by {filters.sort || "Rating"}

            -
            -
            + return ( +
            +
            + + filter +
            +

            Restaurants

            +

            Sorted by {filters.sort || "Rating"}

            +
            +
            -
            { - event.preventDefault(); - event.target.parentNode.removeAttribute("open"); - }} - > - - handleSelectionChange(event, "category") - } - name="category" - icon="/food.svg" - /> + { + event.preventDefault(); + event.target.parentNode.removeAttribute("open"); + }} + > + handleSelectionChange(event, "category")} + name="category" + icon="/food.svg" + /> - handleSelectionChange(event, "city")} - name="city" - icon="/location.svg" - /> + handleSelectionChange(event, "city")} + name="city" + icon="/location.svg" + /> - - handleSelectionChange(event, "price") - } - name="price" - icon="/price.svg" - /> + handleSelectionChange(event, "price")} + name="price" + icon="/price.svg" + /> - handleSelectionChange(event, "sort")} - name="sort" - icon="/sortBy.svg" - /> + handleSelectionChange(event, "sort")} + name="sort" + icon="/sortBy.svg" + /> -
            - - - - -
            - -
            +
            + + + + +
            + +
            -
            - {Object.entries(filters).map(([type, value]) => { - // The main filter bar already specifies what - // sorting is being used. So skip showing the - // sorting as a 'tag' - if (type == "sort" || value == "") { - return null; - } - return ( - - ); - })} -
            -
            - ); +
            + {Object.entries(filters).map(([type, value]) => { + // The main filter bar already specifies what + // sorting is being used. So skip showing the + // sorting as a 'tag' + if (type == "sort" || value == "") { + return null; + } + return ( + + ); + })} +
            + + ); } diff --git a/nextjs-start/src/components/Header.jsx b/nextjs-start/src/components/Header.jsx index 3f9a7e20..92af84db 100644 --- a/nextjs-start/src/components/Header.jsx +++ b/nextjs-start/src/components/Header.jsx @@ -1,76 +1,78 @@ -'use client'; +"use client"; import React, { useState, useEffect } from "react"; import Link from "next/link"; import { - signInWithGoogle, - signOut, - onAuthStateChanged, + signInWithGoogle, + signOut, + onAuthStateChanged, } from "@/src/lib/firebase/auth.js"; import { addFakeRestaurantsAndReviews } from "@/src/lib/firebase/firestore.js"; import { useRouter } from "next/navigation"; import { firebaseConfig } from "@/src/lib/firebase/config"; function useUserSession(initialUser) { - return; + return; } -export default function Header({initialUser}) { +export default function Header({ initialUser }) { + const user = useUserSession(initialUser); - const user = useUserSession(initialUser) ; + const handleSignOut = (event) => { + event.preventDefault(); + signOut(); + }; - const handleSignOut = event => { - event.preventDefault(); - signOut(); - }; + const handleSignIn = (event) => { + event.preventDefault(); + signInWithGoogle(); + }; - const handleSignIn = event => { - event.preventDefault(); - signInWithGoogle(); - }; + return ( +
            + + FriendlyEats + Friendly Eats + + {user ? ( + <> +
            +

            + {user.email} + {user.displayName} +

            - return ( -
            - - FriendlyEats - Friendly Eats - - {user ? ( - <> -
            -

            - {user.email} - {user.displayName} -

            +
            + ... +
            - - ) : ( - - )} -
            - ); +
          • + + Sign Out + +
          • +
          +
    +
    + + ) : ( + + )} + + ); } diff --git a/nextjs-start/src/components/RatingPicker.jsx b/nextjs-start/src/components/RatingPicker.jsx index 1b2ce114..19183bf6 100644 --- a/nextjs-start/src/components/RatingPicker.jsx +++ b/nextjs-start/src/components/RatingPicker.jsx @@ -3,64 +3,64 @@ import React from "react"; // A HTML and CSS only rating picker thanks to: https://codepen.io/chris22smith/pen/MJzLJN const RatingPicker = () => { - return ( -

    - - + return ( +

    + + - - + + - - + + - - + + - - -

    - ); + + +

    + ); }; export default RatingPicker; diff --git a/nextjs-start/src/components/Restaurant.jsx b/nextjs-start/src/components/Restaurant.jsx index 9f584299..cd34dca5 100644 --- a/nextjs-start/src/components/Restaurant.jsx +++ b/nextjs-start/src/components/Restaurant.jsx @@ -4,21 +4,19 @@ // It receives data from src/app/restaurant/[id]/page.jsx import { React, useState, useEffect, Suspense } from "react"; -import dynamic from 'next/dynamic'; -import { - getRestaurantSnapshotById, -} from "@/src/lib/firebase/firestore.js"; -import {useUser} from '@/src/lib/getUser'; +import dynamic from "next/dynamic"; +import { getRestaurantSnapshotById } from "@/src/lib/firebase/firestore.js"; +import { useUser } from "@/src/lib/getUser"; import RestaurantDetails from "@/src/components/RestaurantDetails.jsx"; import { updateRestaurantImage } from "@/src/lib/firebase/storage.js"; -const ReviewDialog = dynamic(() => import('@/src/components/ReviewDialog.jsx')); +const ReviewDialog = dynamic(() => import("@/src/components/ReviewDialog.jsx")); export default function Restaurant({ id, initialRestaurant, initialUserId, - children + children, }) { const [restaurantDetails, setRestaurantDetails] = useState(initialRestaurant); const [isOpen, setIsOpen] = useState(false); @@ -63,15 +61,21 @@ export default function Restaurant({ handleRestaurantImage={handleRestaurantImage} setIsOpen={setIsOpen} isOpen={isOpen} - >{children} - {userId && Loading...

    }>
    } + > + {children} + + {userId && ( + Loading...

    }> + +
    + )} ); } diff --git a/nextjs-start/src/components/RestaurantDetails.jsx b/nextjs-start/src/components/RestaurantDetails.jsx index 7dadf5ce..c3db5ef9 100644 --- a/nextjs-start/src/components/RestaurantDetails.jsx +++ b/nextjs-start/src/components/RestaurantDetails.jsx @@ -4,63 +4,63 @@ import React from "react"; import renderStars from "@/src/components/Stars.jsx"; const RestaurantDetails = ({ - restaurant, - userId, - handleRestaurantImage, - setIsOpen, - isOpen, - children + restaurant, + userId, + handleRestaurantImage, + setIsOpen, + isOpen, + children, }) => { - return ( -
    - {restaurant.name} + return ( +
    + {restaurant.name} -
    - {userId && ( - review { - setIsOpen(!isOpen); - }} - src="/review.svg" - /> - )} - +
    -
    -
    -

    {restaurant.name}

    +
    +
    +

    {restaurant.name}

    -
    -
      {renderStars(restaurant.avgRating)}
    +
    +
      {renderStars(restaurant.avgRating)}
    - ({restaurant.numRatings}) -
    + ({restaurant.numRatings}) +
    -

    - {restaurant.category} | {restaurant.city} -

    -

    {"$".repeat(restaurant.price)}

    - {children} -
    -
    -
    - ); +

    + {restaurant.category} | {restaurant.city} +

    +

    {"$".repeat(restaurant.price)}

    + {children} + + +
    + ); }; export default RestaurantDetails; diff --git a/nextjs-start/src/components/RestaurantListings.jsx b/nextjs-start/src/components/RestaurantListings.jsx index e173e6be..d17f1ec7 100644 --- a/nextjs-start/src/components/RestaurantListings.jsx +++ b/nextjs-start/src/components/RestaurantListings.jsx @@ -11,101 +11,98 @@ import { getRestaurantsSnapshot } from "@/src/lib/firebase/firestore.js"; import Filters from "@/src/components/Filters.jsx"; const RestaurantItem = ({ restaurant }) => ( -
  • - - - -
  • +
  • + + + +
  • ); const ActiveResturant = ({ restaurant }) => ( -
    - - -
    +
    + + +
    ); const ImageCover = ({ photo, name }) => ( -
    - {name} -
    +
    + {name} +
    ); const ResturantDetails = ({ restaurant }) => ( -
    -

    {restaurant.name}

    - - -
    +
    +

    {restaurant.name}

    + + +
    ); const RestaurantRating = ({ restaurant }) => ( -
    -
      {renderStars(restaurant.avgRating)}
    - ({restaurant.numRatings}) -
    +
    +
      {renderStars(restaurant.avgRating)}
    + ({restaurant.numRatings}) +
    ); const RestaurantMetadata = ({ restaurant }) => ( -
    -

    - {restaurant.category} | {restaurant.city} -

    -

    {"$".repeat(restaurant.price)}

    -
    +
    +

    + {restaurant.category} | {restaurant.city} +

    +

    {"$".repeat(restaurant.price)}

    +
    ); export default function RestaurantListings({ - initialRestaurants, - searchParams, + initialRestaurants, + searchParams, }) { - const router = useRouter(); - - // The initial filters are the search params from the URL, useful for when the user refreshes the page - const initialFilters = { - city: searchParams.city || "", - category: searchParams.category || "", - price: searchParams.price || "", - sort: searchParams.sort || "", - }; - - const [restaurants, setRestaurants] = useState(initialRestaurants); - const [filters, setFilters] = useState(initialFilters); - - useEffect(() => { - routerWithFilters(router, filters); - }, [router, filters]); - - useEffect(() => { - return getRestaurantsSnapshot(data => { - setRestaurants(data); - }, filters); - }, [filters]); - - return ( -
    - -
      - {restaurants.map(restaurant => ( - - ))} -
    -
    - ); + const router = useRouter(); + + // The initial filters are the search params from the URL, useful for when the user refreshes the page + const initialFilters = { + city: searchParams.city || "", + category: searchParams.category || "", + price: searchParams.price || "", + sort: searchParams.sort || "", + }; + + const [restaurants, setRestaurants] = useState(initialRestaurants); + const [filters, setFilters] = useState(initialFilters); + + useEffect(() => { + routerWithFilters(router, filters); + }, [router, filters]); + + useEffect(() => { + return getRestaurantsSnapshot((data) => { + setRestaurants(data); + }, filters); + }, [filters]); + + return ( +
    + +
      + {restaurants.map((restaurant) => ( + + ))} +
    +
    + ); } function routerWithFilters(router, filters) { - const queryParams = new URLSearchParams(); + const queryParams = new URLSearchParams(); - for (const [key, value] of Object.entries(filters)) { - if (value !== undefined && value !== "") { - queryParams.append(key, value); - } - } + for (const [key, value] of Object.entries(filters)) { + if (value !== undefined && value !== "") { + queryParams.append(key, value); + } + } - const queryString = queryParams.toString(); - router.push(`?${queryString}`); + const queryString = queryParams.toString(); + router.push(`?${queryString}`); } diff --git a/nextjs-start/src/components/ReviewDialog.jsx b/nextjs-start/src/components/ReviewDialog.jsx index 113c43c3..114c904b 100644 --- a/nextjs-start/src/components/ReviewDialog.jsx +++ b/nextjs-start/src/components/ReviewDialog.jsx @@ -1,89 +1,84 @@ -'use client'; +"use client"; // This components handles the review dialog and uses a next.js feature known as Server Actions to handle the form submission -import {useEffect, useLayoutEffect, useRef} from "react"; +import { useEffect, useLayoutEffect, useRef } from "react"; import RatingPicker from "@/src/components/RatingPicker.jsx"; import { handleReviewFormSubmission } from "@/src/app/actions.js"; const ReviewDialog = ({ - isOpen, - handleClose, - review, - onChange, - userId, - id, + isOpen, + handleClose, + review, + onChange, + userId, + id, }) => { - const dialog = useRef(); + const dialog = useRef(); - // dialogs only render their backdrop when called with `showModal` - useLayoutEffect(() => { - if (isOpen) { - dialog.current.showModal(); - } else { - dialog.current.close(); - } - - }, [isOpen, dialog]); + // dialogs only render their backdrop when called with `showModal` + useLayoutEffect(() => { + if (isOpen) { + dialog.current.showModal(); + } else { + dialog.current.close(); + } + }, [isOpen, dialog]); - const handleClick = (e) => { - // close if clicked outside the modal - if (e.target === dialog.current) { - handleClose(); - } - }; + const handleClick = (e) => { + // close if clicked outside the modal + if (e.target === dialog.current) { + handleClose(); + } + }; - return ( - -
    { - handleClose(); - }} - > -
    -

    Add your review

    -
    -
    - + return ( + + { + handleClose(); + }} + > +
    +

    Add your review

    +
    +
    + -

    - onChange(e.target.value, "text")} - /> -

    +

    + onChange(e.target.value, "text")} + /> +

    - - -
    -
    - - - - -
    - -
    - ); + + +
    +
    + + + + +
    + +
    + ); }; export default ReviewDialog; diff --git a/nextjs-start/src/components/Reviews/Review.jsx b/nextjs-start/src/components/Reviews/Review.jsx index d52294b2..2362ac05 100644 --- a/nextjs-start/src/components/Reviews/Review.jsx +++ b/nextjs-start/src/components/Reviews/Review.jsx @@ -1,23 +1,40 @@ import renderStars from "@/src/components/Stars.jsx"; - export function Review({ rating, text, timestamp }) { - return (
  • -
      {renderStars(rating)}
    -

    {text}

    + return ( +
  • +
      {renderStars(rating)}
    +

    {text}

    - -
  • ); + + + ); } export function ReviewSkeleton() { - return (
  • -
    -
    -

    {' '}

    -
  • ); + return ( +
  • +
    +
    +
    +
    +

    {" "}

    +
  • + ); } diff --git a/nextjs-start/src/components/Reviews/ReviewsList.jsx b/nextjs-start/src/components/Reviews/ReviewsList.jsx index 939e5b97..d4b4ec61 100644 --- a/nextjs-start/src/components/Reviews/ReviewsList.jsx +++ b/nextjs-start/src/components/Reviews/ReviewsList.jsx @@ -8,9 +8,12 @@ import { getFirestore } from "firebase/firestore"; import { getAuthenticatedAppForUser } from "@/src/lib/firebase/serverApp"; export default async function ReviewsList({ restaurantId, userId }) { - const {firebaseServerApp} = await getAuthenticatedAppForUser(); - const reviews = await getReviewsByRestaurantId(getFirestore(firebaseServerApp), restaurantId); - + const { firebaseServerApp } = await getAuthenticatedAppForUser(); + const reviews = await getReviewsByRestaurantId( + getFirestore(firebaseServerApp), + restaurantId, + ); + return ( { - return getReviewsSnapshotByRestaurantId( - restaurantId, - (data) => { - setReviews(data); - } - ); + return getReviewsSnapshotByRestaurantId(restaurantId, (data) => { + setReviews(data); + }); }, [restaurantId]); return (
    diff --git a/nextjs-start/src/components/Stars.jsx b/nextjs-start/src/components/Stars.jsx index 2ddc2e0a..daa91ac0 100644 --- a/nextjs-start/src/components/Stars.jsx +++ b/nextjs-start/src/components/Stars.jsx @@ -1,45 +1,45 @@ // This component displays star ratings export default function renderStars(avgRating) { - const arr = []; - for (let i = 0; i < 5; i++) { - if (i < Math.floor(avgRating)) { - arr.push( -
  • - - - -
  • - ); - } else { - arr.push( -
  • - - - -
  • - ); - } - } - return arr; + const arr = []; + for (let i = 0; i < 5; i++) { + if (i < Math.floor(avgRating)) { + arr.push( +
  • + + + +
  • , + ); + } else { + arr.push( +
  • + + + +
  • , + ); + } + } + return arr; } diff --git a/nextjs-start/src/components/Tag.jsx b/nextjs-start/src/components/Tag.jsx index 986bc24c..7002c17d 100644 --- a/nextjs-start/src/components/Tag.jsx +++ b/nextjs-start/src/components/Tag.jsx @@ -3,16 +3,16 @@ // On click, the tag is removed and the filter is reset export default function Tag({ type, value, updateField }) { - return ( - - {value} - - - ); + return ( + + {value} + + + ); } diff --git a/nextjs-start/src/lib/fakeRestaurants.js b/nextjs-start/src/lib/fakeRestaurants.js index 621516fd..f797449b 100644 --- a/nextjs-start/src/lib/fakeRestaurants.js +++ b/nextjs-start/src/lib/fakeRestaurants.js @@ -1,88 +1,78 @@ import { - randomNumberBetween, - getRandomDateAfter, - getRandomDateBefore, + randomNumberBetween, + getRandomDateAfter, + getRandomDateBefore, } from "@/src/lib/utils.js"; import { randomData } from "@/src/lib/randomData.js"; import { Timestamp } from "firebase/firestore"; export async function generateFakeRestaurantsAndReviews() { - const restaurantsToAdd = 5; - const data = []; + const restaurantsToAdd = 5; + const data = []; - for (let i = 0; i < restaurantsToAdd; i++) { - const restaurantTimestamp = Timestamp.fromDate(getRandomDateBefore()); + for (let i = 0; i < restaurantsToAdd; i++) { + const restaurantTimestamp = Timestamp.fromDate(getRandomDateBefore()); - const ratingsData = []; + const ratingsData = []; - // Generate a random number of ratings/reviews for this restaurant - for (let j = 0; j < randomNumberBetween(0, 5); j++) { - const ratingTimestamp = Timestamp.fromDate( - getRandomDateAfter(restaurantTimestamp.toDate()) - ); + // Generate a random number of ratings/reviews for this restaurant + for (let j = 0; j < randomNumberBetween(0, 5); j++) { + const ratingTimestamp = Timestamp.fromDate( + getRandomDateAfter(restaurantTimestamp.toDate()), + ); - const ratingData = { - rating: randomData.restaurantReviews[ - randomNumberBetween( - 0, - randomData.restaurantReviews.length - 1 - ) - ].rating, - text: randomData.restaurantReviews[ - randomNumberBetween( - 0, - randomData.restaurantReviews.length - 1 - ) - ].text, - userId: `User #${randomNumberBetween()}`, - timestamp: ratingTimestamp, - }; + const ratingData = { + rating: + randomData.restaurantReviews[ + randomNumberBetween(0, randomData.restaurantReviews.length - 1) + ].rating, + text: randomData.restaurantReviews[ + randomNumberBetween(0, randomData.restaurantReviews.length - 1) + ].text, + userId: `User #${randomNumberBetween()}`, + timestamp: ratingTimestamp, + }; - ratingsData.push(ratingData); - } + ratingsData.push(ratingData); + } - const avgRating = ratingsData.length - ? ratingsData.reduce( - (accumulator, currentValue) => - accumulator + currentValue.rating, - 0 - ) / ratingsData.length - : 0; + const avgRating = ratingsData.length + ? ratingsData.reduce( + (accumulator, currentValue) => accumulator + currentValue.rating, + 0, + ) / ratingsData.length + : 0; - const restaurantData = { - category: - randomData.restaurantCategories[ - randomNumberBetween( - 0, - randomData.restaurantCategories.length - 1 - ) - ], - name: randomData.restaurantNames[ - randomNumberBetween(0, randomData.restaurantNames.length - 1) - ], - avgRating, - city: randomData.restaurantCities[ - randomNumberBetween(0, randomData.restaurantCities.length - 1) - ], - numRatings: ratingsData.length, - sumRating: ratingsData.reduce( - (accumulator, currentValue) => - accumulator + currentValue.rating, - 0 - ), - price: randomNumberBetween(1, 4), - photo: `https://storage.googleapis.com/firestorequickstarts.appspot.com/food_${randomNumberBetween( - 1, - 22 - )}.png`, - timestamp: restaurantTimestamp, - }; + const restaurantData = { + category: + randomData.restaurantCategories[ + randomNumberBetween(0, randomData.restaurantCategories.length - 1) + ], + name: randomData.restaurantNames[ + randomNumberBetween(0, randomData.restaurantNames.length - 1) + ], + avgRating, + city: randomData.restaurantCities[ + randomNumberBetween(0, randomData.restaurantCities.length - 1) + ], + numRatings: ratingsData.length, + sumRating: ratingsData.reduce( + (accumulator, currentValue) => accumulator + currentValue.rating, + 0, + ), + price: randomNumberBetween(1, 4), + photo: `https://storage.googleapis.com/firestorequickstarts.appspot.com/food_${randomNumberBetween( + 1, + 22, + )}.png`, + timestamp: restaurantTimestamp, + }; - data.push({ - restaurantData, - ratingsData, - }); - } - return data; + data.push({ + restaurantData, + ratingsData, + }); + } + return data; } diff --git a/nextjs-start/src/lib/firebase/clientApp.js b/nextjs-start/src/lib/firebase/clientApp.js index d05ec760..d639898a 100644 --- a/nextjs-start/src/lib/firebase/clientApp.js +++ b/nextjs-start/src/lib/firebase/clientApp.js @@ -1,4 +1,4 @@ -'use client'; +"use client"; import { initializeApp } from "firebase/app"; import { firebaseConfig } from "./config"; diff --git a/nextjs-start/src/lib/firebase/firestore.js b/nextjs-start/src/lib/firebase/firestore.js index d8941f4c..23019c39 100644 --- a/nextjs-start/src/lib/firebase/firestore.js +++ b/nextjs-start/src/lib/firebase/firestore.js @@ -1,138 +1,138 @@ import { generateFakeRestaurantsAndReviews } from "@/src/lib/fakeRestaurants.js"; import { - collection, - onSnapshot, - query, - getDocs, - doc, - getDoc, - updateDoc, - orderBy, - Timestamp, - runTransaction, - where, - addDoc, - getFirestore, + collection, + onSnapshot, + query, + getDocs, + doc, + getDoc, + updateDoc, + orderBy, + Timestamp, + runTransaction, + where, + addDoc, + getFirestore, } from "firebase/firestore"; import { db } from "@/src/lib/firebase/clientApp"; export async function updateRestaurantImageReference( - restaurantId, - publicImageUrl + restaurantId, + publicImageUrl, ) { - const restaurantRef = doc(collection(db, "restaurants"), restaurantId); - if (restaurantRef) { - await updateDoc(restaurantRef, { photo: publicImageUrl }); - } + const restaurantRef = doc(collection(db, "restaurants"), restaurantId); + if (restaurantRef) { + await updateDoc(restaurantRef, { photo: publicImageUrl }); + } } const updateWithRating = async ( - transaction, - docRef, - newRatingDocument, - review + transaction, + docRef, + newRatingDocument, + review, ) => { - return; + return; }; export async function addReviewToRestaurant(db, restaurantId, review) { - return; + return; } function applyQueryFilters(q, { category, city, price, sort }) { - return; + return; } export async function getRestaurants(db = db, filters = {}) { - return []; + return []; } export function getRestaurantsSnapshot(cb, filters = {}) { - return; + return; } export async function getRestaurantById(db, restaurantId) { - if (!restaurantId) { - console.log("Error: Invalid ID received: ", restaurantId); - return; - } - const docRef = doc(db, "restaurants", restaurantId); - const docSnap = await getDoc(docRef); - return { - ...docSnap.data(), - timestamp: docSnap.data().timestamp.toDate(), - }; + if (!restaurantId) { + console.log("Error: Invalid ID received: ", restaurantId); + return; + } + const docRef = doc(db, "restaurants", restaurantId); + const docSnap = await getDoc(docRef); + return { + ...docSnap.data(), + timestamp: docSnap.data().timestamp.toDate(), + }; } export function getRestaurantSnapshotById(restaurantId, cb) { - return; + return; } export async function getReviewsByRestaurantId(db, restaurantId) { - if (!restaurantId) { - console.log("Error: Invalid restaurantId received: ", restaurantId); - return; - } - - const q = query( - collection(db, "restaurants", restaurantId, "ratings"), - orderBy("timestamp", "desc") - ); - - const results = await getDocs(q); - return results.docs.map(doc => { - return { - id: doc.id, - ...doc.data(), - // Only plain objects can be passed to Client Components from Server Components - timestamp: doc.data().timestamp.toDate(), - }; - }); + if (!restaurantId) { + console.log("Error: Invalid restaurantId received: ", restaurantId); + return; + } + + const q = query( + collection(db, "restaurants", restaurantId, "ratings"), + orderBy("timestamp", "desc"), + ); + + const results = await getDocs(q); + return results.docs.map((doc) => { + return { + id: doc.id, + ...doc.data(), + // Only plain objects can be passed to Client Components from Server Components + timestamp: doc.data().timestamp.toDate(), + }; + }); } export function getReviewsSnapshotByRestaurantId(restaurantId, cb) { - if (!restaurantId) { - console.log("Error: Invalid restaurantId received: ", restaurantId); - return; - } - - const q = query( - collection(db, "restaurants", restaurantId, "ratings"), - orderBy("timestamp", "desc") - ); - return onSnapshot(q, querySnapshot => { - const results = querySnapshot.docs.map(doc => { - return { - id: doc.id, - ...doc.data(), - // Only plain objects can be passed to Client Components from Server Components - timestamp: doc.data().timestamp.toDate(), - }; - }); - cb(results); - }); + if (!restaurantId) { + console.log("Error: Invalid restaurantId received: ", restaurantId); + return; + } + + const q = query( + collection(db, "restaurants", restaurantId, "ratings"), + orderBy("timestamp", "desc"), + ); + return onSnapshot(q, (querySnapshot) => { + const results = querySnapshot.docs.map((doc) => { + return { + id: doc.id, + ...doc.data(), + // Only plain objects can be passed to Client Components from Server Components + timestamp: doc.data().timestamp.toDate(), + }; + }); + cb(results); + }); } export async function addFakeRestaurantsAndReviews() { - const data = await generateFakeRestaurantsAndReviews(); - for (const { restaurantData, ratingsData } of data) { - try { - const docRef = await addDoc( - collection(db, "restaurants"), - restaurantData - ); - - for (const ratingData of ratingsData) { - await addDoc( - collection(db, "restaurants", docRef.id, "ratings"), - ratingData - ); - } - } catch (e) { - console.log("There was an error adding the document"); - console.error("Error adding document: ", e); - } - } + const data = await generateFakeRestaurantsAndReviews(); + for (const { restaurantData, ratingsData } of data) { + try { + const docRef = await addDoc( + collection(db, "restaurants"), + restaurantData, + ); + + for (const ratingData of ratingsData) { + await addDoc( + collection(db, "restaurants", docRef.id, "ratings"), + ratingData, + ); + } + } catch (e) { + console.log("There was an error adding the document"); + console.error("Error adding document: ", e); + } + } } diff --git a/nextjs-start/src/lib/firebase/serverApp.js b/nextjs-start/src/lib/firebase/serverApp.js index 6a66b85c..a43cf865 100644 --- a/nextjs-start/src/lib/firebase/serverApp.js +++ b/nextjs-start/src/lib/firebase/serverApp.js @@ -11,5 +11,5 @@ import { getAuth } from "firebase/auth"; // Returns an authenticated client SDK instance for use in Server Side Rendering // and Static Site Generation export async function getAuthenticatedAppForUser() { - throw new Error('not implemented'); -} \ No newline at end of file + throw new Error("not implemented"); +} diff --git a/nextjs-start/src/lib/randomData.js b/nextjs-start/src/lib/randomData.js index 41045dc8..48afd631 100644 --- a/nextjs-start/src/lib/randomData.js +++ b/nextjs-start/src/lib/randomData.js @@ -3,146 +3,146 @@ // menu in the top right corner and select "Add random data" export const randomData = { - restaurantNames: [ - "Savory Bites", - "Gourmet Delight", - "Wholesome Kitchen", - "Cheesy Cravings", - "Spice Fusion", - "Burger Bonanza", - "Pasta Paradise", - "Taco Tango", - "Sushi Sensation", - "Pizza Pizzazz", - "Cafe Mocha", - "Mouthwatering BBQ", - "Thai Temptations", - "Sizzling Steaks", - "Veggie Heaven", - "Seafood Symphony", - "Wok & Roll", - "French Flavors", - "Tandoori Nights", - "Mediterranean Magic", - "Enchilada Express", - "Ramen Ramble", - "Deli Delights", - "Crepes & Co.", - "Hot Pot Haven", - "Tasty Tandoor", - "Bistro Bliss", - "Curry Corner", - "Pancake Paradise", - "Pita Panache", - "Biryani Bliss", - "Garden Grub", - "Dim Sum Delish", - "Cajun Craze", - "Fondue Fantasy", - "Bagel Bar", - "Tapas Talk", - "Pho Fusion", - "Brunch Bunch", - "Steaming Samosas", - "Falafel Frenzy", - ], - restaurantCities: [ - "New York", - "Los Angeles", - "London", - "Paris", - "Tokyo", - "Mumbai", - "Dubai", - "Amsterdam", - "Seoul", - "Singapore", - "Istanbul", - ], - restaurantCategories: [ - "Italian", - "Chinese", - "Japanese", - "Mexican", - "Indian", - "Mediterranean", - "Caribbean", - "Cajun", - "German", - "Russian", - "Cuban", - "Organic", - "Tapas", - ], - restaurantReviews: [ - { text: "The food was exceptional, absolutely loved it!", rating: 5 }, - { text: "Delicious dishes and excellent service!", rating: 5 }, - { text: "The flavors were so rich and satisfying.", rating: 5 }, - { text: "Great ambiance and friendly staff.", rating: 5 }, - { text: "A delightful culinary experience!", rating: 5 }, - { - text: "Mouthwatering dishes that left me wanting more.", - rating: 5, - }, - { text: "Perfectly cooked and seasoned meals.", rating: 5 }, - { text: "Incredible presentation and top-notch taste.", rating: 5 }, - { text: "Good food and cozy atmosphere.", rating: 4 }, - { text: "Enjoyed the meal, would come back again.", rating: 4 }, - { text: "Tasty options and reasonable prices.", rating: 4 }, - { text: "Friendly staff, but the food was just okay.", rating: 3 }, - { text: "Decent experience, but nothing extraordinary.", rating: 3 }, - { text: "The food was average, could be better.", rating: 3 }, - { - text: "Service was slow, and the dishes were disappointing.", - rating: 2, - }, - { text: "Expected more, but left unsatisfied.", rating: 2 }, - { text: "Underwhelming taste and presentation.", rating: 2 }, - { text: "Disappointing experience overall.", rating: 2 }, - { - text: "The food was terrible, will never go back again.", - rating: 1, - }, - { - text: "Worst restaurant experience I've had so far.", - rating: 1, - }, - { text: "Avoid this place, the food was inedible.", rating: 1 }, - { text: "Unpleasant service and tasteless dishes.", rating: 1 }, - { text: "Simply outstanding! A culinary masterpiece.", rating: 5 }, - { text: "Couldn't get enough of the amazing flavors.", rating: 5 }, - { text: "Top-notch quality, worth every penny.", rating: 5 }, - { text: "Highly recommended for food enthusiasts.", rating: 5 }, - { text: "Exquisite dishes that pleased my taste buds.", rating: 5 }, - { text: "A gem of a place, impeccable in every aspect.", rating: 5 }, - { text: "Excellent selection of dishes on the menu.", rating: 5 }, - { text: "A fantastic dining experience overall.", rating: 5 }, - { - text: "The atmosphere was lovely, perfect for a date night.", - rating: 4, - }, - { text: "Good food, but the service could be improved.", rating: 4 }, - { - text: "Pleasantly surprised by the variety of flavors.", - rating: 4, - }, - { text: "Well-prepared dishes and friendly staff.", rating: 4 }, - { text: "Satisfying meals, suitable for a quick bite.", rating: 4 }, - { text: "The food was okay, nothing special.", rating: 3 }, - { - text: "Service could be better, but the taste was fine.", - rating: 3, - }, - { - text: "An average experience, didn't leave a lasting impression.", - rating: 3, - }, - { text: "Expected more value for the price.", rating: 2 }, - { text: "Mediocre taste and lackluster presentation.", rating: 2 }, - { - text: "Regret spending money on such a disappointing meal.", - rating: 2, - }, - { text: "Not up to par with my expectations.", rating: 2 }, - ], + restaurantNames: [ + "Savory Bites", + "Gourmet Delight", + "Wholesome Kitchen", + "Cheesy Cravings", + "Spice Fusion", + "Burger Bonanza", + "Pasta Paradise", + "Taco Tango", + "Sushi Sensation", + "Pizza Pizzazz", + "Cafe Mocha", + "Mouthwatering BBQ", + "Thai Temptations", + "Sizzling Steaks", + "Veggie Heaven", + "Seafood Symphony", + "Wok & Roll", + "French Flavors", + "Tandoori Nights", + "Mediterranean Magic", + "Enchilada Express", + "Ramen Ramble", + "Deli Delights", + "Crepes & Co.", + "Hot Pot Haven", + "Tasty Tandoor", + "Bistro Bliss", + "Curry Corner", + "Pancake Paradise", + "Pita Panache", + "Biryani Bliss", + "Garden Grub", + "Dim Sum Delish", + "Cajun Craze", + "Fondue Fantasy", + "Bagel Bar", + "Tapas Talk", + "Pho Fusion", + "Brunch Bunch", + "Steaming Samosas", + "Falafel Frenzy", + ], + restaurantCities: [ + "New York", + "Los Angeles", + "London", + "Paris", + "Tokyo", + "Mumbai", + "Dubai", + "Amsterdam", + "Seoul", + "Singapore", + "Istanbul", + ], + restaurantCategories: [ + "Italian", + "Chinese", + "Japanese", + "Mexican", + "Indian", + "Mediterranean", + "Caribbean", + "Cajun", + "German", + "Russian", + "Cuban", + "Organic", + "Tapas", + ], + restaurantReviews: [ + { text: "The food was exceptional, absolutely loved it!", rating: 5 }, + { text: "Delicious dishes and excellent service!", rating: 5 }, + { text: "The flavors were so rich and satisfying.", rating: 5 }, + { text: "Great ambiance and friendly staff.", rating: 5 }, + { text: "A delightful culinary experience!", rating: 5 }, + { + text: "Mouthwatering dishes that left me wanting more.", + rating: 5, + }, + { text: "Perfectly cooked and seasoned meals.", rating: 5 }, + { text: "Incredible presentation and top-notch taste.", rating: 5 }, + { text: "Good food and cozy atmosphere.", rating: 4 }, + { text: "Enjoyed the meal, would come back again.", rating: 4 }, + { text: "Tasty options and reasonable prices.", rating: 4 }, + { text: "Friendly staff, but the food was just okay.", rating: 3 }, + { text: "Decent experience, but nothing extraordinary.", rating: 3 }, + { text: "The food was average, could be better.", rating: 3 }, + { + text: "Service was slow, and the dishes were disappointing.", + rating: 2, + }, + { text: "Expected more, but left unsatisfied.", rating: 2 }, + { text: "Underwhelming taste and presentation.", rating: 2 }, + { text: "Disappointing experience overall.", rating: 2 }, + { + text: "The food was terrible, will never go back again.", + rating: 1, + }, + { + text: "Worst restaurant experience I've had so far.", + rating: 1, + }, + { text: "Avoid this place, the food was inedible.", rating: 1 }, + { text: "Unpleasant service and tasteless dishes.", rating: 1 }, + { text: "Simply outstanding! A culinary masterpiece.", rating: 5 }, + { text: "Couldn't get enough of the amazing flavors.", rating: 5 }, + { text: "Top-notch quality, worth every penny.", rating: 5 }, + { text: "Highly recommended for food enthusiasts.", rating: 5 }, + { text: "Exquisite dishes that pleased my taste buds.", rating: 5 }, + { text: "A gem of a place, impeccable in every aspect.", rating: 5 }, + { text: "Excellent selection of dishes on the menu.", rating: 5 }, + { text: "A fantastic dining experience overall.", rating: 5 }, + { + text: "The atmosphere was lovely, perfect for a date night.", + rating: 4, + }, + { text: "Good food, but the service could be improved.", rating: 4 }, + { + text: "Pleasantly surprised by the variety of flavors.", + rating: 4, + }, + { text: "Well-prepared dishes and friendly staff.", rating: 4 }, + { text: "Satisfying meals, suitable for a quick bite.", rating: 4 }, + { text: "The food was okay, nothing special.", rating: 3 }, + { + text: "Service could be better, but the taste was fine.", + rating: 3, + }, + { + text: "An average experience, didn't leave a lasting impression.", + rating: 3, + }, + { text: "Expected more value for the price.", rating: 2 }, + { text: "Mediocre taste and lackluster presentation.", rating: 2 }, + { + text: "Regret spending money on such a disappointing meal.", + rating: 2, + }, + { text: "Not up to par with my expectations.", rating: 2 }, + ], }; diff --git a/nextjs-start/src/lib/utils.js b/nextjs-start/src/lib/utils.js index 22abdc85..bdd5d27d 100644 --- a/nextjs-start/src/lib/utils.js +++ b/nextjs-start/src/lib/utils.js @@ -1,19 +1,19 @@ export function randomNumberBetween(min = 0, max = 1000) { - return Math.floor(Math.random() * (max - min + 1) + min); + return Math.floor(Math.random() * (max - min + 1) + min); } export function getRandomDateBefore(startingDate = new Date()) { - const randomNumberOfDays = randomNumberBetween(20, 80); - const randomDate = new Date( - startingDate - randomNumberOfDays * 24 * 60 * 60 * 1000 - ); - return randomDate; + const randomNumberOfDays = randomNumberBetween(20, 80); + const randomDate = new Date( + startingDate - randomNumberOfDays * 24 * 60 * 60 * 1000, + ); + return randomDate; } export function getRandomDateAfter(startingDate = new Date()) { - const randomNumberOfDays = randomNumberBetween(1, 19); - const randomDate = new Date( - startingDate.getTime() + randomNumberOfDays * 24 * 60 * 60 * 1000 - ); - return randomDate; + const randomNumberOfDays = randomNumberBetween(1, 19); + const randomDate = new Date( + startingDate.getTime() + randomNumberOfDays * 24 * 60 * 60 * 1000, + ); + return randomDate; } From 3cc2f6143b01acd3dc07c0d598e92f77cf63105e Mon Sep 17 00:00:00 2001 From: James Daniels Date: Tue, 10 Sep 2024 15:14:56 -0400 Subject: [PATCH 16/38] Double quotes less disruptive --- nextjs-end/.prettierrc.json | 5 +- nextjs-end/auth-service-worker.js | 34 +-- nextjs-end/src/app/actions.js | 16 +- nextjs-end/src/app/layout.js | 14 +- nextjs-end/src/app/page.js | 12 +- nextjs-end/src/app/restaurant/[id]/error.jsx | 4 +- nextjs-end/src/app/restaurant/[id]/page.jsx | 20 +- nextjs-end/src/app/styles.css | 14 +- nextjs-end/src/components/Filters.jsx | 122 +++++----- nextjs-end/src/components/Header.jsx | 46 ++-- nextjs-end/src/components/RatingPicker.jsx | 64 +++--- nextjs-end/src/components/Restaurant.jsx | 20 +- .../src/components/RestaurantDetails.jsx | 36 +-- .../src/components/RestaurantListings.jsx | 36 +-- nextjs-end/src/components/ReviewDialog.jsx | 28 +-- nextjs-end/src/components/Reviews/Review.jsx | 28 +-- .../src/components/Reviews/ReviewSummary.jsx | 18 +- .../src/components/Reviews/ReviewsList.jsx | 14 +- .../components/Reviews/ReviewsListClient.jsx | 14 +- nextjs-end/src/components/Stars.jsx | 32 +-- nextjs-end/src/components/Tag.jsx | 8 +- nextjs-end/src/lib/fakeRestaurants.js | 6 +- nextjs-end/src/lib/firebase/auth.js | 8 +- nextjs-end/src/lib/firebase/clientApp.js | 12 +- nextjs-end/src/lib/firebase/config.js | 2 +- nextjs-end/src/lib/firebase/firestore.js | 70 +++--- nextjs-end/src/lib/firebase/serverApp.js | 12 +- nextjs-end/src/lib/firebase/storage.js | 12 +- nextjs-end/src/lib/getUser.js | 8 +- nextjs-end/src/lib/randomData.js | 208 +++++++++--------- nextjs-start/.prettierrc.json | 5 + 31 files changed, 465 insertions(+), 463 deletions(-) create mode 100644 nextjs-start/.prettierrc.json diff --git a/nextjs-end/.prettierrc.json b/nextjs-end/.prettierrc.json index 48c545c5..be084604 100644 --- a/nextjs-end/.prettierrc.json +++ b/nextjs-end/.prettierrc.json @@ -1,8 +1,5 @@ { "trailingComma": "all", "semi": true, - "tabWidth": 2, - "singleQuote": true, - "jsxSingleQuote": true, - "plugins": [] + "tabWidth": 2 } diff --git a/nextjs-end/auth-service-worker.js b/nextjs-end/auth-service-worker.js index b24d82cc..c7945c70 100644 --- a/nextjs-end/auth-service-worker.js +++ b/nextjs-end/auth-service-worker.js @@ -1,13 +1,13 @@ -import { initializeApp } from 'firebase/app'; -import { getAuth, getIdToken, onAuthStateChanged } from 'firebase/auth'; +import { initializeApp } from "firebase/app"; +import { getAuth, getIdToken, onAuthStateChanged } from "firebase/auth"; // extract firebase config from query string const serializedFirebaseConfig = new URLSearchParams(self.location.search).get( - 'firebaseConfig', + "firebaseConfig", ); if (!serializedFirebaseConfig) { throw new Error( - 'Firebase Config object not found in service worker query string.', + "Firebase Config object not found in service worker query string.", ); } @@ -16,29 +16,29 @@ const firebaseConfig = JSON.parse(serializedFirebaseConfig); const app = initializeApp(firebaseConfig); const auth = getAuth(app); -self.addEventListener('install', () => { - console.log('Service worker installed with Firebase config', firebaseConfig); +self.addEventListener("install", () => { + console.log("Service worker installed with Firebase config", firebaseConfig); self.skipWaiting(); }); -self.addEventListener('activate', (event) => { +self.addEventListener("activate", (event) => { event.waitUntil(self.clients.claim()); }); -self.addEventListener('fetch', (event) => { +self.addEventListener("fetch", (event) => { const { origin, pathname } = new URL(event.request.url); if (origin !== self.location.origin) return; // Use a magic url to ensure that auth state is in sync between // the client and the sw, this helps with actions such as router.refresh(); - if (pathname.startsWith('/__/auth/wait/')) { - const uid = pathname.split('/').at(-1); + if (pathname.startsWith("/__/auth/wait/")) { + const uid = pathname.split("/").at(-1); event.respondWith(waitForMatchingUid(uid)); return; } - if (pathname.startsWith('/_next/')) return; + if (pathname.startsWith("/_next/")) return; // Don't add headers to non-get requests or those with an extension—this // helps with css, images, fonts, json, etc. - if (event.request.method === 'GET' && pathname.includes('.')) return; + if (event.request.method === "GET" && pathname.includes(".")) return; event.respondWith(fetchWithFirebaseHeaders(event.request)); }); @@ -46,20 +46,20 @@ async function fetchWithFirebaseHeaders(request) { const authIdToken = await getAuthIdToken(); if (authIdToken) { const headers = new Headers(request.headers); - headers.append('Authorization', `Bearer ${authIdToken}`); + headers.append("Authorization", `Bearer ${authIdToken}`); request = new Request(request, { headers }); } return await fetch(request).catch((reason) => { console.error(reason); - return new Response('Fail.', { + return new Response("Fail.", { status: 500, - headers: { 'content-type': 'text/html' }, + headers: { "content-type": "text/html" }, }); }); } async function waitForMatchingUid(_uid) { - const uid = _uid === 'undefined' ? undefined : _uid; + const uid = _uid === "undefined" ? undefined : _uid; await auth.authStateReady(); await new Promise((resolve) => { const unsubscribe = onAuthStateChanged(auth, (user) => { @@ -71,7 +71,7 @@ async function waitForMatchingUid(_uid) { }); return new Response(undefined, { status: 200, - headers: { 'cache-control': 'no-store' }, + headers: { "cache-control": "no-store" }, }); } diff --git a/nextjs-end/src/app/actions.js b/nextjs-end/src/app/actions.js index dee55aee..79cc8a61 100644 --- a/nextjs-end/src/app/actions.js +++ b/nextjs-end/src/app/actions.js @@ -1,8 +1,8 @@ -'use server'; +"use server"; -import { addReviewToRestaurant } from '@/src/lib/firebase/firestore.js'; -import { getAuthenticatedAppForUser } from '@/src/lib/firebase/serverApp.js'; -import { getFirestore } from 'firebase/firestore'; +import { addReviewToRestaurant } from "@/src/lib/firebase/firestore.js"; +import { getAuthenticatedAppForUser } from "@/src/lib/firebase/serverApp.js"; +import { getFirestore } from "firebase/firestore"; // This is a Server Action // https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions @@ -10,11 +10,11 @@ export async function handleReviewFormSubmission(data) { const { firebaseServerApp } = await getAuthenticatedAppForUser(); const db = getFirestore(firebaseServerApp); - await addReviewToRestaurant(db, data.get('restaurantId'), { - text: data.get('text'), - rating: data.get('rating'), + await addReviewToRestaurant(db, data.get("restaurantId"), { + text: data.get("text"), + rating: data.get("rating"), // This came from a hidden form field - userId: data.get('userId'), + userId: data.get("userId"), }); } diff --git a/nextjs-end/src/app/layout.js b/nextjs-end/src/app/layout.js index 6604e904..dad6ea92 100644 --- a/nextjs-end/src/app/layout.js +++ b/nextjs-end/src/app/layout.js @@ -1,20 +1,20 @@ -import '@/src/app/styles.css'; -import Header from '@/src/components/Header.jsx'; -import { getAuthenticatedAppForUser } from '@/src/lib/firebase/serverApp'; +import "@/src/app/styles.css"; +import Header from "@/src/components/Header.jsx"; +import { getAuthenticatedAppForUser } from "@/src/lib/firebase/serverApp"; // Force next.js to treat this route as server-side rendered // Without this line, during the build process, next.js will treat this route as static and build a static HTML file for it -export const dynamic = 'force-dynamic'; +export const dynamic = "force-dynamic"; export const metadata = { - title: 'FriendlyEats', + title: "FriendlyEats", description: - 'FriendlyEats is a restaurant review website built with Next.js and Firebase.', + "FriendlyEats is a restaurant review website built with Next.js and Firebase.", }; export default async function RootLayout({ children }) { const { currentUser } = await getAuthenticatedAppForUser(); return ( - +
    diff --git a/nextjs-end/src/app/page.js b/nextjs-end/src/app/page.js index f092e885..e07647f3 100644 --- a/nextjs-end/src/app/page.js +++ b/nextjs-end/src/app/page.js @@ -1,12 +1,12 @@ -import RestaurantListings from '@/src/components/RestaurantListings.jsx'; -import { getRestaurants } from '@/src/lib/firebase/firestore.js'; -import { getAuthenticatedAppForUser } from '@/src/lib/firebase/serverApp.js'; -import { getFirestore } from 'firebase/firestore'; +import RestaurantListings from "@/src/components/RestaurantListings.jsx"; +import { getRestaurants } from "@/src/lib/firebase/firestore.js"; +import { getAuthenticatedAppForUser } from "@/src/lib/firebase/serverApp.js"; +import { getFirestore } from "firebase/firestore"; // Force next.js to treat this route as server-side rendered // Without this line, during the build process, next.js will treat this route as static and build a static HTML file for it -export const dynamic = 'force-dynamic'; +export const dynamic = "force-dynamic"; // This line also forces this route to be server-side rendered // export const revalidate = 0; @@ -20,7 +20,7 @@ export default async function Home({ searchParams }) { searchParams, ); return ( -
    +
    { diff --git a/nextjs-end/src/app/restaurant/[id]/page.jsx b/nextjs-end/src/app/restaurant/[id]/page.jsx index 0ab46dfc..d8e16acf 100644 --- a/nextjs-end/src/app/restaurant/[id]/page.jsx +++ b/nextjs-end/src/app/restaurant/[id]/page.jsx @@ -1,18 +1,18 @@ -import Restaurant from '@/src/components/Restaurant.jsx'; -import { Suspense } from 'react'; -import { getRestaurantById } from '@/src/lib/firebase/firestore.js'; +import Restaurant from "@/src/components/Restaurant.jsx"; +import { Suspense } from "react"; +import { getRestaurantById } from "@/src/lib/firebase/firestore.js"; import { getAuthenticatedAppForUser, getAuthenticatedAppForUser as getUser, -} from '@/src/lib/firebase/serverApp.js'; +} from "@/src/lib/firebase/serverApp.js"; import ReviewsList, { ReviewsListSkeleton, -} from '@/src/components/Reviews/ReviewsList'; +} from "@/src/components/Reviews/ReviewsList"; import { GeminiSummary, GeminiSummarySkeleton, -} from '@/src/components/Reviews/ReviewSummary'; -import { getFirestore } from 'firebase/firestore'; +} from "@/src/components/Reviews/ReviewSummary"; +import { getFirestore } from "firebase/firestore"; export default async function Home({ params }) { const { currentUser } = await getUser(); @@ -23,11 +23,11 @@ export default async function Home({ params }) { ); return ( -
    +
    }> @@ -36,7 +36,7 @@ export default async function Home({ params }) { } > - +
    ); diff --git a/nextjs-end/src/app/styles.css b/nextjs-end/src/app/styles.css index d4036677..b9584b99 100644 --- a/nextjs-end/src/app/styles.css +++ b/nextjs-end/src/app/styles.css @@ -1,4 +1,4 @@ -@import url('https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,500;0,700;1,800&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap'); +@import url("https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,500;0,700;1,800&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"); * { box-sizing: border-box; @@ -8,7 +8,7 @@ body { font-family: - 'Roboto', + "Roboto", ui-sans-serif, system-ui, -apple-system; @@ -289,9 +289,9 @@ a { } .restaurant__review_summary { - max-width: '50vw'; - height: '75px'; - padding-top: '10px'; + max-width: "50vw"; + height: "75px"; + padding-top: "10px"; } .img__section { @@ -419,7 +419,7 @@ a { } .radio-label:before { - content: '★'; + content: "★"; display: inline-block; font-size: 32px; } @@ -454,7 +454,7 @@ a { .average-rating::before { --percent: calc(4.3 / 5 * 100%); - content: '★★★★★'; + content: "★★★★★"; position: absolute; top: 0; left: 0; diff --git a/nextjs-end/src/components/Filters.jsx b/nextjs-end/src/components/Filters.jsx index 985dcca2..515e7961 100644 --- a/nextjs-end/src/components/Filters.jsx +++ b/nextjs-end/src/components/Filters.jsx @@ -1,6 +1,6 @@ // The filters shown on the restaurant listings page -import Tag from '@/src/components/Tag.jsx'; +import Tag from "@/src/components/Tag.jsx"; function FilterSelect({ label, options, value, onChange, name, icon }) { return ( @@ -11,7 +11,7 @@ function FilterSelect({ label, options, value, onChange, name, icon }) { @@ -33,104 +33,104 @@ export default function Filters({ filters, setFilters }) { }; return ( -
    -
    +
    +
    - filter + filter

    Restaurants

    -

    Sorted by {filters.sort || 'Rating'}

    +

    Sorted by {filters.sort || "Rating"}

    { event.preventDefault(); - event.target.parentNode.removeAttribute('open'); + event.target.parentNode.removeAttribute("open"); }} > handleSelectionChange(event, 'category')} - name='category' - icon='/food.svg' + onChange={(event) => handleSelectionChange(event, "category")} + name="category" + icon="/food.svg" /> handleSelectionChange(event, 'city')} - name='city' - icon='/location.svg' + onChange={(event) => handleSelectionChange(event, "city")} + name="city" + icon="/location.svg" /> handleSelectionChange(event, 'price')} - name='price' - icon='/price.svg' + onChange={(event) => handleSelectionChange(event, "price")} + name="price" + icon="/price.svg" /> handleSelectionChange(event, 'sort')} - name='sort' - icon='/sortBy.svg' + onChange={(event) => handleSelectionChange(event, "sort")} + name="sort" + icon="/sortBy.svg" />
    - @@ -138,12 +138,12 @@ export default function Filters({ filters, setFilters }) {
    -
    +
    {Object.entries(filters).map(([type, value]) => { // The main filter bar already specifies what // sorting is being used. So skip showing the // sorting as a 'tag' - if (type == 'sort' || value == '') { + if (type == "sort" || value == "") { return null; } return ( diff --git a/nextjs-end/src/components/Header.jsx b/nextjs-end/src/components/Header.jsx index 880f414f..76dd4c04 100644 --- a/nextjs-end/src/components/Header.jsx +++ b/nextjs-end/src/components/Header.jsx @@ -1,14 +1,14 @@ -'use client'; -import React, { useState, useEffect } from 'react'; -import Link from 'next/link'; +"use client"; +import React, { useState, useEffect } from "react"; +import Link from "next/link"; import { signInWithGoogle, signOut, onAuthStateChanged, -} from '@/src/lib/firebase/auth.js'; -import { addFakeRestaurantsAndReviews } from '@/src/lib/firebase/firestore.js'; -import { useRouter } from 'next/navigation'; -import { firebaseConfig } from '@/src/lib/firebase/config'; +} from "@/src/lib/firebase/auth.js"; +import { addFakeRestaurantsAndReviews } from "@/src/lib/firebase/firestore.js"; +import { useRouter } from "next/navigation"; +import { firebaseConfig } from "@/src/lib/firebase/config"; function useUserSession(initialUser) { // The initialUser comes from the server via a server component @@ -18,16 +18,16 @@ function useUserSession(initialUser) { // Register the service worker that sends auth state back to server // The service worker is built with npm run build-service-worker useEffect(() => { - if ('serviceWorker' in navigator) { + if ("serviceWorker" in navigator) { const serializedFirebaseConfig = encodeURIComponent( JSON.stringify(firebaseConfig), ); const serviceWorkerUrl = `/auth-service-worker.js?firebaseConfig=${serializedFirebaseConfig}`; navigator.serviceWorker - .register(serviceWorkerUrl, { scope: '/', updateViaCache: 'none' }) + .register(serviceWorkerUrl, { scope: "/", updateViaCache: "none" }) .then((registration) => { - console.log('scope is: ', registration.scope); + console.log("scope is: ", registration.scope); registration.update(); }); } @@ -38,9 +38,9 @@ function useUserSession(initialUser) { if (user?.uid === authUser?.uid) { return; } - if ('serviceWorker' in navigator) { + if ("serviceWorker" in navigator) { await navigator.serviceWorker.ready; - await fetch(`/__/auth/wait/${authUser?.uid}`, { method: 'HEAD' }).catch( + await fetch(`/__/auth/wait/${authUser?.uid}`, { method: "HEAD" }).catch( () => undefined, ); } @@ -67,35 +67,35 @@ export default function Header({ initialUser }) { return (
    - - FriendlyEats + + FriendlyEats Friendly Eats {user ? ( <> -
    +

    {user.email} {user.displayName}

    -
    +
    ...
    ) : ( -
    - - A placeholder user image + diff --git a/nextjs-end/src/components/RatingPicker.jsx b/nextjs-end/src/components/RatingPicker.jsx index f69aa72b..19183bf6 100644 --- a/nextjs-end/src/components/RatingPicker.jsx +++ b/nextjs-end/src/components/RatingPicker.jsx @@ -1,62 +1,62 @@ -import React from 'react'; +import React from "react"; // A HTML and CSS only rating picker thanks to: https://codepen.io/chris22smith/pen/MJzLJN const RatingPicker = () => { return ( -

    +

    -

    diff --git a/nextjs-end/src/components/Restaurant.jsx b/nextjs-end/src/components/Restaurant.jsx index 11d915fa..cd34dca5 100644 --- a/nextjs-end/src/components/Restaurant.jsx +++ b/nextjs-end/src/components/Restaurant.jsx @@ -1,16 +1,16 @@ -'use client'; +"use client"; // This components shows one individual restaurant // It receives data from src/app/restaurant/[id]/page.jsx -import { React, useState, useEffect, Suspense } from 'react'; -import dynamic from 'next/dynamic'; -import { getRestaurantSnapshotById } from '@/src/lib/firebase/firestore.js'; -import { useUser } from '@/src/lib/getUser'; -import RestaurantDetails from '@/src/components/RestaurantDetails.jsx'; -import { updateRestaurantImage } from '@/src/lib/firebase/storage.js'; +import { React, useState, useEffect, Suspense } from "react"; +import dynamic from "next/dynamic"; +import { getRestaurantSnapshotById } from "@/src/lib/firebase/firestore.js"; +import { useUser } from "@/src/lib/getUser"; +import RestaurantDetails from "@/src/components/RestaurantDetails.jsx"; +import { updateRestaurantImage } from "@/src/lib/firebase/storage.js"; -const ReviewDialog = dynamic(() => import('@/src/components/ReviewDialog.jsx')); +const ReviewDialog = dynamic(() => import("@/src/components/ReviewDialog.jsx")); export default function Restaurant({ id, @@ -25,7 +25,7 @@ export default function Restaurant({ const userId = useUser()?.uid || initialUserId; const [review, setReview] = useState({ rating: 0, - text: '', + text: "", }); const onChange = (value, name) => { @@ -44,7 +44,7 @@ export default function Restaurant({ const handleClose = () => { setIsOpen(false); - setReview({ rating: 0, text: '' }); + setReview({ rating: 0, text: "" }); }; useEffect(() => { diff --git a/nextjs-end/src/components/RestaurantDetails.jsx b/nextjs-end/src/components/RestaurantDetails.jsx index 64e08e15..c3db5ef9 100644 --- a/nextjs-end/src/components/RestaurantDetails.jsx +++ b/nextjs-end/src/components/RestaurantDetails.jsx @@ -1,7 +1,7 @@ // This component shows restaurant metadata, and offers some actions to the user like uploading a new restaurant image, and adding a review. -import React from 'react'; -import renderStars from '@/src/components/Stars.jsx'; +import React from "react"; +import renderStars from "@/src/components/Stars.jsx"; const RestaurantDetails = ({ restaurant, @@ -12,41 +12,41 @@ const RestaurantDetails = ({ children, }) => { return ( -
    +
    {restaurant.name} -
    +
    {userId && ( review { setIsOpen(!isOpen); }} - src='/review.svg' + src="/review.svg" /> )}
    -
    -
    +
    +

    {restaurant.name}

    -
    +
      {renderStars(restaurant.avgRating)}
    ({restaurant.numRatings}) @@ -55,7 +55,7 @@ const RestaurantDetails = ({

    {restaurant.category} | {restaurant.city}

    -

    {'$'.repeat(restaurant.price)}

    +

    {"$".repeat(restaurant.price)}

    {children}
    diff --git a/nextjs-end/src/components/RestaurantListings.jsx b/nextjs-end/src/components/RestaurantListings.jsx index 270d721b..d17f1ec7 100644 --- a/nextjs-end/src/components/RestaurantListings.jsx +++ b/nextjs-end/src/components/RestaurantListings.jsx @@ -1,14 +1,14 @@ -'use client'; +"use client"; // This components handles the restaurant listings page // It receives data from src/app/page.jsx, such as the initial restaurants and search params from the URL -import Link from 'next/link'; -import { React, useState, useEffect } from 'react'; -import { useRouter } from 'next/navigation'; -import renderStars from '@/src/components/Stars.jsx'; -import { getRestaurantsSnapshot } from '@/src/lib/firebase/firestore.js'; -import Filters from '@/src/components/Filters.jsx'; +import Link from "next/link"; +import { React, useState, useEffect } from "react"; +import { useRouter } from "next/navigation"; +import renderStars from "@/src/components/Stars.jsx"; +import { getRestaurantsSnapshot } from "@/src/lib/firebase/firestore.js"; +import Filters from "@/src/components/Filters.jsx"; const RestaurantItem = ({ restaurant }) => (
  • @@ -26,13 +26,13 @@ const ActiveResturant = ({ restaurant }) => ( ); const ImageCover = ({ photo, name }) => ( -
    +
    {name}
    ); const ResturantDetails = ({ restaurant }) => ( -
    +

    {restaurant.name}

    @@ -40,18 +40,18 @@ const ResturantDetails = ({ restaurant }) => ( ); const RestaurantRating = ({ restaurant }) => ( -
    +
      {renderStars(restaurant.avgRating)}
    ({restaurant.numRatings})
    ); const RestaurantMetadata = ({ restaurant }) => ( -
    +

    {restaurant.category} | {restaurant.city}

    -

    {'$'.repeat(restaurant.price)}

    +

    {"$".repeat(restaurant.price)}

    ); @@ -63,10 +63,10 @@ export default function RestaurantListings({ // The initial filters are the search params from the URL, useful for when the user refreshes the page const initialFilters = { - city: searchParams.city || '', - category: searchParams.category || '', - price: searchParams.price || '', - sort: searchParams.sort || '', + city: searchParams.city || "", + category: searchParams.category || "", + price: searchParams.price || "", + sort: searchParams.sort || "", }; const [restaurants, setRestaurants] = useState(initialRestaurants); @@ -85,7 +85,7 @@ export default function RestaurantListings({ return (
    -
      +
        {restaurants.map((restaurant) => ( ))} @@ -98,7 +98,7 @@ function routerWithFilters(router, filters) { const queryParams = new URLSearchParams(); for (const [key, value] of Object.entries(filters)) { - if (value !== undefined && value !== '') { + if (value !== undefined && value !== "") { queryParams.append(key, value); } } diff --git a/nextjs-end/src/components/ReviewDialog.jsx b/nextjs-end/src/components/ReviewDialog.jsx index 6b69b63d..48cd7643 100644 --- a/nextjs-end/src/components/ReviewDialog.jsx +++ b/nextjs-end/src/components/ReviewDialog.jsx @@ -1,10 +1,10 @@ -'use client'; +"use client"; // This components handles the review dialog and uses a next.js feature known as Server Actions to handle the form submission -import { useLayoutEffect, useRef } from 'react'; -import RatingPicker from '@/src/components/RatingPicker.jsx'; -import { handleReviewFormSubmission } from '@/src/app/actions.js'; +import { useLayoutEffect, useRef } from "react"; +import RatingPicker from "@/src/components/RatingPicker.jsx"; +import { handleReviewFormSubmission } from "@/src/app/actions.js"; const ReviewDialog = ({ isOpen, @@ -48,30 +48,30 @@ const ReviewDialog = ({

        onChange(e.target.value, 'text')} + onChange={(e) => onChange(e.target.value, "text")} />

        - - + +