Giter VIP home page Giter VIP logo

store-block's Introduction

Store Block course template

Here you'll learn how to create awesome Store Framework blocks!

store-block's People

Contributors

ataideverton avatar dependabot[bot] avatar vtexgithubbot avatar

Watchers

 avatar  avatar

store-block's Issues

Conhecendo uma app VTEX IO

Conhecendo uma app VTEX IO

Introdução

Antes de começar, é necessário relembrar alguns conceitos importantes para uma maior compreensão do fluxo lógico ao desenvolver uma app.

manifest.json

vendor

Define o nome da conta VTEX que está desenvolvendo a app. Essa conta é responsável pela manutenção e distribuição da app (pode ser instalada em outras contas ou somente na própria)

O vendor vtex é utilizado em casos de apps nativas.

name

Identifica o nome da aplicação. Não deve ter caracteres especiais - exceto - - ou caracteres maiúsculos.

version

Identifica a versão atual da app. Para versionamento, utilizamos a especificação Semantic Versioning 2.0.0. O formato do versionamento é bem definido, com o uso de patches, minors e majors.

Abaixo um resumo da especificação:

  • Patches: você deve criar um patch quando está consertando um bug de forma retrocompatível
  • Minors: você deve criar uma versão minor quando adicionar funcionalidade de forma retrocompatível.
  • Majors: você deve criar uma versão major quando você realiza mudanças incompatíveis de API (o que costumamos chamar de breaking changes)

Exemplo: Se uma API que está na versão 2.3.2 e uma nova funcionalidade não tiver breaking changes, você pode atualizar a versão para 2.4.0.

No momento que o deploy é feito, há um worker chamado housekeeper responsável por atualizar a versão automaticamente para todas as contas. No caso de minors e patches, o housekeeper atualiza a app automaticamente em todas as contas, já que as mudanças são retrocompatíveis. Atualizações de majors, no entanto, possuem breaking changes, por isso o housekeeper não atualiza a app em todas as contas; sendo assim, a atualização deve ser feita manualmente.

builders

O desenvolvimento de apps no VTEX IO utiliza o conceito de Code as Configuration (CaC). Este paradigma é abstraído através do campo de builders que facilita o desenvolvimento, abstraindo a configuração de serviços.

Exemplo: para criar uma extensão no painel administrativo criam-se apps que utilizam o builder de admin.

image

Ao linkar a app, portanto, uma pasta de nome correspondente é enviada ao seu builder, que, por sua vez, transforma cada arquivo em configuração para o serviço competente.

dependencies

Uma app pode depender de outras aplicações. Esse campo lista todas as dependências necessárias para o correto funcionamento da app.

Exemplo

No exemplo da estrutura do manifest.json abaixo, é possível observar características mencionadas acima. Em particular, a versão é 0.0.1, onde os números são, respectivamente, major, minor e patch.

{
  "vendor": "vtex",
  "name": "countdown",
  "version": "0.0.1",
  "title": "Countdown",
  "description": "Countdown component",
  "defaultLocale": "pt-BR",
  "builders": {
    "messages": "1.x",
    "store": "0.x",
    "react": "3.x"
  },
  "mustUpdateAt": "2019-04-02",
  "scripts": {
    "postreleasy": "vtex publish --verbose"
  },
  "dependencies": {
    "vtex.styleguide": "9.x",
    "vtex.css-handles": "0.x"
  },
  "$schema": "https://raw.githubusercontent.com/vtex/node-vtex-a pi/master/gen/manifest.schema"
}

Definindo um resolver GraphQL

Definindo um resolver GraphQL

Introdução

Agora que já foi certificado que o tipo foi configurado corretamente, será definida a função que resolve a query. Em GraphQL, é possível definir resolvers para qualquer campo do schema e também para tipos. Para saber mais, leia aqui.

A função resolver será definida para a query gif no serviço node da aplicação. Esta função será, propriamente, a implementação da funcionalidade que está sendo criada.

No arquivo node/resolvers/helloWorld.ts você verá que já há um resolver definido para o campo helloWorld, algo semelhante será feito. A assinatura de uma função resolver é a seguinte:

const resolver = (parent, args, context) => {};
Nome Descrição
parent O último objeto que foi resolvido (o pai de tal elemento no grafo do schema). Não é útil para queries principais.
args O objeto de argumentos que foram passados para aquela query. É também conhecido como variables
context Um valor de contexto provido para todo resolver. Aqui você poderá ler informações sobre a requisição, ou usar algum serviço da VTEX.

Atividade

  1. Na pasta node/resolvers, crie um arquivo chamado giphy.ts, nele você implementará o resolver do campo gif. A princípio, apenas só é desejável ver que está tudo funcionando, então o resolver irá apenas retornar uma string "it works!":

    // node/resolvers/giphy.ts
    export const gif = (_: any,
      __: any,
      ___: Context 
    ) => { return 'it works!' }
  2. No arquivo node/index.ts, há um objeto exportado com as funções resolvers, adicione um campo chamado gif, este nome deve ser igual ao nome do campo definido no schema.graphql.

    // node/resolvers/index.ts
    export default new Service<Clients, {}>({
      clients,
      graphql: {
        resolvers: {
          Query: {
            helloWorld,
    +       gif 
          },
        },
      },
    })
  3. Após isso, salve o arquivo e veja o output do vtex link. Caso seu GraphiQL já esteja aberto, você poderá refazer a query e verificar se o resultado esperado foi obtido.

É importante notar que o tipo de dado retornado pelo seu resolver deve casar com o tipo definido no schema.graphql, senão o GraphQL não vai retornar o valor corretamente. Como nosso campo gif está tipado para retornar uma String e retornamos 'it works!', está tudo bem!

