Integrar o Firebase a um app Next.js

1. Antes de começar

Neste codelab, você aprenderá a integrar o Firebase a um app da Web Next.js chamado Friendly Eats, que é um site de avaliações de restaurantes.

App da Web Friendly Eats

O app da Web completo oferece recursos úteis que demonstram como o Firebase pode ajudar você a criar apps Next.js. Esses recursos incluem o seguinte:

  • Build e implantação automáticos:este codelab usa o Firebase App Hosting para criar e implantar automaticamente o código do Next.js sempre que você envia para uma ramificação configurada.
  • Login e logout:o app da Web completo permite que você faça login com o Google e saia. O login e a persistência do usuário são gerenciados inteiramente pelo Firebase Authentication.
  • Imagens: o app da Web concluído permite que os usuários que fizeram login façam upload de imagens de restaurantes. Os recursos de imagem são armazenados no Cloud Storage para Firebase. O SDK do Firebase para JavaScript fornece um URL público para as imagens enviadas. Esse URL público é armazenado no documento do restaurante relevante no Cloud Firestore.
  • Avaliações: o app da Web concluído permite que os usuários conectados postem avaliações de restaurantes com uma nota e uma mensagem de texto. As informações das avaliações são armazenadas no Cloud Firestore.
  • Filtros: o app da Web concluído permite que os usuários que fizeram login filtrem a lista de restaurantes por categoria, local e preço. Também é possível personalizar o método de classificação usado. Os dados são acessados do Cloud Firestore, e as consultas do Firestore são aplicadas com base nos filtros usados.

Pré-requisitos

  • Uma conta no GitHub
  • Conhecimento de Next.js e JavaScript.

Neste curso, você vai aprender a:

  • Como usar o Firebase com o roteador de apps Next.js e a renderização do lado do servidor.
  • Como manter imagens no Cloud Storage para Firebase.
  • Como ler e gravar dados em um banco de dados do Cloud Firestore.
  • Como usar o login com o Google com o SDK do Firebase para JavaScript.

Pré-requisitos

  • Git
  • Uma versão estável recente do Node.js
  • Um navegador da sua escolha, como o Google Chrome
  • Ambiente de desenvolvimento com um editor de código e um terminal
  • Uma Conta do Google para a criação e o gerenciamento do seu projeto do Firebase
  • A capacidade de fazer upgrade do seu projeto do Firebase para o plano de preços Blaze

2. Configurar o ambiente de desenvolvimento e o repositório do GitHub

Este codelab fornece a base de código inicial do app e depende da CLI do Firebase.

Crie um repositório do GitHub

A origem do codelab pode ser encontrada em https://github.com/firebase/friendlyeats-web. O repositório contém projetos de exemplo para várias plataformas. No entanto, este codelab usa apenas o diretório nextjs-start. Anote os seguintes diretórios:

* `nextjs-start`: contains the starter code upon which you build.
* `nextjs-end`: contains the solution code for the finished web app.

Copie a pasta nextjs-start para seu próprio repositório:

  1. Usando um terminal, crie uma nova pasta no computador e mude para o novo diretório:
    mkdir codelab-friendlyeats-web
    
    cd codelab-friendlyeats-web
    
  2. Use o pacote npm giget para buscar apenas a pasta nextjs-start:
    npx giget@latest gh:firebase/friendlyeats-web/nextjs-start#master . --install
    
  3. Acompanhe as mudanças localmente com o git:
    git init
    
    git commit -a -m "codelab starting point"
    
    git branch -M main
    
  4. Crie um novo repositório do GitHub: https://github.com/new. Dê o nome que quiser.
  5. Copie o novo URL criado pelo GitHub. Ele vai ficar assim:
    • https://github.com/<USER_NAME>/<REPOSITORY_NAME>.git ou
    • git@github.com:<USER_NAME>/<REPOSITORY_NAME>.git
  6. Para enviar as alterações locais para o novo repositório do GitHub, execute o comando a seguir. Substitua o URL do repositório real pelo marcador de posição <REPOSITORY_URL>.
    git remote add origin <REPOSITORY_URL>
    
    git push -u origin main
    
  7. O código inicial vai aparecer no seu repositório do GitHub.

