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