É importante notar que o tipo de dado retornado pelo seu resolver deve casar com o tipo definido no schema.graphql, senão o GraphQL não vai retornar o valor corretamente. Como nosso campo gif está tipado para retornar uma String e retornamos 'it works!', está tudo bem!

image

Tornando o bloco countdown customizável

Tornando o bloco countdown customizável

Introdução

Agora que temos um h1, é possível utilizá-lo para mostrar informações que dependam de uma prop do componente. Para isso, alguns conceitos serão apresentados, já que são necessários para desenvolver uma aplicação.

Conceitos

  • O Hook

    Hooks são APIs que permitem utilizar funcionalidades de React. Sem hooks, uma componente funcional em React, é simplesmente uma função que retorna uma tag escrita em tsx. Hooks permitem entre outras coisas, armazenar estado entre diferentes renderizações e executar efeitos colaterais. Obs.: eles não funcionam dentro de classes.

    Exemplo:

    const [count, setCount] = useState(0);
  • Interface para definir as props

    Define as props e também os tipos associados.

    interface CountdownProps {}
  • Definição das configurações de um bloco

    No VTEX IO, oferecemos uma ferramenta de gestão de conteúdo da loja, chamada Site Editor. Com essa ferramenta, encontrada no admin da VTEX, podemos alterar imagens e texto de componentes sem precisar modificar o código da loja.

    Para que o seu bloco possa aceitar configurações do usuário, é utilizado um JSON schema, que irá gerar um formulário para o Site Editor. Abaixo é possível ver um exemplo de schema:

    // react/Countdown.tsx
    Countdown.schema = {
        title: 'editor.countdown.title',
        description: 'editor.countdown.description',
        type: 'object',
        properties: {},
    }

    O schema também é responsável por definir os textos que serão vistos pelo usuário do admin no formulário.

Atividade

  1. Na interface definida no Countdown.tsx, adicione uma prop chamada targetDate, ela é do tipo string. Com isso, estamos definindo uma prop do componente que será utilizada para inicializar o contador.

    A definição da prop em si é feita através da declaração dela na interface CountdownProps no arquivo Countdown.tsx, mostrada anteriormente. Assim, adicione uma linha que defina uma prop chamada targetDate, do tipo string.

    // react/Countdown.tsx
    interface CountdownProps {
    +   targetDate: string    
    }
  2. Feito isso, é preciso utilizá-la no componente, substituindo o texto de antes, "Teste Countdown" por um outro texto, através do Site Editor.

    No futuro, esse targetDate será utilizado para definir a data de término para o contador. Porém, por enquanto, esse campo pode ser genérico.

    Primeiramente, é preciso alterar o componente para utilizar a prop targetDate definida anteriormente. Para isso, é preciso adicionar dentro do componente React a variável a ser utilizada no h1. Você lembra do bloco de código do componente na etapa anterior? Vamos utilizá-lo novamente para fazer as alterações.

    // react/Countdown.tsx
    const Countdown: StorefrontFunctionComponent<CountdownProps> = ({ targetDate }) => {
      return (
        <div>
          <h1>{ targetDate }</h1>
        </div>
      ) 
    }
  3. Além disso, para alterar essa propriedade através do Site Editor, é necessário adicionar essa mesma prop ao schema. Isso é feito através da adição de um objeto com chave targetDate dentro do objeto properties no schema. Ou seja:

    // react/Countdown.tsx
    Countdown.schema = {
      title: 'countdown.title',
      description: 'countdown.description',
      type: 'object',
      properties: {
    +   targetDate: {
    +      title: 'Sou um título',
    +      description: 'Sou uma descrição',
    +      type: 'string',
    +      default: null,
    +   },
      },
    }

Pronto! Agora você pode alterar o conteúdo do texto através do Site Editor. Vamos ver como ficou? Vá até o Site Editor e clique em Countdown no menu lateral, isso abrirá o menu de edição da app, que será como a imagem abaixo.

Agora, no campo abaixo do título, digite alguma coisa e veja a alteração, que passará a exibir o texto que você digitou.

image

O que é GraphQL?

O que é GraphQL?

Introdução

GraphQL é uma linguagem de query tipada para APIs. Com ele é possível ter uma completa descrição dos dados de uma API além de oferecer ao cliente total controle sobre o que deseja consumir. GraphQL não está relacionado a nenhuma tecnologia de storage ou banco de dados.

Um serviço GraphQL funciona a partir de um schema, que representa uma árvore de tipos em que as raízes são sempre uma Query ou Mutation. O desenvolvedor define que funções vão responder cada um dos tipos. Exemplo: um serviço GraphQL hipotético que diz qual o usuário está logado, assim como os pedidos daquele usuário poderia ser feito dessa forma:

type Query {
  loggedUser: User
}

type User {
  id: ID
  orders: [Order]
}

type Order {
  id: ID
  totalValue: Float
}
const queryLoggedUser = (request) => request.auth.user

const userOrders = (user) => user.getOrders()

GraphQL no VTEX IO

Conforme visto anteriormente, no VTEX IO, builders são usados para abstrair configurações e complexidades no uso de tecnologias chave. No caso de aplicações back-end, assim como no caso de React, utiliza-se TypeScript e são oferecidos builders de node e graphql, utilizados em conjunto.

O builder de graphql é utilizado para criar o schema com seus respectivos tipos e campos. Já o de node para criar as funções que resolvem os tipos e campos definidos no schema.
Nesta etapa do curso será visto um exemplo de como esses dois builders se conectam.