Instalar ou atualizar a CLI do Firebase

Execute o seguinte comando para verificar se você tem a CLI do Firebase instalada e se ela é a v13.9.0 ou mais recente:

firebase --version

Se você encontrar uma versão anterior ou não tiver a CLI do Firebase instalada, execute o comando de instalação:

npm install -g firebase-tools@latest

Se não for possível instalar a CLI do Firebase devido a erros de permissão, consulte a documentação do npm ou use outra opção de instalação.

Fazer login no Firebase

  1. Execute o seguinte comando para fazer login na CLI do Firebase:
    firebase login
    
  2. Para que o Firebase colete dados, insira Y ou N.
  3. No navegador, selecione sua Conta do Google e clique em Permitir.

3. Configurar seu projeto do Firebase

Nesta seção, você vai configurar um projeto do Firebase e associar um app da Web do Firebase a ele. Você também vai configurar os serviços do Firebase usados pelo app da Web de exemplo.

Criar um projeto do Firebase

  1. No Console do Firebase, clique em Adicionar projeto.
  2. Na caixa de texto Digite o nome do projeto, digite FriendlyEats Codelab ou o nome do projeto que você quiser e clique em Continuar.
  3. No modal Confirmar plano de faturamento do Firebase, confirme se o plano é Blaze e clique em Confirmar plano.
  4. Neste codelab, você não precisa do Google Analytics. Portanto, desative a opção Ativar o Google Analytics para este projeto.
  5. Clique em Criar projeto.
  6. Aguarde o provisionamento do projeto e clique em Continuar.
  7. No projeto do Firebase, acesse Configurações do projeto. Anote o ID do projeto, porque você vai precisar dele mais tarde. Esse identificador exclusivo é como seu projeto é identificado (por exemplo, na CLI do Firebase).

Fazer upgrade do plano de preços do Firebase

Para usar o Firebase App Hosting e o Cloud Storage para Firebase, seu projeto do Firebase precisa estar no plano de preços de pagamento por uso (Blaze), ou seja, vinculado a uma conta do Cloud Billing.

  • Uma conta do Cloud Billing exige uma forma de pagamento, como cartão de crédito.
  • Se você ainda não conhece o Firebase e o Google Cloud, confira se tem qualificação para receber um crédito de US$300 e uma conta de teste sem custo financeiro do Cloud Billing.
  • Se você estiver fazendo este codelab como parte de um evento, pergunte ao organizador se há créditos do Cloud disponíveis.

Para fazer upgrade do seu projeto para o plano Blaze, siga estas etapas:

  1. No console do Firebase, selecione Fazer upgrade do seu plano.
  2. Selecione o plano Blaze. Siga as instruções na tela para vincular uma conta do Cloud Billing ao seu projeto.
    Se você precisou criar uma conta do Cloud Billing como parte desse upgrade, talvez seja necessário voltar ao fluxo de upgrade no console do Firebase para concluir o upgrade.

Adicionar um app da Web ao seu projeto do Firebase

  1. Navegue até a Visão geral do projeto no seu projeto do Firebase e clique em e41f2efdd9539c31.png Web.

    Se você já tiver apps registrados no seu projeto, clique em Adicionar app para ver o ícone da Web.
  2. Na caixa de texto Apelido do app, digite um apelido fácil de lembrar, como My Next.js app.
  3. Deixe a caixa de seleção Também configurar o Firebase Hosting para este app desmarcada.
  4. Clique em Registrar app > Próxima > Próxima > Continuar para o console.

Configurar serviços do Firebase no console do Firebase

