Mélanie  Faria

Mélanie Faria

1640850190

Como Criar Um Aplicativo CRUD Com Next.js E FaunaDB

Durante a programação, os desenvolvedores encontram problemas que exigem a reutilização de código, levando a uma programação repetitiva que pode ser uma perda de tempo e reduzir a produtividade. Isso dá origem à necessidade de código-fonte reutilizável, denominado “fragmentos de código”. Esses trechos evitam código repetitivo durante a programação, podem ser salvos para uso futuro e são compartilháveis.

Neste tutorial, construiremos um site para ajudar os usuários a salvar trechos de código diários usando a estrutura de desenvolvimento da Web Next.js e com base no banco de dados Fauna para lidar com o armazenamento, manipulação e exibição de trechos de código. Ao trabalhar neste projeto divertido, também aprenderemos como criar um aplicativo CRUD básico com Next.js e FaunaDB que também pode ser usado para construir outros projetos semelhantes.

Uma versão funcional deste projeto pode ser encontrada no GitHub . Para acompanhar, você precisará do Node instalado em sua máquina , bem como uma conta FaunaDB e uma conta Google (para autenticação).

Instalando Dependências

Nesta seção, veremos como instalar Next.js usando o npx create-next-appcomando. Isso inicializará o Next CLI e construirá um novo aplicativo Next.js.

Também instalaremos as dependências que usaremos para o back end - FaunaDB e SWR - por meio da linha de comando. SWR (state-while-revalidate) é um gancho Next.js para buscar dados. Entraremos em detalhes mais adiante neste tutorial.

Instalação do Next.js

Para instalar Next.js, digite o seguinte comando na CLI:

npx create-next-app snippetapp

O comando acima cria um diretório de projeto chamado snippetappcom o modelo inicial Next.js, que contém os arquivos necessários para codificação com Next. Quando o Next terminar de instalar, mude para o diretório recém-criado:

cd snippetapp

Instalação do FaunaDB e SWR

Para instalar o Fauna, usaremos o seguinte comando na CLI:

npm install --save faunadb

Em seguida, para instalar o SWR:

npm install swr@0.3.8

Com isso, instalamos todas as dependências que usaremos para construir nosso aplicativo e agora podemos prosseguir com a configuração de nosso banco de dados no Fauna.

Configuração do FaunaDB

FaunaDB é um banco de dados em tempo real sem servidor. Ele transforma um banco de dados tradicional em uma API de dados flexível que ainda retém os recursos de um banco de dados e seu desempenho, ao mesmo tempo que oferece acesso seguro e escalonável aos dados do aplicativo.

Aqui, criaremos uma conta de usuário e configuraremos o banco de dados para armazenar os dados que usaremos em nosso aplicativo de snippet.

Criação de uma conta de usuário

Para criar uma conta de usuário, navegue até a página de inscrição do Fauna e crie uma conta.

Criando uma conta FaunaDB

 

 

Depois de criar uma conta de usuário, você será redirecionado para o painel.

O painel FaunaDB

Crie o banco de dados e a coleção de snippet

Aqui, criaremos um banco de dados com as coleções necessárias para gerenciar os trechos de código de nosso aplicativo. Clique em CRIAR BANCO DE DADOS . Vamos criar um banco de dados chamado snippets.

Criando um banco de dados de snippets FaunaDB

Na nova página que se abre, clique em NOVA COLEÇÃO e crie uma coleção chamada codesnippet.

página de coleção

Depois de criar uma coleção, obtemos uma página onde podemos criar um documento.

Crie um documento

Aqui, você clica em NOVO DOCUMENTO . Um documento JSON será aberto, onde você pode inserir os detalhes, conforme ilustrado abaixo.

Criação de documentos

{
  name: "Prompt User",
  description: "prompts the user",
  language: "javascript",
  code: "prompt('would you like to continue')"
}

Aqui, nós definimos um trecho com atributos: name, description, languagee code. Clique em SALVAR para salvar a nova coleção. Adicionamos com sucesso um snippet ao nosso banco de dados. Agora podemos prosseguir para obter nossas credenciais de acesso para usar em nosso aplicativo.

Criação de chave secreta

No painel, clique em Segurança . Isso abre uma nova página para criar nossa chave de segurança.

Criando uma chave de segurança FaunaDB

 

 

Aqui, definiremos a função como “servidor” em vez de “admin” e você poderá dar um nome à chave. Clique no botão SALVAR para gerar sua chave.

Criação de um .envarquivo

Agora vamos criar um .envarquivo dentro do diretório do nosso projeto. Este arquivo armazenará nossa chave secreta gerada. No .envarquivo, temos o seguinte:

FAUNA_SECRET = paste your key here

Criação de uma página de snippet de código

Nesta seção, construiremos a página de exibição e upload para os snippets e também adicionaremos funcionalidade a ela.

Abra o diretório do projeto em seu editor de código e navegue até o index.jsarquivo na pasta de páginas. Aqui, vamos limpar o código e começar a construir nosso aplicativo:

import Head from "next/head"
import Image from "next/image"
import styles from "../styles/Home.module.css"

export default function Home() {
  return (
    <div className={styles.container}>
      <Head>
        <title>View Snippet</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.main}>
        <h1 className={styles.title}>Re-usuable Code Snippets</h1>
        <p className={styles.info}>Add your code snippets here...</p>
        <button>Create new snippet</button>
      </main>
    </div>
  )
}

Criando nossos componentes

Agora vamos criar um arquivo de componente que renderizará nossos snippets. Crie uma pasta nomeada componentem seu diretório de trabalho e crie um arquivo nomeado Snippets.jsdentro dela com o seguinte código:

import React from "react"
import styles from "../styles/Home.module.css"

function Snippets() {
  return (
    <div className={styles.cont}>
      <p className={styles.lang}>language</p>
      <h3 className={styles.name}>name of snippet</h3>
      <p className={styles.descp}>description of snippet</p>
      {/* Code will be displayed here*/}
      <div className={styles.links}>
        <a>Edit</a>
        <a>Delete</a>
      </div>
    </div>
  )
}

export default Snippets

Importando nossos componentes para o aplicativo

Agora adicionaremos importações para este arquivo em nosso index.js:

import Snippets from "../components/Snippets"

E use-o em nosso aplicativo:

<button>Create new snippet</button>
<Snippets/>

Estilizando nosso aplicativo

Agora podemos estilizar nossa página. Navegue até o Home.module.cssarquivo na stylespasta e substitua os estilos lá pelo seguinte:

.container{
  display: flex;
  height: 100%;
  min-height: 100vh;
  background: rgb(48, 48, 255);
  flex-direction: column;
  align-items: center;
  color: #fff;
  font-family: Montserrat;
}
.cont{
  color: #333;
  margin-top: 5px;
  background: rgb(214, 214, 214);
  border-radius: 15px;
  padding: 10px 15px;
}
.main button{
  width: fit-content;
  flex-grow: unset;
  display: inline-block;
  padding: 5px 10px;
  outline: none;
  border: none;
  border-radius: 5%;
  font-weight: bold;
  color: rgb(48, 48, 255);
}
.main button:hover{
  cursor: pointer;
}
.links{
  margin-top: 10px;
}
.links a{
  margin-left: 5px;
}
.links a:hover{
  cursor: pointer;
}

Visualizando nosso aplicativo

Nesse ponto, você deve conseguir iniciar o servidor de desenvolvimento com npm run dev, visite http: // localhost: 3000 e veja o esqueleto de nosso aplicativo.

Configurando a área de exibição de snippet

A seguir, criaremos a seção de exibição para o código do snippet. Crie um novo arquivo chamado Code.jsna pasta de componentes e importe-o para Snippets.js:

import React from 'react'
import styles from '../styles/Home.module.css'
import Code from "./Code";

function Snippets() {
  return (
    <div className={styles.cont}>
      <p className={styles.lang}>language</p>
      <h3 className={styles.name}>name of snippet</h3>
      <p className={styles.descp}>description of snippet</p>

      {/* Code will be displayed here*/}
      <Code />

      <div className={styles.links}>
        <a>Edit</a>
        <a>Delete</a>
      </div>
    </div>
  )
}

export default Snippets

Para o realce da sintaxe do código, usaremos dois pacotes, a saber, react-syntax-highlighter e react-copy-to-clipboard . Podemos fazer o download por meio da CLI:

npm install react-syntax-highlighter react-copy-to-clipboard --save

Então em Code.js:

import React from "react"
import { PrismLight as SyntaxHighlighter } from "react-syntax-highlighter"
import {atomDark} from "react-syntax-highlighter/dist/cjs/styles/prism"
import { CopyToClipboard } from "react-copy-to-clipboard"
import styles from "../styles/Home.module.css"
function Code() {
  const codeString = "npm install import react from 'react'"
  const [show, setshow] = React.useState(false)
  return (
    <div>
      <button onClick={() => setshow(!show)}>Show Code</button>
      {show ? (
        <div>
          <CopyToClipboard text={codeString}>
            <button className={styles.btn}>Copy</button>
          </CopyToClipboard>
          <SyntaxHighlighter language="javascript" style={atomDark}>
            {codeString}
          </SyntaxHighlighter>
        </div>
      ) : null}
    </div>
  )
}
export default Code

Aqui, criamos um componente para exibir o código com destaque de sintaxe. Também adicionamos a funcionalidade de cópia e alternância de exibição. Agora no stylesarquivo:

 

 

.btn{
  left: 80%;
  position: relative;
}

Testando os blocos de código

Para visualizar essa mudança, você pode executar npm run devna linha de comando e visualizá-la em seu navegador. Temos a string “npm install import react from 'react'” exibida com destaque de sintaxe como um bloco de código. Há também um botão para ocultar e exibir o trecho de código e um botão que nos permite copiar o código do bloco de código.

Inicialização FaunaDB

Nesta seção, buscaremos dados de nosso banco de dados FaunaDB para nosso aplicativo. Crie um arquivo chamado Fauna.jsno diretório do seu projeto:

const faunadb = require("faunadb")
const faunaClient = new faunadb.Client({
  secret: process.env.FAUNA_SECRET
})
const q = faunadb.query
const getResponse = async () => {
  const { data } = await faunaClient.query(
    q.Map(
      q.Paginate(q.Documents(q.Collection("codesnippet"))),
      q.Lambda("doc", q.Get(q.Var("doc")))
    )
  )
  const snippets = data.map((snippet) => {
    snippet.id = snippet.ref.id
    delete snippet.ref
    return snippet
  })
  return snippets
}

module.exports = {
  getResponse,
}

Aqui, inicializamos o FaunaDB com nossa chave secreta. Também configuramos uma asyncsolicitação para consultar nossa coleção e retornar os dados. Armazenamos os dados retornados em uma variável chamada snippetse excluímos o ref para melhor estruturar os dados. Outras funcionalidades para criar, atualizar e excluir snippets serão adicionadas posteriormente neste tutorial.

Observe que, se você estiver recebendo um erro não autorizado no console, pode ser necessário especificar o nome de domínio do terminal de destino. O padrão é db.fauna.com, mas desde a introdução de Grupos de regiões , três domínios de nuvem estão disponíveis. Use o domínio correto para o Grupo de Região do seu banco de dados:

  • Clássico (EUA e UE): db.fauna.com
  • Estados Unidos (EUA): db.us.fauna.com
  • Europa (UE): db.eu.fauna.com

Código de exemplo:

const faunaClient = new faunadb.Client({
  secret: process.env.FAUNA_SECRET,
  domain: "db.eu.fauna.com"
})

Lidando com nossas solicitações de API

Também criaremos um arquivo para lidar com nossa solicitação de API para nosso banco de dados. Dentro da apipasta em pages, crie um arquivo chamado snippets.jscom o seguinte código:

import { getResponse } from "../../Fauna.js"
export default async function handler(req, res) {
  console.log(req)
  if (req.method !== "GET") {
    return res.status(405)
  }
  try {
    const snippets = await getResponse()
    return res.status(200).json(snippets)
  } catch (err) {
    console.log(err)
      res.status(500).json({ msg: "Something went wrong." })
  }
}

Acima, simplesmente configuramos uma função para lidar com as solicitações de nosso banco de dados. Os snippets são retornados como Jsone registrarão os erros, se ocorrerem. Em Next.js, qualquer arquivo armazenado na apipasta é tratado como endpoints de API em vez de uma página e é renderizado no lado do servidor.

O que é SWR?

Como dito anteriormente, SWR (state-while-revalidate) é um gancho Next.js para buscar dados. É uma solução perfeita para buscar dados atualizados com frequência e é uma boa opção para nosso aplicativo.

Configurando SWR

Usaremos isso para buscar dados do FaunaDB. Para usar isso, precisamos importá-lo para index.js:

import useSWR from "swr"

export default function Home() {
  const { data:snippets, mutate }=useSWR("api/snippets")
  ...
})

Aqui, importamos o SWR e o usamos para buscar dados conforme configurado em snippets.js. Em seguida, armazenamos esses snippets na snippetsvariável e os enviaremos a partir daí. Agora, passaremos o snippetspara nosso Snippetscomponente para exibir:

- <Snippets />

+ {snippets &&
+   snippets.map((snippet) => (
+     <Snippets
+       key={snippet.id}
+       snippet={snippet}
+       snippetDeleted={mutate}
+     />
+   ))
+ }

Acima, passamos a chave e o snippet para Snippets. Também configuramos uma mutatepropriedade para atualizar (buscar novamente) snippets quando um snippet é excluído. Para usar os dados passados, modificamos o Snippetscomponente com o seguinte:

function Snippets({snippet}) {
  return (
    <div className={styles.cont}>
      <p className={styles.lang}>{snippet.data.language}</p>
      <h3 className={styles.name}>{snippet.data.name}</h3>
      <p className={styles.descp}>{snippet.data.description}</p>

      <Code snippet={snippet}/>

      <div className={styles.links}>
        <a>Edit</a>
        <a>Delete</a>
      </div>
    </div>
  )
}

Acima, inserimos a linguagem, o nome e a descrição do snippet recebido do FaunaDB em nosso código. Para obter o código do Fauna em nosso aplicativo, também tivemos que passar o snippet prop para o Codecomponente.

Em seguida, no Codecomponente:

 

 

function Code({snippet}){
  ...
  <div>
    <CopyToClipboard text={snippet.data.code}>
      <button className={styles.btn}>Copy</button>
    </CopyToClipboard>
    <SyntaxHighlighter language="javascript" style={atomDark}>
      {snippet.data.code}
    </SyntaxHighlighter>
  </div>
  ...
}

Agora terminamos com a GetSnippetfuncionalidade. Se voltarmos ao FaunaDB e criarmos um novo snippet, veremos o que está na imagem abaixo.

adicionou outro documento ao FaunaDB

{
  "name": "console.log()",
  "language": "javascript",
  "description": "logs out data",
  "code": "console.log('Hello, world!')"'
}

Executando o Código

Para rodar na CLI:

npm run dev

Se você abrir a página em seu navegador, terá um resultado semelhante à imagem abaixo.

Página de exibição de snippet

Nós criamos com sucesso uma página de exibição de snippet com funcionalidades para mostrar e ocultar o código e copiar o snippet de código.

A página de upload de trechos

Precisamos criar um link para a página de upload de nosso componente inicial. Next.js tem disposições que tornam o roteamento mais fácil sem a necessidade de instalação react-routere outras dependências, como você faria se estivesse usando o código React nativo.

Em index.js, vamos importar o Linkmódulo de next:

import Link from "next/link"

Em seguida, adicione-o ao nosso botão Criar novo snippet :

- <button>Create new snippet</button>

+ <Link href="/upload">
+   <button>Create new snippet</button>
+ </Link>

Vamos criar uma nova página em nossa pagespasta e nomeá-la upload.js.

De volta ao nosso Fauna.jsarquivo, criaremos e também exportaremos uma função para criar snippets em nosso aplicativo:

const createSnippet = async (code, language, description, name) => {
  return await faunaClient.query(q.Create(q.Collection("codesnippet"), {
    data:{code, language, description, name}
  }))
}

module.exports = {
  getResponse,
  createSnippet,
}

Aqui, criamos a função createSnippet, que pegará alguns parâmetros e os passará como dados no novo documento que será criado no banco de dados.

Adicionando uma função para criar snippets

Também configuraremos nosso endpoint para criar snippets. Crie um novo arquivo chamado createSnippet.jsna apipasta e preencha-o com o seguinte código:

 

 

import { createSnippet } from "../../Fauna"

export default async function handler(req, res) {
  const { code, language, description, name } = req.body
  if (req.method !== "POST") {
    return res.status(405).json({msg:"unauthorized"})
  }
  try {
    const createdSnippet = await createSnippet(code, language, description, name)
    return res.status(200).json(createdSnippet)
  } catch (error) {
    console.log(error)
    res.status(500).json({msg:"unauthorized"})
  }
}

Criando nossa página de upload

Agora vamos criar a página de upload em nosso upload.jsarquivo. Para o nosso formulário criar fragmentos, usaremos o formulário react-hook . Vamos instalar isso por meio da CLI:

npm install react-hook-form

Então, em nosso upload.jsarquivo:

import React from "react"
import { useForm } from "react-hook-form"
import { useRouter } from "next/router"
import style from "../styles/form.module.css"
import { Link } from "next/link"

function upload({ snippet }) {
  const { register, handleSubmit, errors, reset } = useForm()
  const router = useRouter()
  const createSnippet = async (data) => {
    const { code, language, description, name } = data
    console.log(data)
    try {
      // code here to push to Fauna
    } catch (error) {
      console.log(error)
    }
  }
  return (
    <div className={style.cont}>
      <form
        className={style.form}
        onSubmit={handleSubmit(snippet ? updateSnippet : createSnippet)}
      >
        <div>
          <label htmlFor="name">Name</label>
          <input
            className={style.input}
            type="text"
            id="name"
            {...register("name", { required: true })}
          />
        </div>
        <div>
          <label className={style.label} htmlFor="language">
            language
          </label>
          <select
            className={style.select}
            type="text"
            id="language"
            {...register("language", { required: true })}
          >
            <option>Javascript</option>
            <option>Html</option>
            <option>CSS</option>
          </select>
        </div>
        <div>
          <label className={style.label} htmlFor="description">
            description
          </label>
          <textarea
            className={style.input}
            rows={7}
            type="text"
            id="description"
            placeholder="snippet description"
            {...register("description", { required: true })}
          />
        </div>
        <div>
          <label className={style.label} htmlFor="code">
            Code
          </label>
          <textarea
            className={style.input}
            rows={8}
            columns={8}
            type="text"
            id="code"
            {...register("code", { required: true })}
            placeholder="background: none;"
          />
        </div>
        <div>
          <button className={style.button}>Submit</button>
          <button className={style.button}>Cancel</button>
        </div>
      </form>
    </div>
  )
}
export default upload

Estilizando nosso formulário

Acima, criamos nosso formulário usando o react-hook-formpacote. Usamos a handleSubmitfunção useForm()no operador ternário. Após o envio do formulário, ele determina se o envio feito é para criar ou atualizar um snippet existente. Com register, adicionamos a propriedade necessária a todos os campos de nosso formulário. Também adicionamos importações para uma folha de estilo chamada, form.module.cssonde temos os seguintes estilos para nosso formulário:

