WEB3DEV

Cover image for Como criar um To-Do dApp usando Vercel + Cedalio
Dimitris Carvalho Calixto
Dimitris Carvalho Calixto

Posted on • Atualizado em

Como criar um To-Do dApp usando Vercel + Cedalio

Introdução

Este tutorial assume um conhecimento básico dos conceitos de React, Typescript e Blockchain, como carteira e chave privada.

O principal objetivo deste projeto é criar um verdadeiro dApp que utilize Cedalio como uma plataforma de armazenamento. Escolhemos a Polygon-Mumbai como nossa rede. Se você quiser, pode escolher outra cadeia que nós suportamos.

Se você quiser pular no código, por favor, seja nosso convidado. Ou você pode usar nossa demonstração online.

App

Requisitos

  • Carteira Metamask (No momento suportamos Ethereum, Polygon, Metis e Hard Hat) Para este exemplo, usaremos a Polygon Mumbai. Por favor, acrescente a rede Polygon-Mumbai à sua carteira.
  • NodeJS 14v
  • NPM ou YARN
  • NPX
  • Ferramentas para desenvolvedores React
  • Material UI (style component)
  • Web3modal (Wallet Connect)
  • Beautiful dnd react
  • Apollo Client Devtool
  • Cedalio CLI

Começando

Neste tutorial, queremos mostrar como criar um aplicativo de tarefas (To Do app) que usa o Cedalio como camada de armazenamento. Nós nos concentramos em armazenar dados on-chain e consumi-los com um cliente usando um esquema simples de GraphQL e um cliente como ApolloClient em uma aplicação React. Obtemos os dados da blockchain usando o cliente Apollo GraphQL implementado em TypeScript. Decidimos criar um Contrato Inteligente de Banco de Dados para todos os usuários e filtrar as tarefas de cada cliente no frontend.

Um banco de dados de contrato inteligente para todos os usuários.

Para acelerar o tutorial, recomendamos clonar o repositório e segui-lo junto com o código-fonte.

Usamos o WalletConnect para fazer o login em nosso aplicativo de tarefase filtrar as tarefas criadas por sua carteira. Para obter um projectId você deve se inscrever aqui e criar um projeto. Uma vez que você tenha o projectId, crie um arquivo .env para o dApp de tarefas Cedalio com uma variável de ambiente chamada REACT_APP_WC_PROJECT_ID. Saiba mais sobre o WalletConnect.

Em cada componente criado neste tutorial, temos 3 seções principais: as definições de consulta GraphQL com a notação gql, estados para lidar com as mudanças e entradas do usuário, e os componentes JSX usados para renderizar.

Image

Note que utilizamos o gancho (hook) useAccount. Isto nos permite saber se a carteira já está conectada ou não, e enviá-la para ListComponent, que valida se o endereço existe (usuário está conectado) para renderizar a lista de tarefas.


 const { address } = useAccount()

Enter fullscreen mode Exit fullscreen mode

Definir a arquitetura do dApp

Temos duas abordagens diferentes de arquitetura utilizando o Cedalio. Você pode ter um banco de dados de contratos inteligentes para cada usuário, ou um banco de dados de contratos inteligentes para todos os usuários. Na primeira opção todos os usuários são os proprietários do contrato inteligente que detém as informações, e na outra opção o administrador do dApp é o proprietário do banco de dados do contrato inteligente. Para este exemplo, escolhemos a segunda opção (um banco de dados para todos os usuários) para simplificar o exemplo e focar na criação de um dApp de ponta a ponta usando nossa tecnologia. No próximo post, falaremos sobre como implementar um banco de dados para cada usuário.

Temos React + Apollo como nosso cliente de frontend e Cedalio como nosso backend GraphQL, usando a Polygon Mumbai como rede.

Image

Definir o schema do GraphQL

Primeiro, precisamos definir nosso esquema básico do GraphQL, criamos um tipo de objeto para armazenar as tarefas e um arquivo chamado todo.graphql

O esquema é parecido com este:


type Todo {

   id: UUID!

   title: String!

   description: String

   priority: Int!

   owner: String!

   tags: [String!]

   status: String

}

Enter fullscreen mode Exit fullscreen mode

Então podemos usar a CLI do Cedalio para compilar o esquema para verificar se está correto. Se você ainda não tem nossa CLI instalada, siga estes passos.

Image


>bifrost compile --schema-file /path/to/your/todo.graphql

Enter fullscreen mode Exit fullscreen mode

Algo útil é exportar o esquema para ver todas as mutações e consultas que o Cedalio cria automaticamente para seu esquema. Você também pode revisar os tipos de entrada para todas as mutações. Para exportar o esquema executado:


