- Blog ➔
- Programação ↴
Eu criei uma extensão em estilo RPG retro para a minha loja do Magento 2
Criando um marketplace personalizado no Magento 2 com Vue, Tailwind e um toque de RPG
Escrito em 2 de fevereiro de 2025 - 🕒 11 min. de leituraO meu mais novo projeto (ou side-gig?), Zenkai Zone, já se transformou em um site de e-commerce sólido, mas sejamos francos — usar apenas o template padrão do Magento 2 é meio sem graça, quer dizer, vamos lá, eu sou o cara que criou um jogo inteiro para este blog, eu deveria conseguir trazer um pouco de diversão para o meu e-commerce. Não é mesmo?
Ideação
Eu queria dar um toque especial, injetar um pouco de personalidade e tornar a experiência de compra mais envolvente. Então, decidi criar o GameShop — uma extensão para Magento 2 desenvolvida especificamente para a Zenkai Zone que transforma a vitrine em algo reminiscentemente de uma loja de RPG old-school. Com uma página dedicada, completa com um avatar de lojista, endpoints de API customizados e um toque de magia em SEO para compartilhamento nas mídias sociais, a ideia era deixar meu site menos monótono e mais divertido.
Neste guia, vou mostrar como combinei Vue.js, TailwindCSS, APIs customizadas do Magento e melhorias estratégicas de SEO para dar aquele toque especial na Zenkai Zone. Ao longo do caminho, você verá trechos de código e meus comentários sobre o que está acontecendo nos bastidores. Vamos nessa!
Visão Geral do Projeto: O que Estamos Construindo
- Uma nova página de vitrine alimentada por Vue + Tailwind em
/game-shop
, personalizada para a Zenkai Zone. - Endpoints de API para gerenciar carrinho, categorias e produtos de formas que o núcleo do Magento talvez não permita tanta flexibilidade.
- Metadados amigáveis para SEO e tags Open Graph para uma pré-visualização matadora no compartilhamento em redes sociais.
- Uma nova configuração no painel de administração para ativar ou desativar toda a extensão sem precisar mexer no código.
Estrutura do Módulo Magento 2
Segue a estrutura organizada de arquivos para o módulo GameShop. Mantê-la organizada significa que sempre posso encontrar o que preciso — mesmo se estiver no meio de outra ideia para a Zenkai Zone:
app/code/Werules/GameShop/
├── Api/
│ ├── CartManagementInterface.php
│ ├── CategoryManagementInterface.php
│ ├── ProductManagementInterface.php
├── Controller/
│ ├── Index/
│ │ ├── Index.php
├── etc/
│ ├── adminhtml/
│ │ ├── system.xml
│ ├── frontend/
│ │ ├── di.xml
│ │ ├── module.xml
│ │ ├── routes.xml
│ ├── webapi.xml
├── Model/
│ ├── CartManagement.php
│ ├── CategoryManagement.php
│ ├── ProductManagement.php
├── view/
│ ├── frontend/
│ │ ├── layout/
│ │ │ ├── werules_gameshop_index_index.xml
│ │ ├── templates/
│ │ │ ├── index.phtml
│ │ ├── web/
│ │ │ ├── css/
│ │ │ │ ├── gameshop.css
│ │ │ ├── js/
│ │ │ │ ├── vue.global.js
│ │ │ │ ├── tailwind.min.js
│ │ │ ├── images/
│ │ │ │ ├── shopkeeper.png
├── registration.php
Passo 1: Criando a Página Customizada no Frontend
Definindo a Rota
O primeiro passo foi configurar a rota. No arquivo routes.xml
, mapeei /game-shop
para a rota do módulo customizado, garantindo que todo o tráfego para essa extensão seja direcionado corretamente.
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
<router id="standard">
<route id="werules_gameshop" frontName="game-shop">
<module name="Werules_GameShop"/>
</route>
</router>
</config>
Criando o Controller
Em seguida, veio o controller Index.php
. Este arquivo decide se a nova página do GameShop será exibida ou, se a extensão estiver desativada, mostra a página padrão de “rota não encontrada” do Magento.
<?php
namespace Werules\GameShop\Controller\Index;
use Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;
use Magento\Framework\Controller\ResultFactory;
use Magento\Framework\App\Config\ScopeConfigInterface;
class Index extends Action
{
protected $scopeConfig;
public function __construct(
Context $context,
ScopeConfigInterface $scopeConfig
) {
parent::__construct($context);
$this->scopeConfig = $scopeConfig;
}
public function execute()
{
// Check if the extension is activated for [Zenkai Zone](https://zenkaizone.com)
$isEnabled = $this->scopeConfig->isSetFlag('werules/gameshop/enabled', \Magento\Store\Model\ScopeInterface::SCOPE_STORE);
if (!$isEnabled) {
// If not, redirect to Magento’s default no-route page
return $this->resultFactory->create(ResultFactory::TYPE_FORWARD)->forward('noroute');
}
// Otherwise, load our new, fun page
$resultPage = $this->resultFactory->create(ResultFactory::TYPE_PAGE);
$resultPage->getConfig()->getTitle()->set(__('Game Shop'));
return $resultPage;
}
}
Passo 2: Aprimorando as APIs do Magento
O sistema de API do Magento 2 é robusto, mas eu queria mais controle para a Zenkai Zone. Ao introduzir interfaces e implementações customizadas, pude adaptar o comportamento exatamente como necessário.
API de Gerenciamento de Carrinho (CartManagement.php
)
Se a extensão estiver desativada, a API retorna educadamente zero; caso contrário, ela se comporta como as funções padrão de carrinho — só que com um toque extra de personalidade.
public function getCartItemCount()
{
if (!$this->isModuleEnabled()) {
return 0;
}
$quote = $this->cart->getQuote();
return (int)$quote->getItemsQty();
}
public function addItemToCart($productId, $qty = 1)
{
if (!$this->isModuleEnabled()) {
return ['success' => false, 'message' => 'GameShop is disabled.', 'cart_count' => 0];
}
// Normal add-to-cart logic here
}
API de Gerenciamento de Produtos (ProductManagement.php
)
De forma similar, esta API fornece listagens de produtos de qualquer categoria, mas somente se a extensão estiver ativa.
public function getProductsByCategoryId($categoryId)
{
if (!$this->isModuleEnabled()) {
return [];
}
// Standard product retrieval logic
}
Desenhando uma Interface Inspirada em RPG Retrô para o GameShop
Eu queria dar à Zenkai Zone uma dose extra de personalidade. Injetando uma vibe de “loja clássica de RPG” usando Vue.js para reatividade e TailwindCSS para estilização, a vitrine se transforma em algo divertido e memorável — bem mais envolvente do que uma página de e-commerce típica.
Segue o layout:
- Cabeçalho fixo exibindo o nome da loja.
- Barra lateral do lojista com um avatar, contagem de itens do carrinho em tempo real e opções de menu (Comprar, Vender, Falar, Sair).
- Painel principal exibindo listagens dinâmicas de categorias e produtos, além de uma visualização detalhada do produto.
Montando o Layout
Dentro do arquivo index.phtml
, configurei o container para minha aplicação Vue:
Container da Nossa Aplicação Vue.js
<div id="app" class="min-h-screen bg-black text-orange-200 font-mono p-6 flex flex-col space-y-4 text-lg">
- O Vue monta na
#app
. - O fundo preto + texto laranja evocam aquele visual nostálgico de consoles antigos.
Um Cabeçalho Fixo para o Nome da Loja
<div class="border-2 border-orange-500 p-3 sticky top-0 bg-black z-50">
<h1 class="text-3xl md:text-4xl font-bold tracking-widest uppercase">
<?php echo $currentStoreName; ?>
</h1>
</div>
- O cabeçalho fixo mantém o nome da loja visível, para que os clientes sempre saibam que estão na Zenkai Zone.
Navegação Lateral: Avatar do Lojista & Menu
Esta seção é toda sobre personalidade. O avatar do lojista dá as boas-vindas aos visitantes, enquanto a contagem do carrinho e os botões do menu (Comprar, Vender, Falar, Sair) adicionam um toque interativo.
<!-- Shopkeeper Avatar -->
<div class="border-2 border-orange-500 p-2 flex justify-center items-center bg-black">
<img src="<?= $block->getViewFileUrl('Werules_GameShop::images/shopkeeper.png'); ?>"
alt="<?= __('Shopkeeper Avatar'); ?>"
class="w-32 h-32 object-cover border border-orange-500 bg-black avatar">
</div>
<!-- Cart Count -->
<div class="mb-4">
<div class="text-lg text-orange-400"><?= __('Cart items:'); ?></div>
<div class="text-2xl font-bold">{{ cartCount }}</div>
</div>
Atualizações em tempo real via Vue mantêm a interface dinâmica e responsiva.
Adicionando os Botões do Menu
<button class="block w-full py-3 border border-orange-500 hover:bg-orange-900 text-center px-4 text-lg"
:class="{'bg-orange-500 text-black': menuSelection === 'Buy'}"
@click="onMenuItemClick('Buy')">
<?= __('Buy'); ?>
</button>
<button class="block w-full py-3 border border-orange-500 hover:bg-orange-900 text-center px-4 text-lg"
:class="{'bg-orange-500 text-black': menuSelection === 'Sell'}"
@click="onMenuItemClick('Sell')">
<?= __('Sell'); ?>
</button>
<button class="block w-full py-3 border border-orange-500 hover:bg-orange-900 text-center px-4 text-lg"
@click="onMenuItemClick('Exit')">
<?= __('Exit'); ?>
</button>
- A estilização condicional destaca a seleção atual.
- O botão Sair pode redirecionar os clientes de volta ao site principal da Zenkai Zone, se necessário.
Área de Conteúdo Principal: Categorias & Listagens de Produtos
Quando os usuários selecionam “Comprar”, eles veem categorias exibidas no topo. Ao selecionar uma categoria, uma grade de produtos é mostrada — conteúdo dinâmico alimentado pelas nossas APIs customizadas.
Abas de Categorias
<!-- Category Tabs -->
<div v-if="activeView === 'list'" class="flex flex-wrap gap-2 mb-3">
<button v-for="cat in categories" :key="cat.id"
class="px-3 py-1 border border-orange-500 hover:bg-orange-500 hover:text-black transition"
:class="{'bg-orange-500 text-black': currentCategoryId === cat.id}"
@click="loadProducts(cat.id, cat.name)">
{{ cat.name }}
</button>
</div>
- As categorias são buscadas através do endpoint
rest/V1/gameshop/categories
. - Um loop Vue
v-for
garante que cada categoria seja renderizada automaticamente.
Grade de Listagem de Produtos
<div v-if="activeView === 'list'">
<div v-if="products.length === 0" class="text-center text-orange-400 text-lg mt-6">
<?= __('No products found in this category.'); ?>
</div>
<div class="space-y-3">
<div v-for="product in products" :key="product.id"
class="border border-orange-500 p-4 flex flex-col md:flex-row items-center cursor-pointer hover:bg-orange-900 transition"
@click="showDetails(product)">
<img :src="product.image_url"
class="w-32 h-32 object-cover border border-orange-500 bg-black">
<div class="flex-1 text-center md:text-left">
<span class="block text-lg">{{ product.name }}</span>
<span class="block text-xl text-orange-300 mt-2">{{ formatPrice(product.price) }}</span>
</div>
</div>
</div>
</div>
- Clicar em um produto exibe a visualização detalhada.
- O design garante que cada produto se destaque, adicionando um toque especial à experiência de compra.
Visualização Detalhada do Produto
Quando um produto é selecionado, uma visualização detalhada mostra seu nome, preço, descrição e um botão de Adicionar ao Carrinho.
<div v-if="activeView === 'detail' && currentProduct" class="space-y-4">
<p class="italic text-lg mb-2">
<?= sprintf(__('Ah, the %s! This item might do something special...'), '<strong>{{ currentProduct.name }}</strong>'); ?>
</p>
<div class="flex flex-col md:flex-row space-y-4 items-start border border-orange-500 p-4">
<img :src="currentProduct.image_url"
class="w-72 h-72 object-cover border border-orange-500 bg-black">
<div class="flex-1">
<h3 class="text-2xl font-bold">{{ currentProduct.name }}</h3>
<p class="text-orange-300 text-xl mb-3">{{ formatPrice(product.price) }}</p>
<p class="text-lg text-orange-300">
{{ currentProduct.description }}
</p>
</div>
</div>
<div class="flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:space-x-6">
<button @click="addToCart(currentProduct.id)"
class="px-5 py-3 bg-orange-500 text-black border border-orange-500 hover:bg-orange-600 transition text-xl">
<?= __('Add to Cart'); ?>
</button>
<button @click="goBackToList()"
class="px-5 py-3 border border-orange-500 hover:bg-orange-900 text-orange-200 transition text-xl">
<?= __('Back'); ?>
</button>
</div>
</div>
A simples alternância entre as visualizações “list” e “detail” — alimentada pelo Vue — mantém a experiência do usuário suave e sem interrupções.
Conversa com o Lojista: O Menu “Falar”
Para dar um toque de personalidade, o menu “Falar” permite que o lojista compartilhe frases de efeito aleatórias. É um recurso opcional que adiciona ainda mais caráter à experiência de compra.
<div v-else-if="menuSelection === 'Talk'" class="flex items-center justify-center h-64 border-2 border-orange-500 p-6">
<p class="text-3xl text-orange-400 text-center">
{{ currentTalkLine }}
</p>
</div>
methods: {
onMenuItemClick(selection) {
if (selection === 'Talk') {
const randomIndex = Math.floor(Math.random() * this.talkLines.length);
this.currentTalkLine = this.talkLines[randomIndex];
}
}
}
Este pequeno recurso pode até ser estendido para oferecer descontos surpresa ou recomendações de produtos.
Passo 3: Aprimoramentos em SEO
Para impulsionar o compartilhamento nas mídias sociais da Zenkai Zone, adicionei meta tags e dados JSON-LD dentro do <head>
. Isso garante que plataformas como Facebook e Twitter exibam uma pré-visualização atraente com a imagem do lojista.
Atualize werules_gameshop_index_index.xml
<referenceBlock name="head.additional">
<block class="Magento\Framework\View\Element\Template"
name="werules.gameshop.head"
template="Werules_GameShop::seo/head.phtml"/>
</referenceBlock>
Crie o head.phtml
<?php
$mediaUrl = $block->getViewFileUrl('Werules_GameShop::images/shopkeeper.png');
$baseUrl = $block->getBaseUrl();
$pageTitle = __('GameShop - Buy & Sell Games');
$pageDescription = __('Find the best gaming deals, buy and sell games easily!');
?>
<!-- Open Graph Meta Tags -->
<meta property="og:image" content="<?= $mediaUrl ?>"/>
<meta property="og:image:alt" content="GameShop Shopkeeper Avatar"/>
<meta property="og:url" content="<?= $baseUrl ?>game-shop"/>
<!-- Twitter Card -->
<meta name="twitter:image" content="<?= $mediaUrl ?>"/>
<!-- JSON-LD Structured Data -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebPage",
"name": "<?= $pageTitle ?>",
"description": "<?= $pageDescription ?>",
"image": "<?= $mediaUrl ?>",
"url": "<?= $baseUrl ?>game-shop",
"publisher": {
"@type": "Organization",
"name": "GameShop",
"logo": "<?= $mediaUrl ?>"
}
}
</script>
Com essas tags implementadas, a extensão não só tem uma ótima aparência como também apresenta um desempenho superior em SEO e compartilhamento nas mídias sociais.
Etapas Finais & Testes
Ativando a Extensão
bin/magento module:enable Werules_GameShop
bin/magento setup:upgrade
bin/magento cache:flush
Agora o seu módulo está oficialmente ativo.
Testando o Frontend
Acesse https://seumagento.com/game-shop
e se você vir a interface em preto e laranja com o lojista, o trabalho está concluído.
Claro, você também pode conferir uma demonstração ao vivo da extensão GameShop em zenkaizone.com/game-shop.
Conclusão
É isso aí! Transformamos a vitrine da Zenkai Zone criando uma extensão customizada para Magento 2 que não só introduz novas APIs para carrinho e produtos, mas também traz um design divertido, inspirado em RPG, com excelente integração de SEO. Para o futuro, há muito espaço para expandir este conceito:
- Autenticação de usuários para permitir que os clientes salvem seu progresso na loja virtual.
- Integração de checkout com a rota customizada
game-shop
para uma experiência completa de vitrine alternativa. - Filtragem avançada de produtos para combinar gêneros de jogos ou até mesmo referências retrô aleatórias.
Eu me diverti muito misturando um pouco da nostalgia dos jogos com a funcionalidade moderna do e-commerce usando Vue.js e TailwindCSS. O resultado final é uma extensão peculiar e envolvente que torna a Zenkai Zone muito mais interessante. Quem diria que fazer compras online poderia se transformar em uma aventura?
Contribuindo com a Extensão GameShop
Como em todos os meus projetos, a extensão GameShop é open-source e está disponível no GitHub. Fique à vontade para fazer um fork, testar e contribuir com o projeto. Adoraria ver o que você vai criar!
Para onde vamos a partir daqui? Talvez integrar o ChatGPT para oferecer recomendações de produtos personalizadas ou adicionar um sistema de moedas para que os clientes possam trocar “moedas de ouro” por descontos. Independentemente do próximo passo, o objetivo é claro: manter a experiência de compra sempre fresca e divertida.
Happy coding!
Tags:
Posts relacionados
Publicar um comentário
Comentários
Nenhum comentário.