Configurar o Authentication

  1. No console do Firebase, navegue até Autenticação.
  2. Clique em Começar.
  3. Na coluna Outros provedores, clique em Google > Ativar.
  4. Na caixa de texto Nome voltado ao público do projeto, digite um nome fácil de lembrar, como My Next.js app.
  5. No menu suspenso E-mail de suporte do projeto, selecione seu endereço de e-mail.
  6. Clique em Salvar.

Configurar o Cloud Firestore

  1. No painel esquerdo do Console do Firebase, expanda Build e selecione Banco de dados do Firestore.
  2. Clique em Criar banco de dados.
  3. Deixe o ID do banco de dados definido como (default).
  4. Selecione um local para o banco de dados e clique em Próxima.
    No caso de apps reais, escolha um local próximo aos usuários.
  5. Clique em Iniciar no modo de teste. Leia o aviso sobre as regras de segurança.
    Mais adiante neste codelab, você vai adicionar regras de segurança para proteger seus dados. Não distribua ou exponha um aplicativo publicamente sem adicionar regras de segurança ao seu banco de dados.
  6. Clique em Criar.

Configurar o Cloud Storage para Firebase

  1. No painel à esquerda do Console do Firebase, expanda Build e selecione Armazenamento.
  2. Clique em Começar.
  3. Selecione um local para o bucket do Storage padrão.
    Os buckets em US-WEST1, US-CENTRAL1 e US-EAST1 podem aproveitar o nível"Sempre sem custos financeiros" do Google Cloud Storage. Os buckets em todos os outros locais seguem os preços e usos do Google Cloud Storage.
  4. Clique em Iniciar no modo de teste. Leia o aviso sobre as regras de segurança.
    Mais adiante neste codelab, você vai adicionar regras de segurança para proteger seus dados. Não distribua ou exponha um aplicativo publicamente sem adicionar regras de segurança ao bucket do Storage.
  5. Clique em Criar.

4. Revisar a base de código inicial

Nesta seção, você vai revisar algumas áreas da base de código inicial do app às quais vai adicionar funcionalidades neste codelab.

Estrutura de pastas e arquivos

A tabela a seguir contém uma visão geral da estrutura de pastas e arquivos do app:

Pastas e arquivos

Descrição

src/components

Componentes do React para filtros, cabeçalhos, detalhes de restaurantes e avaliações

src/lib

Funções utilitárias que não estão necessariamente vinculadas ao React ou Next.js.

src/lib/firebase

Código específico e configuração do Firebase

public

Recursos estáticos no app da Web, como ícones

src/app

Roteamento com o roteador de apps Next.js

src/app/restaurant

Um gerenciador de rotas de API

package.json e package-lock.json

Dependências do projeto com npm

next.config.js

Configuração específica do Next.js (as ações do servidor estão ativadas)

jsconfig.json

Configuração do serviço de linguagem JavaScript

Componentes do servidor e do cliente

O app é um app da Web Next.js que usa o Roteador de apps. A renderização do servidor é usada em todo o app. Por exemplo, o arquivo src/app/page.js é um componente do servidor responsável pela página principal. O arquivo src/components/RestaurantListings.jsx é um componente do cliente indicado pela diretiva "use client" no início do arquivo.

Declarações de importação

Você vai notar instruções de importação como estas:

import RatingPicker from "@/src/components/RatingPicker.jsx";

O app usa o símbolo @ para evitar caminhos de importação relativos pesados e é possível graças a aliases de caminho.

APIs específicas do Firebase

Todo o código da API do Firebase é encapsulado no diretório src/lib/firebase. Os componentes individuais do React importam as funções encapsuladas do diretório src/lib/firebase, em vez de importar as funções do Firebase diretamente.

Dados simulados

Os dados simulados de restaurantes e avaliações estão no arquivo src/lib/randomData.js. Os dados desse arquivo são reunidos no código do arquivo src/lib/fakeRestaurants.js.