Definição de Schema

Como dito anteriormente, em um schema GraphQL existem dois tipos básicos: Query e Mutation. Convencionou-se que queries são utilizadas para busca de dados (seria equivalente ao verbo HTTP GET). Enquanto as mutations são utilizadas em casos que ocorrem efeitos colaterais ao realizar uma operação, por exemplo alterar algum campo em um banco de dados ou escrever em alguma API (seria equivalente aos verbos HTTP: POST, PUT, PATCH, DELETE).

Para o escopo desse curso, só serão construídas queries, mas o conceito é exatamente o mesmo. O que muda é a semântica da função que resolve o campo.

Vamos adicionar um campo no tipo Query chamado helloWorld que será do tipo String.

type Query {
+  helloWorld: String
}

Ao linkar sua app, você terá acesso a uma URL contendo um link para uma IDE de GraphQL (pode ser visto na imagem abaixo) chamada GraphiQL, esse ambiente permite que você teste seu schema de forma rápida, sem precisar desenvolver um cliente em código (pense nele como um Postman GraphQL)

image

Esse campo precisa ter uma função que irá resolvê-lo na pasta node. É o que iremos fazer agora.

Definição de Resolver

Resolvers são funções responsáveis por "resolver" uma query e devolver o dado solicitado. Vale ressaltar que o retorno de um resolver é uma promise, ou seja, o GraphQL espera a resolução dessa promessa para devolver os resultados obtidos.

Na pasta node, vá na pasta resolvers e crie um novo arquivo helloWorld.ts. Nesse arquivo iremos criar a função que resolverá o campo helloWorld que adicionamos anteriormente no tipo Query.

export const helloWorld = () => 'Hello World'

Agora, se você acessar novamente o GraphiQL e realizar a query abaixo, verá que o GraphiQL retorna o Hello World, conforme o esperado.

{
  helloWorld
}

Atividade

Agora, nos voltaremos à nossa aplicação de countdown com gif.

  1. Crie o campo gif no schema GraphQL. Esse campo receberá como argumento um term do tipo String que irá em uma atividade futura ser utilizado para realizar uma busca na API do gif pelo termo passado como argumento.
//
type Query {
   helloWorld: String
+  gif(term: String): String
}

image

Criando um bloco GIF

Criando um bloco GIF

Introdução

Agora, será criado um bloco que irá ser utilizado com um GIF. Porém, como ainda não foi feita a conexão para fazer uma query e, portanto, uma conexão com o back-end, esse bloco terá um placeholder no lugar do GIF (não se preocupe, o GIF será adicionado na próxima etapa!).

A maioria dos conceitos abordados nessa etapa já foram vistos anteriormente, como a criação de um novo componente React, a adição de uma interface e a mudança do tema.

Atividade

  1. Crie o arquivo Gif.tsx na pasta /react; seu formato é muito semelhante ao presente em Title.tsx, mas com as modificações necessárias. Vale ressaltar que o texto a ser exibido é um placeholder, logo, pode ser qualquer coisa dentro de uma div que utilize os estilos já mostrados anteriormente.

    É encorajado que você tente fazer esse item sozinho, mas se precisar de ajuda, o esqueleto do código está logo abaixo.

    import React from 'react'
    
    import { useCssHandles } from 'vtex.css-handles'
    
    const CSS_HANDLES = ['gif'] as const 
    
    const Gif: StorefrontFunctionComponent<GifProps> = ({ }) => {
        const handles = useCssHandles(CSS_HANDLES)
        return (
            <div className={`${handles.gif} t-heading-2 fw3 w-100 c-muted-1 db tc`}>
                Vou ser um GIF em breve...
            </div>
        )   
    }
    
    interface GifProps {
        
    }
    
    Gif.schema = {
        title: 'editor.countdown-gif.title',
        description: 'editor.countdown-gif.description',
        type: 'object',
        properties: {
        }
    }
    
    export default Gif

    Lembrando que o que está definido no schema é referente às strings internacionalizadas presentes no Site Editor.

  2. Agora que tem-se o esqueleto do bloco gif, é preciso adicionar a interface equivalente, como foi feito para o contador e para o título. Vá ao arquivo interfaces.json, na pasta /store e adicione a interface do bloco que você acabou de criar. Não se esqueça de que o campo component deve ter o mesmo nome do arquivo React (Gif.tsx).

  3. Por fim, é necessário adicionar o bloco ao tema, na home da loja. Para isso, vá ao store-theme, na pasta /store/blocks/home e, no arquivo home.jsonc, adicione o bloco gif.

O resultado esperado nesse passo é:

image

Modificando o bloco countdown para ter um estilo configurável

Modificando o bloco countdown para ter estilos configuráveis

Introdução

Agora que já implementamos o countdown, que tal adicionar um pouco de customização? Nessa etapa, você irá aprender conceitos básicos a respeito de CSS handles e Tachyons para, em seguida, customizar o estilo da sua app.

CSS Handles

Os handles de CSS são utilizados para customizar os componentes da sua loja através de classes de CSS no código do tema. Todas essas configurações são definidas através da app vtex.css-handles, responsável por declarar todos os pontos de customização do seu bloco.

Definindo os nomes dos seus handles e adicionando aos seus respectivos elementos HTML, é possível entregar ao usuário do tema pontos de customização que que permitam criar layouts flexíveis.

Tachyons