bifrost export-schema --schema-file /path/to/your/todo.graphq

Enter fullscreen mode Exit fullscreen mode

Este é o resultado que você obtém. Se preferir, você pode redirecionar a saída para um arquivo :D:


scalar UUID

enum RelationType {

  OWNS

  REFERENCES

  EMBEDDED

  BELONGS_TO

}

directive @belongsTo(type: String!) on OBJECT

directive @relation(type: String!, via: String) on FIELD_DEFINITION

directive @embedded on FIELD_DEFINITION

directive @belongsTo(via: String!) on FIELD_DEFINITION

type Todo {

  id: UUID!

  title: String!

  description: String

  priority: Int!

  owner: String!

  tags: [String!]

  status: String

}

input TodoInput {

  title: String!

  description: String

  priority: Int!

  owner: String!

  tags: [String!]

  status: String

}

input TodoUpdateInput {

  title: String

  description: String

  priority: Int

  owner: String

  tags: [String!]

  status: String

}

type Query {

  todoById(id: UUID!): Todo

  allTodos: [Todo!]!

}

type Mutation {

  createTodo(todo: TodoInput!): Todo!

  updateTodo(id: UUID!, fields: TodoUpdateInput!): Todo!

}

schema {

  query: Query

  mutation: Mutation

}

Enter fullscreen mode Exit fullscreen mode

Implementando seu Esquema

Uma vez definido seu esquema, você pode implementá-lo e começar a executar mutações e consultas contra ele. Atualmente, para implantar seu esquema, você precisa de uma carteira com tokens suficientes da blockchain onde você deseja implantá-lo. Há muitas opções para escolher. Recomendamos a MetaMask.

Agora você está pronto para implantar e servir seu esquema, mas primeiro temos que definir uma variável de ambiente - nosso endereço de chave privada da Metamask - . Seja muito cauteloso ao utilizar esta chave. Tenha em mente que só acessamos sua chave privada para assinar as transações que são enviadas para a blockchain de sua escolha ao implantar um esquema ou executar uma mutação. Aqui você pode encontrar mais informações sobre isto.

**


export PRIVATE_KEY='your-private-key'

**```

 **

Por favor, verifique se você tem tokens suficientes para implantar este contrato inteligente e para executar transações futuras. Se você precisar de mais tokens, você tem duas opções: A [Torneira Polygon](https://faucet.polygon.technology) ou a [Torneira Alchemy](https://mumbaifaucet.com).


![Image](https://web3dev-forem-production.s3.amazonaws.com/uploads/articles/egnwt8z17ud396ur7oyb.png)





Enter fullscreen mode Exit fullscreen mode

bifrost deploy --schema-file /path/to/your/todo.graphql --network polygon-mumbai






Enter fullscreen mode Exit fullscreen mode

Please enter the schema name: todo-v1

✅ Schema '/Users/nicomagni/Desktop/Cedalio/ToDo/todo.graphql' was successfully compiled!

Deploying Graphql schema 'todo.graphql' with wallet address '0x#########################################' in network 'polygon-mumbai'... 🚀

📣 deploying may take between 10 to 30 seconds depending on network congestion

✅ Schema 'todo-v1' was successfully deployed!

You can launch GraphQL server for this schema by executing

bifrost serve --schema-name todo-v1




Uma vez implantado seu esquema, você está pronto para [iniciar um servidor GraphQL local](https://docs.cedalio.com/quickstart/getting-started/run-locally#serve-your-graphql-schema) e executar consultas e mutações. Este servidor local do GraphQL  destina-se a ser usado para fins de desenvolvimento e testes. Para iniciar o servidor, tudo o que você precisa fazer é executar o seguinte:


![Image](https://web3dev-forem-production.s3.amazonaws.com/uploads/articles/nsgnxxb3myvnl0uu1brw.png)





Enter fullscreen mode Exit fullscreen mode

bifrost serve --schema-name todo-v1




Como mostrado no log de saída, você pode copiar o endereço do banco de dados do contrato inteligente no explorador da blockchain. Isto é útil para entender como as transações são executadas contra o banco de dados do contrato inteligente.

Uma vez que um servidor GraphQL esteja rodando, você pode executar consultas e mutações. Por padrão, o endpoint do GraphQL está disponível em `http://localhost:8080/graphql`. Com o servidor em execução, agora podemos criar um objeto `ToDo` no esquema de tarefas executando a mutação `createTodo` que é gerada automaticamente pelo Bifrost.

Se você quiser testar seu servidor, você pode criar o item ToDo em execução:



Enter fullscreen mode Exit fullscreen mode

curl -v -H 'Content-Type: application/json' \