.form {
  max-width: 800px;
  display: flex;
  justify-content: center;
  flex-direction: column;
  align-items: center;
}

.cont{
  background: rgb(48, 48, 255);
  height: 100%;
  min-height: 100vh;
  padding: 10px 0 0 0;
  display: flex;
  justify-content: center;
  align-items: center;
}

.select,
.input {
  display: block;
  box-sizing: border-box;
  width: 100%;
  border-radius: 4px;
  border: 1px solid black;
  padding: 10px 15px;
  margin-bottom: 15px;
  font-size: 14px;
}

.label{
  line-height: 2;
  text-align: left;
  display: block;
  margin: 5px;
  color: white;
  font-size: 14px;
  font-weight: 200;
}

.button {
  background : #fff;
  color: #444;
  border: none;
  border-radius: 5%;
  margin-right: 8px;
}

Criação de um endpoint de API para criar snippets

Para enviar os dados do nosso formulário para o banco de dados FaunaDB, adicione o seguinte código ao try...catchbloco na createSnippetfunção em upload.js:

try {
  await fetch("/api/createSnippet", {
    method: "POST",
    body: JSON.stringify({ code, language, description, name }),
    headers: {
      "Content-type": "application/json"
    },
  })
  router.push("/")
} catch (error) {
  console.log(error)
}

Testando nosso formulário

Execute o código e navegue até a página de upload. Agora, se adicionarmos um novo snippet ao formulário e clicarmos em Enviar , veremos o que está ilustrado abaixo.

Página de upload de snippet

Quando navegamos para nosso componente inicial, podemos ver o trecho criado.

Criou um novo snippet

Criação de uma função para editar trechos

Para criar nossa funcionalidade de edição de snippet, de volta ao Fauna.jsarquivo, criaremos e exportaremos uma função para lidar com esta tarefa:

const updateSnippet = async (id, code, language, description, name) => {
  return await faunaClient.query(q.Update(q.Ref(q.Collection("codesnippet"), id), {
    data: {code, language, name, description},
  }))
}
module.exports = {
  ...
  updateSnippet,
}

Criação de um endpoint de API para editar snippets

Esta função é semelhante à createSnippetfunção, mas também recebe um parâmetro de id. Ele usa esse ID para identificar quais trechos devem ser editados. Se o idcorresponder, atualizamos os dados com os outros parâmetros. Também criaremos um arquivo de endpoint no apidiretório chamado updateSnippet.jspara lidar com as atualizações:

import { updateSnippet } from "../../Fauna"
export default async function handler(req, res) {
  const { id, code, language, description, name } = req.body
  if (req.method !== "PUT") {
    return res.status(405).json({ msg: "unauthorized" })
  }
  try {
    const updated = await updateSnippet(
      id,
      code,
      language,
      description,
      name
    )
    return res.status(200).json(updated)
  }
  catch (error) {
    console.log(error)
    res.status(500).json({ msg: "unauthorized" })
  }
}

Vinculando nosso botão Editar

Agora, vá para o Snippetscomponente e modifique-o para usar esta função. Primeiro, vamos importar o Linkmódulo:

...
import Link from "next/link"

Também modificamos nosso editbotão:

- <a>Edit</a>

+ <Link href={`/edit/${snippet.id}`}>
+   <a>Edit</a>
+ </Link>

Manipulação do snippet de edição

Quando clicado, ele envia uma solicitação para a página editcom o iddo snippet selecionado. Na pagespasta, crie uma pasta chamada editcom um arquivo [id].jsdentro dela:

 

 

import { getSnippetById } from "../../Fauna"
import Upload from "../upload"
export default function Home({ snippet }) {
  const email = ""
  const user = ""
  return (
    <div>
      <h3>Update a snippet</h3>
      <Upload snippet={snippet} email={email} user={user}/>
    </div>
  )
}

export async function getServerSideProps(context) {
  try {
    //get and update record
    const id = context.params.id
  }
  catch (error) {
    console.log(error)
    context.res.statusCode = 302
    context.res.setHeader("Location", "/")
    return {props: {}}
  }
}

Em [id].js, estamos passando o snippet de código propspara a página de upload de snippet. No entanto, desta vez, a página de upload conterá os dados armazenados no snippet de código referenciado pelo id. Para buscar o snippet por ID, precisaremos criar a getSnippetByIdfunção no Fauna.jsarquivo:

const getSnippetById = async (id) => {
  const snippet = await faunaClient.query(q.Get(q.Ref(q.Collection("codesnippet"),id)))
  snippet.id = snippet.ref.id
  delete snippet.ref
  return snippet
}

module.exports = {
  getResponse,
  createSnippet,
  updateSnippet,
  getSnippetById,
}

À medida que exportamos a função, de volta ao [id].jsarquivo, podemos usá-la para buscar um snippet específico com seu ID:

try {
  const id = context.params.id;
  const snippet = await getSnippetById(id);
  return {
    props: { snippet },
  };
} catch (error) {
  // as before
}

Modificando Snippets Armazenados

Agora, no upload.jsarquivo, vamos modificá-lo para poder acessar os dados armazenados se um snippet for editado:

- const { register, handleSubmit, errors, reset } = useForm()

+ const { register, handleSubmit, errors, reset } = useForm({
+   defaultValues: {
+     code: snippet ? snippet.data.code : "",
+     language: snippet ? snippet.data.language : "",
+     description: snippet ? snippet.data.description : "",
+     name: snippet ? snippet.data.name : "",
+   }
+ })

O código acima verifica se o snippet armazenou dados nele. Se ele retorna true, ele retorna os dados para os parâmetros: code, language, descriptione code. Se retornar false, ele retornará uma string vazia.

A seguir, criaremos uma função para atualizar o snippet de código:

const createSnippet = async (data) => { ... }

const updateSnippet = async (data) => {
  const { code, language, description, name } = data
  const id = snippet.id
  try {
    await fetch("/api/updateSnippet", {
      method: "PUT",
      body: JSON.stringify({ code, language, description, name, id }),
      headers: {
        "Content-Type": "application/json",
      },
    })
    router.push("/")
  }
  catch (error) {
    console.log(error)
  }
}

return ( ,,, )

Teste de funcionalidade de trecho de edição

Se executarmos nosso código, podemos editar os trechos de código criados anteriormente clicando no botão Editar , fazendo alterações nos dados do formulário e clicando em Enviar .

Adicionar funcionalidade de exclusão de snippet

Agora, se retornarmos ao Homecomponente em nosso navegador, poderemos editar e atualizar trechos de código. Podemos finalmente adicionar a funcionalidade final para excluir nosso trecho de código. Crie e exporte uma nova função - deleteSnippet- no Fauna.jsarquivo:

const deleteSnippet = async (id) => {
  return await faunaClient.query(q.Delete(q.Ref(q.Collection("codesnippet"),id)))
}

module.exports = {
  ...
  deleteSnippet,
}

Criação de endpoint de API para a funcionalidade de exclusão

Criaremos outro endpoint para esta função em nossa apipasta chamada deleteSnippet.jse o preencheremos com o seguinte código:

import { deleteSnippet } from "../../Fauna"
export default async function handler(req, res) {
  if (req.method !== "DELETE") {
    return res.status(405).json({ msg: "unauthorized" })
  }
  const { id } = req.body
  try {
    const deleted = await deleteSnippet(id)
    return res.status(200).json(deleted)
  }
  catch (error) {
    console.log(error)
    res.status(500).join({ msg: "error occured" })
  }
}

Em seguida, modificamos o Snippets.jsarquivo para adicionar a nova funcionalidade:

function Snippets({ snippet, snippetDeleted }) {
  ...
}

Em seguida, crie uma deleteSnippetfunção para buscar o endpoint do apie exclua o snippet referenciado pelo ID:

function Snippets({snippet, snippetDeleted}) {
  const deleteSnippet = async () => {
    try {
      await fetch("/api/deleteSnippet", {
        method: "DELETE",
        body: JSON.stringify({ id: snippet.id }),
        headers: {
          "Content-Type": "application/json",
        },
      });
      snippetDeleted();
    } catch (e) {
      console.log(e);
    }
  };

  return (
    <div className={styles.cont}>
      <p className={styles.lang}>{snippet.data.language}</p>
      <h3 className={styles.name}>{snippet.data.name}</h3>
      <p className={styles.descp}>{snippet.data.description}</p>

      <Code snippet={snippet}/>

      <div className={styles.links}>
        <Link href={`/edit/${snippet.id}`}>
          <a>Edit</a>
        </Link>
        <a onClick={deleteSnippet}>Delete</a>
      </div>
    </div>
  )
}

Também atualizamos o elemento âncora para chamar a deleteSnippetfunção quando ela é clicada.

Teste de funcionalidade de exclusão

Adicionamos funcionalidade para excluir trechos de código. Agora podemos excluir trechos clicando no botão Excluir em nosso aplicativo.

Isso conclui as funcionalidades do aplicativo snippet. Agora prosseguiremos para adicionar medidas de autenticação ao nosso aplicativo para permitir que apenas usuários autorizados criem ou modifiquem snippets em nosso aplicativo.

 

 

Autenticação de usuário

Por que precisamos de autenticação? Atualmente, os usuários podem criar fragmentos, mas também podem excluir e modificar fragmentos que não criaram. Precisamos fornecer um meio para autorizar os usuários a acessar nosso site - e, portanto, a necessidade de autenticação do usuário.

Instalaremos next-auth para autenticação por meio de nossa CLI:

npm i next-auth

Estaremos usando um token JWT para nossa autenticação. JWT é um padrão usado para criar tokens de acesso para um aplicativo.

Crie uma pasta com o nome authem sua apipasta e, dentro dela, crie um arquivo [...nextauth].jscom o seguinte código:

import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google"

export default NextAuth({
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth?prompt=consent&access_type=offline&response_type=code",
    })
  ],
  jwt: {
    encryption: true
  },
  secret: process.env.secret,
  callbacks: {
    async jwt(token, account) {
      if (account ?.accessToken) {
        token.accessToken = account.accessToken
      }
      return token
    },
    redirect: async (url, _baseUrl)=>{
      if (url === "/profile") {
        return Promise.resolve("/")
      }
      return  Promise.resolve("/")
    }
  }
})

Depois disso, agruparemos nossos componentes no _app.jsarquivo:

import '../styles/globals.css'
import {Provider} from "next-auth/client"

function MyApp({ Component, pageProps }) {
  return (
    <Provider session={pageProps.session}>
      <Component {...pageProps} />
    </Provider>
  )
}
export default MyApp

Manipulação de autenticação

Modificaremos nosso Homecomponente index.jspara retornar ao nosso componente se o usuário for autenticado; caso contrário, ele retornará um botão que leva à página de autenticação:

import {signIn, signOut, useSession} from "next-auth/client"
...

Então dentro Home:

export default function Home() {
  const { data:snippets, mutate }=useSWR("api/snippets")

  const [session, loadingSession] = useSession()
  if (loadingSession) {
    <>
      <p>...authenticating</p>
    </>
  }
  ...
}

O código acima verifica se o aplicativo é loadingSession. Se verdadeiro, ele retorna o pbloco de tags, caso contrário, retorna o resto do nosso aplicativo, se houver session. A seguir, vamos renderizar o “login” se não houver sessão:

return (
  <div className={styles.container}>
    <Head> ... </Head>

    {!session && (
      <>
        <h1>Sign in to access snippet app</h1>
        <button onClick={() => signIn()}>Sign In</button>
      </>
    )}

    {session && (
      <>
        <main className={styles.main}>
          <h3>welcome {session.user.email}</h3>
          <button onClick={() => signOut()}>Sign Out</button>
          ...
        </main>
      </>
    )}
  </div>
)

Para fazer uso do “serviço de login do Google”, precisamos de credenciais de acesso do console da nuvem do Google. Para obter isso, faça login em sua conta do Google e navegue até o console do Google Cloud . Clique em CRIAR PROJETO na página, digite o nome do seu projeto e clique em Criar .

Na nova página que se abre, clique em + CRIAR CREDENCIAIS na barra de menu superior e, por fim, selecione o ID do cliente OAuth no menu suspenso.

Console do Google Cloud

Na página que se abre, você receberá uma notificação com um botão pedindo para você “Configurar tela de consentimento”. Clique neste botão.

Na próxima página, selecione Externo sob o tipo de usuário e clique em Criar . Insira os campos obrigatórios para o seu “Nome do aplicativo” e “Email” e clique em Salvar e continuar .

 

 

Nas seções Escopos e usuários de teste , role para baixo e clique em Salvar e continuar .

Por fim, clique em Voltar ao painel e clique no botão Publicar .

Agora, podemos criar nossa chave clicando em Credenciais no menu lateral e, em seguida, em Criar Credenciais na barra de menu superior. Selecione Oauth Client ID na lista suspensa e você verá uma página solicitando o tipo de aplicativo.

Selecione Aplicativo da Web e, em “Origens JavaScript autorizadas”, clique em Adicionar URI e digite http://localhost. Por fim, em “URIs de redirecionamento autorizados”, clique em Adicionar URI e entre http://localhost/api/auth/callback/googleno campo, antes de clicar em Criar .

Criação de chave Oauth

Copie o ID do cliente e a chave secreta do cliente do pop-up que é aberto e adicione-os ao .envarquivo:

GOOGLE_CLIENT_ID=id
GOOGLE_CLIENT_SECRET=secret

Agora podemos fazer login usando a autenticação do Google em nosso aplicativo. Além disso, configuraremos nosso upload.jsarquivo como uma rota protegida para que usuários não autorizados não possam criar novos snippets:

import { getSession } from "next-auth/client"

function Upload({ snippet, user }) { ... }

export async function getServerSideProps(context) {
  const session = await getSession(context)
  if (!session) {
    context.res.writeHead(302, { Location: "/" })
    context.res.end()
    return {}
  }
  return {
    props: {
      user: session.user,
    }
  }
}

export default Upload;

Testando a autenticação do usuário

Se executarmos nosso aplicativo com o npm run devcomando, a princípio obteremos uma página solicitando que “entremos”. Não podemos navegar para a página de upload por meio do /uploadcaminho em nosso URL. Só podemos acessar nosso aplicativo quando usamos o recurso de login do Google para fazer login em nosso aplicativo.

Autorizando usuários a criar um snippet

Por fim, modificaremos a createSnippetfuncionalidade para adicionar o e-mail do usuário ao banco de dados e só mostraremos os botões Editar e Excluir se o e-mail corresponder.

Em Fauna.js, altere a createSnippetfunção da seguinte maneira:

const createSnippet = async (code, language, description, name, mail) => {
  return await faunaClient.query(q.Create(q.Collection("codesnippet"), {
    data:{code, language, description, name, mail}
  }))
}

No createSnippet.jsarquivo, faça as seguintes alterações:

- const { code, language, description, name } = req.body;
+ const { code, language, description, name, mail } = req.body;

- const createdSnippet = await createSnippet(code, language, description, name);
+ const createdSnippet = await createSnippet(code, language, description, name, mail);

Em upload.js:

function upload({ snippet, user }) {
+  const email = user.email;
   ...
}

E altere a createSnippetfunção e a updateSnippetfunção, da seguinte maneira:

const createSnippet = async (data) => {
  const { code, language, description, name, mail } = data;
  console.log(data)
  try {
    await fetch("/api/createSnippet", {
      method: "POST",
      body: JSON.stringify({ code, language, description, name, mail:email }),
      headers: {
        "Content-type": "application/json"
      },
    })
    router.push("/")
  } catch (error) {
    console.log(error)
  }
}

const updateSnippet = async (data) => {
  const { code, language, description, name } = data
  const id = snippet.id
  try {
    await fetch("/api/updateSnippet", {
      method: "PUT",
      body: JSON.stringify({ code, language, description, name, mail:email }),
      headers: {
        "Content-Type": "application/json",
      },
    })
    router.push("/")
  }
  catch (error) {
    console.log(error)
  }
}

Agora podemos continuar fazendo com que os botões Editar e Excluir sejam exibidos apenas se o e-mail corresponder.

Primeiro, passamos os user.mailcomo adereços para o Snippetcomponente em index.js:

<Snippets
  key={snippet.id}
  snippet={snippet}
  snippetDeleted={mutate}
+ email={session.user.email}
/>

Então em Snippet.js:

function Snippets({ snippet, snippetDeleted, email }) {
...
  {email == snippet.data.mail && (
    <>
      <div className={styles.links}>
        <Link href={`/edit/${snippet.id}`}>
          <a>Edit</a>
        </Link>
        <a onClick={deleteSnippet}>Delete</a>
      </div>
    </>
  )}
  ...
}

Testando nosso aplicativo

Execute npm run devna CLI e abra o aplicativo em seu navegador. Agora, se você criar um novo snippet, o e-mail do usuário será adicionado ao banco de dados. Se o e-mail não corresponder, os botões Editar e Excluir não serão exibidos na página de exibição do snippet. Você pode testar isso fazendo login com um endereço de e-mail diferente do usado para criar os trechos de código.

Conclusão

Finalmente chegamos ao final deste tutorial. Aprendemos como construir um aplicativo CRUD com Next.js e FaunaDB e como realizar operações CRUD com base na autenticação do usuário.

fonte: https://www.sitepoint.com/nextjs-faunadb-build-code-snippet-app/

#nextjs #faunadb 

What is GEEK

Buddha Community

Como Criar Um Aplicativo CRUD Com Next.js E FaunaDB

NBB: Ad-hoc CLJS Scripting on Node.js

Nbb

Not babashka. Node.js babashka!?

Ad-hoc CLJS scripting on Node.js.

Status

Experimental. Please report issues here.

Goals and features

Nbb's main goal is to make it easy to get started with ad hoc CLJS scripting on Node.js.

Additional goals and features are:

  • Fast startup without relying on a custom version of Node.js.
  • Small artifact (current size is around 1.2MB).
  • First class macros.
  • Support building small TUI apps using Reagent.
  • Complement babashka with libraries from the Node.js ecosystem.

Requirements

Nbb requires Node.js v12 or newer.

How does this tool work?

CLJS code is evaluated through SCI, the same interpreter that powers babashka. Because SCI works with advanced compilation, the bundle size, especially when combined with other dependencies, is smaller than what you get with self-hosted CLJS. That makes startup faster. The trade-off is that execution is less performant and that only a subset of CLJS is available (e.g. no deftype, yet).

Usage

Install nbb from NPM:

$ npm install nbb -g

Omit -g for a local install.

Try out an expression:

$ nbb -e '(+ 1 2 3)'
6

And then install some other NPM libraries to use in the script. E.g.:

$ npm install csv-parse shelljs zx

Create a script which uses the NPM libraries:

(ns script
  (:require ["csv-parse/lib/sync$default" :as csv-parse]
            ["fs" :as fs]
            ["path" :as path]
            ["shelljs$default" :as sh]
            ["term-size$default" :as term-size]
            ["zx$default" :as zx]
            ["zx$fs" :as zxfs]
            [nbb.core :refer [*file*]]))

(prn (path/resolve "."))

(prn (term-size))

(println (count (str (fs/readFileSync *file*))))

(prn (sh/ls "."))

(prn (csv-parse "foo,bar"))

(prn (zxfs/existsSync *file*))