O Tachyons é um framework para CSS funcional. Diferentemente de outros frameworks conhecidos, como o Bootstrap, ele não apresenta componentes UI "pré-buildados". Na verdade, seu objetivo é justamente separar as regras de CSS em partes pequenas e reutilizáveis. Esse tipo de estratégia é comumente conhecida como Subatomic Design System e, caso você tenha interesse, pode encontrar uma referência neste link. Essa estratégia torna frameworks como o Tachyons muito flexíveis, escaláveis e rápidos.

Grande parte das definições de Tachyons podem ser alteradas, de forma que sua loja passe a ter um estilo mais customizado. Para isso, basta definir um arquivo JSON na pasta styles/configs; essas informações podem ser encontradas de forma mais detalhada em: Build a store using VTEX IO - Customizing styles.

Atividade

  1. Importe o hook useCssHandles. Para isso, volte ao Countdown.tsx e faça o import:

    // react/Countdown.tsx
    import { useCssHandles } from 'vtex.css-handles'
  2. Além disso, defina em um Array todos os handles que serão necessários (neste caso, será utilizado apenas 'countdown'):

    // react/Countdown.tsx
    const CSS_HANDLES = ['countdown']
  3. Utilize o useCssHandles no componente Countdown para definir o handle do countdown:

    // react/Countdown.tsx
    const Countdown: StorefrontFunctionComponent<CountdownProps> = ({ targetDate = DEFAULT_TARGET_DATE }) => {
      const [timeRemaining, setTime] = useState<TimeSplit>({
        hours: '00',
        minutes: '00',
        seconds: '00'
      })
    
    + const handles = useCssHandles(CSS_HANDLES)
    
      tick(targetDate, setTime)
    
      return (
        <div>
          <h1>
            { `${timeRemaining.hours}:${timeRemaining.minutes}:${timeRemaining.seconds}` }
          </h1>
        </div>
      )
    }
  4. Por fim, é preciso utilizar o handle no componente a fim de ver a customização. Para isso, é necessário utilizar a prop className com as classes a serem utilizadas e as classes de Tachyons, para os estilos globais.

    // react/Countdown.tsx
    
    import React from 'react'
    ...
    
    const Countdown: StorefrontFunctionComponent<CountdownProps> = ({ targetDate = DEFAULT_TARGET_DATE }) => {
      const [timeRemaining, setTime] = useState<TimeSplit>({
        hours: '00',
        minutes: '00',
        seconds: '00'
      })
    
      const handles = useCssHandles(CSS_HANDLES)
    
      tick(targetDate, setTime)
    
      return (
    +   <div className={`${handles.countdown} t-heading-2 fw3 w-100 c-muted-1 db tc`}>
          {`${timeRemaining.hours}:${timeRemaining.minutes}:${timeRemaining.seconds}`}
        </div>
      )
    }

Vamos ver o resultado?

image

Definindo um client no VTEX IO

Definindo um client no VTEX IO

Introdução

Já foi visto como criar um resolver GraphQL; agora o desenvolvimento da funcionalidade será continuado. Comumente, a implementação de uma funcionalidade em uma app requer a comunicação com outros clientes, sejam externos ou internos (outras apps VTEX).

Para realizar comunicação com outros clientes, é necessário criar um client. Um client é uma entidade que extende uma definição genérica exportada node-vtex-api. Seu propósito é facilitar e fornecer auditoria de requisições com outras APIs.

Você pode ver um exemplo de um client criado para se comunicar com um serviço externo na app service-example.

Além de External Clients, como este do exemplo, você pode criar:

  • App Clients, para comunicação HTTP com outras apps dentro da conta VTEX IO;
  • App GraphQL Clients, para comunicação através de GraphQL com outras apps também do IO;
  • Infra Clients para comunicação com serviços de Infra do VTEX IO.

Após a criação de um client, é necessário adicioná-lo na exportação do Service. Depois disso, os clients estarão disponíveis para acesso na assinatura da função de requisição dos handlers através de:

ctx.clients.

Atividade

Criando o client

  1. Crie um arquivo em node/clients chamado giphy.ts.
  2. A partir do client de exemplo, crie um GiphyClient que se comunica com a API do Giphy na URL https://api.giphy.com/v1/gifs/
  3. O client precisa ter apenas um método chamado translate que aceita um term: string e retornará uma URL de GIF. Este método deverá chamar o endpoint translate da API.
    OBS.: Use a api_key dp2scGnUcDee5yLRI1qJMTRTAAJey9Tl para testar seu client.
  4. Após criar (e exportar) o client em giphy.ts, em node/clients/index.ts importe Giphy from './giphy' e adicione na classe Clients:
    public get giphy() {
        return this.getOrSet('giphy', Giphy)
    }
    

Alterando o resolver

  1. Agora, voltando ao resolver, é possível utilizar ctx.giphy.translate para finalizar a implementação da funcionalidade. Retorne a chamada deste método, informando o termo passado como parâmetro para o resolver. Para isso, volte ao arquivo giphy.ts e modifique a função, que irá utilizar o método translateGif. Dessa forma, define-se o resolver:

    // node/resolvers/giphy.ts
    export const gif = async (
    _: any,
    { term }: { term: string },
    { clients: { giphy }}: Context
    ) => giphy.translateGif(term)

    É possível ver que a estrutura é bem semelhante ao esqueleto de resolver mostrado anteriormente, há parent, args e, por fim, context. Como já foi adicionado o resolver ao arquivo node/resolver/index.ts, não é preciso alterá-lo novamente.

  2. Adicione no arquivo manifest.json uma policy para acessar URL externa:

    {
        ...
        "policies": [
            {
            "name": "outbound-access",
            "attrs": {
                "host": "api.giphy.com",
                "path": "*"
            }
            }
        ],
        ...
    }
  3. Teste no GraphiQL sua modificação

    Para testar o resultado, basta fazer uma query no GraphiQL e colocar no campo de query variables a string que você deseja utilizar para pesquisar um GIF. O resultado esperado é que você receberá uma resposta com o URL de um GIF.

    No nosso exemplo, definimos uma query da seguinte forma:

    query buscaGif ($term: String) {
        gif(term:$term)
    }

    Além disso, no campo de query variables, definimos term como cats:

    {
        "term": "cats"
    }

    E o nosso resultado foi:

    {
        "data": {
            "gif": "https://media2.giphy.com/media/3o72EX5QZ9N9d51dqo/giphy.gif?cid=96678fa42d14d68f9c3ebdfaff64b84de51f012598e0a2e9&rid=giphy.gif"
        }
    }

