Skip to content

Como criar um To-Do dApp usando Vercel + Cedalio

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()

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

}

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

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

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

}

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 ou a Torneira Alchemy.

Image


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

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!

   - The configuration file was saved at: '/Users/USER/.bifrost/todo-v1/deployment.json'

   - The smart-contract database address is: '0xd8771c6e0c3f01fd78b5e9c301893c4633180fc9'

   - The address of the library contract is: '0xb3eb32b495ecf76c3788e7a6acafdca0952d4654'

   - You can see the database contract here: 'https://mumbai.polygonscan.com/address/0xd8771c6e0c3f01fd78b5e9c301893c4633180fc9'

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 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


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:


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)'

E obtenha todos as tarefas executando:


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

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

    '[http://localhost:8080/graphql](http://localhost:8080/graphql)'

Criando uma aplicação React

Agora vamos desenvolver o aplicativo React. Se preferir, você pode iniciar um aplicativo do zero ou clonar nosso repositório 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

}

}

`;


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]


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



![Componente UI para criar uma Tarefa.](https://web3dev-forem-production.s3.amazonaws.com/uploads/articles/z06kzvaxkdrbbhecwzfy.png)

## 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

}


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

**```**

const GET_TODOS = gql`

query GetTodos{

      allTodos {

          id

          title

          description

          priority

          owner

          tags

          status

      }

}

`;

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 &lt;p>Loading...&lt;/p>;

   if (error) {

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

Crie um componente que exibe as tarefas como cards:


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

                   &lt;CardComponent key={todo.id}

                                  todo={todo}

                                  ownerAddress={ownerAddress}

                                  setState={setTodos}

                                  index={index}

                                  updateState={update}

                                  onUpdateTodo={onUpdateTodo} />

               ))

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

   }

 }

`;

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

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>

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"

   }

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}/>

               ))

           )

       }

   }

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.