(zx/$ #js ["ls"])

Call the script:

$ nbb script.cljs
"/private/tmp/test-script"
#js {:columns 216, :rows 47}
510
#js ["node_modules" "package-lock.json" "package.json" "script.cljs"]
#js [#js ["foo" "bar"]]
true
$ ls
node_modules
package-lock.json
package.json
script.cljs

Macros

Nbb has first class support for macros: you can define them right inside your .cljs file, like you are used to from JVM Clojure. Consider the plet macro to make working with promises more palatable:

(defmacro plet
  [bindings & body]
  (let [binding-pairs (reverse (partition 2 bindings))
        body (cons 'do body)]
    (reduce (fn [body [sym expr]]
              (let [expr (list '.resolve 'js/Promise expr)]
                (list '.then expr (list 'clojure.core/fn (vector sym)
                                        body))))
            body
            binding-pairs)))

Using this macro we can look async code more like sync code. Consider this puppeteer example:

(-> (.launch puppeteer)
      (.then (fn [browser]
               (-> (.newPage browser)
                   (.then (fn [page]
                            (-> (.goto page "https://clojure.org")
                                (.then #(.screenshot page #js{:path "screenshot.png"}))
                                (.catch #(js/console.log %))
                                (.then #(.close browser)))))))))

Using plet this becomes:

(plet [browser (.launch puppeteer)
       page (.newPage browser)
       _ (.goto page "https://clojure.org")
       _ (-> (.screenshot page #js{:path "screenshot.png"})
             (.catch #(js/console.log %)))]
      (.close browser))

See the puppeteer example for the full code.

Since v0.0.36, nbb includes promesa which is a library to deal with promises. The above plet macro is similar to promesa.core/let.

Startup time

$ time nbb -e '(+ 1 2 3)'
6
nbb -e '(+ 1 2 3)'   0.17s  user 0.02s system 109% cpu 0.168 total

The baseline startup time for a script is about 170ms seconds on my laptop. When invoked via npx this adds another 300ms or so, so for faster startup, either use a globally installed nbb or use $(npm bin)/nbb script.cljs to bypass npx.

Dependencies

NPM dependencies

Nbb does not depend on any NPM dependencies. All NPM libraries loaded by a script are resolved relative to that script. When using the Reagent module, React is resolved in the same way as any other NPM library.

Classpath

To load .cljs files from local paths or dependencies, you can use the --classpath argument. The current dir is added to the classpath automatically. So if there is a file foo/bar.cljs relative to your current dir, then you can load it via (:require [foo.bar :as fb]). Note that nbb uses the same naming conventions for namespaces and directories as other Clojure tools: foo-bar in the namespace name becomes foo_bar in the directory name.

To load dependencies from the Clojure ecosystem, you can use the Clojure CLI or babashka to download them and produce a classpath:

$ classpath="$(clojure -A:nbb -Spath -Sdeps '{:aliases {:nbb {:replace-deps {com.github.seancorfield/honeysql {:git/tag "v2.0.0-rc5" :git/sha "01c3a55"}}}}}')"

and then feed it to the --classpath argument:

$ nbb --classpath "$classpath" -e "(require '[honey.sql :as sql]) (sql/format {:select :foo :from :bar :where [:= :baz 2]})"
["SELECT foo FROM bar WHERE baz = ?" 2]

Currently nbb only reads from directories, not jar files, so you are encouraged to use git libs. Support for .jar files will be added later.

Current file

The name of the file that is currently being executed is available via nbb.core/*file* or on the metadata of vars:

(ns foo
  (:require [nbb.core :refer [*file*]]))

(prn *file*) ;; "/private/tmp/foo.cljs"

(defn f [])
(prn (:file (meta #'f))) ;; "/private/tmp/foo.cljs"

Reagent

Nbb includes reagent.core which will be lazily loaded when required. You can use this together with ink to create a TUI application:

$ npm install ink

ink-demo.cljs:

(ns ink-demo
  (:require ["ink" :refer [render Text]]
            [reagent.core :as r]))

(defonce state (r/atom 0))

(doseq [n (range 1 11)]
  (js/setTimeout #(swap! state inc) (* n 500)))

(defn hello []
  [:> Text {:color "green"} "Hello, world! " @state])

(render (r/as-element [hello]))

Promesa

Working with callbacks and promises can become tedious. Since nbb v0.0.36 the promesa.core namespace is included with the let and do! macros. An example:

(ns prom
  (:require [promesa.core :as p]))

(defn sleep [ms]
  (js/Promise.
   (fn [resolve _]
     (js/setTimeout resolve ms))))

(defn do-stuff
  []
  (p/do!
   (println "Doing stuff which takes a while")
   (sleep 1000)
   1))

(p/let [a (do-stuff)
        b (inc a)
        c (do-stuff)
        d (+ b c)]
  (prn d))
$ nbb prom.cljs
Doing stuff which takes a while
Doing stuff which takes a while
3

Also see API docs.

Js-interop

Since nbb v0.0.75 applied-science/js-interop is available:

(ns example
  (:require [applied-science.js-interop :as j]))

(def o (j/lit {:a 1 :b 2 :c {:d 1}}))

(prn (j/select-keys o [:a :b])) ;; #js {:a 1, :b 2}
(prn (j/get-in o [:c :d])) ;; 1

Most of this library is supported in nbb, except the following:

  • destructuring using :syms
  • property access using .-x notation. In nbb, you must use keywords.

See the example of what is currently supported.

Examples

See the examples directory for small examples.

Also check out these projects built with nbb:

API

See API documentation.

Migrating to shadow-cljs

See this gist on how to convert an nbb script or project to shadow-cljs.

Build

Prequisites:

  • babashka >= 0.4.0
  • Clojure CLI >= 1.10.3.933
  • Node.js 16.5.0 (lower version may work, but this is the one I used to build)

To build:

  • Clone and cd into this repo
  • bb release

Run bb tasks for more project-related tasks.

Download Details:
Author: borkdude
Download Link: Download The Source Code
Official Website: https://github.com/borkdude/nbb 
License: EPL-1.0

#node #javascript

Mélanie  Faria

Mélanie Faria

1660212000

Crie Um Aplicativo CRUD Com Next.js E FaunaDB

Durante a programação, os desenvolvedores encontram problemas que exigem a reutilização de código, levando a uma programação repetitiva que pode desperdiçar tempo e reduzir a produtividade. Isso dá origem à necessidade de código-fonte reutilizável chamado “snippets de código”. Esses trechos evitam código repetitivo durante a programação, podem ser salvos para uso futuro e são compartilháveis.

Neste tutorial, criaremos um site para ajudar os usuários a salvar trechos de código diários usando a estrutura de desenvolvimento da Web Next.js e alimentado pelo banco de dados Fauna para lidar com o armazenamento, manipulação e exibição de trechos de código. Trabalhando neste projeto divertido, também aprenderemos como criar um aplicativo CRUD básico com Next.js e FaunaDB que também pode ser usado para construir outros projetos semelhantes.

Uma versão funcional deste projeto pode ser encontrada no GitHub . Para acompanhar, você precisará do Node instalado em sua máquina , bem como uma conta FaunaDB e uma conta Google (para autenticação).

Instalando dependências

Nesta seção, veremos como instalar o Next.js usando o npx create-next-appcomando. Isso inicializará a Next CLI e criará um novo aplicativo Next.js.

Também instalaremos as dependências que usaremos para o back-end — FaunaDB e SWR — por meio da linha de comando. SWR (state-while-revalidate) é um gancho Next.js para buscar dados. Vamos entrar nisso em profundidade mais tarde neste tutorial.

Instalação do Next.js

Para instalar o Next.js, digite o seguinte comando na CLI:

npx create-next-app snippetapp

O comando acima cria um diretório de projeto chamado snippetappcom o modelo inicial Next.js, que contém os arquivos necessários para codificação com Next. Quando o Next terminar a instalação, mude para o diretório recém-criado:

cd snippetapp

Instalação FaunaDB e SWR

Para instalar o Fauna, usaremos o seguinte comando na CLI:

npm install --save faunadb

Em seguida, para instalar o SWR:

npm install swr@0.3.8

Com isso, instalamos todas as dependências que usaremos para construir nossa aplicação e agora podemos prosseguir para configurar nosso banco de dados no Fauna.

Configuração do FaunaDB

O FaunaDB é um banco de dados em tempo real sem servidor. Ele transforma um banco de dados tradicional em uma API de dados flexível que ainda retém os recursos de um banco de dados e seu desempenho, ao mesmo tempo em que oferece acesso seguro e escalável aos dados do aplicativo.

Aqui, criaremos uma conta de usuário e configuraremos o banco de dados para armazenar os dados que usaremos em nosso aplicativo de snippet.

Criando uma conta de usuário

Para criar uma conta de usuário, navegue até a página de inscrição do Fauna e crie uma conta.

Criando uma conta FaunaDB

Depois de criar uma conta de usuário, você será redirecionado para o painel.

O painel do FaunaDB

Crie o banco de dados e a coleção de snippets

Aqui, criaremos um banco de dados com as coleções necessárias para gerenciar os trechos de código do nosso aplicativo. Clique em CRIAR BANCO DE DADOS . Vamos criar um banco de dados chamado snippets.

Criando um banco de dados de trechos do FaunaDB

Na nova página que se abre, clique em NOVA COLEÇÃO e crie uma coleção chamada codesnippet.

página de coleção

Depois de criar uma coleção, obtemos uma página onde podemos criar um documento.

Criar um documento

Aqui, você clicará em NOVO DOCUMENTO . Um documento JSON será aberto, onde você poderá inserir os detalhes, conforme ilustrado abaixo.

Criação de documentos

{
  name: "Prompt User",
  description: "prompts the user",
  language: "javascript",
  code: "prompt('would you like to continue')"
}

Aqui, definimos um snippet com atributos: name, description, languagee code. Clique em SALVAR para salvar a nova coleção. Adicionamos com sucesso um snippet ao nosso banco de dados. Agora podemos prosseguir para obter nossas credenciais de acesso para usar em nosso aplicativo.

Criação de chave secreta

No painel, clique em Segurança . Isso abre uma nova página para criar nossa chave de segurança.

Criando uma chave de segurança do FaunaDB

Aqui, definiremos a função como “servidor” em vez de “admin”, e você pode dar um nome à chave. Clique no botão SAVE para gerar sua chave.

Criando um .envarquivo

Agora vamos criar um .envarquivo dentro do diretório do nosso projeto. Este arquivo armazenará nossa chave secreta gerada. No .envarquivo temos isso:

FAUNA_SECRET = paste your key here

Criando uma página de trecho de código

Nesta seção, criaremos a página de exibição e upload para os snippets e também adicionaremos funcionalidades a ela.

Abra o diretório do projeto em seu editor de código e navegue até o index.jsarquivo na pasta de páginas. Aqui vamos limpar o código e começar a construir nosso aplicativo:

import Head from "next/head"
import Image from "next/image"
import styles from "../styles/Home.module.css"

export default function Home() {
  return (
    <div className={styles.container}>
      <Head>
        <title>View Snippet</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.main}>
        <h1 className={styles.title}>Re-usuable Code Snippets</h1>
        <p className={styles.info}>Add your code snippets here...</p>
        <button>Create new snippet</button>
      </main>
    </div>
  )
}

Criando nossos componentes

Agora vamos criar um arquivo componente que irá renderizar nossos trechos. Crie uma pasta nomeada componentem seu diretório de trabalho e crie um arquivo nomeado Snippets.jsdentro dela com o seguinte código:

import React from "react"
import styles from "../styles/Home.module.css"

function Snippets() {
  return (
    <div className={styles.cont}>
      <p className={styles.lang}>language</p>
      <h3 className={styles.name}>name of snippet</h3>
      <p className={styles.descp}>description of snippet</p>
      {/* Code will be displayed here*/}
      <div className={styles.links}>
        <a>Edit</a>
        <a>Delete</a>
      </div>
    </div>
  )
}

export default Snippets

Importando nossos componentes para o aplicativo

Agora adicionaremos importações para este arquivo em nosso index.js:

import Snippets from "../components/Snippets"

E use-o em nosso aplicativo:

<button>Create new snippet</button>
<Snippets/>

Estilizando nosso aplicativo

Agora podemos estilizar nossa página. Navegue até o Home.module.cssarquivo na stylespasta e substitua os estilos pelo seguinte:

.container{
  display: flex;
  height: 100%;
  min-height: 100vh;
  background: rgb(48, 48, 255);
  flex-direction: column;
  align-items: center;
  color: #fff;
  font-family: Montserrat;
}
.cont{
  color: #333;
  margin-top: 5px;
  background: rgb(214, 214, 214);
  border-radius: 15px;
  padding: 10px 15px;
}
.main button{
  width: fit-content;
  flex-grow: unset;
  display: inline-block;
  padding: 5px 10px;
  outline: none;
  border: none;
  border-radius: 5%;
  font-weight: bold;
  color: rgb(48, 48, 255);
}
.main button:hover{
  cursor: pointer;
}
.links{
  margin-top: 10px;
}
.links a{
  margin-left: 5px;
}
.links a:hover{
  cursor: pointer;
}

Visualizando nosso aplicativo

Neste ponto, você poderá iniciar o servidor dev com npm run dev, visite http://localhost:3000 e veja o esqueleto do nosso aplicativo.

Configurando a Área de Exibição de Fragmentos

Em seguida, criaremos a seção de exibição para o código do snippet. Crie um novo arquivo chamado Code.jsna pasta de componentes e importe-o para Snippets.js:

import React from 'react'
import styles from '../styles/Home.module.css'
import Code from "./Code";

function Snippets() {
  return (
    <div className={styles.cont}>
      <p className={styles.lang}>language</p>
      <h3 className={styles.name}>name of snippet</h3>
      <p className={styles.descp}>description of snippet</p>

      {/* Code will be displayed here*/}
      <Code />

      <div className={styles.links}>
        <a>Edit</a>
        <a>Delete</a>
      </div>
    </div>
  )
}

export default Snippets

Para o realce de sintaxe do código, usaremos dois pacotes, a saber, react-syntax-highlighter e react-copy-to-clipboard . Podemos fazer o download através da CLI:

npm install react-syntax-highlighter react-copy-to-clipboard --save

Então em Code.js:

import React from "react"
import { PrismLight as SyntaxHighlighter } from "react-syntax-highlighter"
import {atomDark} from "react-syntax-highlighter/dist/cjs/styles/prism"
import { CopyToClipboard } from "react-copy-to-clipboard"
import styles from "../styles/Home.module.css"
function Code() {
  const codeString = "npm install import react from 'react'"
  const [show, setshow] = React.useState(false)
  return (
    <div>
      <button onClick={() => setshow(!show)}>Show Code</button>
      {show ? (
        <div>
          <CopyToClipboard text={codeString}>
            <button className={styles.btn}>Copy</button>
          </CopyToClipboard>
          <SyntaxHighlighter language="javascript" style={atomDark}>
            {codeString}
          </SyntaxHighlighter>
        </div>
      ) : null}
    </div>
  )
}
export default Code

Aqui, criamos um componente para exibir código com realce de sintaxe. Também adicionamos a funcionalidade de cópia e alternância de exibição. Agora no stylesarquivo:

.btn{
  left: 80%;
  position: relative;
}

Testando os blocos de código

Para visualizar essa alteração, você pode executar npm run devna linha de comando e visualizá-la em seu navegador. Temos a string “npm install import react from 'react'” exibida com destaque de sintaxe como um bloco de código. Há também um botão para ocultar e exibir o trecho de código e um botão que nos permite copiar o código do bloco de código.

Inicialização do FaunaDB

Nesta seção, buscaremos dados do nosso banco de dados FaunaDB para nosso aplicativo. Crie um arquivo chamado Fauna.jsno diretório do seu projeto:

const faunadb = require("faunadb")
const faunaClient = new faunadb.Client({
  secret: process.env.FAUNA_SECRET
})
const q = faunadb.query
const getResponse = async () => {
  const { data } = await faunaClient.query(
    q.Map(
      q.Paginate(q.Documents(q.Collection("codesnippet"))),
      q.Lambda("doc", q.Get(q.Var("doc")))
    )
  )
  const snippets = data.map((snippet) => {
    snippet.id = snippet.ref.id
    delete snippet.ref
    return snippet
  })
  return snippets
}

module.exports = {
  getResponse,
}

Aqui, inicializamos o FaunaDB com nossa chave secreta. Também configuramos uma asyncsolicitação para consultar nossa coleção e retornar os dados. Armazenamos os dados retornados em uma variável chamada snippetse excluímos a ref para estruturar melhor os dados. Outras funcionalidades para criar, atualizar e excluir snippets serão adicionadas posteriormente neste tutorial.

Observe que, se você estiver recebendo um erro não autorizado no console, talvez seja necessário especificar o nome de domínio do endpoint de destino. O padrão é db.fauna.com, mas desde a introdução dos Region Groups , três domínios de nuvem estão disponíveis. Use o domínio correto para o Grupo de regiões do seu banco de dados:

  • Clássico (EUA e UE):db.fauna.com
  • Estados Unidos (EUA):db.us.fauna.com
  • Europa (UE):db.eu.fauna.com

Código de exemplo:

const faunaClient = new faunadb.Client({
  secret: process.env.FAUNA_SECRET,
  domain: "db.eu.fauna.com"
})

Lidando com nossas solicitações de API

Também criaremos um arquivo para lidar com nossa solicitação de API para nosso banco de dados. Dentro da apipasta em pages, crie um arquivo chamado snippets.jscom o seguinte código:

import { getResponse } from "../../Fauna.js"
export default async function handler(req, res) {
  console.log(req)
  if (req.method !== "GET") {
    return res.status(405)
  }
  try {
    const snippets = await getResponse()
    return res.status(200).json(snippets)
  } catch (err) {
    console.log(err)
      res.status(500).json({ msg: "Something went wrong." })
  }
}

Acima, simplesmente configuramos uma função para lidar com solicitações do nosso banco de dados. Os snippets são retornados como Jsone registrarão erros se ocorrerem. No Next.js, qualquer arquivo armazenado na apipasta é tratado como endpoints de API em vez de uma página e é renderizado no lado do servidor.

O que é SWR?

Como dito anteriormente, SWR (state-while-revalidate) é um gancho Next.js para buscar dados. É uma solução perfeita para buscar dados atualizados com frequência e é uma boa opção para o nosso aplicativo.

Configurando o SWR

Usaremos isso para buscar dados do FaunaDB. Para usar isso, precisamos importá-lo para index.js:

import useSWR from "swr"

export default function Home() {
  const { data:snippets, mutate }=useSWR("api/snippets")
  ...
})

Aqui, importamos o SWR e o usamos para buscar dados conforme configurado em snippets.js. Em seguida, armazenamos esses trechos na snippetsvariável e os produziremos a partir daí. Agora vamos passar o snippetspara o nosso Snippetscomponente para exibir:

- <Snippets />

+ {snippets &&
+   snippets.map((snippet) => (
+     <Snippets
+       key={snippet.id}
+       snippet={snippet}
+       snippetDeleted={mutate}
+     />
+   ))
+ }

Acima, passamos a chave e o snippet para Snippets. Também configuramos uma mutatepropriedade para atualizar (re-buscar) snippets quando um snippet é excluído. Para usar os dados passados, modificamos o Snippetscomponente com o seguinte:

function Snippets({snippet}) {
  return (
    <div className={styles.cont}>
      <p className={styles.lang}>{snippet.data.language}</p>
      <h3 className={styles.name}>{snippet.data.name}</h3>
      <p className={styles.descp}>{snippet.data.description}</p>

      <Code snippet={snippet}/>

      <div className={styles.links}>
        <a>Edit</a>
        <a>Delete</a>
      </div>
    </div>
  )
}

Acima, inserimos o idioma do snippet, nome e descrição recebidos do FaunaDB em nosso código. Para obter o código da Fauna em nosso aplicativo, também tivemos que passar o snippet prop para o Codecomponente.

Então no Codecomponente:

function Code({snippet}){
  ...
  <div>
    <CopyToClipboard text={snippet.data.code}>
      <button className={styles.btn}>Copy</button>
    </CopyToClipboard>
    <SyntaxHighlighter language="javascript" style={atomDark}>
      {snippet.data.code}
    </SyntaxHighlighter>
  </div>
  ...
}

Agora terminamos com a GetSnippetfuncionalidade. Se retornarmos ao FaunaDB e criarmos um novo snippet, veremos o que está ilustrado abaixo.

adicionou outro documento ao FaunaDB

{
  "name": "console.log()",
  "language": "javascript",
  "description": "logs out data",
  "code": "console.log('Hello, world!')"'
}

Executando o Código

Para executar na CLI:

npm run dev

Se você abrir a página em seu navegador, terá um resultado semelhante ao da imagem abaixo.

Página de exibição de trecho

Criamos com sucesso uma página de exibição de snippet com funcionalidades para mostrar e ocultar o código e copiar o snippet de código.

A página de carregamento de fragmentos

Precisaremos criar um link para a página de upload do nosso componente inicial. O Next.js tem provisões que facilitam o roteamento sem que você precise instalar react-routere outras dependências, como faria se estivesse usando o código React nativo.

Em index.js, importaremos o Linkmódulo de next:

import Link from "next/link"

Em seguida, adicione-o ao nosso botão Criar novo trecho :

- <button>Create new snippet</button>

+ <Link href="/upload">
+   <button>Create new snippet</button>
+ </Link>

Vamos criar uma nova página em nossa pagespasta e nomeá-la upload.js.

De volta ao nosso Fauna.jsarquivo, vamos criar e também exportar uma função para criar snippets em nosso aplicativo:

const createSnippet = async (code, language, description, name) => {
  return await faunaClient.query(q.Create(q.Collection("codesnippet"), {
    data:{code, language, description, name}
  }))
}

module.exports = {
  getResponse,
  createSnippet,
}

Aqui, criamos a função createSnippet, que receberá alguns parâmetros e os passará como dados no novo documento que será criado no banco de dados.

Adicionando uma função para criar snippets

Também configuraremos nosso endpoint para criar snippets. Crie um novo arquivo chamado createSnippet.jsna apipasta e preencha-o com o seguinte código:

import { createSnippet } from "../../Fauna"

export default async function handler(req, res) {
  const { code, language, description, name } = req.body
  if (req.method !== "POST") {
    return res.status(405).json({msg:"unauthorized"})
  }
  try {
    const createdSnippet = await createSnippet(code, language, description, name)
    return res.status(200).json(createdSnippet)
  } catch (error) {
    console.log(error)
    res.status(500).json({msg:"unauthorized"})
  }
}

Criando nossa página de upload

Agora vamos criar a página de upload em nosso upload.jsarquivo. Para nosso formulário criar snippets, usaremos o react-hook-form . Vamos instalar isso via CLI:

npm install react-hook-form

Então, em nosso upload.jsarquivo:

import React from "react"
import { useForm } from "react-hook-form"
import { useRouter } from "next/router"
import style from "../styles/form.module.css"
import { Link } from "next/link"

function upload({ snippet }) {
  const { register, handleSubmit, errors, reset } = useForm()
  const router = useRouter()
  const createSnippet = async (data) => {
    const { code, language, description, name } = data
    console.log(data)
    try {
      // code here to push to Fauna
    } catch (error) {
      console.log(error)
    }
  }
  return (
    <div className={style.cont}>
      <form
        className={style.form}
        onSubmit={handleSubmit(snippet ? updateSnippet : createSnippet)}
      >
        <div>
          <label htmlFor="name">Name</label>
          <input
            className={style.input}
            type="text"
            id="name"
            {...register("name", { required: true })}
          />
        </div>
        <div>
          <label className={style.label} htmlFor="language">
            language
          </label>
          <select
            className={style.select}
            type="text"
            id="language"
            {...register("language", { required: true })}
          >
            <option>Javascript</option>
            <option>Html</option>
            <option>CSS</option>
          </select>
        </div>
        <div>
          <label className={style.label} htmlFor="description">
            description
          </label>
          <textarea
            className={style.input}
            rows={7}
            type="text"
            id="description"
            placeholder="snippet description"
            {...register("description", { required: true })}
          />
        </div>
        <div>
          <label className={style.label} htmlFor="code">
            Code
          </label>
          <textarea
            className={style.input}
            rows={8}
            columns={8}
            type="text"
            id="code"
            {...register("code", { required: true })}
            placeholder="background: none;"
          />
        </div>
        <div>
          <button className={style.button}>Submit</button>
          <button className={style.button}>Cancel</button>
        </div>
      </form>
    </div>
  )
}
export default upload

Estilizando nosso formulário

Acima, criamos nosso formulário usando o react-hook-formpacote. Usamos a handleSubmitfunção useForm()no operador ternário. Ao enviar o formulário, determina se o envio realizado é para criar ou atualizar um trecho existente. Com register, adicionamos a propriedade obrigatória a todos os campos do nosso formulário. Também adicionamos importações para uma folha de estilo chamada form.module.cssonde temos os seguintes estilos para nosso formulário:

.form {
  max-width: 800px;
  display: flex;
  justify-content: center;
  flex-direction: column;
  align-items: center;
}

.cont{
  background: rgb(48, 48, 255);
  height: 100%;
  min-height: 100vh;
  padding: 10px 0 0 0;
  display: flex;
  justify-content: center;
  align-items: center;
}

.select,
.input {
  display: block;
  box-sizing: border-box;
  width: 100%;
  border-radius: 4px;
  border: 1px solid black;
  padding: 10px 15px;
  margin-bottom: 15px;
  font-size: 14px;
}

.label{
  line-height: 2;
  text-align: left;
  display: block;
  margin: 5px;
  color: white;
  font-size: 14px;
  font-weight: 200;
}

.button {
  background : #fff;
  color: #444;
  border: none;
  border-radius: 5%;
  margin-right: 8px;
}

Como criar um endpoint de API para criar snippets

Para enviar os dados do nosso formulário para o banco de dados FaunaDB, adicione o seguinte código ao try...catchbloco na createSnippetfunção em upload.js:

try {
  await fetch("/api/createSnippet", {
    method: "POST",
    body: JSON.stringify({ code, language, description, name }),
    headers: {
      "Content-type": "application/json"
    },
  })
  router.push("/")
} catch (error) {
  console.log(error)
}

Testando nosso formulário

Execute o código e navegue até a página de upload. Agora, se adicionarmos um novo snippet ao formulário e clicarmos em Submit , veremos o que está na imagem abaixo.

Página de carregamento de fragmentos

Quando navegamos para nosso componente inicial, podemos ver o trecho criado.

Criou um novo trecho

Criando uma função para editar snippets

Para criar nossa funcionalidade de edição de trechos, de volta ao Fauna.jsarquivo, criaremos e exportaremos uma função para lidar com essa tarefa:

const updateSnippet = async (id, code, language, description, name) => {
  return await faunaClient.query(q.Update(q.Ref(q.Collection("codesnippet"), id), {
    data: {code, language, name, description},
  }))
}
module.exports = {
  ...
  updateSnippet,
}

Como criar um endpoint de API para editar snippets

Essa função é semelhante à createSnippetfunção, mas também recebe um parâmetro de id. Ele usa esse ID para identificar quais snippets devem ser editados. Se idcorresponder, atualizamos os dados com os outros parâmetros. Também criaremos um arquivo de endpoint no apidiretório chamado updateSnippet.jspara lidar com as atualizações:

import { updateSnippet } from "../../Fauna"
export default async function handler(req, res) {
  const { id, code, language, description, name } = req.body
  if (req.method !== "PUT") {
    return res.status(405).json({ msg: "unauthorized" })
  }
  try {
    const updated = await updateSnippet(
      id,
      code,
      language,
      description,
      name
    )
    return res.status(200).json(updated)
  }
  catch (error) {
    console.log(error)
    res.status(500).json({ msg: "unauthorized" })
  }
}

Vinculando nosso botão Editar

Agora, vá para o Snippetscomponente e modifique este componente para fazer uso desta função. Primeiro, vamos importar o Linkmódulo:

...
import Link from "next/link"

Também modificamos nosso editbotão:

- <a>Edit</a>

+ <Link href={`/edit/${snippet.id}`}>
+   <a>Edit</a>
+ </Link>

Manipulando o snippet de edição

Quando clicado, ele envia uma solicitação para a página editcom o idsnippet selecionado. Na pagespasta, crie uma pasta nomeada editcom um arquivo [id].jsdentro dela:

import { getSnippetById } from "../../Fauna"
import Upload from "../upload"
export default function Home({ snippet }) {
  const email = ""
  const user = ""
  return (
    <div>
      <h3>Update a snippet</h3>
      <Upload snippet={snippet} email={email} user={user}/>
    </div>
  )
}

export async function getServerSideProps(context) {
  try {
    //get and update record
    const id = context.params.id
  }
  catch (error) {
    console.log(error)
    context.res.statusCode = 302
    context.res.setHeader("Location", "/")
    return {props: {}}
  }
}

Em [id].js, estamos passando o snippet de código propspara a página de upload do snippet. No entanto, desta vez, a página de upload conterá os dados armazenados no snippet de código referenciado pelo id. Para buscar o snippet por ID, precisaremos criar a getSnippetByIdfunção no Fauna.jsarquivo:

const getSnippetById = async (id) => {
  const snippet = await faunaClient.query(q.Get(q.Ref(q.Collection("codesnippet"),id)))
  snippet.id = snippet.ref.id
  delete snippet.ref
  return snippet
}

module.exports = {
  getResponse,
  createSnippet,
  updateSnippet,
  getSnippetById,
}

À medida que exportamos a função, de volta ao [id].jsarquivo, podemos usá-la para buscar um trecho específico com seu ID:

try {
  const id = context.params.id;
  const snippet = await getSnippetById(id);
  return {
    props: { snippet },
  };
} catch (error) {
  // as before
}

Modificando snippets armazenados

Agora, no upload.jsarquivo, vamos modificá-lo para poder acessar os dados armazenados se um snippet for editado:

- const { register, handleSubmit, errors, reset } = useForm()

+ const { register, handleSubmit, errors, reset } = useForm({
+   defaultValues: {
+     code: snippet ? snippet.data.code : "",
+     language: snippet ? snippet.data.language : "",
+     description: snippet ? snippet.data.description : "",
+     name: snippet ? snippet.data.name : "",
+   }
+ })

O código acima verifica se o snippet tem dados armazenados nele. Se retornar true, retorna os dados para os parâmetros: code, language, descriptione code. Se ele retornar false, ele retornará uma string vazia.

Em seguida, criaremos uma função para atualizar o snippet de código:

const createSnippet = async (data) => { ... }

const updateSnippet = async (data) => {
  const { code, language, description, name } = data
  const id = snippet.id
  try {
    await fetch("/api/updateSnippet", {
      method: "PUT",
      body: JSON.stringify({ code, language, description, name, id }),
      headers: {
        "Content-Type": "application/json",
      },
    })
    router.push("/")
  }
  catch (error) {
    console.log(error)
  }
}

return ( ,,, )

Testando a funcionalidade de edição de trecho

Se executarmos nosso código, podemos editar os trechos de código criados anteriormente clicando no botão Editar , alterando os dados do formulário e clicando em Enviar .

Adicionando a funcionalidade Excluir snippet

Agora, se retornarmos ao Homecomponente em nosso navegador, poderemos editar e atualizar trechos de código. Podemos finalmente adicionar a funcionalidade final para excluir nosso trecho de código. Crie e exporte uma nova função — deleteSnippet— no Fauna.jsarquivo:

const deleteSnippet = async (id) => {
  return await faunaClient.query(q.Delete(q.Ref(q.Collection("codesnippet"),id)))
}

module.exports = {
  ...
  deleteSnippet,
}

Criando o endpoint da API para a funcionalidade de exclusão

Vamos criar outro endpoint para esta função em nossa apipasta chamada deleteSnippet.jse preenchê-la com o seguinte código:

import { deleteSnippet } from "../../Fauna"
export default async function handler(req, res) {
  if (req.method !== "DELETE") {
    return res.status(405).json({ msg: "unauthorized" })
  }
  const { id } = req.body
  try {
    const deleted = await deleteSnippet(id)
    return res.status(200).json(deleted)
  }
  catch (error) {
    console.log(error)
    res.status(500).join({ msg: "error occured" })
  }
}

Em seguida, modificamos o Snippets.jsarquivo para adicionar a nova funcionalidade:

function Snippets({ snippet, snippetDeleted }) {
  ...
}

Em seguida, crie uma deleteSnippetfunção para buscar o endpoint do apie exclua o snippet referenciado pelo ID:

function Snippets({snippet, snippetDeleted}) {
  const deleteSnippet = async () => {
    try {
      await fetch("/api/deleteSnippet", {
        method: "DELETE",
        body: JSON.stringify({ id: snippet.id }),
        headers: {
          "Content-Type": "application/json",
        },
      });
      snippetDeleted();
    } catch (e) {
      console.log(e);
    }
  };

  return (
    <div className={styles.cont}>
      <p className={styles.lang}>{snippet.data.language}</p>
      <h3 className={styles.name}>{snippet.data.name}</h3>
      <p className={styles.descp}>{snippet.data.description}</p>

      <Code snippet={snippet}/>

      <div className={styles.links}>
        <Link href={`/edit/${snippet.id}`}>
          <a>Edit</a>
        </Link>
        <a onClick={deleteSnippet}>Delete</a>
      </div>
    </div>
  )
}

Também atualizamos o elemento âncora para chamar a deleteSnippetfunção quando ela é clicada.

Testando a funcionalidade de exclusão

Adicionamos a funcionalidade para excluir trechos de código. Agora podemos excluir trechos clicando no botão Excluir em nosso aplicativo.

Isso conclui as funcionalidades do aplicativo snippet. Vamos agora adicionar medidas de autenticação ao nosso aplicativo para permitir que apenas usuários autorizados criem ou modifiquem snippets em nosso aplicativo.

Autenticação de usuário

Por que precisamos de autenticação? Atualmente, os usuários podem criar snippets, mas também podem excluir e modificar snippets que não criaram. Precisaremos fornecer um meio para autorizar os usuários a acessar nosso site - e, portanto, a necessidade de autenticação do usuário.

Instalaremos o next-auth para autenticação por meio de nossa CLI:

npm i next-auth

Usaremos um token JWT para nossa autenticação. JWT é um padrão usado para criar tokens de acesso para um aplicativo.

Crie uma pasta nomeada authem sua apipasta e dentro dela, crie um arquivo [...nextauth].jscom o seguinte código:

import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google"

export default NextAuth({
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth?prompt=consent&access_type=offline&response_type=code",
    })
  ],
  jwt: {
    encryption: true
  },
  secret: process.env.secret,
  callbacks: {
    async jwt(token, account) {
      if (account ?.accessToken) {
        token.accessToken = account.accessToken
      }
      return token
    },
    redirect: async (url, _baseUrl)=>{
      if (url === "/profile") {
        return Promise.resolve("/")
      }
      return  Promise.resolve("/")
    }
  }
})

Depois disso, encerraremos nossos componentes no _app.jsarquivo:

import '../styles/globals.css'
import {Provider} from "next-auth/client"

function MyApp({ Component, pageProps }) {
  return (
    <Provider session={pageProps.session}>
      <Component {...pageProps} />
    </Provider>
  )
}
export default MyApp

Manipulando a autenticação

Vamos modificar nosso Homecomponente index.jspara retornar ao nosso componente se o usuário for autenticado, caso contrário ele retornará um botão que leva à página de autenticação:

import {signIn, signOut, useSession} from "next-auth/client"
...

Então dentro de Home:

export default function Home() {
  const { data:snippets, mutate }=useSWR("api/snippets")

  const [session, loadingSession] = useSession()
  if (loadingSession) {
    <>
      <p>...authenticating</p>
    </>
  }
  ...
}

O código acima verifica se o aplicativo é loadingSession. Se true, ele retorna o pbloco de tags, caso contrário, retorna o restante do nosso aplicativo se houver session. Em seguida, renderizaremos o “log in” se não houver sessão:

return (
  <div className={styles.container}>
    <Head> ... </Head>

    {!session && (
      <>
        <h1>Sign in to access snippet app</h1>
        <button onClick={() => signIn()}>Sign In</button>
      </>
    )}

    {session && (
      <>
        <main className={styles.main}>
          <h3>welcome {session.user.email}</h3>
          <button onClick={() => signOut()}>Sign Out</button>
          ...
        </main>
      </>
    )}
  </div>
)

Para fazer uso do “serviço de login do Google”, precisamos de credenciais de acesso do console de nuvem do Google. Para obter isso, faça login na sua conta do Google e navegue até o console do Google Cloud . Clique em CRIAR PROJETO na página, digite o nome do seu projeto e clique em Criar .

Na nova página que se abre, clique em + CRIAR CREDENCIAIS na barra de menu superior e, finalmente, selecione ID do cliente OAuth no menu suspenso.

Console do Google Cloud

Na página que se abre, você receberá uma notificação com um botão solicitando “Configurar a tela de consentimento”. Clique neste botão.

Na próxima página, selecione Externo no tipo de usuário e clique em Criar . Digite os campos obrigatórios para seu “Nome do aplicativo” e “E-mail” e clique em Salvar e continuar .

Nas seções Scopes and Test users , role para baixo e clique em Save and Continue .

Por fim, clique em Voltar ao painel e clique no botão Publicar .

Agora, podemos criar nossa chave clicando em Credenciais no menu lateral e, em seguida, Criar Credenciais na barra de menu superior. Selecione Oauth Client ID na lista suspensa e você verá uma página solicitando o tipo de aplicativo.

Selecione Aplicativo da Web e, em “Origens de JavaScript autorizadas”, clique em Adicionar URI e insira http://localhost. Por fim, em “URIs de redirecionamento autorizados”, clique em Adicionar URI e digite http://localhost/api/auth/callback/googleno campo, antes de clicar em Criar .

Criação de chave Oauth

Copie o ID do cliente e o segredo do cliente do pop-up que é aberto e adicione-os ao .envarquivo:

GOOGLE_CLIENT_ID=id
GOOGLE_CLIENT_SECRET=secret

Agora podemos fazer login usando a autenticação do Google em nosso aplicativo. Além disso, configuraremos nosso upload.jsarquivo como uma rota protegida para que usuários não autorizados não possam criar novos snippets:

import { getSession } from "next-auth/client"

function Upload({ snippet, user }) { ... }

export async function getServerSideProps(context) {
  const session = await getSession(context)
  if (!session) {
    context.res.writeHead(302, { Location: "/" })
    context.res.end()
    return {}
  }
  return {
    props: {
      user: session.user,
    }
  }
}

export default Upload;

Testando a autenticação do usuário

Se executarmos nosso aplicativo com o npm run devcomando, primeiro obteremos uma página solicitando que façamos login. Não podemos navegar para a página de upload pelo /uploadcaminho em nosso URL. Só podemos acessar nosso aplicativo quando usamos o recurso de login do Google para fazer login em nosso aplicativo.

Autorizando usuários a criar um snippet

Por fim, modificaremos a createSnippetfuncionalidade para adicionar o e-mail do usuário ao banco de dados e só mostraremos os botões Editar e Excluir se o e-mail corresponder.

Em Fauna.js, altere a createSnippetfunção assim:

const createSnippet = async (code, language, description, name, mail) => {
  return await faunaClient.query(q.Create(q.Collection("codesnippet"), {
    data:{code, language, description, name, mail}
  }))
}

No createSnippet.jsarquivo, faça as seguintes alterações:

- const { code, language, description, name } = req.body;
+ const { code, language, description, name, mail } = req.body;

- const createdSnippet = await createSnippet(code, language, description, name);
+ const createdSnippet = await createSnippet(code, language, description, name, mail);

Em upload.js:

function upload({ snippet, user }) {
+  const email = user.email;
   ...
}

E altere a createSnippetfunção e a updateSnippetfunção, como segue:

const createSnippet = async (data) => {
  const { code, language, description, name, mail } = data;
  console.log(data)
  try {
    await fetch("/api/createSnippet", {
      method: "POST",
      body: JSON.stringify({ code, language, description, name, mail:email }),
      headers: {
        "Content-type": "application/json"
      },
    })
    router.push("/")
  } catch (error) {
    console.log(error)
  }
}

const updateSnippet = async (data) => {
  const { code, language, description, name } = data
  const id = snippet.id
  try {
    await fetch("/api/updateSnippet", {
      method: "PUT",
      body: JSON.stringify({ code, language, description, name, mail:email }),
      headers: {
        "Content-Type": "application/json",
      },
    })
    router.push("/")
  }
  catch (error) {
    console.log(error)
  }
}

Agora podemos continuar fazendo com que os botões Editar e Excluir sejam exibidos apenas se o email corresponder.

Primeiro, passamos as user.mailprops para o Snippetcomponente em index.js:

<Snippets
  key={snippet.id}
  snippet={snippet}
  snippetDeleted={mutate}
+ email={session.user.email}
/>

Então em Snippet.js:

function Snippets({ snippet, snippetDeleted, email }) {
...
  {email == snippet.data.mail && (
    <>
      <div className={styles.links}>
        <Link href={`/edit/${snippet.id}`}>
          <a>Edit</a>
        </Link>
        <a onClick={deleteSnippet}>Delete</a>
      </div>
    </>
  )}
  ...
}

Testando nosso aplicativo

Execute npm run devna CLI e abra o aplicativo em seu navegador. Agora, se você criar um novo snippet, o email do usuário será adicionado ao banco de dados. Se o e-mail não corresponder, os botões Editar e Excluir não serão exibidos na página de exibição do snippet. Você pode testar isso fazendo login com um endereço de e-mail diferente daquele usado para criar os snippets de código.

Conclusão

Finalmente chegamos ao fim deste tutorial. Aprendemos como criar um aplicativo CRUD com Next.js e FaunaDB e como realizar operações CRUD com base na autenticação do usuário.

Para conferir o código completo, visite o repositório GitHub

Fonte: https://www.sitepoint.com/nextjs-faunadb-build-code-snippet-app/

#nextjs #faunadb #crud 

Mélanie  Faria

Mélanie Faria

1640850190

Como Criar Um Aplicativo CRUD Com Next.js E FaunaDB

Durante a programação, os desenvolvedores encontram problemas que exigem a reutilização de código, levando a uma programação repetitiva que pode ser uma perda de tempo e reduzir a produtividade. Isso dá origem à necessidade de código-fonte reutilizável, denominado “fragmentos de código”. Esses trechos evitam código repetitivo durante a programação, podem ser salvos para uso futuro e são compartilháveis.

Neste tutorial, construiremos um site para ajudar os usuários a salvar trechos de código diários usando a estrutura de desenvolvimento da Web Next.js e com base no banco de dados Fauna para lidar com o armazenamento, manipulação e exibição de trechos de código. Ao trabalhar neste projeto divertido, também aprenderemos como criar um aplicativo CRUD básico com Next.js e FaunaDB que também pode ser usado para construir outros projetos semelhantes.

Uma versão funcional deste projeto pode ser encontrada no GitHub . Para acompanhar, você precisará do Node instalado em sua máquina , bem como uma conta FaunaDB e uma conta Google (para autenticação).

Instalando Dependências

Nesta seção, veremos como instalar Next.js usando o npx create-next-appcomando. Isso inicializará o Next CLI e construirá um novo aplicativo Next.js.

Também instalaremos as dependências que usaremos para o back end - FaunaDB e SWR - por meio da linha de comando. SWR (state-while-revalidate) é um gancho Next.js para buscar dados. Entraremos em detalhes mais adiante neste tutorial.

Instalação do Next.js

Para instalar Next.js, digite o seguinte comando na CLI:

npx create-next-app snippetapp

O comando acima cria um diretório de projeto chamado snippetappcom o modelo inicial Next.js, que contém os arquivos necessários para codificação com Next. Quando o Next terminar de instalar, mude para o diretório recém-criado:

cd snippetapp

Instalação do FaunaDB e SWR

Para instalar o Fauna, usaremos o seguinte comando na CLI:

npm install --save faunadb

Em seguida, para instalar o SWR:

npm install swr@0.3.8

Com isso, instalamos todas as dependências que usaremos para construir nosso aplicativo e agora podemos prosseguir com a configuração de nosso banco de dados no Fauna.

Configuração do FaunaDB

FaunaDB é um banco de dados em tempo real sem servidor. Ele transforma um banco de dados tradicional em uma API de dados flexível que ainda retém os recursos de um banco de dados e seu desempenho, ao mesmo tempo que oferece acesso seguro e escalonável aos dados do aplicativo.

Aqui, criaremos uma conta de usuário e configuraremos o banco de dados para armazenar os dados que usaremos em nosso aplicativo de snippet.

Criação de uma conta de usuário

Para criar uma conta de usuário, navegue até a página de inscrição do Fauna e crie uma conta.

Criando uma conta FaunaDB

 

 

Depois de criar uma conta de usuário, você será redirecionado para o painel.

O painel FaunaDB

Crie o banco de dados e a coleção de snippet

Aqui, criaremos um banco de dados com as coleções necessárias para gerenciar os trechos de código de nosso aplicativo. Clique em CRIAR BANCO DE DADOS . Vamos criar um banco de dados chamado snippets.

Criando um banco de dados de snippets FaunaDB

Na nova página que se abre, clique em NOVA COLEÇÃO e crie uma coleção chamada codesnippet.

página de coleção

Depois de criar uma coleção, obtemos uma página onde podemos criar um documento.

Crie um documento

Aqui, você clica em NOVO DOCUMENTO . Um documento JSON será aberto, onde você pode inserir os detalhes, conforme ilustrado abaixo.

Criação de documentos

{
  name: "Prompt User",
  description: "prompts the user",
  language: "javascript",
  code: "prompt('would you like to continue')"
}

Aqui, nós definimos um trecho com atributos: name, description, languagee code. Clique em SALVAR para salvar a nova coleção. Adicionamos com sucesso um snippet ao nosso banco de dados. Agora podemos prosseguir para obter nossas credenciais de acesso para usar em nosso aplicativo.

Criação de chave secreta

No painel, clique em Segurança . Isso abre uma nova página para criar nossa chave de segurança.

Criando uma chave de segurança FaunaDB

 

 

Aqui, definiremos a função como “servidor” em vez de “admin” e você poderá dar um nome à chave. Clique no botão SALVAR para gerar sua chave.

Criação de um .envarquivo

Agora vamos criar um .envarquivo dentro do diretório do nosso projeto. Este arquivo armazenará nossa chave secreta gerada. No .envarquivo, temos o seguinte:

FAUNA_SECRET = paste your key here

Criação de uma página de snippet de código

Nesta seção, construiremos a página de exibição e upload para os snippets e também adicionaremos funcionalidade a ela.

Abra o diretório do projeto em seu editor de código e navegue até o index.jsarquivo na pasta de páginas. Aqui, vamos limpar o código e começar a construir nosso aplicativo:

import Head from "next/head"
import Image from "next/image"
import styles from "../styles/Home.module.css"

export default function Home() {
  return (
    <div className={styles.container}>
      <Head>
        <title>View Snippet</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.main}>
        <h1 className={styles.title}>Re-usuable Code Snippets</h1>
        <p className={styles.info}>Add your code snippets here...</p>
        <button>Create new snippet</button>
      </main>
    </div>
  )
}

Criando nossos componentes

Agora vamos criar um arquivo de componente que renderizará nossos snippets. Crie uma pasta nomeada componentem seu diretório de trabalho e crie um arquivo nomeado Snippets.jsdentro dela com o seguinte código:

import React from "react"
import styles from "../styles/Home.module.css"

function Snippets() {
  return (
    <div className={styles.cont}>
      <p className={styles.lang}>language</p>
      <h3 className={styles.name}>name of snippet</h3>
      <p className={styles.descp}>description of snippet</p>
      {/* Code will be displayed here*/}
      <div className={styles.links}>
        <a>Edit</a>
        <a>Delete</a>
      </div>
    </div>
  )
}

export default Snippets

Importando nossos componentes para o aplicativo

Agora adicionaremos importações para este arquivo em nosso index.js:

import Snippets from "../components/Snippets"

E use-o em nosso aplicativo:

<button>Create new snippet</button>
<Snippets/>

Estilizando nosso aplicativo

Agora podemos estilizar nossa página. Navegue até o Home.module.cssarquivo na stylespasta e substitua os estilos lá pelo seguinte:

.container{
  display: flex;
  height: 100%;
  min-height: 100vh;
  background: rgb(48, 48, 255);
  flex-direction: column;
  align-items: center;
  color: #fff;
  font-family: Montserrat;
}
.cont{
  color: #333;
  margin-top: 5px;
  background: rgb(214, 214, 214);
  border-radius: 15px;
  padding: 10px 15px;
}
.main button{
  width: fit-content;
  flex-grow: unset;
  display: inline-block;
  padding: 5px 10px;
  outline: none;
  border: none;
  border-radius: 5%;
  font-weight: bold;
  color: rgb(48, 48, 255);
}
.main button:hover{
  cursor: pointer;
}
.links{
  margin-top: 10px;
}
.links a{
  margin-left: 5px;
}
.links a:hover{
  cursor: pointer;
}

Visualizando nosso aplicativo

Nesse ponto, você deve conseguir iniciar o servidor de desenvolvimento com npm run dev, visite http: // localhost: 3000 e veja o esqueleto de nosso aplicativo.

Configurando a área de exibição de snippet

A seguir, criaremos a seção de exibição para o código do snippet. Crie um novo arquivo chamado Code.jsna pasta de componentes e importe-o para Snippets.js:

import React from 'react'
import styles from '../styles/Home.module.css'
import Code from "./Code";

function Snippets() {
  return (
    <div className={styles.cont}>
      <p className={styles.lang}>language</p>
      <h3 className={styles.name}>name of snippet</h3>
      <p className={styles.descp}>description of snippet</p>

      {/* Code will be displayed here*/}
      <Code />

      <div className={styles.links}>
        <a>Edit</a>
        <a>Delete</a>
      </div>
    </div>
  )
}

export default Snippets

Para o realce da sintaxe do código, usaremos dois pacotes, a saber, react-syntax-highlighter e react-copy-to-clipboard . Podemos fazer o download por meio da CLI:

npm install react-syntax-highlighter react-copy-to-clipboard --save

Então em Code.js:

import React from "react"
import { PrismLight as SyntaxHighlighter } from "react-syntax-highlighter"
import {atomDark} from "react-syntax-highlighter/dist/cjs/styles/prism"
import { CopyToClipboard } from "react-copy-to-clipboard"
import styles from "../styles/Home.module.css"
function Code() {
  const codeString = "npm install import react from 'react'"
  const [show, setshow] = React.useState(false)
  return (
    <div>
      <button onClick={() => setshow(!show)}>Show Code</button>
      {show ? (
        <div>
          <CopyToClipboard text={codeString}>
            <button className={styles.btn}>Copy</button>
          </CopyToClipboard>
          <SyntaxHighlighter language="javascript" style={atomDark}>
            {codeString}
          </SyntaxHighlighter>
        </div>
      ) : null}
    </div>
  )
}
export default Code

Aqui, criamos um componente para exibir o código com destaque de sintaxe. Também adicionamos a funcionalidade de cópia e alternância de exibição. Agora no stylesarquivo:

 

 

.btn{
  left: 80%;
  position: relative;
}

Testando os blocos de código

Para visualizar essa mudança, você pode executar npm run devna linha de comando e visualizá-la em seu navegador. Temos a string “npm install import react from 'react'” exibida com destaque de sintaxe como um bloco de código. Há também um botão para ocultar e exibir o trecho de código e um botão que nos permite copiar o código do bloco de código.

Inicialização FaunaDB

Nesta seção, buscaremos dados de nosso banco de dados FaunaDB para nosso aplicativo. Crie um arquivo chamado Fauna.jsno diretório do seu projeto:

const faunadb = require("faunadb")
const faunaClient = new faunadb.Client({
  secret: process.env.FAUNA_SECRET
})
const q = faunadb.query
const getResponse = async () => {
  const { data } = await faunaClient.query(
    q.Map(
      q.Paginate(q.Documents(q.Collection("codesnippet"))),
      q.Lambda("doc", q.Get(q.Var("doc")))
    )
  )
  const snippets = data.map((snippet) => {
    snippet.id = snippet.ref.id
    delete snippet.ref
    return snippet
  })
  return snippets
}

module.exports = {
  getResponse,
}

Aqui, inicializamos o FaunaDB com nossa chave secreta. Também configuramos uma asyncsolicitação para consultar nossa coleção e retornar os dados. Armazenamos os dados retornados em uma variável chamada snippetse excluímos o ref para melhor estruturar os dados. Outras funcionalidades para criar, atualizar e excluir snippets serão adicionadas posteriormente neste tutorial.

Observe que, se você estiver recebendo um erro não autorizado no console, pode ser necessário especificar o nome de domínio do terminal de destino. O padrão é db.fauna.com, mas desde a introdução de Grupos de regiões , três domínios de nuvem estão disponíveis. Use o domínio correto para o Grupo de Região do seu banco de dados:

  • Clássico (EUA e UE): db.fauna.com
  • Estados Unidos (EUA): db.us.fauna.com
  • Europa (UE): db.eu.fauna.com

Código de exemplo:

const faunaClient = new faunadb.Client({
  secret: process.env.FAUNA_SECRET,
  domain: "db.eu.fauna.com"
})

Lidando com nossas solicitações de API

Também criaremos um arquivo para lidar com nossa solicitação de API para nosso banco de dados. Dentro da apipasta em pages, crie um arquivo chamado snippets.jscom o seguinte código:

import { getResponse } from "../../Fauna.js"
export default async function handler(req, res) {
  console.log(req)
  if (req.method !== "GET") {
    return res.status(405)
  }
  try {
    const snippets = await getResponse()
    return res.status(200).json(snippets)
  } catch (err) {
    console.log(err)
      res.status(500).json({ msg: "Something went wrong." })
  }
}

Acima, simplesmente configuramos uma função para lidar com as solicitações de nosso banco de dados. Os snippets são retornados como Jsone registrarão os erros, se ocorrerem. Em Next.js, qualquer arquivo armazenado na apipasta é tratado como endpoints de API em vez de uma página e é renderizado no lado do servidor.

O que é SWR?

Como dito anteriormente, SWR (state-while-revalidate) é um gancho Next.js para buscar dados. É uma solução perfeita para buscar dados atualizados com frequência e é uma boa opção para nosso aplicativo.

Configurando SWR

Usaremos isso para buscar dados do FaunaDB. Para usar isso, precisamos importá-lo para index.js:

import useSWR from "swr"

export default function Home() {
  const { data:snippets, mutate }=useSWR("api/snippets")
  ...
})

Aqui, importamos o SWR e o usamos para buscar dados conforme configurado em snippets.js. Em seguida, armazenamos esses snippets na snippetsvariável e os enviaremos a partir daí. Agora, passaremos o snippetspara nosso Snippetscomponente para exibir:

- <Snippets />

+ {snippets &&
+   snippets.map((snippet) => (
+     <Snippets
+       key={snippet.id}
+       snippet={snippet}
+       snippetDeleted={mutate}
+     />
+   ))
+ }

Acima, passamos a chave e o snippet para Snippets. Também configuramos uma mutatepropriedade para atualizar (buscar novamente) snippets quando um snippet é excluído. Para usar os dados passados, modificamos o Snippetscomponente com o seguinte:

function Snippets({snippet}) {
  return (
    <div className={styles.cont}>
      <p className={styles.lang}>{snippet.data.language}</p>
      <h3 className={styles.name}>{snippet.data.name}</h3>
      <p className={styles.descp}>{snippet.data.description}</p>

      <Code snippet={snippet}/>

      <div className={styles.links}>
        <a>Edit</a>
        <a>Delete</a>
      </div>
    </div>
  )
}

Acima, inserimos a linguagem, o nome e a descrição do snippet recebido do FaunaDB em nosso código. Para obter o código do Fauna em nosso aplicativo, também tivemos que passar o snippet prop para o Codecomponente.

Em seguida, no Codecomponente:

 

 

function Code({snippet}){
  ...
  <div>
    <CopyToClipboard text={snippet.data.code}>
      <button className={styles.btn}>Copy</button>
    </CopyToClipboard>
    <SyntaxHighlighter language="javascript" style={atomDark}>
      {snippet.data.code}
    </SyntaxHighlighter>
  </div>
  ...
}

Agora terminamos com a GetSnippetfuncionalidade. Se voltarmos ao FaunaDB e criarmos um novo snippet, veremos o que está na imagem abaixo.

adicionou outro documento ao FaunaDB

{
  "name": "console.log()",
  "language": "javascript",
  "description": "logs out data",
  "code": "console.log('Hello, world!')"'
}

Executando o Código

Para rodar na CLI:

npm run dev

Se você abrir a página em seu navegador, terá um resultado semelhante à imagem abaixo.

Página de exibição de snippet

Nós criamos com sucesso uma página de exibição de snippet com funcionalidades para mostrar e ocultar o código e copiar o snippet de código.

A página de upload de trechos

Precisamos criar um link para a página de upload de nosso componente inicial. Next.js tem disposições que tornam o roteamento mais fácil sem a necessidade de instalação react-routere outras dependências, como você faria se estivesse usando o código React nativo.

Em index.js, vamos importar o Linkmódulo de next:

import Link from "next/link"

Em seguida, adicione-o ao nosso botão Criar novo snippet :

- <button>Create new snippet</button>

+ <Link href="/upload">
+   <button>Create new snippet</button>
+ </Link>

Vamos criar uma nova página em nossa pagespasta e nomeá-la upload.js.

De volta ao nosso Fauna.jsarquivo, criaremos e também exportaremos uma função para criar snippets em nosso aplicativo:

const createSnippet = async (code, language, description, name) => {
  return await faunaClient.query(q.Create(q.Collection("codesnippet"), {
    data:{code, language, description, name}
  }))
}

module.exports = {
  getResponse,
  createSnippet,
}

Aqui, criamos a função createSnippet, que pegará alguns parâmetros e os passará como dados no novo documento que será criado no banco de dados.

Adicionando uma função para criar snippets

Também configuraremos nosso endpoint para criar snippets. Crie um novo arquivo chamado createSnippet.jsna apipasta e preencha-o com o seguinte código:

 

 

import { createSnippet } from "../../Fauna"

export default async function handler(req, res) {
  const { code, language, description, name } = req.body
  if (req.method !== "POST") {
    return res.status(405).json({msg:"unauthorized"})
  }
  try {
    const createdSnippet = await createSnippet(code, language, description, name)
    return res.status(200).json(createdSnippet)
  } catch (error) {
    console.log(error)
    res.status(500).json({msg:"unauthorized"})
  }
}

Criando nossa página de upload

Agora vamos criar a página de upload em nosso upload.jsarquivo. Para o nosso formulário criar fragmentos, usaremos o formulário react-hook . Vamos instalar isso por meio da CLI:

npm install react-hook-form

Então, em nosso upload.jsarquivo:

import React from "react"
import { useForm } from "react-hook-form"
import { useRouter } from "next/router"
import style from "../styles/form.module.css"
import { Link } from "next/link"

function upload({ snippet }) {
  const { register, handleSubmit, errors, reset } = useForm()
  const router = useRouter()
  const createSnippet = async (data) => {
    const { code, language, description, name } = data
    console.log(data)
    try {
      // code here to push to Fauna
    } catch (error) {
      console.log(error)
    }
  }
  return (
    <div className={style.cont}>
      <form
        className={style.form}
        onSubmit={handleSubmit(snippet ? updateSnippet : createSnippet)}
      >
        <div>
          <label htmlFor="name">Name</label>
          <input
            className={style.input}
            type="text"
            id="name"
            {...register("name", { required: true })}
          />
        </div>
        <div>
          <label className={style.label} htmlFor="language">
            language
          </label>
          <select
            className={style.select}
            type="text"
            id="language"
            {...register("language", { required: true })}
          >
            <option>Javascript</option>
            <option>Html</option>
            <option>CSS</option>
          </select>
        </div>
        <div>
          <label className={style.label} htmlFor="description">
            description
          </label>
          <textarea
            className={style.input}
            rows={7}
            type="text"
            id="description"
            placeholder="snippet description"
            {...register("description", { required: true })}
          />
        </div>
        <div>
          <label className={style.label} htmlFor="code">
            Code
          </label>
          <textarea
            className={style.input}
            rows={8}
            columns={8}
            type="text"
            id="code"
            {...register("code", { required: true })}
            placeholder="background: none;"
          />
        </div>
        <div>
          <button className={style.button}>Submit</button>
          <button className={style.button}>Cancel</button>
        </div>
      </form>
    </div>
  )
}
export default upload

Estilizando nosso formulário

Acima, criamos nosso formulário usando o react-hook-formpacote. Usamos a handleSubmitfunção useForm()no operador ternário. Após o envio do formulário, ele determina se o envio feito é para criar ou atualizar um snippet existente. Com register, adicionamos a propriedade necessária a todos os campos de nosso formulário. Também adicionamos importações para uma folha de estilo chamada, form.module.cssonde temos os seguintes estilos para nosso formulário:

.form {
  max-width: 800px;
  display: flex;
  justify-content: center;
  flex-direction: column;
  align-items: center;
}

.cont{
  background: rgb(48, 48, 255);
  height: 100%;
  min-height: 100vh;
  padding: 10px 0 0 0;
  display: flex;
  justify-content: center;
  align-items: center;
}

.select,
.input {
  display: block;
  box-sizing: border-box;
  width: 100%;
  border-radius: 4px;
  border: 1px solid black;
  padding: 10px 15px;
  margin-bottom: 15px;
  font-size: 14px;
}

.label{
  line-height: 2;
  text-align: left;
  display: block;
  margin: 5px;
  color: white;
  font-size: 14px;
  font-weight: 200;
}

.button {
  background : #fff;
  color: #444;
  border: none;
  border-radius: 5%;
  margin-right: 8px;
}

Criação de um endpoint de API para criar snippets

Para enviar os dados do nosso formulário para o banco de dados FaunaDB, adicione o seguinte código ao try...catchbloco na createSnippetfunção em upload.js:

try {
  await fetch("/api/createSnippet", {
    method: "POST",
    body: JSON.stringify({ code, language, description, name }),
    headers: {
      "Content-type": "application/json"
    },
  })
  router.push("/")
} catch (error) {
  console.log(error)
}

Testando nosso formulário

Execute o código e navegue até a página de upload. Agora, se adicionarmos um novo snippet ao formulário e clicarmos em Enviar , veremos o que está ilustrado abaixo.

Página de upload de snippet

Quando navegamos para nosso componente inicial, podemos ver o trecho criado.

Criou um novo snippet

Criação de uma função para editar trechos

Para criar nossa funcionalidade de edição de snippet, de volta ao Fauna.jsarquivo, criaremos e exportaremos uma função para lidar com esta tarefa:

const updateSnippet = async (id, code, language, description, name) => {
  return await faunaClient.query(q.Update(q.Ref(q.Collection("codesnippet"), id), {
    data: {code, language, name, description},
  }))
}
module.exports = {
  ...
  updateSnippet,
}

Criação de um endpoint de API para editar snippets

Esta função é semelhante à createSnippetfunção, mas também recebe um parâmetro de id. Ele usa esse ID para identificar quais trechos devem ser editados. Se o idcorresponder, atualizamos os dados com os outros parâmetros. Também criaremos um arquivo de endpoint no apidiretório chamado updateSnippet.jspara lidar com as atualizações:

import { updateSnippet } from "../../Fauna"
export default async function handler(req, res) {
  const { id, code, language, description, name } = req.body
  if (req.method !== "PUT") {
    return res.status(405).json({ msg: "unauthorized" })
  }
  try {
    const updated = await updateSnippet(
      id,
      code,
      language,
      description,
      name
    )
    return res.status(200).json(updated)
  }
  catch (error) {
    console.log(error)
    res.status(500).json({ msg: "unauthorized" })
  }
}

Vinculando nosso botão Editar

Agora, vá para o Snippetscomponente e modifique-o para usar esta função. Primeiro, vamos importar o Linkmódulo:

...
import Link from "next/link"

Também modificamos nosso editbotão:

- <a>Edit</a>

+ <Link href={`/edit/${snippet.id}`}>
+   <a>Edit</a>
+ </Link>

Manipulação do snippet de edição

Quando clicado, ele envia uma solicitação para a página editcom o iddo snippet selecionado. Na pagespasta, crie uma pasta chamada editcom um arquivo [id].jsdentro dela:

 

 

import { getSnippetById } from "../../Fauna"
import Upload from "../upload"
export default function Home({ snippet }) {
  const email = ""
  const user = ""
  return (
    <div>
      <h3>Update a snippet</h3>
      <Upload snippet={snippet} email={email} user={user}/>
    </div>
  )
}

export async function getServerSideProps(context) {
  try {
    //get and update record
    const id = context.params.id
  }
  catch (error) {
    console.log(error)
    context.res.statusCode = 302
    context.res.setHeader("Location", "/")
    return {props: {}}
  }
}

Em [id].js, estamos passando o snippet de código propspara a página de upload de snippet. No entanto, desta vez, a página de upload conterá os dados armazenados no snippet de código referenciado pelo id. Para buscar o snippet por ID, precisaremos criar a getSnippetByIdfunção no Fauna.jsarquivo:

const getSnippetById = async (id) => {
  const snippet = await faunaClient.query(q.Get(q.Ref(q.Collection("codesnippet"),id)))
  snippet.id = snippet.ref.id
  delete snippet.ref
  return snippet
}

module.exports = {
  getResponse,
  createSnippet,
  updateSnippet,
  getSnippetById,
}

À medida que exportamos a função, de volta ao [id].jsarquivo, podemos usá-la para buscar um snippet específico com seu ID:

try {
  const id = context.params.id;
  const snippet = await getSnippetById(id);
  return {
    props: { snippet },
  };
} catch (error) {
  // as before
}

Modificando Snippets Armazenados

Agora, no upload.jsarquivo, vamos modificá-lo para poder acessar os dados armazenados se um snippet for editado:

- const { register, handleSubmit, errors, reset } = useForm()

+ const { register, handleSubmit, errors, reset } = useForm({
+   defaultValues: {
+     code: snippet ? snippet.data.code : "",
+     language: snippet ? snippet.data.language : "",
+     description: snippet ? snippet.data.description : "",
+     name: snippet ? snippet.data.name : "",
+   }
+ })

O código acima verifica se o snippet armazenou dados nele. Se ele retorna true, ele retorna os dados para os parâmetros: code, language, descriptione code. Se retornar false, ele retornará uma string vazia.

A seguir, criaremos uma função para atualizar o snippet de código:

const createSnippet = async (data) => { ... }

const updateSnippet = async (data) => {
  const { code, language, description, name } = data
  const id = snippet.id
  try {
    await fetch("/api/updateSnippet", {
      method: "PUT",
      body: JSON.stringify({ code, language, description, name, id }),
      headers: {
        "Content-Type": "application/json",
      },
    })
    router.push("/")
  }
  catch (error) {
    console.log(error)
  }
}

return ( ,,, )

Teste de funcionalidade de trecho de edição

Se executarmos nosso código, podemos editar os trechos de código criados anteriormente clicando no botão Editar , fazendo alterações nos dados do formulário e clicando em Enviar .

Adicionar funcionalidade de exclusão de snippet

Agora, se retornarmos ao Homecomponente em nosso navegador, poderemos editar e atualizar trechos de código. Podemos finalmente adicionar a funcionalidade final para excluir nosso trecho de código. Crie e exporte uma nova função - deleteSnippet- no Fauna.jsarquivo:

const deleteSnippet = async (id) => {
  return await faunaClient.query(q.Delete(q.Ref(q.Collection("codesnippet"),id)))
}

module.exports = {
  ...
  deleteSnippet,
}

Criação de endpoint de API para a funcionalidade de exclusão

Criaremos outro endpoint para esta função em nossa apipasta chamada deleteSnippet.jse o preencheremos com o seguinte código:

import { deleteSnippet } from "../../Fauna"
export default async function handler(req, res) {
  if (req.method !== "DELETE") {
    return res.status(405).json({ msg: "unauthorized" })
  }
  const { id } = req.body
  try {
    const deleted = await deleteSnippet(id)
    return res.status(200).json(deleted)
  }
  catch (error) {
    console.log(error)
    res.status(500).join({ msg: "error occured" })
  }
}

Em seguida, modificamos o Snippets.jsarquivo para adicionar a nova funcionalidade:

function Snippets({ snippet, snippetDeleted }) {
  ...
}

Em seguida, crie uma deleteSnippetfunção para buscar o endpoint do apie exclua o snippet referenciado pelo ID:

function Snippets({snippet, snippetDeleted}) {
  const deleteSnippet = async () => {
    try {
      await fetch("/api/deleteSnippet", {
        method: "DELETE",
        body: JSON.stringify({ id: snippet.id }),
        headers: {
          "Content-Type": "application/json",
        },
      });
      snippetDeleted();
    } catch (e) {
      console.log(e);
    }
  };

  return (
    <div className={styles.cont}>
      <p className={styles.lang}>{snippet.data.language}</p>
      <h3 className={styles.name}>{snippet.data.name}</h3>
      <p className={styles.descp}>{snippet.data.description}</p>

      <Code snippet={snippet}/>

      <div className={styles.links}>
        <Link href={`/edit/${snippet.id}`}>
          <a>Edit</a>
        </Link>
        <a onClick={deleteSnippet}>Delete</a>
      </div>
    </div>
  )
}

Também atualizamos o elemento âncora para chamar a deleteSnippetfunção quando ela é clicada.

Teste de funcionalidade de exclusão

Adicionamos funcionalidade para excluir trechos de código. Agora podemos excluir trechos clicando no botão Excluir em nosso aplicativo.

Isso conclui as funcionalidades do aplicativo snippet. Agora prosseguiremos para adicionar medidas de autenticação ao nosso aplicativo para permitir que apenas usuários autorizados criem ou modifiquem snippets em nosso aplicativo.

 

 

Autenticação de usuário

Por que precisamos de autenticação? Atualmente, os usuários podem criar fragmentos, mas também podem excluir e modificar fragmentos que não criaram. Precisamos fornecer um meio para autorizar os usuários a acessar nosso site - e, portanto, a necessidade de autenticação do usuário.

Instalaremos next-auth para autenticação por meio de nossa CLI:

npm i next-auth

Estaremos usando um token JWT para nossa autenticação. JWT é um padrão usado para criar tokens de acesso para um aplicativo.

Crie uma pasta com o nome authem sua apipasta e, dentro dela, crie um arquivo [...nextauth].jscom o seguinte código:

import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google"

export default NextAuth({
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth?prompt=consent&access_type=offline&response_type=code",
    })
  ],
  jwt: {
    encryption: true
  },
  secret: process.env.secret,
  callbacks: {
    async jwt(token, account) {
      if (account ?.accessToken) {
        token.accessToken = account.accessToken
      }
      return token
    },
    redirect: async (url, _baseUrl)=>{
      if (url === "/profile") {
        return Promise.resolve("/")
      }
      return  Promise.resolve("/")
    }
  }
})

Depois disso, agruparemos nossos componentes no _app.jsarquivo:

import '../styles/globals.css'
import {Provider} from "next-auth/client"

function MyApp({ Component, pageProps }) {
  return (
    <Provider session={pageProps.session}>
      <Component {...pageProps} />
    </Provider>
  )
}
export default MyApp

Manipulação de autenticação

Modificaremos nosso Homecomponente index.jspara retornar ao nosso componente se o usuário for autenticado; caso contrário, ele retornará um botão que leva à página de autenticação:

import {signIn, signOut, useSession} from "next-auth/client"
...

Então dentro Home:

export default function Home() {
  const { data:snippets, mutate }=useSWR("api/snippets")

  const [session, loadingSession] = useSession()
  if (loadingSession) {
    <>
      <p>...authenticating</p>
    </>
  }
  ...
}

O código acima verifica se o aplicativo é loadingSession. Se verdadeiro, ele retorna o pbloco de tags, caso contrário, retorna o resto do nosso aplicativo, se houver session. A seguir, vamos renderizar o “login” se não houver sessão:

return (
  <div className={styles.container}>
    <Head> ... </Head>

    {!session && (
      <>
        <h1>Sign in to access snippet app</h1>
        <button onClick={() => signIn()}>Sign In</button>
      </>
    )}

    {session && (
      <>
        <main className={styles.main}>
          <h3>welcome {session.user.email}</h3>
          <button onClick={() => signOut()}>Sign Out</button>
          ...
        </main>
      </>
    )}
  </div>
)

Para fazer uso do “serviço de login do Google”, precisamos de credenciais de acesso do console da nuvem do Google. Para obter isso, faça login em sua conta do Google e navegue até o console do Google Cloud . Clique em CRIAR PROJETO na página, digite o nome do seu projeto e clique em Criar .

Na nova página que se abre, clique em + CRIAR CREDENCIAIS na barra de menu superior e, por fim, selecione o ID do cliente OAuth no menu suspenso.

Console do Google Cloud

Na página que se abre, você receberá uma notificação com um botão pedindo para você “Configurar tela de consentimento”. Clique neste botão.

Na próxima página, selecione Externo sob o tipo de usuário e clique em Criar . Insira os campos obrigatórios para o seu “Nome do aplicativo” e “Email” e clique em Salvar e continuar .

 

 

Nas seções Escopos e usuários de teste , role para baixo e clique em Salvar e continuar .

Por fim, clique em Voltar ao painel e clique no botão Publicar .

Agora, podemos criar nossa chave clicando em Credenciais no menu lateral e, em seguida, em Criar Credenciais na barra de menu superior. Selecione Oauth Client ID na lista suspensa e você verá uma página solicitando o tipo de aplicativo.

Selecione Aplicativo da Web e, em “Origens JavaScript autorizadas”, clique em Adicionar URI e digite http://localhost. Por fim, em “URIs de redirecionamento autorizados”, clique em Adicionar URI e entre http://localhost/api/auth/callback/googleno campo, antes de clicar em Criar .

Criação de chave Oauth

Copie o ID do cliente e a chave secreta do cliente do pop-up que é aberto e adicione-os ao .envarquivo:

GOOGLE_CLIENT_ID=id
GOOGLE_CLIENT_SECRET=secret

Agora podemos fazer login usando a autenticação do Google em nosso aplicativo. Além disso, configuraremos nosso upload.jsarquivo como uma rota protegida para que usuários não autorizados não possam criar novos snippets:

import { getSession } from "next-auth/client"

function Upload({ snippet, user }) { ... }

export async function getServerSideProps(context) {
  const session = await getSession(context)
  if (!session) {
    context.res.writeHead(302, { Location: "/" })
    context.res.end()
    return {}
  }
  return {
    props: {
      user: session.user,
    }
  }
}

export default Upload;

Testando a autenticação do usuário

Se executarmos nosso aplicativo com o npm run devcomando, a princípio obteremos uma página solicitando que “entremos”. Não podemos navegar para a página de upload por meio do /uploadcaminho em nosso URL. Só podemos acessar nosso aplicativo quando usamos o recurso de login do Google para fazer login em nosso aplicativo.

Autorizando usuários a criar um snippet

Por fim, modificaremos a createSnippetfuncionalidade para adicionar o e-mail do usuário ao banco de dados e só mostraremos os botões Editar e Excluir se o e-mail corresponder.

Em Fauna.js, altere a createSnippetfunção da seguinte maneira:

const createSnippet = async (code, language, description, name, mail) => {
  return await faunaClient.query(q.Create(q.Collection("codesnippet"), {
    data:{code, language, description, name, mail}
  }))
}

No createSnippet.jsarquivo, faça as seguintes alterações:

- const { code, language, description, name } = req.body;
+ const { code, language, description, name, mail } = req.body;

- const createdSnippet = await createSnippet(code, language, description, name);
+ const createdSnippet = await createSnippet(code, language, description, name, mail);

Em upload.js:

function upload({ snippet, user }) {
+  const email = user.email;
   ...
}

E altere a createSnippetfunção e a updateSnippetfunção, da seguinte maneira:

const createSnippet = async (data) => {
  const { code, language, description, name, mail } = data;
  console.log(data)
  try {
    await fetch("/api/createSnippet", {
      method: "POST",
      body: JSON.stringify({ code, language, description, name, mail:email }),
      headers: {
        "Content-type": "application/json"
      },
    })
    router.push("/")
  } catch (error) {
    console.log(error)
  }
}

const updateSnippet = async (data) => {
  const { code, language, description, name } = data
  const id = snippet.id
  try {
    await fetch("/api/updateSnippet", {
      method: "PUT",
      body: JSON.stringify({ code, language, description, name, mail:email }),
      headers: {
        "Content-Type": "application/json",
      },
    })
    router.push("/")
  }
  catch (error) {
    console.log(error)
  }
}

Agora podemos continuar fazendo com que os botões Editar e Excluir sejam exibidos apenas se o e-mail corresponder.

Primeiro, passamos os user.mailcomo adereços para o Snippetcomponente em index.js:

<Snippets
  key={snippet.id}
  snippet={snippet}
  snippetDeleted={mutate}
+ email={session.user.email}
/>

Então em Snippet.js:

function Snippets({ snippet, snippetDeleted, email }) {
...
  {email == snippet.data.mail && (
    <>
      <div className={styles.links}>
        <Link href={`/edit/${snippet.id}`}>
          <a>Edit</a>
        </Link>
        <a onClick={deleteSnippet}>Delete</a>
      </div>
    </>
  )}
  ...
}

Testando nosso aplicativo

Execute npm run devna CLI e abra o aplicativo em seu navegador. Agora, se você criar um novo snippet, o e-mail do usuário será adicionado ao banco de dados. Se o e-mail não corresponder, os botões Editar e Excluir não serão exibidos na página de exibição do snippet. Você pode testar isso fazendo login com um endereço de e-mail diferente do usado para criar os trechos de código.

Conclusão

Finalmente chegamos ao final deste tutorial. Aprendemos como construir um aplicativo CRUD com Next.js e FaunaDB e como realizar operações CRUD com base na autenticação do usuário.

fonte: https://www.sitepoint.com/nextjs-faunadb-build-code-snippet-app/

#nextjs #faunadb 

Como Criar Um Aplicativo Web De Snippet Com Next.js E FaunaDB

Durante a programação, os desenvolvedores encontram problemas que exigem a reutilização de código, levando a uma programação repetitiva que pode desperdiçar tempo e reduzir a produtividade. Isso dá origem à necessidade de código-fonte reutilizável chamado “snippets de código”. Esses trechos evitam código repetitivo durante a programação, podem ser salvos para uso futuro e são compartilháveis.

Neste tutorial, criaremos um site para ajudar os usuários a salvar trechos de código diários usando a estrutura de desenvolvimento da Web Next.js e alimentado pelo banco de dados Fauna para lidar com o armazenamento, manipulação e exibição de trechos de código. Ao trabalhar neste projeto divertido, também aprenderemos como criar um aplicativo CRUD básico com Next.js e FaunaDB que também pode ser usado para construir outros projetos semelhantes.

Uma versão funcional deste projeto pode ser encontrada no GitHub . Para acompanhar, você precisará do Node instalado em sua máquina , bem como uma conta FaunaDB e uma conta Google (para autenticação).

Instalando dependências

Nesta seção, veremos como instalar o Next.js usando o npx create-next-appcomando. Isso inicializará a Next CLI e criará um novo aplicativo Next.js.

Também instalaremos as dependências que usaremos para o back-end — FaunaDB e SWR — por meio da linha de comando. SWR (state-while-revalidate) é um gancho Next.js para buscar dados. Entraremos em detalhes mais adiante neste tutorial.

Instalação do Next.js

Para instalar o Next.js, digite o seguinte comando na CLI:

npx create-next-app snippetapp

O comando acima cria um diretório de projeto chamado snippetappcom o modelo inicial Next.js, que contém os arquivos necessários para codificação com Next. Quando o Next terminar a instalação, mude para o diretório recém-criado:

cd snippetapp

Instalação FaunaDB e SWR

Para instalar o Fauna, usaremos o seguinte comando na CLI:

npm install --save faunadb

Em seguida, para instalar o SWR:

npm install swr@0.3.8

Com isso, instalamos todas as dependências que usaremos para construir nossa aplicação e agora podemos prosseguir para configurar nosso banco de dados no Fauna.

 

Configuração do FaunaDB

O FaunaDB é um banco de dados em tempo real sem servidor. Ele transforma um banco de dados tradicional em uma API de dados flexível que ainda retém os recursos de um banco de dados e seu desempenho, ao mesmo tempo em que oferece acesso seguro e escalável aos dados do aplicativo.

Aqui, criaremos uma conta de usuário e configuraremos o banco de dados para armazenar os dados que usaremos em nosso aplicativo de snippet.

Criando uma conta de usuário

Para criar uma conta de usuário, navegue até a página de inscrição do Fauna e crie uma conta.

Criando uma conta FaunaDB

Depois de criar uma conta de usuário, você será redirecionado para o painel.

O painel do FaunaDB

Crie o banco de dados e a coleção de snippets

Aqui, criaremos um banco de dados com as coleções necessárias para gerenciar os trechos de código do nosso aplicativo. Clique em CRIAR BANCO DE DADOS . Vamos criar um banco de dados chamado snippets.

Criando um banco de dados de trechos do FaunaDB

Na nova página que se abre, clique em NOVA COLEÇÃO e crie uma coleção chamada codesnippet.

página de coleção

Depois de criar uma coleção, obtemos uma página onde podemos criar um documento.

Criar um documento

Aqui, você clicará em NOVO DOCUMENTO . Um documento JSON será aberto, onde você poderá inserir os detalhes, conforme ilustrado abaixo.

Criação de documentos

{
  name: "Prompt User",
  description: "prompts the user",
  language: "javascript",
  code: "prompt('would you like to continue')"
}

Aqui, definimos um snippet com atributos: name, description, languagee code. Clique em SALVAR para salvar a nova coleção. Adicionamos com sucesso um snippet ao nosso banco de dados. Agora podemos prosseguir para obter nossas credenciais de acesso para usar em nosso aplicativo.

Criação de chave secreta

No painel, clique em Segurança . Isso abre uma nova página para criar nossa chave de segurança.

Criando uma chave de segurança do FaunaDB

Aqui, definiremos a função como “servidor” em vez de “admin”, e você pode dar um nome à chave. Clique no botão SAVE para gerar sua chave.

Criando um .envarquivo

Agora vamos criar um .envarquivo dentro do diretório do nosso projeto. Este arquivo armazenará nossa chave secreta gerada. No .envarquivo temos isso:

FAUNA_SECRET = paste your key here

Criando uma página de trecho de código

Nesta seção, criaremos a página de exibição e upload para os snippets e também adicionaremos funcionalidades a ela.

Abra o diretório do projeto em seu editor de código e navegue até o index.jsarquivo na pasta de páginas. Aqui vamos limpar o código e começar a construir nosso aplicativo:

import Head from "next/head"
import Image from "next/image"
import styles from "../styles/Home.module.css"

export default function Home() {
  return (
    <div className={styles.container}>
      <Head>
        <title>View Snippet</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.main}>
        <h1 className={styles.title}>Re-usuable Code Snippets</h1>
        <p className={styles.info}>Add your code snippets here...</p>
        <button>Create new snippet</button>
      </main>
    </div>
  )
}

Criando nossos componentes

Agora vamos criar um arquivo componente que irá renderizar nossos trechos. Crie uma pasta nomeada componentem seu diretório de trabalho e crie um arquivo nomeado Snippets.jsdentro dela com o seguinte código:

import React from "react"
import styles from "../styles/Home.module.css"

function Snippets() {
  return (
    <div className={styles.cont}>
      <p className={styles.lang}>language</p>
      <h3 className={styles.name}>name of snippet</h3>
      <p className={styles.descp}>description of snippet</p>
      {/* Code will be displayed here*/}
      <div className={styles.links}>
        <a>Edit</a>
        <a>Delete</a>
      </div>
    </div>
  )
}

export default Snippets

Importando nossos componentes para o aplicativo

Agora adicionaremos importações para este arquivo em nosso index.js:

import Snippets from "../components/Snippets"

E use-o em nosso aplicativo:

<button>Create new snippet</button>
<Snippets/>

Estilizando nosso aplicativo

Agora podemos estilizar nossa página. Navegue até o Home.module.cssarquivo na stylespasta e substitua os estilos pelo seguinte:

.container{
  display: flex;
  height: 100%;
  min-height: 100vh;
  background: rgb(48, 48, 255);
  flex-direction: column;
  align-items: center;
  color: #fff;
  font-family: Montserrat;
}
.cont{
  color: #333;
  margin-top: 5px;
  background: rgb(214, 214, 214);
  border-radius: 15px;
  padding: 10px 15px;
}
.main button{
  width: fit-content;
  flex-grow: unset;
  display: inline-block;
  padding: 5px 10px;
  outline: none;
  border: none;
  border-radius: 5%;
  font-weight: bold;
  color: rgb(48, 48, 255);
}
.main button:hover{
  cursor: pointer;
}
.links{
  margin-top: 10px;
}
.links a{
  margin-left: 5px;
}
.links a:hover{
  cursor: pointer;
}

 

Visualizando nosso aplicativo

Neste ponto, você deve ser capaz de iniciar o servidor dev com npm run dev, visite http://localhost:3000 e veja o esqueleto do nosso aplicativo.

Configurando a Área de Exibição de Fragmentos

Em seguida, criaremos a seção de exibição para o código do snippet. Crie um novo arquivo chamado Code.jsna pasta de componentes e importe-o para Snippets.js:

import React from 'react'
import styles from '../styles/Home.module.css'
import Code from "./Code";

function Snippets() {
  return (
    <div className={styles.cont}>
      <p className={styles.lang}>language</p>
      <h3 className={styles.name}>name of snippet</h3>
      <p className={styles.descp}>description of snippet</p>

      {/* Code will be displayed here*/}
      <Code />

      <div className={styles.links}>
        <a>Edit</a>
        <a>Delete</a>
      </div>
    </div>
  )
}

export default Snippets

Para o realce de sintaxe do código, usaremos dois pacotes, a saber, react-syntax-highlighter e react-copy-to-clipboard . Podemos fazer o download através da CLI:

npm install react-syntax-highlighter react-copy-to-clipboard --save

Então em Code.js:

import React from "react"
import { PrismLight as SyntaxHighlighter } from "react-syntax-highlighter"
import {atomDark} from "react-syntax-highlighter/dist/cjs/styles/prism"
import { CopyToClipboard } from "react-copy-to-clipboard"
import styles from "../styles/Home.module.css"
function Code() {
  const codeString = "npm install import react from 'react'"
  const [show, setshow] = React.useState(false)
  return (
    <div>
      <button onClick={() => setshow(!show)}>Show Code</button>
      {show ? (
        <div>
          <CopyToClipboard text={codeString}>
            <button className={styles.btn}>Copy</button>
          </CopyToClipboard>
          <SyntaxHighlighter language="javascript" style={atomDark}>
            {codeString}
          </SyntaxHighlighter>
        </div>
      ) : null}
    </div>
  )
}
export default Code

Aqui, criamos um componente para exibir código com realce de sintaxe. Também adicionamos a funcionalidade de cópia e alternância de exibição. Agora no stylesarquivo:

.btn{
  left: 80%;
  position: relative;
}

 

Testando os blocos de código

Para visualizar essa alteração, você pode executar npm run devna linha de comando e visualizá-la em seu navegador. Temos a string “npm install import react from 'react'” exibida com destaque de sintaxe como um bloco de código. Há também um botão para ocultar e exibir o trecho de código e um botão que nos permite copiar o código do bloco de código.

Inicialização do FaunaDB

Nesta seção, buscaremos dados do nosso banco de dados FaunaDB para nosso aplicativo. Crie um arquivo chamado Fauna.jsno diretório do seu projeto:

const faunadb = require("faunadb")
const faunaClient = new faunadb.Client({
  secret: process.env.FAUNA_SECRET
})
const q = faunadb.query
const getResponse = async () => {
  const { data } = await faunaClient.query(
    q.Map(
      q.Paginate(q.Documents(q.Collection("codesnippet"))),
      q.Lambda("doc", q.Get(q.Var("doc")))
    )
  )
  const snippets = data.map((snippet) => {
    snippet.id = snippet.ref.id
    delete snippet.ref
    return snippet
  })
  return snippets
}

module.exports = {
  getResponse,
}

Here, we’ve initialized FaunaDB with our secret key. We’ve also set up an async request to query our collection and return the data. We’ve stored the returned data in a variable named snippets, and deleted the ref to better structure the data. Other functionalities for creating, updating and deleting snippets will be added later in this tutorial.

Note that, if you’re getting an unauthorized error in the console, you may need to specify the domain name of the target endpoint. The default is db.fauna.com, but since the introduction of Region Groups, three cloud domains are available. Use the correct domain for your database’s Region Group:

  • Classic (US and EU): db.fauna.com
  • United States (US): db.us.fauna.com
  • Europe (EU): db.eu.fauna.com

Example code:

const faunaClient = new faunadb.Client({
  secret: process.env.FAUNA_SECRET,
  domain: "db.eu.fauna.com"
})

Handling our API requests

Também criaremos um arquivo para lidar com nossa solicitação de API para nosso banco de dados. Dentro da apipasta em pages, crie um arquivo chamado snippets.jscom o seguinte código:

import { getResponse } from "../../Fauna.js"
export default async function handler(req, res) {
  console.log(req)
  if (req.method !== "GET") {
    return res.status(405)
  }
  try {
    const snippets = await getResponse()
    return res.status(200).json(snippets)
  } catch (err) {
    console.log(err)
      res.status(500).json({ msg: "Something went wrong." })
  }
}

Acima, simplesmente configuramos uma função para lidar com solicitações do nosso banco de dados. Os snippets são retornados como Jsone registrarão erros se ocorrerem. No Next.js, qualquer arquivo armazenado na apipasta é tratado como endpoints de API em vez de uma página e é renderizado no lado do servidor.

 

O que é SWR?

Como dito anteriormente, SWR (state-while-revalidate) é um gancho Next.js para buscar dados. É uma solução perfeita para buscar dados atualizados com frequência e é uma boa opção para o nosso aplicativo.

Configurando o SWR

Usaremos isso para buscar dados do FaunaDB. Para usar isso, precisamos importá-lo para index.js:

import useSWR from "swr"

export default function Home() {
  const { data:snippets, mutate }=useSWR("api/snippets")
  ...
})

Aqui, importamos o SWR e o usamos para buscar dados conforme configurado em snippets.js. Em seguida, armazenamos esses trechos na snippetsvariável e os produziremos a partir daí. Agora vamos passar o snippetspara o nosso Snippetscomponente para exibir:

- <Snippets />

+ {snippets &&
+   snippets.map((snippet) => (
+     <Snippets
+       key={snippet.id}
+       snippet={snippet}
+       snippetDeleted={mutate}
+     />
+   ))
+ }

Acima, passamos a chave e o snippet para Snippets. Também configuramos uma mutatepropriedade para atualizar (re-buscar) snippets quando um snippet é excluído. Para usar os dados passados, modificamos o Snippetscomponente com o seguinte:

function Snippets({snippet}) {
  return (
    <div className={styles.cont}>
      <p className={styles.lang}>{snippet.data.language}</p>
      <h3 className={styles.name}>{snippet.data.name}</h3>
      <p className={styles.descp}>{snippet.data.description}</p>

      <Code snippet={snippet}/>

      <div className={styles.links}>
        <a>Edit</a>
        <a>Delete</a>
      </div>
    </div>
  )
}

Acima, inserimos o idioma do snippet, nome e descrição recebidos do FaunaDB em nosso código. Para obter o código da Fauna em nosso aplicativo, também tivemos que passar o snippet prop para o Codecomponente.

Então no Codecomponente:

function Code({snippet}){
  ...
  <div>
    <CopyToClipboard text={snippet.data.code}>
      <button className={styles.btn}>Copy</button>
    </CopyToClipboard>
    <SyntaxHighlighter language="javascript" style={atomDark}>
      {snippet.data.code}
    </SyntaxHighlighter>
  </div>
  ...
}

Agora terminamos com a GetSnippetfuncionalidade. Se retornarmos ao FaunaDB e criarmos um novo snippet, veremos o que está ilustrado abaixo.

adicionou outro documento ao FaunaDB

{
  "name": "console.log()",
  "language": "javascript",
  "description": "logs out data",
  "code": "console.log('Hello, world!')"'
}

Executando o código

Para executar na CLI:

npm run dev

Se você abrir a página em seu navegador, terá um resultado semelhante à imagem abaixo.

Página de exibição de trecho

Criamos com sucesso uma página de exibição de snippet com funcionalidades para mostrar e ocultar o código e copiar o snippet de código.

 

A página de carregamento de fragmentos

Precisaremos criar um link para a página de upload do nosso componente inicial. O Next.js tem provisões que facilitam o roteamento sem que você precise instalar react-routere outras dependências, como faria se estivesse usando o código React nativo.

Em index.js, importaremos o Linkmódulo de next:

import Link from "next/link"

Em seguida, adicione-o ao nosso botão Criar novo trecho :

- <button>Create new snippet</button>

+ <Link href="/upload">
+   <button>Create new snippet</button>
+ </Link>

Vamos criar uma nova página em nossa pagespasta e nomeá-la upload.js.

De volta ao nosso Fauna.jsarquivo, vamos criar e também exportar uma função para criar snippets em nosso aplicativo:

const createSnippet = async (code, language, description, name) => {
  return await faunaClient.query(q.Create(q.Collection("codesnippet"), {
    data:{code, language, description, name}
  }))
}

module.exports = {
  getResponse,
  createSnippet,
}

Aqui, criamos a função createSnippet, que receberá alguns parâmetros e os passará como dados no novo documento que será criado no banco de dados.

Adicionando uma função para criar snippets

Também configuraremos nosso endpoint para criar snippets. Crie um novo arquivo chamado createSnippet.jsna apipasta e preencha-o com o seguinte código:

import { createSnippet } from "../../Fauna"

export default async function handler(req, res) {
  const { code, language, description, name } = req.body
  if (req.method !== "POST") {
    return res.status(405).json({msg:"unauthorized"})
  }
  try {
    const createdSnippet = await createSnippet(code, language, description, name)
    return res.status(200).json(createdSnippet)
  } catch (error) {
    console.log(error)
    res.status(500).json({msg:"unauthorized"})
  }
}

Criando nossa página de upload

Agora vamos criar a página de upload em nosso upload.jsarquivo. Para nosso formulário criar snippets, usaremos o react-hook-form . Vamos instalar isso via CLI:

npm install react-hook-form

Então, em nosso upload.jsarquivo:

import React from "react"
import { useForm } from "react-hook-form"
import { useRouter } from "next/router"
import style from "../styles/form.module.css"
import { Link } from "next/link"

function upload({ snippet }) {
  const { register, handleSubmit, errors, reset } = useForm()
  const router = useRouter()
  const createSnippet = async (data) => {
    const { code, language, description, name } = data
    console.log(data)
    try {
      // code here to push to Fauna
    } catch (error) {
      console.log(error)
    }
  }
  return (
    <div className={style.cont}>
      <form
        className={style.form}
        onSubmit={handleSubmit(snippet ? updateSnippet : createSnippet)}
      >
        <div>
          <label htmlFor="name">Name</label>
          <input
            className={style.input}
            type="text"
            id="name"
            {...register("name", { required: true })}
          />
        </div>
        <div>
          <label className={style.label} htmlFor="language">
            language
          </label>
          <select
            className={style.select}
            type="text"
            id="language"
            {...register("language", { required: true })}
          >
            <option>Javascript</option>
            <option>Html</option>
            <option>CSS</option>
          </select>
        </div>
        <div>
          <label className={style.label} htmlFor="description">
            description
          </label>
          <textarea
            className={style.input}
            rows={7}
            type="text"
            id="description"
            placeholder="snippet description"
            {...register("description", { required: true })}
          />
        </div>
        <div>
          <label className={style.label} htmlFor="code">
            Code
          </label>
          <textarea
            className={style.input}
            rows={8}
            columns={8}
            type="text"
            id="code"
            {...register("code", { required: true })}
            placeholder="background: none;"
          />
        </div>
        <div>
          <button className={style.button}>Submit</button>
          <button className={style.button}>Cancel</button>
        </div>
      </form>
    </div>
  )
}
export default upload

Estilizando nosso formulário

Acima, criamos nosso formulário usando o react-hook-formpacote. Usamos a handleSubmitfunção useForm()no operador ternário. Ao enviar o formulário, determina se o envio realizado é para criar ou atualizar um trecho existente. Com register, adicionamos a propriedade obrigatória a todos os campos do nosso formulário. Também adicionamos importações para uma folha de estilo chamada form.module.cssonde temos os seguintes estilos para nosso formulário:

.form {
  max-width: 800px;
  display: flex;
  justify-content: center;
  flex-direction: column;
  align-items: center;
}

.cont{
  background: rgb(48, 48, 255);
  height: 100%;
  min-height: 100vh;
  padding: 10px 0 0 0;
  display: flex;
  justify-content: center;
  align-items: center;
}

.select,
.input {
  display: block;
  box-sizing: border-box;
  width: 100%;
  border-radius: 4px;
  border: 1px solid black;
  padding: 10px 15px;
  margin-bottom: 15px;
  font-size: 14px;
}

.label{
  line-height: 2;
  text-align: left;
  display: block;
  margin: 5px;
  color: white;
  font-size: 14px;
  font-weight: 200;
}

.button {
  background : #fff;
  color: #444;
  border: none;
  border-radius: 5%;
  margin-right: 8px;
}

Como criar um endpoint de API para criar snippets

Para enviar os dados do nosso formulário para o banco de dados FaunaDB, adicione o seguinte código ao try...catchbloco na createSnippetfunção em upload.js:

try {
  await fetch("/api/createSnippet", {
    method: "POST",
    body: JSON.stringify({ code, language, description, name }),
    headers: {
      "Content-type": "application/json"
    },
  })
  router.push("/")
} catch (error) {
  console.log(error)
}

 

Testando nosso formulário

Execute o código e navegue até a página de upload. Agora, se adicionarmos um novo snippet ao formulário e clicarmos em Submit , veremos o que está na foto abaixo.

Página de carregamento de fragmentos

Quando navegamos para nosso componente inicial, podemos ver o trecho criado.

Criou um novo trecho

Criando uma função para editar snippets

Para criar nossa funcionalidade de edição de trechos, de volta ao Fauna.jsarquivo, criaremos e exportaremos uma função para lidar com esta tarefa:

const updateSnippet = async (id, code, language, description, name) => {
  return await faunaClient.query(q.Update(q.Ref(q.Collection("codesnippet"), id), {
    data: {code, language, name, description},
  }))
}
module.exports = {
  ...
  updateSnippet,
}

Como criar um endpoint de API para editar snippets

Essa função é semelhante à createSnippetfunção, mas também recebe um parâmetro de id. Ele usa esse ID para identificar quais snippets devem ser editados. Se idcorresponder, atualizamos os dados com os outros parâmetros. Também criaremos um arquivo de endpoint no apidiretório chamado updateSnippet.jspara lidar com as atualizações:

import { updateSnippet } from "../../Fauna"
export default async function handler(req, res) {
  const { id, code, language, description, name } = req.body
  if (req.method !== "PUT") {
    return res.status(405).json({ msg: "unauthorized" })
  }
  try {
    const updated = await updateSnippet(
      id,
      code,
      language,
      description,
      name
    )
    return res.status(200).json(updated)
  }
  catch (error) {
    console.log(error)
    res.status(500).json({ msg: "unauthorized" })
  }
}

 

Vinculando nosso botão Editar

Agora, vá para o Snippetscomponente e modifique este componente para fazer uso desta função. Primeiro, vamos importar o Linkmódulo:

...
import Link from "next/link"

Também modificamos nosso editbotão:

- <a>Edit</a>

+ <Link href={`/edit/${snippet.id}`}>
+   <a>Edit</a>
+ </Link>

Manipulando o snippet de edição

Quando clicado, ele envia uma solicitação para a página editcom o idsnippet selecionado. Na pagespasta, crie uma pasta nomeada editcom um arquivo [id].jsdentro dela:

import { getSnippetById } from "../../Fauna"
import Upload from "../upload"
export default function Home({ snippet }) {
  const email = ""
  const user = ""
  return (
    <div>
      <h3>Update a snippet</h3>
      <Upload snippet={snippet} email={email} user={user}/>
    </div>
  )
}

export async function getServerSideProps(context) {
  try {
    //get and update record
    const id = context.params.id
  }
  catch (error) {
    console.log(error)
    context.res.statusCode = 302
    context.res.setHeader("Location", "/")
    return {props: {}}
  }
}

Em [id].js, estamos passando o snippet de código propspara a página de upload do snippet. No entanto, desta vez, a página de upload conterá os dados armazenados no snippet de código referenciado pelo id. Para buscar o snippet por ID, precisaremos criar a getSnippetByIdfunção no Fauna.jsarquivo:

const getSnippetById = async (id) => {
  const snippet = await faunaClient.query(q.Get(q.Ref(q.Collection("codesnippet"),id)))
  snippet.id = snippet.ref.id
  delete snippet.ref
  return snippet
}

module.exports = {
  getResponse,
  createSnippet,
  updateSnippet,
  getSnippetById,
}

À medida que exportamos a função, de volta ao [id].jsarquivo, podemos usá-la para buscar um trecho específico com seu ID:

try {
  const id = context.params.id;
  const snippet = await getSnippetById(id);
  return {
    props: { snippet },
  };
} catch (error) {
  // as before
}

Modificando snippets armazenados

Agora, no upload.jsarquivo, vamos modificá-lo para poder acessar os dados armazenados se um snippet for editado:

- const { register, handleSubmit, errors, reset } = useForm()

+ const { register, handleSubmit, errors, reset } = useForm({
+   defaultValues: {
+     code: snippet ? snippet.data.code : "",
+     language: snippet ? snippet.data.language : "",
+     description: snippet ? snippet.data.description : "",
+     name: snippet ? snippet.data.name : "",
+   }
+ })

O código acima verifica se o snippet tem dados armazenados nele. Se retornar true, retorna os dados para os parâmetros: code, language, descriptione code. Se ele retornar false, ele retornará uma string vazia.

Em seguida, criaremos uma função para atualizar o snippet de código:

const createSnippet = async (data) => { ... }

const updateSnippet = async (data) => {
  const { code, language, description, name } = data
  const id = snippet.id
  try {
    await fetch("/api/updateSnippet", {
      method: "PUT",
      body: JSON.stringify({ code, language, description, name, id }),
      headers: {
        "Content-Type": "application/json",
      },
    })
    router.push("/")
  }
  catch (error) {
    console.log(error)
  }
}

return ( ,,, )

 

Testando a funcionalidade de edição de trecho

Se executarmos nosso código, podemos editar os trechos de código criados anteriormente clicando no botão Editar , alterando os dados do formulário e clicando em Enviar .

Adicionando a funcionalidade Excluir snippet

Agora, se retornarmos ao Homecomponente em nosso navegador, poderemos editar e atualizar trechos de código. Podemos finalmente adicionar a funcionalidade final para excluir nosso trecho de código. Crie e exporte uma nova função — deleteSnippet— no Fauna.jsarquivo:

const deleteSnippet = async (id) => {
  return await faunaClient.query(q.Delete(q.Ref(q.Collection("codesnippet"),id)))
}

module.exports = {
  ...
  deleteSnippet,
}

Criando o endpoint da API para a funcionalidade de exclusão

Vamos criar outro endpoint para esta função em nossa apipasta chamada deleteSnippet.jse preenchê-la com o seguinte código:

import { deleteSnippet } from "../../Fauna"
export default async function handler(req, res) {
  if (req.method !== "DELETE") {
    return res.status(405).json({ msg: "unauthorized" })
  }
  const { id } = req.body
  try {
    const deleted = await deleteSnippet(id)
    return res.status(200).json(deleted)
  }
  catch (error) {
    console.log(error)
    res.status(500).join({ msg: "error occured" })
  }
}

Em seguida, modificamos o Snippets.jsarquivo para adicionar a nova funcionalidade:

function Snippets({ snippet, snippetDeleted }) {
  ...
}

Em seguida, crie uma deleteSnippetfunção para buscar o endpoint do apie exclua o snippet referenciado pelo ID:

function Snippets({snippet, snippetDeleted}) {
  const deleteSnippet = async () => {
    try {
      await fetch("/api/deleteSnippet", {
        method: "DELETE",
        body: JSON.stringify({ id: snippet.id }),
        headers: {
          "Content-Type": "application/json",
        },
      });
      snippetDeleted();
    } catch (e) {
      console.log(e);
    }
  };

  return (
    <div className={styles.cont}>
      <p className={styles.lang}>{snippet.data.language}</p>
      <h3 className={styles.name}>{snippet.data.name}</h3>
      <p className={styles.descp}>{snippet.data.description}</p>

      <Code snippet={snippet}/>

      <div className={styles.links}>
        <Link href={`/edit/${snippet.id}`}>
          <a>Edit</a>
        </Link>
        <a onClick={deleteSnippet}>Delete</a>
      </div>
    </div>
  )
}

Também atualizamos o elemento âncora para chamar a deleteSnippetfunção quando ela é clicada.

 

Testando a funcionalidade de exclusão

Adicionamos a funcionalidade para excluir trechos de código. Agora podemos excluir trechos clicando no botão Excluir em nosso aplicativo.

Isso conclui as funcionalidades do aplicativo snippet. Vamos agora adicionar medidas de autenticação ao nosso aplicativo para permitir que apenas usuários autorizados criem ou modifiquem snippets em nosso aplicativo.

Autenticação de usuário

Por que precisamos de autenticação? Atualmente, os usuários podem criar snippets, mas também podem excluir e modificar snippets que não criaram. Precisaremos fornecer um meio para autorizar os usuários a acessar nosso site - e, portanto, a necessidade de autenticação do usuário.

Instalaremos o next-auth para autenticação por meio de nossa CLI:

npm i next-auth

Usaremos um token JWT para nossa autenticação. JWT é um padrão usado para criar tokens de acesso para um aplicativo.

Crie uma pasta nomeada authem sua apipasta e dentro dela, crie um arquivo [...nextauth].jscom o seguinte código:

import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google"

export default NextAuth({
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth?prompt=consent&access_type=offline&response_type=code",
    })
  ],
  jwt: {
    encryption: true
  },
  secret: process.env.secret,
  callbacks: {
    async jwt(token, account) {
      if (account ?.accessToken) {
        token.accessToken = account.accessToken
      }
      return token
    },
    redirect: async (url, _baseUrl)=>{
      if (url === "/profile") {
        return Promise.resolve("/")
      }
      return  Promise.resolve("/")
    }
  }
})