5. Criar um back-end do App Hosting

Nesta seção, você vai configurar um back-end do App Hosting para monitorar uma ramificação no repositório do Git.

Ao final desta seção, você terá um back-end do App Hosting conectado ao seu repositório no GitHub, que vai criar e lançar automaticamente uma nova versão do app sempre que você enviar um novo commit para a ramificação main.

Implantar regras de segurança

O código já tem conjuntos de regras de segurança para o Firestore e o Cloud Storage para Firebase. Após a implantação das regras de segurança, os dados no banco de dados e no bucket ficam mais bem protegidos contra uso indevido.

  1. No terminal, configure a CLI para usar o projeto do Firebase que você criou anteriormente:
    firebase use --add
    
    Quando solicitado um alias, digite friendlyeats-codelab.
  2. Para implantar essas regras de segurança, execute este comando no terminal:
    firebase deploy --only firestore:rules,storage
    
  3. Se aparecer a pergunta "Cloud Storage for Firebase needs an IAM Role to use cross-service rules. Grant the new role?", pressione Enter para selecionar Sim.

Adicionar a configuração do Firebase ao código do app da Web

  1. No console do Firebase, acesse as Configurações do projeto.
  2. Role a tela para baixo até a seção Seus apps e selecione o app da Web do Firebase que você criou anteriormente neste codelab.
  3. Copie a variável firebaseConfig e as propriedades e os valores dela.
  4. Abra o arquivo apphosting.yaml no editor de código e preencha os valores das variáveis de ambiente com os valores de configuração do console do Firebase.
  5. No arquivo, substitua as propriedades atuais pelas que você copiou.
  6. Salve o arquivo.

Criar um back-end

  1. Acesse a página "App Hosting" no console do Firebase:

Estado zero do console do App Hosting, com um botão &quot;Começar&quot;

  1. Clique em "Começar" para iniciar o fluxo de criação do back-end. Configure o back-end da seguinte maneira:
  2. Siga as instruções na primeira etapa para conectar o repositório do GitHub que você criou anteriormente.
  3. Defina as configurações de implantação:
    1. Manter o diretório raiz como /
    2. Defina a ramificação ativa como main
    3. Ativar os lançamentos automáticos
  4. Nomeie o back-end como friendlyeats-codelab.
  5. Em "Criar ou associar um app da Web do Firebase", selecione o app que você configurou anteriormente no menu suspenso "Selecionar um app da Web do Firebase".
  6. Clique em "Concluir e implantar". Depois de um momento, você vai ser direcionado a uma nova página com o status do seu novo back-end do App Hosting.
  7. Quando o lançamento for concluído, clique no seu domínio sem custo financeiro em "Domínios". Isso pode levar alguns minutos para começar a funcionar devido à propagação do DNS.

Você implantou o app da Web inicial. Sempre que você enviar uma nova confirmação para a ramificação main do repositório do GitHub, um novo build e lançamento serão iniciados no console do Firebase, e seu site será atualizado automaticamente após a conclusão do lançamento.

6. Adicionar autenticação ao app da Web

Nesta seção, você vai adicionar uma autenticação ao app da Web para fazer login nele.

Implementar as funções de login e logout

  1. No arquivo src/lib/firebase/auth.js, substitua as funções onAuthStateChanged, signInWithGoogle e signOut pelo seguinte código:
export function onAuthStateChanged(cb) {
	return _onAuthStateChanged(auth, cb);
}

export async function signInWithGoogle() {
  const provider = new GoogleAuthProvider();

  try {
    await signInWithPopup(auth, provider);
  } catch (error) {
    console.error("Error signing in with Google", error);
  }
}

export async function signOut() {
  try {
    return auth.signOut();
  } catch (error) {
    console.error("Error signing out with Google", error);
  }
}

Esse código usa as seguintes APIs do Firebase:

API Firebase

