Pular para o conteúdo principal

Arquitetura de Componentes

Este documento descreve a organização, convenções e uso dos componentes do projeto, incluindo os dois design systems (loja e admin), os componentes de negócio e os padrões de criação de novos componentes.


1. Organização

Os componentes estão distribuídos em três diretórios raiz sob components/:

DiretórioTipoPropósitoExemplos
components/ui/Design system compartilhadoPrimitivos visuais usados em toda a aplicação (loja + admin básico)Button, Input, Badge, Logo, StorageImage
components/admin/ui/Design system exclusivo do adminComponentes de UI otimizados para interfaces de gestão, densos em informaçãoDataTable, Dialog, Drawer, Tabs, RevenueChart
components/store/Componentes de negócio — lojaFuncionalidades específicas da experiência de compraCartDrawer, ProductCard, PdpVariantSection
components/admin/Componentes de negócio — adminFuncionalidades específicas da gestão e operaçãoProductForm, AdminSidebar, NfeCard, VariantManager
components/store/sections/Seções da homeBlocos visuais da página inicial gerenciados pelo editor PuckHeroSection, AboutSection, FeaturedProductsSection

Relação entre os design systems

components/ui/ ← compartilhado; importável em qualquer contexto
components/admin/ui/ ← exclusivo do admin; NÃO deve ser importado na loja

A separação é intencional: o design system do admin tem densidade maior, paleta neutra (grays) e componentes complexos como DataTable e RevenueChart que não fazem sentido na loja. Manter os dois isolados evita que mudanças no admin afetem a experiência do consumidor.


2. Design System da Loja (components/ui/)

Primitivos e utilitários compartilhados entre loja e, quando necessário, o admin.

ComponentePropósitoQuando usar
ButtonBotão com variantes (primary, secondary, ghost, outline) e estados (loading, disabled)Toda ação clicável que não seja um link de navegação
InputCampo de texto base com suporte a label, erro e íconeFormulários genéricos sem necessidade de máscara
CardContainer com borda arredondada e sombra sutilAgrupar informações relacionadas visualmente
BadgeEtiqueta compacta para status ou categoriasStatus de pedido, tags de produto, indicadores
AlertMensagem de feedback com variantes (success, error, warning, info)Erros de formulário, confirmações, avisos contextuais
DatePickerSeletor de data com calendárioFiltros por período, agendamentos
EmptyStateEstado vazio com ícone, título e ação opcionalListas vazias, resultados de busca sem itens
LogoLogotipo da LJ Velas & Aromas em SVGHeader, footer, página de auth
SpinnerIndicador de carregamento animadoEstados de loading em botões e seções
StorageImageWrapper de next/image para URLs de uploadImagens de produto, fotos de perfil (ver seção 9)
SectionHeaderTítulo + subtítulo padronizado para seçõesCabeçalhos de seções da home e páginas internas
EnvBannerBanner visível apenas em ambientes não-produçãoInserido automaticamente no root layout
AnalyticsScriptsGoogle Analytics + Microsoft Clarity via next/scriptInserido uma vez no root layout
ClarityScriptScript isolado do Microsoft ClarityUsado internamente por AnalyticsScripts
CepInputInput com máscara de CEP (XXXXX-XXX)Endereço de entrega, calculadora de frete
CpfInputInput com máscara de CPF (XXX.XXX.XXX-XX)Cadastro de cliente, emissão de NF-e
CnpjInputInput com máscara de CNPJ (XX.XXX.XXX/XXXX-XX)Configurações da empresa, NF-e
CurrencyInputInput formatado em BRL (R$ 0,00)Preços, valores de cupom, configuração de frete
PhoneInputInput com máscara de telefone brasileiroCadastro de cliente, contato
MaskedInputInput genérico com máscara customizávelBase dos inputs de máscara acima
store-selectSelect estilizado para a lojaOrdenação de produtos, seleção de quantidade

3. Design System do Admin (components/admin/ui/)

Componentes exclusivos do painel administrativo. Não importar na loja.