-d '{"query":"mutation { createTodo(todo: { title:\"Something\", priority: 1, owner:\"0x305d4bcaE378094F1923f8BB352824D9496510b1\",tags:[\"health\",\"rutine\"]\n}) { id title tags} }"}' \

'[http://localhost:8080/graphql](http://localhost:8080/graphql)'
Enter fullscreen mode Exit fullscreen mode



E obtenha todos as tarefas executando:



Enter fullscreen mode Exit fullscreen mode

curl -v -H 'Content-Type: application/json' \

-d '{"query":"query { allTodos { id title tags status } }"}' \

'[http://localhost:8080/graphql](http://localhost:8080/graphql)'
Enter fullscreen mode Exit fullscreen mode



## Criando uma aplicação React

Agora vamos desenvolver o aplicativo React. Se preferir, você pode iniciar um aplicativo [do zero](https://github.com/facebook/create-react-app) ou [clonar nosso repositório](https://github.com/cedalio/To-Do-single-db)  e seguir as instruções abaixo para entender como ele funciona.

## Adicionar uma tarefa

Em nosso TodoInputComponent temos que usar o apollo-cliente para fazer a mutação que cria uma nova tarefa. Primeiro definimos nossa mutação em nosso **TodoInputComponent.tsx:**

**

```**

const CREATE_TODO = gql`

 mutation CreateTodo($title:String!, $description:String, $priority:Int!, $owner:String!,$tags:String, $status:String){

   createTodo(todo: {title: $title, description:$description, priority:$priority, owner:$owner, tags:$tags, status: $status }){

    id

    title

    description

    priority

    owner

    tags

    status

   }

 }

`;

Enter fullscreen mode Exit fullscreen mode

Depois criamos as variáveis e tipos que precisamos (título, descrição, prioridade, proprietário, etiqueta e status) e depois as adicionamos à mutação createTodo. Você pode usar o esquema exportado para verificar os tipos de entrada de mutação necessários.

Estamos usando os estados do React para lidar com as entradas e erros do usuário:


const [priority, setPriority] = React.useState("");

const [title, setTitle] = React.useState("");

const [description, setDescription] = React.useState("");

const [titleError, setTitleError] = React.useState(false);

const [descriptionError, setDescriptionError] = React.useState(false);

const [tag, setTag] = React.useState<string[]>([]);

const priorities = [1, 2, 3, 4]

Enter fullscreen mode Exit fullscreen mode

Estamos usando o gancho useMutation do cliente Apollo para vincular a execução de nossa mutação:


const [createTodo, { data, loading, error }] = useMutation(CREATE_TODO);

Enter fullscreen mode Exit fullscreen mode

Componente UI para criar uma Tarefa.

Renderize as tarefas

Primeiro precisamos definir nosso tipo 'todo' para lidar com as tarefas em toda a aplicação.


type Todo = {

   title: string,

   description?: string,

   tags: Array<string>,

   priority: number,

   id: string,

   owner: string,

   status: string

}

Enter fullscreen mode Exit fullscreen mode

Então, precisamos definir nossa consulta na ListComponent.tsx:

**


const GET_TODOS = gql`

query GetTodos{

      allTodos {

          id

          title

          description

          priority

          owner

          tags

          status

      }

}

`;

Enter fullscreen mode Exit fullscreen mode

Para apresentar a lista de tarefas criadas em nossa conta, usamos o gancho useQuery do apollo-cliente.


const { loading, error, data } = useQuery(GET_TODOS);

   React.useEffect(() => {

    if (data) setTodos(data.allTodos);

   }, [data]);

   if (loading) return <p>Loading...</p>;

   if (error) {

    return <p>Error : {error.message}</p>;

Enter fullscreen mode Exit fullscreen mode

Crie um componente que exibe as tarefas como cards:


todos.filter((todo) => todo.status === "ready").map((todo: Todo, index) => (

                <CardComponent key={todo.id}

                                todo={todo}

                                ownerAddress={ownerAddress}

                                setState={setTodos}

                                index={index}

                                updateState={update}

                                onUpdateTodo={onUpdateTodo} />

            ))

Enter fullscreen mode Exit fullscreen mode

Definimos o status como "ready"(pronto) como o padrão. Os outros estados válidos são "done" (Feito) e "delete" (apagar).

Image

Marcar como Done e Delete

Usamos a mutação de atualização para mudar o status de uma tarefa, estamos usando o mesmo padrão de design que usamos no passado. O CardComponent.tsx se parece com isto:


const UPDATE_TODO = gql`

 mutation UpdateTodo($id:UUID!, $status:String){

   updateTodo(id: $id, fields:{status: $status} ){

    id

    title

    description

    priority

    owner

    tags

    status

   }

 }

`;

Enter fullscreen mode Exit fullscreen mode

Isto recebe 2 variáveis, a UUID da tarefa que queremos mudar e o campo (neste caso, o status) que precisamos atualizar. Como já fizemos anteriormente, precisamos da função updateTodo do gancho useMutation


const [updateTodo, { data, loading, error }] = useMutation(UPDATE_TODO);

Enter fullscreen mode Exit fullscreen mode

Também queremos executar a mutação para atualização quando o usuário arrasta a tarefa nas zonas delete/done, isto requer a bela biblioteca dnd, que nos permite definir nosso cartão de tarefas como arrastável (draggable), e as 3 colunas que precisamos (delete, ready e done) como soltáveis (droppable). O cartão da tarefa deve ter a etiqueta <Draggable />


&lt;DragDropContext onDragEnd={onDragEnd} onDragStart={onDragStart} onDragUpdate={onDragUpdate}>

    &lt;div className="container">

        &lt;Droppable droppableId='delete'>

            {(provided) => (

                &lt;div {...provided.droppableProps} ref={provided.innerRef} className="delete-container">

                    &lt;HighlightOffIcon fontSize="large" sx={{ height: "200px", width: "200px", color: deleteIconColor }} />

                    {provided.placeholder}

                &lt;/div>

            )}

        &lt;/Droppable>

        &lt;Droppable droppableId='ready'>

            {(provided) => (

                &lt;div {...provided.droppableProps} ref={provided.innerRef} className="list-container">

                    {displayTodos()}

                    {provided.placeholder}

                &lt;/div>

            )}

        &lt;/Droppable>

        &lt;Droppable droppableId='done'>

            {(provided) => (

                &lt;div {...provided.droppableProps} ref={provided.innerRef} className="done-container">

                    &lt;CheckCircleOutlineIcon fontSize="large" sx={{ height: "200px", width: "200px", color: doneIconColor }} />

                    {provided.placeholder}

                &lt;/div>

            )}

        &lt;/Droppable>

    &lt;/div>

    &lt;TodoInputComponent setState={setNewTodo} address={ownerAddress} />

    &lt;Snackbar open={open} autoHideDuration={6000} onClose={handleClose}>

        &lt;Alert onClose={handleClose} severity="success" sx={{ width: '100%' }}>

            A operação foi um sucesso!

        &lt;/Alert>

    &lt;/Snackbar>

&lt;/DragDropContext>

Enter fullscreen mode Exit fullscreen mode

Adicionar um Estado Vazio

Precisamos ter um item de tarefa com estado vazio para nossos usuários quando eles chegam e não temos nenhum item de tarefa ainda criado.


const defaultTodo = {

    title: "Este é o seu primeiro cartão de tarefas

!",

    description: "Comprar comida para o meu cachorro e trocar a água

",

    tags: ["healt", "rutine"],

    priority: 1,

    id: "abcdefg12345",

    owner: "abcdefg123",

    status: "ready"

   }

Enter fullscreen mode Exit fullscreen mode

E incluí-lo na função displayTodos. Observe que filtramos os resultados da consulta e consideramos "exibíveis" (displayable) os que têm status = ready.


const displayTodos = () => {

    const displayableTodos = todos.filter((todo) => todo.status === "ready")

    if (displayableTodos.length === 0) {

        return &lt;CardComponent key="default" todo={defaultTodo} ownerAddress={defaultTodo.owner} setState={setTodos} index={1} updateState={update} onUpdateTodo={onUpdateTodo} default={true}/>;

    }

    else {

        return (

            displayableTodos.map((todo: Todo, index) => (

                &lt;CardComponent key={todo.id} todo={todo} ownerAddress={ownerAddress} setState={setTodos} index={index} updateState={update} onUpdateTodo={onUpdateTodo} default={false}/>

            ))

        )

    }

   }

Enter fullscreen mode Exit fullscreen mode

Image

Quando a transação na blockchain é feita e confirmada, vemos uma notificação (toast) na parte inferior esquerda como uma mensagem para o usuário.

Conclusão

Com o Cedalio podemos criar um dApp apenas definindo o modelo de dados e o Cedalio cria automaticamente os resolvedores para você. Você também pode armazenar todas as informações on-chain sem se debater com o Solidity ou contratos inteligentes.

Finalmente, você também pode escolher uma rede ou cadeia diferente para implantar seu DB (Bando de Dados) apenas mudando o sinalizador de implantação e isso é tudo o que você precisa fazer.

Artigo escrito no Cedalio . A versão original pode ser encontrada aqui. Traduzido e adaptado por Dimitris Calixto.

Latest comments (0)