Descrição

GoogleAuthProvider

Cria uma instância do provedor de autenticação do Google.

signInWithPopup

Inicia um fluxo de autenticação baseado em caixa de diálogo.

auth.signOut

Desconecta o usuário.

No arquivo src/components/Header.jsx, o código já invoca as funções signInWithGoogle e signOut.

  1. Crie uma confirmação com a mensagem "Adicionar autenticação do Google" e envie para o repositório do GitHub. 1. Abra a página "App Hosting" no Console do Firebase e aguarde a conclusão do novo lançamento.
  2. No app da Web, atualize a página e clique em Fazer login com o Google. O app da Web não é atualizado, então não está claro se o login foi bem-sucedido.

Enviar o estado de autenticação para o servidor

Para transmitir o estado de autenticação ao servidor, vamos usar um service worker. Substitua as funções fetchWithFirebaseHeaders e getAuthIdToken pelo seguinte código:

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);
}

async function getAuthIdToken(auth) {
  await auth.authStateReady();
  if (!auth.currentUser) return;
  return await getIdToken(auth.currentUser);
}

Ler o estado de autenticação no servidor

Vamos usar o FirebaseServerApp para espelhar o estado de autenticação do cliente no servidor.

Abra src/lib/firebase/serverApp.js e substitua a função getAuthenticatedAppForUser:

export async function getAuthenticatedAppForUser() {
  const idToken = headers().get("Authorization")?.split("Bearer ")[1];
  console.log('firebaseConfig', JSON.stringify(firebaseConfig));
  const firebaseServerApp = initializeServerApp(
    firebaseConfig,
    idToken
      ? {
          authIdToken: idToken,
        }
      : {}
  );

  const auth = getAuth(firebaseServerApp);
  await auth.authStateReady();

  return { firebaseServerApp, currentUser: auth.currentUser };
}

Receber notificações sobre mudanças na autenticação

Para receber as mudanças na autenticação, siga estas etapas:

  1. Navegue até o arquivo src/components/Header.jsx.
  2. Substitua a função useUserSession por este código:
function useUserSession(initialUser) {
	// 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)
			.then((registration) => console.log("scope is: ", registration.scope));
		}
	  }, []);

	useEffect(() => {
		const unsubscribe = 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()
			}
		})
		// eslint-disable-next-line react-hooks/exhaustive-deps
	}, [user])

	return user;
}

Esse código usa um hook de estado do React para atualizar o usuário quando a função onAuthStateChanged especifica que há uma mudança no estado de autenticação.

Verificar mudanças

O layout raiz no arquivo src/app/layout.js renderiza o cabeçalho e transmite o usuário, se disponível, como uma propriedade.

<Header initialUser={currentUser?.toJSON()} />

Isso significa que o componente <Header> renderiza dados do usuário, se disponíveis, durante o ambiente de execução do servidor. Se houver alguma atualização de autenticação durante o ciclo de vida da página após o carregamento inicial da página, ela será processada pelo gerenciador onAuthStateChanged.

Agora é hora de lançar um novo build e verificar o que você criou.

  1. Crie uma confirmação com a mensagem "Mostrar estado de login" e envie para seu repositório do GitHub.
  2. Abra a página "App Hosting" no Console do Firebase e aguarde a conclusão do novo lançamento.
  3. Verifique o novo comportamento de autenticação:
    1. No navegador, atualize o app da Web. Seu nome de exibição aparece no cabeçalho.
    2. Saia e faça login novamente. A página é atualizada em tempo real sem ser atualizada. Repita essa etapa com diferentes usuários.
    3. Opcional: clique com o botão direito do mouse no app da Web, selecione Conferir código-fonte da página e pesquise o nome de exibição. Ele aparece no código-fonte HTML bruto retornado do servidor.

7. Acessar informações do restaurante

O app da Web inclui dados simulados de restaurantes e avaliações.