Conectando back-end e front-end

Conectando back-end e front-end

Introdução

O componente React que irá renderizar GIFs já foi feito. O que é preciso fazer agora é criar uma query para ser executada pelo resolver que foi criado nos passos anteriores. Para realizar queries GraphQL em React, é utilizado o Apollo Client, uma biblioteca de gerenciamento de estado que facilita a integração de uma API GraphQL com a aplicação front-end.

O time do Apollo Client disponibiliza uma integração nativa com React, por meio de hooks. Dessa forma, realizar uma query significa usar um hook que não só realizará as queries e fará o fetch dos dados, mas também proverá cache e atualização do estado do UI. Essa integração, chamada react-apollo já está declarada no package.json.

Query de Gifs

  1. Crie uma pasta react/queries e nela adicione um arquivo gifs.gql que irá conter a query a ser feita. Em particular, essa query irá receber um termo, que será a palavra-chave a ser utilizada para procurar GIFs no Giphy. Ela chamará o resolver gif, implementado e testado no GraphiQL no passo anterior.

    query getGif ($term: String) {
        gif(term:$term)
    }
  2. Defina a prop term na interface GifProps e a utilize como prop do componente React Gif. Não se esqueça de atribuir um valor padrão.

  3. Agora, é necessário importar o método useQuery e utilizá-lo para fazer a query que retornará a URL de um GIF. Além disso, também é preciso importar a query, definida anteriormente, que se encontra no arquivo gifs.gql.

    // react/Gif.tsx
    import React from 'react'
    +import { useQuery } from 'react-apollo'
    
    import { useCssHandles } from 'vtex.css-handles'
    
    +import getGif from './queries/gifs.gql'
  4. Defina a query usando o getGif importado e o useQuery:

    + const { data, loading, error } = useQuery(getGif, {
    +     variables: { term }
    + })
  5. Em um primeiro momento, verifique se a query está funcionando através de console.log(data), que deve mostrar a URL do gif.

    {
        data: {
            gif: "https://media2.giphy.com/media/3o72EX5QZ9N9d51dqo/giphy.gif?cid=96678fa42d14d68f9c3ebdfaff64b84de51f012598e0a2e9&rid=giphy.gif"
        }
    }
    
  6. Para ver o GIF na home da loja, é necessário adicionar uma imagem que possua como src o valor retornado em data.gif.

    // react/Gif.tsx
    const Gif: StorefrontFunctionComponent<GifProps> = ({ term = 'VTEX' }) => {
        const handles = useCssHandles(CSS_HANDLES)
        const { data, loading, error } = useQuery(getGif, {
          variables: { term }
        })
        return (
            <div className={`${handles.gif} t-heading-2 fw3 w-100 c-muted-1 db tc`}>
                <img src={data.gif} />
            </div>
        )
    }
  7. Por fim, altere o schema para adicionar o campo de term no Site Editor e, como feito anteriormente na etapa de internacionalização, defina as strings necessárias nos arquivos dentro da pasta messages/

    // react/Gif.tsx
    Gif.schema = {
        title: 'admin/gif.title',
        description: 'admin/gif.description',
        type: 'object',
        properties: {
            term: {
                title: 'admin/gif.term.title',
                description: 'admin/gif.term.description',
                type: 'string',
                default: null,
            },
        },
    }

Resultado na home:

image

Resultado no Site Editor:

image

Criando a funcionalidade do bloco countdown

Criando a funcionalidade do bloco countdown

Introdução

Agora que o básico do nosso componente está funcional, é hora de implementar efetivamente o contador. Para isso, é preciso utilizar um hook do React, chamado useState;

O hook useState

É chamado dentro de um componente funcional para atualizar e consumir o state de um componente. O state simboliza o estado atual de um componente.

O useState retorna um par: o valor do estado atual e uma função para atualizá-lo.

Voltando ao exemplo apresentado na etapa anterior, podemos mostrar na prática os conceitos abordados anteriormente. Para lembrar do exemplo, veja o código abaixo:

const [count, setCount] = useState(0);

No trecho acima é importante observar três coisas:

  • Na variável count, é possível consumir o estado atual;
  • setCount é uma função para atualizá-lo;
  • 0 é o valor do estado inicial
const [timeRemaining, setTime] = useState<TimeSplit>({
  hours: '00', 
  minutes: '00', 
  seconds: '00'
})