ComponentePropósito
ButtonVariante admin do botão — mesma API do ui/Button mas com paleta neutra
InputCampo de texto com estilo admin
TextareaÁrea de texto multilinha
CardContainer do admin — sem bordas decorativas, fundo branco sobre gray-50
BadgeEtiquetas de status para pedidos, estoque e usuários
AlertAlertas contextuais dentro do painel
CheckboxCheckbox acessível com label integrada
DataTableTabela completa com ordenação, paginação e seleção de linhas
DatePickerSeletor de datas para filtros de dashboard e relatórios
DialogModal com overlay — confirmações, formulários compactos
DrawerPainel lateral deslizante — detalhes de pedido, edição rápida
DropdownMenuMenu contextual com ações — tabelas de produtos e pedidos
EmptyStateEstado vazio para listagens admin
FormFieldAgrupamento label + input + mensagem de erro para formulários
LabelLabel acessível para campos de formulário
PageHeaderCabeçalho de página com título, subtítulo e slot de ações
SelectDropdown de seleção estilizado para admin
SeparatorLinha divisória horizontal
SkeletonPlaceholder de carregamento para tabelas e cards
StatsCardCard de métrica com título, valor, variação e ícone
SwitchToggle on/off — ativar/desativar produtos, configurações
TabsNavegação por abas dentro de uma página
TooltipDica flutuante ao hover
TopbarBarra superior do admin com título da página e ações globais
MotionWrapperWrapper de Framer Motion para animações de entrada
RevenueChartGráfico de receita usando Recharts

4. Componentes de Loja (components/store/)

Componentes de negócio da experiência de compra.

ComponenteDescrição
cart-drawer.tsxGaveta lateral do carrinho — exibe itens, subtotal, cupom e botão de checkout. Controlado por useCartStore.
product-card.tsxCard de produto para listagens — imagem, nome, preço, badge de desconto. Server Component quando possível.
product-card-actions.tsxAções do card (favoritar, adicionar ao carrinho) separadas em Client Component para não contaminar o card inteiro.
product-image-gallery.tsxGaleria de imagens do produto na PDP com zoom e navegação por miniaturas.
add-to-cart-button.tsxBotão de adicionar ao carrinho com controle de quantidade e feedback de loading.
coupon-input.tsxCampo para inserção e validação de cupom de desconto. Chama /api/coupons/validate.
pdp-variant-section.tsxSeletor de variantes na PDP (ex.: volume, aroma, intensidade) com atualização de preço e estoque.
pdp-shipping-calculator.tsxCalculadora de frete na PDP — input de CEP, chamada à API de frete, exibição de opções.
pdp-action-buttons.tsxAgrupamento dos botões de ação da PDP (adicionar ao carrinho, comprar agora).
product-filters.tsxFiltros de listagem (categoria, preço, ordenação) sincronizados com useSearchParams.
product-reviews.tsxSeção de avaliações do produto — exibição e formulário de envio.
shipping-calculator.tsxVersão standalone da calculadora de frete (usada no carrinho e na página de checkout).
variant-selector.tsxSeletor de atributo único de variante (reutilizável dentro de pdp-variant-section).
header.tsxCabeçalho da loja com logo, navegação, busca e ícone do carrinho.
footer.tsxRodapé da loja com links, redes sociais e informações legais.
mp-card-form.tsxFormulário de cartão de crédito integrado ao MercadoPago SDK.
checkout-success-content.tsxConteúdo da página de confirmação de pedido após pagamento.

Seções da Home (components/store/sections/)

Seções visuais da página inicial configuráveis pelo editor Puck (/admin/editor). Cada seção é um componente independente registrado como bloco do editor.


5. Componentes Admin (components/admin/)

Componentes de negócio do painel administrativo.

ComponenteDescrição
admin-sidebar.tsxSidebar de navegação do admin — links, ícones Lucide, estado collapsed persistido em useAdminUIStore.
product-form.tsxFormulário completo de produto — nome, descrição, preço, categorias, imagens, variantes, SEO.
product-image-manager.tsxUpload e reordenação de imagens do produto com drag-and-drop.
variant-manager.tsxGerenciamento de variantes — criação de tipos (volume, aroma) e combinações (SKUs).
stats-card.tsxCard de estatística do dashboard com valor, variação percentual e sparkline.
rich-text-editor.tsxEditor de texto rico (baseado em Tiptap ou similar) para descrições de produto.
nfe-card.tsxCard de emissão de NF-e para um pedido — dados fiscais, status, botão de emissão.
dashboard-period-section.tsxSeção do dashboard com seletor de período e cards de métricas correspondentes.
categories-dnd-list.tsxLista de categorias com reordenação drag-and-drop para definir ordem de exibição.
category-form.tsxFormulário de criação e edição de categoria.
clone-product-button.tsxBotão de clonagem rápida de produto — duplica todos os campos exceto slug.
configuracoes-client.tsxFormulário de configurações gerais da loja (nome, e-mail, redes sociais).
delete-category-button.tsxBotão de exclusão de categoria com confirmação via Dialog.
kit-status-select.tsxSelect de status de kit (ativo/inativo/em breve).
product-table-actions.tsxMenu de ações nas linhas da tabela de produtos (editar, clonar, excluir).
puck-image-field.tsxCampo de imagem customizado para o editor Puck.
stock-adjustment-form.tsxFormulário de ajuste de estoque com motivo e quantidade.
toggle-client-status-button.tsxBotão de ativação/desativação de cliente.
variant-types-client.tsxGerenciamento dos tipos de variante globais (ex.: "Volume", "Aroma").