Depois disso, encerraremos nossos componentes no _app.jsarquivo:

import '../styles/globals.css'
import {Provider} from "next-auth/client"

function MyApp({ Component, pageProps }) {
  return (
    <Provider session={pageProps.session}>
      <Component {...pageProps} />
    </Provider>
  )
}
export default MyApp

 

Manipulando a autenticação

Vamos modificar nosso Homecomponente index.jspara retornar ao nosso componente se o usuário for autenticado, caso contrário ele retornará um botão que leva à página de autenticação:

import {signIn, signOut, useSession} from "next-auth/client"
...

Então dentro de Home:

export default function Home() {
  const { data:snippets, mutate }=useSWR("api/snippets")

  const [session, loadingSession] = useSession()
  if (loadingSession) {
    <>
      <p>...authenticating</p>
    </>
  }
  ...
}

O código acima verifica se o aplicativo é loadingSession. Se true, ele retorna o pbloco de tags, caso contrário, retorna o restante do nosso aplicativo se houver session. Em seguida, renderizaremos o “log in” se não houver sessão:

return (
  <div className={styles.container}>
    <Head> ... </Head>

    {!session && (
      <>
        <h1>Sign in to access snippet app</h1>
        <button onClick={() => signIn()}>Sign In</button>
      </>
    )}

    {session && (
      <>
        <main className={styles.main}>
          <h3>welcome {session.user.email}</h3>
          <button onClick={() => signOut()}>Sign Out</button>
          ...
        </main>
      </>
    )}
  </div>
)