Atividades

  1. Precisamos importar algumas funções e tipos para continuar:

    Primeiro, vamos importar o hook:

    import React, { useState } from 'react'

    Além disso, é necessário importar o tipo TimeSplit:

    import { TimeSplit } from './typings/global'

    Por fim, é oferecida uma função util que atualizará a contagem regressiva:

    import { tick } from './utils/time'
  2. Adicione o hook de atualização de estado (useState)

    const Countdown: StorefrontFunctionComponent<CountdownProps> = ({ targetDate }) => {
    +   const [timeRemaining, setTime] = useState<TimeSplit>({
    +     hours: '00',
    +     minutes: '00',
    +     seconds: '00'
    +   })
    
        return (
          <div>
            { targetDate }
          </div>
        ) 
    }

    Observe os detalhes: timeRemaining é o estado atual, setTime é a função de atualização do estado, TimeSplit é o tipo e, por fim, o objeto {hours: '00', minutes: '00', seconds: '00'} é o estado inicial do componente.

  3. Adicione uma targetDate padrão para o caso de não haver um valor inicial definido. Para isso, declare uma constante que será utilizada como padrão:

    const DEFAULT_TARGET_DATE = (new Date('2020-03-11')).toISOString()
  4. Utilize a função tick e a constante DEFAULT_TARGET_DATE para fazer o contador:

    const Countdown: StorefrontFunctionComponent<CountdownProps> = ({ targetDate = DEFAULT_TARGET_DATE }) => {
      const [timeRemaining, setTime] = useState<TimeSplit>({
        hours: '00',
        minutes: '00',
        seconds: '00'
    })
    
    + tick(targetDate, setTime)
    
      return (
        <div>
          { targetDate }
        </div>
      ) 
    }
  5. Altere o h1 para que ele exiba o contador que criamos. Para isso, precisamos utilizar o estado atual timeRemaining:

    const Countdown: StorefrontFunctionComponent<CountdownProps> = ({ targetDate = DEFAULT_TARGET_DATE }) => {
      const [timeRemaining, setTime] = useState<TimeSplit>({
        hours: '00',
        minutes: '00',
        seconds: '00'
      })
    
      tick(targetDate, setTime)
    
      return (
        <div>   
    -     <h1>{ targetDate }</h1>
    +     <h1>{ `${timeRemaining.hours}:${timeRemaining.minutes}:${timeRemaining.seconds}` }</h1>
        </div>
      ) 
    }

    A formatação da string do contador está no formato HH:MM:SS, feita através do split em hours, minutes e seconds.

Assim, com essas alterações, veremos a atualização em tempo real do contador! O resultado na home é esse:

image

Componentizando o bloco countdown

Componentizando o bloco countdown

Introdução

Nessa etapa, a app tem dois elementos principais: o título e o contador. Porém, para obter uma maior flexibilidade de posicionamento, customização, é interessante que sejam separadas em dois blocos distintos. Para isso, é preciso apresentar brevemente o conceito de interfaces para, em seguida, ser desenvolvido um novo componente Title. Um exemplo de customização em termos de posicionamento, que será abordada nessa etapa, é:

E se eu quisesse que o título estivesse embaixo ou ao lado do contador?

Interface

Uma interface funciona como um contrato, com restrições bem definidas de como os blocos funcionarão juntos. Define, então, um mapeamento que cria um bloco do Store Framework, a partir de um componente React. É importante destacar que o uso de interfaces, de forma a separar uma app em diversas interfaces torna o poder de customização muito maior.

Ao definir a app na interface, a propriedade component é responsável por definir o componente React que será usado. É importante ressaltar que o nome do component tem que ser igual ao nome do arquivo do componente dentro da pasta react/.

Exemplo de interfaces.json:

{
  "countdown": {
    "component": "Countdown"
  }
}

Atividade

Nessa atividade, será separado o título e adicionado à nossa loja embaixo do contador.