Adicionar um ou mais restaurantes

Para inserir dados simulados de restaurantes no seu banco de dados local do Cloud Firestore, siga estas etapas:

  1. No app da Web, selecione 2cf67d488d8e6332.png > Adicionar restaurantes de exemplo.
  2. No console do Firebase, na página Banco de dados do Firestore, selecione restaurantes. Você encontrará os documentos de nível superior na coleção do restaurante. Cada um deles representa um restaurante.
  3. Clique em alguns documentos para explorar as propriedades de um documento de restaurante.

Mostrar a lista de restaurantes

Seu banco de dados do Cloud Firestore agora tem restaurantes que o app da Web Next.js pode exibir.

Para definir o código de busca de dados, siga estas etapas:

  1. No arquivo src/app/page.js, encontre o componente de servidor <Home /> e revise a chamada para a função getRestaurants, que recupera uma lista de restaurantes no ambiente de execução do servidor. Implemente a função getRestaurants nas etapas a seguir.
  2. No arquivo src/lib/firebase/firestore.js, substitua as funções applyQueryFilters e getRestaurants pelo seguinte código:
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;
}

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(),
		};
	});
}
  1. Crie um commit com a mensagem "Ler a lista de restaurantes do Firestore" e envie para seu repositório do GitHub.
  2. Abra a página "App Hosting" no Console do Firebase e aguarde a conclusão do novo lançamento.
  3. No app da Web, atualize a página. As imagens do restaurante aparecem como blocos na página.

Verificar se as listas do restaurante são carregadas no ambiente de execução do servidor

Usando o framework Next.js, pode não ser óbvio quando os dados são carregados no ambiente de execução do servidor ou do lado do cliente.

Para verificar se as fichas de restaurantes são carregadas no ambiente de execução do servidor, siga estas etapas:

  1. No app da Web, abra o DevTools e desative o JavaScript.

Desativar o JavaScipt no DevTools

  1. Atualize o app da Web. As listas dos restaurantes ainda são carregadas. As informações do restaurante são retornadas na resposta do servidor. Quando o JavaScript está ativado, as informações do restaurante são hidratadas com o código JavaScript do lado do cliente.
  2. No DevTools, reative o JavaScript.

Detectar atualizações de restaurantes com listeners de snapshots do Cloud Firestore

Na seção anterior, você soube como o conjunto inicial de restaurantes foi carregado do arquivo src/app/page.js. O arquivo src/app/page.js é um componente do servidor e é renderizado no servidor, incluindo o código de busca de dados do Firebase.

O arquivo src/components/RestaurantListings.jsx é um componente do cliente e pode ser configurado para hidratar a marcação renderizada pelo servidor.

Se quiser configurar o arquivo src/components/RestaurantListings.jsx para hidratar a marcação renderizada pelo servidor, siga estas etapas:

  1. No arquivo src/components/RestaurantListings.jsx, observe o seguinte código, que já está escrito para você:
useEffect(() => {
        const unsubscribe = getRestaurantsSnapshot(data => {
                setRestaurants(data);
        }, filters);

        return () => {
                unsubscribe();
        };
}, [filters]);

Esse código invoca a função getRestaurantsSnapshot(), que é semelhante à função getRestaurants() implementada em uma etapa anterior. No entanto, essa função de snapshot fornece um mecanismo de callback para que o callback seja invocado sempre que uma mudança for feita na coleção do restaurante.

  1. No arquivo src/lib/firebase/firestore.js, substitua a função getRestaurantsSnapshot() pelo seguinte código:
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);

	const unsubscribe = 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);
	});

	return unsubscribe;
}