Para fazer uso do “serviço de login do Google”, precisamos de credenciais de acesso do console de nuvem do Google. Para obter isso, faça login na sua conta do Google e navegue até o console do Google Cloud . Clique em CRIAR PROJETO na página, digite o nome do seu projeto e clique em Criar .

Na nova página que se abre, clique em + CRIAR CREDENCIAIS na barra de menu superior e, finalmente, selecione ID do cliente OAuth no menu suspenso.

Console do Google Cloud

Na página que se abre, você receberá uma notificação com um botão solicitando “Configurar a tela de consentimento”. Clique neste botão.

Na próxima página, selecione Externo no tipo de usuário e clique em Criar . Digite os campos obrigatórios para seu “Nome do aplicativo” e “E-mail” e clique em Salvar e continuar .

Nas seções Scopes and Test users , role para baixo e clique em Save and Continue .

Por fim, clique em Voltar ao painel e clique no botão Publicar .

Agora, podemos criar nossa chave clicando em Credenciais no menu lateral e, em seguida, Criar Credenciais na barra de menu superior. Selecione Oauth Client ID na lista suspensa e você verá uma página solicitando o tipo de aplicativo.

Selecione Aplicativo da Web e, em “Origens de JavaScript autorizadas”, clique em Adicionar URI e insira http://localhost. Por fim, em “URIs de redirecionamento autorizados”, clique em Adicionar URI e digite http://localhost/api/auth/callback/googleno campo, antes de clicar em Criar .