Alterando o componente Countdown

  1. Remova os imports, o title da interface e altere a constante do CSS handles:
    import React, { useState } from 'react'
    import { TimeSplit } from './typings/global'
    import { tick } from './utils/time'
    import { useCssHandles } from 'vtex.css-handles'
    -import { FormattedMessage } from 'react-intl'
    
    interface CountdownProps {
      targetDate: string,
    -  title: string
    }
    
    const DEFAULT_TARGET_DATE = (new Date('2020-03-02')).toISOString()
    -const CSS_HANDLES = ['container', 'countdown', 'title']
    +const CSS_HANDLES = ['countdown']
  2. Agora, no componente React em si, é preciso retirar o title como prop recebida e a constante do texto do título, além de alterar o que é renderizado:
    //Countdown.tsx
    -const Countdown: StorefrontFunctionComponent<CountdownProps> = ({ title, targetDate = DEFAULT_TARGET_DATE }) => {
    +const Countdown: StorefrontFunctionComponent<CountdownProps> = ({ targetDate = DEFAULT_TARGET_DATE }) => {
      const [
        timeRemaining, 
        setTime
      ] = useState<TimeSplit>({
        hours: '00', 
        minutes: '00', 
        seconds: '00'
      })
      
    -  const titleText = title || <FormattedMessage id="countdown.title" /> 
      const handles = useCssHandles(CSS_HANDLES)
    
      tick(targetDate, setTime)
    
      return (
    -      <div className={`${handles.container} t-heading-2 fw3 w-100 pt7 pb6 c-muted-1`}>
    -        <div className={`${handles.title} db tc`}>
    -          { titleText }
    -        </div>
            <div className={`db tc`}>
              {`${timeRemaining.hours}:${timeRemaining.minutes}:${timeRemaining.seconds}`}
            </div>
    -      </div>
      )
    }
  3. Por fim, retire o título do schema:
    Countdown.schema = {
      title: 'editor.countdown.title',
      description: 'editor.countdown.description',
      type: 'object',
      properties: {
    -    title: { 
    -      title: 'editor.countdown.title.title',
    -      type: 'string',
    -      default: null,
    -    },
        targetDate: {
          title: 'editor.countdown.targetDate.title',
          description: 'editor.countdown.targetDate.description',
          type: 'string',
          default: null,
        },
      },
    }

Criando um novo componente

  1. Crie um novo arquivo dentro da pasta /react, chamado Title.tsx, ele será o novo componente do título. Nele, alguns imports precisam ser feitos. A estrutura básica do código é muito similar a do componente Countdown.

  2. Adicione os imports necessários e a constante do CSS handles:

    //Title.tsx
    import React from 'react'
    import { FormattedMessage } from 'react-intl'
    import { useCssHandles } from 'vtex.css-handles'
    
    const CSS_HANDLES = ['title'] as const 
  3. Altere a função do componente:

    //Title.tsx
    const Title: StorefrontFunctionComponent<TitleProps> = ({title}) => {
      const handles = useCssHandles(CSS_HANDLES)
      const titleText = title || <FormattedMessage id="countdown.title" /> 
    
      return (
        <div className={`${handles.title} t-heading-2 fw3 w-100 c-muted-1 db tc`}>
          { titleText }
        </div> 
      )
    }
  4. Adicione a interface, o schema e o export:

    //Title.tsx
    interface TitleProps {
      title: string
    }
      
    Title.schema = {
      title: 'editor.countdown-title.title',
      description: 'editor.countdown-title.description',
      type: 'object',
      properties: {
        title: { 
          title: 'editor.countdown.title.title',
          type: 'string',
          default: null,
        }
      }
    }
      
    export default Title

Alterando o arquivo interfaces.json

Nesta altura, há dois componentes na app: o título e o contador. Porém, é preciso alterar o arquivo interfaces.json. É preciso declarar os componentes separadamente. No início, nossa interface tinha apenas o Countdown. É necessário adicionar o outro componente:

{
  "countdown": {
    "component": "Countdown"
  },
+    "countdown.title": {
+      "component": "Title"
+    }
}

Adicionando internacionalização

Também é preciso adicionar ao Messages as traduções cujas chaves são as strings do schema que incluímos no arquivo Title.tsx logo acima. Como visto na etapa de Messages, vá à pasta /messages e adicione em cada um dos arquivos as traduções necessárias (pt.json, es.json e en.json). Abaixo há um exemplo para o caso do arquivo en.json:

{
+  "countdown.title": "Countdown",
  "editor.countdown.title": "Countdown",
  "editor.countdown.description": "Countdown component"
}

Adicionando o novo bloco na home da loja

Por fim, para ver as mudanças, volte ao tema para alterá-lo a fim de incluir o novo bloco. Para isso, basta adicionar à home o título! Assim como feito para o contador, é necessário adicionar o countdown.title como um bloco no arquivo home.jsonc do store-theme.

//home.jsonc
{
  "store.home": {
    "blocks": [
      "countdown",
+      "countdown.title",
      ...
    ]
  },
  ...
}     

Pronto! Agora vamos ver como deve ser o resultado:
image

Práticas de internacionalização no VTEX IO

Práticas de internacionalização no VTEX IO

Introdução

Com o bloco customizado na loja, devemos aprender a internacionalizar o conteúdo apresentado.

É importante lembrar que os blocos devem sempre seguir boas práticas de localização, e não devem mostrar strings hardcoded, mas sim sensíveis a linguagem que a loja opera.

Não se preocupe, você não precisará adicionar traduções de todos os textos para as variadas linguagens nas quais o Store Framework é usado. Portanto, nessa etapa, serão apresentados conceitos acerca da internacionalização de apps e como fazê-la.

As Messages

O conceito de messages facilita a adição de novos idiomas ao tema. As messages centralizam todos os serviços de tradução na plataforma. Dada um texto a ser traduzido, Messages irá primeiramente checar o contexto definido pelo usuário para, em seguida, checar as traduções das apps e, por fim, passa pelo sistema de tradução automática.

Na estrutura do diretório, é possível observar que há uma pasta chamada messages, que apresenta três arquivos principais: pt.json, en.json e es.json, cada um responsável pelas traduções: português, inglês e espanhol, respectivamente. Além disso, a fim de fornecer traduções automáticas melhores, é utilizado o arquivo context.json, responsável por evitar ambiguidades.

Para utilizar tais definições, os arquivos de tradução mencionados anteriormente são JSON, cujas chaves são as mensagens e os valores são as traduções.

O arquivo context.json é necessário e precisa conter todas as mensagens, além de oferecer as traduções automáticas em casos excepcionais.

Atividade

Você já deve ter aprendido a usar o nosso builder messages, e será através dele que serão adicionadas strings internacionalizadas nos componentes.

  1. Para isso, na pasta messages, adicione agora uma mensagem de título para o componente:

    messages/pt.json

    {
      ...,
    +	"countdown.title": "Contagem Regressiva"
    }

    messages/en.json

    {
      ...,
    +	"countdown.title": "Countdown"
    }

    messages/es.json

    {
      ...,
    +	"countdown.title": "Cuenta Regresiva"
    }

    messages/context.json

    {
      ...,
    +	"countdown.title": "Countdown"
    }
  2. Feito isso, para renderizar o título deve-se usar o componente FormattedMessage da biblioteca react-intl.

    A biblioteca react-intl dá suporte a várias maneiras de configuração e internacionalização, vale a pena verificá-las.

  3. Adicione a biblioteca usando yarn add react-intl na pasta react

  4. No código do seu componente Countdown.tsx importe o FormattedMessage

    +	import { FormattedMessage } from 'react-intl'
  5. Adicione uma constante que será o seu título:

    const titleText = title || <FormattedMessage id="countdown.title"/>
  6. Agora, junte o título e o contador para renderizá-los. Para isso, defina um container por fora. Além disso, o texto do título será passado através da prop title:

    const Countdown: StorefrontFunctionComponent<CountdownProps> = ({ title, targetDate }) => {
      const [
        timeRemaining, 
        setTime
      ] = useState<TimeSplit>({
        hours: '00', 
        minutes: '00', 
        seconds: '00'
      })
      
      const titleText = title || <FormattedMessage id="countdown.title" /> 
      const handles = useCssHandles(CSS_HANDLES)
    
      tick(targetDate, setTime)
    
      return (
        <div className={`${handles.container} t-heading-2 fw3 w-100 c-muted-1`}>
          <div className={`${handles.title} db tc`}>
            { titleText }
          </div>
          <div className={`${handles.countdown} db tc`}>
            {`${timeRemaining.hours}:${timeRemaining.minutes}:${timeRemaining.seconds}`}
          </div>
        </div>
      )
    }

    Note que são utilizados três handles novos: container, countdown e title. Dessa forma, lembre-se de declará-los na constante CSS_HANDLES, vista na etapa anterior:

    const CSS_HANDLES = ['container', 'countdown', 'title']
  7. Por fim, é preciso adicionar a prop de title no schema:

    Countdown.schema = {
      title: 'editor.countdown.title',
      description: 'editor.countdown.description',
      type: 'object',
      properties: {
    +    title: {
    +      title: 'Sou um título',
    +      type: 'string',
    +      default: null,
    +    },
        targetDate: {
          title: 'Sou um título',
          description: 'Sou uma descrição',
          type: 'string',
          default: null,
        },
      },
    }

Pronto! Agora, para testar sua loja em outros idiomas basta adicionar a query string /?cultureInfo=pt-br na URL, por exemplo. Ao utilizar tal URL, o resultado esperado é esse aqui:

image

Linkando uma app e utilizando-a no tema da loja

Linkando uma app e utilizando-a no tema da loja

Introdução

Para desenvolver um bloco de frente de loja, similar aos que oferecemos nativamente no Store Framework, utilizamos a biblioteca de desenvolvimento de UIs react.

Um pouco sobre tecnologias

É sabido que criar componentes que manipulem estado em react melhora a performance e tende a ser mais fácil, por ser menos verboso que class components. Portanto, nesse curso iremos utilizar sempre function components e hooks e recomendamos que você faça o mesmo sempre que vá começar um projeto novo em react

No VTEX IO, adotamos o typescript como linguagem default para projetos que normalmente utilizariam javascript. Apesar de ser necessário aprender sintaxes novas, acredita-se que o esforço é rapidamente recompensado. Ao utilizar typescript, ganha-se alta previsibilidade de bugs, por oferecer tipagem estática. Além disso, com as IDEs certas, é possível aumentar a velocidade de implementação através de um code completion mais esperto, com a tipagem de objetos no código.

Neste curso, utilizaremos somente typescript. Caso você não tenha familiaridade, será uma excelente oportunidade de experimentar essa linguagem.

Objetivo dessa Etapa

Como você já tem familiaridade com o Store Framework, já sabe que montamos páginas na nossa loja ao compor blocos em JSON, como shelf e sku-selector. Nesta etapa você irá criar um bloco que será utilizado no tema da home page de sua loja.

Boilerplate

Quando você estiver desenvolvendo seu próprio bloco, você pode começar por nosso template de store component.

Atividade

  1. No template clonado, vá para o arquivo Countdown.tsx:

    //react/Countdown.tsx
    import React from 'react'
    
    interface CountdownProps {}
    
    const Countdown: StorefrontFunctionComponent<CountdownProps> = ({}) => {
      return <div></div>
    }
    
    Countdown.schema = {
      title: 'editor.countdown.title',
      description: 'editor.countdown.description',
      type: 'object',
      properties: {},
    }
    
    export default Countdown
  2. Para ver o seu componente na home page, linke o tema em um terminal e a app em outro terminal. Adicione uma tag h1 dentro do nosso componente e declarar o bloco no tema.

    const Countdown: StorefrontFunctionComponent<CountdownProps> = ({}) => {
    -    return <div></div>
    +    return (
    +      <div>
    +        <h1>Teste Countdown</h1>
    +      </div>
    +    )
    }

    Para que o componente seja visto funcionando na loja, é preciso declarar o bloco que a app define no tema. Em primeiro lugar, será necessário ter um tema para adicionar a app, para isso, será necessário cloná-lo do Github. Nesse curso, o store-theme será utilizado. Para clonar o repositório, basta executar o seguinte comando:

    git clone https://github.com/vtex-apps/store-theme.git
    
  3. Com o repositório já clonado, vá até a pasta com cd store-theme; linke o tema e a app no seu workspace. Para que a app seja utilizada no tema, é preciso adicioná-la às suas dependências, que como visto anteriormente, ficam no manifest.json.

    vtex link
    
  4. Adicione ao manifesto do tema "vtex.countdown" como dependência. A versão dela está definida no manifesto da app (0.0.1). Feito isso, o JSON terá mais uma linha, como mostrado abaixo:

    {
        ...
        "dependencies": {
            ...
    +        "vtex.countdown": "0.x",
            ...
        },
        ...
    }
  5. Por fim, é preciso adicionar o bloco na loja. Dentro do arquivo home.jsonc, declare um bloco chamado "countdown".

    {
        "store.home": {
            "blocks": [
                "countdown",
                ...
            ]
            ...
        }
        ...
    }
    

O resultado esperado é encontrar um header na home da sua loja, como a imagem abaixo:

image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.