O 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 que lembra 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.

Google be like: I have never met this page in my life

Eu combinei Vue.js, TailwindCSS, APIs customizadas do Magento e uns ajustes 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.

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

xml
<?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
<?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: APIs customizadas do Magento#

O sistema de API do Magento 2 é sólido, 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.

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

php
public function getProductsByCategoryId($categoryId)
{
    if (!$this->isModuleEnabled()) {
        return [];
    }

    // Standard product retrieval logic
}

Desenhando uma interface de 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 vira algo divertido - bem mais interessante do que uma página de e-commerce típica.

Segue o layout:

  1. Cabeçalho fixo exibindo o nome da loja.
  2. Barra lateral do lojista com um avatar, contagem de itens do carrinho em tempo real e opções de menu (Comprar, Vender, Falar, Sair).
  3. Painel principal exibindo listagens dinâmicas de categorias e produtos, além de uma visualização detalhada do produto.

Tela principal da loja

Montando o layout#

Dentro do arquivo index.phtml, configurei o container para minha aplicação Vue:

O container da aplicação Vue.js#

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

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

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.

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

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

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

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

Tela de listagem de produtos

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.

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

Tela de detalhes do produto

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.

html
<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>
js
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: 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 bacana com a imagem do lojista.

Atualize werules_gameshop_index_index.xml#

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
<?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 fica bonita e também funciona bem para SEO e compartilhamento nas redes sociais.

Etapas finais & testes#

Ativando a extensão#

bash
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í! A vitrine da Zenkai Zone agora tem uma extensão customizada para Magento 2 com novas APIs para carrinho e produtos e um design divertido, inspirado em RPG, com um SEO decente. E ainda tem 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 maluquinha que deixa a Zenkai Zone bem mais divertida.

Contribuindo com a extensão GameShop#

Como em todos os meus projetos, a extensão GameShop é open-source e está disponível no GitHub. Faz um fork, testa e contribui com o projeto se quiser. 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.