Criação de chave Oauth

Copie o ID do cliente e o segredo do cliente do pop-up que é aberto e adicione-os ao .envarquivo:

GOOGLE_CLIENT_ID=id
GOOGLE_CLIENT_SECRET=secret

Agora podemos fazer login usando a autenticação do Google em nosso aplicativo. Além disso, configuraremos nosso upload.jsarquivo como uma rota protegida para que usuários não autorizados não possam criar novos snippets:

import { getSession } from "next-auth/client"

function Upload({ snippet, user }) { ... }

export async function getServerSideProps(context) {
  const session = await getSession(context)
  if (!session) {
    context.res.writeHead(302, { Location: "/" })
    context.res.end()
    return {}
  }
  return {
    props: {
      user: session.user,
    }
  }
}

export default Upload;

Testando a autenticação do usuário

Se executarmos nosso aplicativo com o npm run devcomando, primeiro obteremos uma página solicitando que façamos login. Não podemos navegar para a página de upload pelo /uploadcaminho em nosso URL. Só podemos acessar nosso aplicativo quando usamos o recurso de login do Google para fazer login em nosso aplicativo.

 

Autorizando usuários a criar um snippet

Por fim, modificaremos a createSnippetfuncionalidade para adicionar o e-mail do usuário ao banco de dados e só mostraremos os botões Editar e Excluir se o e-mail corresponder.