As alterações feitas na página do banco de dados do Firestore agora são refletidas no app da Web em tempo real.

  1. Crie uma confirmação com a mensagem "Listen for real-time restaurant updates" e envie para o repositório do GitHub.
  2. Abra a página "App Hosting" no Console do Firebase e aguarde a conclusão do novo lançamento.
  3. No app da Web, selecione 27ca5d1e8ed8adfe.png > Adicionar restaurantes de exemplo. Se a função de snapshot for implementada corretamente, os restaurantes vão aparecer em tempo real, sem precisar atualizar a página.

8. Salvar avaliações enviadas pelos usuários no app da Web

  1. No arquivo src/lib/firebase/firestore.js, substitua a função updateWithRating() pelo seguinte código:
const updateWithRating = async (
	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()),
	});
};

Esse código insere um novo documento do Firestore que representa a nova avaliação. O código também atualiza o documento atual do Firestore que representa o restaurante com números atualizados para o número de avaliações e a classificação média calculada.

  1. Substitua a função addReviewToRestaurant() por este código:
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;
	}
}

Implementar uma ação do servidor Next.js

Uma ação do servidor Next.js oferece uma API conveniente para acessar dados de formulário, como data.get("text"), para acessar o valor de texto do payload de envio de formulário.

Se quiser usar uma ação do servidor do Next.js para processar o envio do formulário de avaliação, siga estas etapas:

  1. No arquivo src/components/ReviewDialog.jsx, encontre o atributo action no elemento <form>.
<form action={handleReviewFormSubmission}>

O valor do atributo action se refere a uma função que você vai implementar na próxima etapa.

  1. No arquivo src/app/actions.js, substitua a função handleReviewFormSubmission() pelo seguinte código:
// This is a next.js server action, which is an alpha feature, so
// use with caution.
// https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions
export async function handleReviewFormSubmission(data) {
        const { app } = await getAuthenticatedAppForUser();
        const db = getFirestore(app);

        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"),
        });
}

Adicionar avaliações de um restaurante

Você implementou a compatibilidade com envios de avaliação, então agora pode verificar se as avaliações foram inseridas corretamente no Cloud Firestore.

Para adicionar uma avaliação e verificar se ela está inserida no Cloud Firestore, siga estas etapas:

  1. Crie uma confirmação com a mensagem "Permitir que os usuários enviem avaliações de restaurantes" e envie para o repositório do GitHub.
  2. Abra a página "App Hosting" no Console do Firebase e aguarde a conclusão do novo lançamento.
  3. Atualize o app da Web e selecione um restaurante na página inicial.
  4. Na página do restaurante, clique em 3e19beef78bb0d0e.png.
  5. Selecione uma nota.
  6. Escreva uma avaliação.
  7. Clique em Enviar. Sua avaliação vai aparecer no topo da lista.
  8. No Cloud Firestore, pesquise o documento do restaurante que você avaliou no painel Adicionar documento e selecione-o.
  9. No painel Iniciar coleção, selecione classificações.
  10. No painel Adicionar documento, encontre o documento da sua avaliação para verificar se ele foi inserido conforme o esperado.

Documentos no emulador do Firestore

9. Salvar arquivos enviados pelo usuário no app da Web

Nesta seção, você vai adicionar uma funcionalidade para substituir a imagem associada a um restaurante quando estiver conectado. Você faz upload da imagem para o Firebase Storage e atualiza o URL da imagem no documento do Cloud Firestore que representa o restaurante.

Para salvar arquivos enviados por usuários no app da Web, siga estas etapas:

  1. No arquivo src/components/Restaurant.jsx, observe o código executado quando o usuário faz upload de um arquivo:
async function handleRestaurantImage(target) {
        const image = target.files ? target.files[0] : null;
        if (!image) {
                return;
        }

        const imageURL = await updateRestaurantImage(id, image);
        setRestaurant({ ...restaurant, photo: imageURL });
}

Nenhuma mudança é necessária, mas você vai implementar o comportamento da função updateRestaurantImage() nas etapas a seguir.

  1. No arquivo src/lib/firebase/storage.js, substitua as funções updateRestaurantImage() e uploadImage() pelo seguinte código:
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);
	}
}

