Roadmap
Avançado

Ecommerce em 2024

O jeito mais moderno de criar um ecommerce

tailwind
vercel
github
medusajs
render
nuxt

Para facilitar nossa vida, vamos criar um componente de produto do carrinho, ou seja, ele tem as informações de um produto mas o estilo é um pouco diferente e terá todas as funções que uma página de carrinho precisa.

Criando o componente de card de produto do carrinho

Então vamos na pasta components, criar um arquivo chamado CartProductCard.vue e colar o seguinte código:

CartProductCard.vue
<script setup lang="ts">
defineProps<{
  image: string;
  title: string;
  variant: string;
  price: number;
  handle: string;
  variantId: string;
}>()

defineEmits(['remove'])
</script>

<template>
  <nuxt-link :to="{path: `/produtos/${handle}`, query: {variant: variantId}}" class="cursor-pointer group block">
    <div class="flex items-center gap-4">
      <div class="aspect-square w-[120px] h-[120px] rounded overflow-hidden">
        <img :src="image" :alt="title" class="w-full h-full object-cover object-center group-hover:scale-105 transition">
      </div>
      <div>
        <h2 class="font-semibold mb-2">
          {{ title }} <span class="font-light">Variante: {{ variant }}</span>
        </h2>
        <div class="mb-2">
          {{ new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(price) }}
        </div>
        <Button size="sm" variant="outline" @click.prevent="$emit('remove')">
          Remover
        </Button>
      </div>
    </div>
  </nuxt-link>
</template>
Clique aqui para expandir

Nesse componente em específico nós adicionamos um emit (emit são eventos disparados do elemento filho para o pai) de remover o item do carrinho, fique à vontade para melhorar o componente e adicionar um emit de, por exemplo, mudar a quantidade de itens no carrinho.

Veja mais como funcionam os emits na documentação oficial do Vue.

Criando a página de carrinho

Agora nós vamos criar um arquivo dentro da pasta pages chamado carrinho.vue e colar o seguinte código:

carrinho.vue
<script setup lang="ts">
import type { Cart } from '@medusajs/medusa/dist/models/cart'

const client = useMedusaClient();
const loading = ref(true);

const cart = ref<Cart>();

onMounted(async() => {
  try {
    loading.value = true;

    const id = localStorage.getItem("cart_id")

    if (id) {
      const response = await client.carts.retrieve(id);
      cart.value = response.cart as unknown as Cart;
    }
  } finally {
    loading.value = false;
  }
})

async function removeItemFromCart(lineItemId: string) {
  try {
    loading.value = true;

    const id = localStorage.getItem("cart_id")

    if (id) {
      const response = await client.carts.lineItems.delete(id, lineItemId);
      cart.value = response.cart as unknown as Cart;
    }
  } finally {
    loading.value = false;
  }
}
</script>

<template>
  <section class="container pt-24">
    <div v-if="loading" class="text-center text-gray-500 pb-20">
      Carregando...
    </div>
    <div v-if="!loading && cart">
      <h1 class="text-4xl font-extrabold mb-10">
        Carrinho
      </h1>
      <div class="grid gap-10 grid-cols-[1fr_440px]">
        <div class="space-y-4">
          <CartProductCard 
          v-for="lineItem in cart.items"
            :key="lineItem.id"
            :image="(lineItem.variant.product.thumbnail as string)" 
            :title="lineItem.title" 
            :variant="lineItem.variant.title" 
            :variant-id="lineItem.variant.id" 
            :price="lineItem.unit_price / 100" 
            :handle="(lineItem.variant.product.handle as string)" 
            @remove="removeItemFromCart(lineItem.id)" 
          />
        </div>
        <div class="bg-zinc-50 rounded p-8 h-fit">
          <h2 class="text-xl font-medium mb-2">
            Resumo do pedido
          </h2>
          <div class="flex items-center text-sm border-b border-zinc-200 py-4">
            <div class="flex-1 text-gray-500">
              Subtotal
            </div>
            <div>
              {{ new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(Number(cart.subtotal) / 100) }}
            </div>
          </div>
          <div class="flex items-center text-sm border-b border-zinc-200 py-4">
            <div class="flex-1 text-gray-500">
              Desconto
            </div>
            <div>
              - {{ new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(Number(cart.discount_total) / 100) }}
            </div>
          </div>
          <div class="flex items-center py-4 text-lg font-medium mb-4">
            <div class="flex-1">
              Total
            </div>
            <div>
              {{ new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(Number(cart.total) / 100) }}
            </div>
          </div>
          <nuxt-link to="/checkout">
            <Button size="lg" class="w-full">
              Fazer pedido
            </Button>
          </nuxt-link>
        </div>
      </div>
    </div>
  </section>
</template>
Clique aqui para expandir

Nesse código contém o estilo da página de carrinho e algumas regras de negócio, então vamos desmembrá-lo um pouco.

Na função onMounted (função que executa logo após a tela ser montada) temos uma função chamada client.carts.retrieve(id) que vai fazer a requisição do carrinho pelo cart_id que está salvo na localStorage, se por acaso o carrinho não existir a lista de produtos vai ficar vazia.

Veja como funciona a função onMounted do ciclo de vida do Vue 3 na documentação oficial.

Já a função removeItemFromCart é executada quando alguém clica no botão "Remover", então nós atualizamos os dados do carrinho para remover um produto de acordo com o id.

Opa, calma aí!

Parece que você não está logado, caso tenha interesse em salvar seu progresso de estudo faça login agora.