Em Fauna.js, altere a createSnippetfunção assim:

const createSnippet = async (code, language, description, name, mail) => {
  return await faunaClient.query(q.Create(q.Collection("codesnippet"), {
    data:{code, language, description, name, mail}
  }))
}

No createSnippet.jsarquivo, faça as seguintes alterações:

- const { code, language, description, name } = req.body;
+ const { code, language, description, name, mail } = req.body;

- const createdSnippet = await createSnippet(code, language, description, name);
+ const createdSnippet = await createSnippet(code, language, description, name, mail);

Em upload.js:

function upload({ snippet, user }) {
+  const email = user.email;
   ...
}

E altere a createSnippetfunção e a updateSnippetfunção, como segue:

const createSnippet = async (data) => {
  const { code, language, description, name, mail } = data;
  console.log(data)
  try {
    await fetch("/api/createSnippet", {
      method: "POST",
      body: JSON.stringify({ code, language, description, name, mail:email }),
      headers: {
        "Content-type": "application/json"
      },
    })
    router.push("/")
  } catch (error) {
    console.log(error)
  }
}

const updateSnippet = async (data) => {
  const { code, language, description, name } = data
  const id = snippet.id
  try {
    await fetch("/api/updateSnippet", {
      method: "PUT",
      body: JSON.stringify({ code, language, description, name, mail:email }),
      headers: {
        "Content-Type": "application/json",
      },
    })
    router.push("/")
  }
  catch (error) {
    console.log(error)
  }
}