async function uploadImage(restaurantId, image) {
	const filePath = `images/${restaurantId}/${image.name}`;
	const newImageRef = ref(storage, filePath);
	await uploadBytesResumable(newImageRef, image);

	return await getDownloadURL(newImageRef);
}

A função updateRestaurantImageReference() já foi implementada para você. Essa função atualiza um documento de restaurante no Cloud Firestore com um URL de imagem atualizado.

Verificar a funcionalidade de upload de imagem

Para verificar se o upload da imagem é o esperado, siga estas etapas:

  1. Crie uma confirmação com a mensagem "Permitir que os usuários mudem a foto de cada restaurante" e envie para o repositório do GitHub.
  2. Abra a página "App Hosting" no Console do Firebase e aguarde a conclusão do novo lançamento.
  3. No app da Web, verifique se você fez login e selecione um restaurante.
  4. Clique em 7067eb41fea41ff0.png e faça upload de uma imagem do seu sistema de arquivos. A imagem sai do ambiente local e é carregada no Cloud Storage. A imagem aparece imediatamente após o upload.
  5. Acesse o Cloud Storage para Firebase.
  6. Navegue até a pasta que representa o restaurante. A imagem que você enviou existe na pasta.

6cf3f9e2303c931c.png

10. Resumir avaliações de restaurantes com a IA generativa

Nesta seção, você vai adicionar um recurso de resumo de avaliações para que um usuário possa entender rapidamente o que todos pensam sobre um restaurante sem precisar ler todas as avaliações.

Armazenar uma chave de API Gemini no Cloud Secret Manager

  1. Para usar a API Gemini, você precisa de uma chave de API. Crie uma chave no Google AI Studio.
  2. O App Hosting se integra ao Cloud Secret Manager para que você possa armazenar valores sensíveis, como chaves de API, com segurança:
    1. Em um terminal, execute o comando para criar um novo secret:
    firebase apphosting:secrets:set gemini-api-key
    
    1. Quando o valor secreto for solicitado, copie e cole sua chave da API Gemini do Google AI Studio.
    2. Quando for perguntado se o novo secret precisa ser adicionado a apphosting.yaml, digite Y para aceitar.

Agora, a chave da API Gemini é armazenada com segurança no Cloud Secret Manager e pode ser acessada pelo back-end do App Hosting.

Implementar o componente de resumo da avaliação

  1. Em src/components/Reviews/ReviewSummary.jsx, substitua a função GeminiSummary pelo seguinte código:
    export async function GeminiSummary({ restaurantId }) {
        const { firebaseServerApp } = await getAuthenticatedAppForUser();
        const reviews = await getReviewsByRestaurantId(
            getFirestore(firebaseServerApp),
            restaurantId
        );
    
        const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
        const model = genAI.getGenerativeModel({ model: "gemini-pro"});
    
        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)}
        `;
    
        try {
            const result = await model.generateContent(prompt);
            const response = await result.response;
            const text = response.text();
    
            return (
                <div className="restaurant__review_summary">
                    <p>{text}</p>
                    <p> Summarized with Gemini</p>
                </div>
            );
        } catch (e) {
            console.error(e);
            return <p>Error contacting Gemini</p>;
        }
    }
    
  2. Crie uma confirmação com a mensagem "Use AI to summarize reviews" e envie para o repositório do GitHub.
  3. Abra a página "App Hosting" no Console do Firebase e aguarde a conclusão do novo lançamento.
  4. Abra a página de um restaurante. Na parte de cima, você vai encontrar um resumo de uma frase de todas as avaliações na página.
  5. Adicione uma nova avaliação e atualize a página. O resumo vai mudar.

11. Conclusão

Parabéns! Você aprendeu a usar o Firebase para adicionar recursos e funcionalidades a um app Next.js. Especificamente, você usou o seguinte:

Saiba mais