6. Convenções

Nomenclatura de Arquivos

  • kebab-case para todos os arquivos: product-card.tsx, admin-sidebar.tsx.
  • Nunca usar PascalCase no sistema de arquivos.

Estrutura Interna

// components/store/product-card.tsx

import type { Product } from "@/types";

interface ProductCardProps {
product: Product;
priority?: boolean; // repassado ao next/image
}

export default function ProductCard({ product, priority = false }: ProductCardProps) {
// ...
}
  • export default para o componente principal do arquivo.
  • Um componente por arquivo (helpers internos são permitidos no mesmo arquivo se pequenos).
  • Props tipadas com interface, nunca type para shapes de objeto.

Ícones

Todos os ícones do projeto usam Lucide React:

import { ShoppingCart, Heart, Search } from "lucide-react";

Não usar outras bibliotecas de ícones. O pacote está listado em optimizePackageImports no next.config.ts, então imports nomeados são tree-shaken automaticamente.

Diretiva "use client"

Colocar sempre na primeira linha do arquivo, antes de qualquer import:

"use client";

import { useState } from "react";
// ...

7. Máscara de Inputs

Os inputs com máscara residem em components/ui/ e são baseados em MaskedInput (primitivo interno). Use o componente específico para cada tipo de dado:

ComponenteMáscaraQuando usar
CepInputXXXXX-XXXEndereço de entrega, calculadora de frete, cadastro de cliente
CpfInputXXX.XXX.XXX-XXCadastro de cliente PF, emissão de NF-e, identificação fiscal
CnpjInputXX.XXX.XXX/XXXX-XXConfigurações da empresa, dados fiscais do admin
CurrencyInputR$ X.XXX,XXPreços de produto, valor mínimo de frete grátis, descontos de cupom
PhoneInput(XX) XXXXX-XXXXCadastro de cliente, formulário de contato
MaskedInputCustomizávelQuando nenhum dos acima se encaixa — aceita padrão de máscara livre

Todos aceitam as mesmas props do <input> HTML padrão (incluindo value, onChange, disabled, required) e são compatíveis com react-hook-form via register.


8. Como Criar um Novo Componente

Template

"use client"; // apenas se necessário

import { /* ícones */ } from "lucide-react";
// outros imports

// ── Types ───────────────────────────────────────────────────────────────────
interface NomeComponenteProps {
// props
}

// ── Component ────────────────────────────────────────────────────────────────
export default function NomeComponente({ prop1, prop2 }: NomeComponenteProps) {
return (
<div>
{/* conteúdo */}
</div>
);
}

Checklist

  • Nome do arquivo em kebab-case (meu-componente.tsx)
  • Arquivo no diretório correto (ui/, store/, admin/, admin/ui/)
  • "use client" apenas se o componente usa hooks, eventos ou browser APIs
  • Props tipadas com interface
  • export default para o componente principal
  • Ícones de lucide-react — não instalar outras bibliotecas
  • Se for um input com máscara, verificar se já existe um componente adequado em components/ui/
  • Se for para o admin, verificar se existe um componente em components/admin/ui/ antes de criar do zero
  • Sem lógica de negócio dentro de componentes de design system (ui/ e admin/ui/) — eles são puramente visuais

9. StorageImage

StorageImage é um wrapper sobre next/image para imagens enviadas via upload (armazenadas no servidor da Hostinger). Ele resolve a URL relativa para o domínio correto e aplica configurações de otimização padronizadas.

Por que usar StorageImage em vez de next/image diretamente?

  • Centraliza a lógica de resolução de URL de upload em um único lugar.
  • Aplica automaticamente sizes e configurações de WebP.
  • Facilita a migração de storage (ex.: mover para S3 exige mudança em um único componente).

Como usar

import StorageImage from "@/components/ui/storage-image";

// Imagem de produto
<StorageImage
src={product.image} // URL relativa ou absoluta do upload
alt={product.name}
width={400}
height={400}
className="rounded-lg object-cover"
/>

// Com prioridade (acima da dobra — LCP)
<StorageImage
src={product.coverImage}
alt={product.name}
width={800}
height={600}
priority
/>

Quando usar next/image diretamente

Use next/image diretamente apenas para imagens estáticas (importadas de public/ ou import de arquivo local), que não passam pelo sistema de upload.