Agora podemos continuar fazendo com que os botões Editar e Excluir sejam exibidos apenas se o email corresponder.

Primeiro, passamos as user.mailprops para o Snippetcomponente em index.js:

<Snippets
  key={snippet.id}
  snippet={snippet}
  snippetDeleted={mutate}
+ email={session.user.email}
/>

Então em Snippet.js:

function Snippets({ snippet, snippetDeleted, email }) {
...
  {email == snippet.data.mail && (
    <>
      <div className={styles.links}>
        <Link href={`/edit/${snippet.id}`}>
          <a>Edit</a>
        </Link>
        <a onClick={deleteSnippet}>Delete</a>
      </div>
    </>
  )}
  ...
}

Testando nosso aplicativo

Execute npm run devna CLI e abra o aplicativo em seu navegador. Agora, se você criar um novo snippet, o email do usuário será adicionado ao banco de dados. Se o e-mail não corresponder, os botões Editar e Excluir não serão exibidos na página de exibição do snippet. Você pode testar isso fazendo login com um endereço de e-mail diferente daquele usado para criar os snippets de código.

 

Conclusão

Finalmente chegamos ao fim deste tutorial. Aprendemos como criar um aplicativo CRUD com Next.js e FaunaDB e como realizar operações CRUD com base na autenticação do usuário.

Fonte: https://www.sitepoint.com

#faunadb #nextjs 

Eva  Murphy

Eva Murphy

1625674200

Google analytics Setup with Next JS, React JS using Router Events - 14

In this video, we are going to implement Google Analytics to our Next JS application. Tracking page views of an application is very important.

Google analytics will allow us to track analytics information.

Frontend: https://github.com/amitavroy/video-reviews
API: https://github.com/amitavdevzone/video-review-api
App link: https://video-reviews.vercel.app

You can find me on:
Twitter: https://twitter.com/amitavroy7​
Discord: https://discord.gg/Em4nuvQk

#next js #js #react js #react #next #google analytics