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ório | Tipo | Propósito | Exemplos |
|---|---|---|---|
components/ui/ | Design system compartilhado | Primitivos visuais usados em toda a aplicação (loja + admin básico) | Button, Input, Badge, Logo, StorageImage |
components/admin/ui/ | Design system exclusivo do admin | Componentes de UI otimizados para interfaces de gestão, densos em informação | DataTable, Dialog, Drawer, Tabs, RevenueChart |
components/store/ | Componentes de negócio — loja | Funcionalidades específicas da experiência de compra | CartDrawer, ProductCard, PdpVariantSection |
components/admin/ | Componentes de negócio — admin | Funcionalidades específicas da gestão e operação | ProductForm, AdminSidebar, NfeCard, VariantManager |
components/store/sections/ | Seções da home | Blocos visuais da página inicial gerenciados pelo editor Puck | HeroSection, 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.
| Componente | Propósito | Quando usar |
|---|---|---|
Button | Botã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 |
Input | Campo de texto base com suporte a label, erro e ícone | Formulários genéricos sem necessidade de máscara |
Card | Container com borda arredondada e sombra sutil | Agrupar informações relacionadas visualmente |
Badge | Etiqueta compacta para status ou categorias | Status de pedido, tags de produto, indicadores |
Alert | Mensagem de feedback com variantes (success, error, warning, info) | Erros de formulário, confirmações, avisos contextuais |
DatePicker | Seletor de data com calendário | Filtros por período, agendamentos |
EmptyState | Estado vazio com ícone, título e ação opcional | Listas vazias, resultados de busca sem itens |
Logo | Logotipo da LJ Velas & Aromas em SVG | Header, footer, página de auth |
Spinner | Indicador de carregamento animado | Estados de loading em botões e seções |
StorageImage | Wrapper de next/image para URLs de upload | Imagens de produto, fotos de perfil (ver seção 9) |
SectionHeader | Título + subtítulo padronizado para seções | Cabeçalhos de seções da home e páginas internas |
EnvBanner | Banner visível apenas em ambientes não-produção | Inserido automaticamente no root layout |
AnalyticsScripts | Google Analytics + Microsoft Clarity via next/script | Inserido uma vez no root layout |
ClarityScript | Script isolado do Microsoft Clarity | Usado internamente por AnalyticsScripts |
CepInput | Input com máscara de CEP (XXXXX-XXX) | Endereço de entrega, calculadora de frete |
CpfInput | Input com máscara de CPF (XXX.XXX.XXX-XX) | Cadastro de cliente, emissão de NF-e |
CnpjInput | Input com máscara de CNPJ (XX.XXX.XXX/XXXX-XX) | Configurações da empresa, NF-e |
CurrencyInput | Input formatado em BRL (R$ 0,00) | Preços, valores de cupom, configuração de frete |
PhoneInput | Input com máscara de telefone brasileiro | Cadastro de cliente, contato |
MaskedInput | Input genérico com máscara customizável | Base dos inputs de máscara acima |
store-select | Select estilizado para a loja | Ordenaçã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.
| Componente | Propósito |
|---|---|
Button | Variante admin do botão — mesma API do ui/Button mas com paleta neutra |
Input | Campo de texto com estilo admin |
Textarea | Área de texto multilinha |
Card | Container do admin — sem bordas decorativas, fundo branco sobre gray-50 |
Badge | Etiquetas de status para pedidos, estoque e usuários |
Alert | Alertas contextuais dentro do painel |
Checkbox | Checkbox acessível com label integrada |
DataTable | Tabela completa com ordenação, paginação e seleção de linhas |
DatePicker | Seletor de datas para filtros de dashboard e relatórios |
Dialog | Modal com overlay — confirmações, formulários compactos |
Drawer | Painel lateral deslizante — detalhes de pedido, edição rápida |
DropdownMenu | Menu contextual com ações — tabelas de produtos e pedidos |
EmptyState | Estado vazio para listagens admin |
FormField | Agrupamento label + input + mensagem de erro para formulários |
Label | Label acessível para campos de formulário |
PageHeader | Cabeçalho de página com título, subtítulo e slot de ações |
Select | Dropdown de seleção estilizado para admin |
Separator | Linha divisória horizontal |
Skeleton | Placeholder de carregamento para tabelas e cards |
StatsCard | Card de métrica com título, valor, variação e ícone |
Switch | Toggle on/off — ativar/desativar produtos, configurações |
Tabs | Navegação por abas dentro de uma página |
Tooltip | Dica flutuante ao hover |
Topbar | Barra superior do admin com título da página e ações globais |
MotionWrapper | Wrapper de Framer Motion para animações de entrada |
RevenueChart | Gráfico de receita usando Recharts |
4. Componentes de Loja (components/store/)
Componentes de negócio da experiência de compra.
| Componente | Descrição |
|---|---|
cart-drawer.tsx | Gaveta lateral do carrinho — exibe itens, subtotal, cupom e botão de checkout. Controlado por useCartStore. |
product-card.tsx | Card de produto para listagens — imagem, nome, preço, badge de desconto. Server Component quando possível. |
product-card-actions.tsx | Ações do card (favoritar, adicionar ao carrinho) separadas em Client Component para não contaminar o card inteiro. |
product-image-gallery.tsx | Galeria de imagens do produto na PDP com zoom e navegação por miniaturas. |
add-to-cart-button.tsx | Botão de adicionar ao carrinho com controle de quantidade e feedback de loading. |
coupon-input.tsx | Campo para inserção e validação de cupom de desconto. Chama /api/coupons/validate. |
pdp-variant-section.tsx | Seletor de variantes na PDP (ex.: volume, aroma, intensidade) com atualização de preço e estoque. |
pdp-shipping-calculator.tsx | Calculadora de frete na PDP — input de CEP, chamada à API de frete, exibição de opções. |
pdp-action-buttons.tsx | Agrupamento dos botões de ação da PDP (adicionar ao carrinho, comprar agora). |
product-filters.tsx | Filtros de listagem (categoria, preço, ordenação) sincronizados com useSearchParams. |
product-reviews.tsx | Seção de avaliações do produto — exibição e formulário de envio. |
shipping-calculator.tsx | Versão standalone da calculadora de frete (usada no carrinho e na página de checkout). |
variant-selector.tsx | Seletor de atributo único de variante (reutilizável dentro de pdp-variant-section). |
header.tsx | Cabeçalho da loja com logo, navegação, busca e ícone do carrinho. |
footer.tsx | Rodapé da loja com links, redes sociais e informações legais. |
mp-card-form.tsx | Formulário de cartão de crédito integrado ao MercadoPago SDK. |
checkout-success-content.tsx | Conteú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.
| Componente | Descrição |
|---|---|
admin-sidebar.tsx | Sidebar de navegação do admin — links, ícones Lucide, estado collapsed persistido em useAdminUIStore. |
product-form.tsx | Formulário completo de produto — nome, descrição, preço, categorias, imagens, variantes, SEO. |
product-image-manager.tsx | Upload e reordenação de imagens do produto com drag-and-drop. |
variant-manager.tsx | Gerenciamento de variantes — criação de tipos (volume, aroma) e combinações (SKUs). |
stats-card.tsx | Card de estatística do dashboard com valor, variação percentual e sparkline. |
rich-text-editor.tsx | Editor de texto rico (baseado em Tiptap ou similar) para descrições de produto. |
nfe-card.tsx | Card de emissão de NF-e para um pedido — dados fiscais, status, botão de emissão. |
dashboard-period-section.tsx | Seção do dashboard com seletor de período e cards de métricas correspondentes. |
categories-dnd-list.tsx | Lista de categorias com reordenação drag-and-drop para definir ordem de exibição. |
category-form.tsx | Formulário de criação e edição de categoria. |
clone-product-button.tsx | Botão de clonagem rápida de produto — duplica todos os campos exceto slug. |
configuracoes-client.tsx | Formulário de configurações gerais da loja (nome, e-mail, redes sociais). |
delete-category-button.tsx | Botão de exclusão de categoria com confirmação via Dialog. |
kit-status-select.tsx | Select de status de kit (ativo/inativo/em breve). |
product-table-actions.tsx | Menu de ações nas linhas da tabela de produtos (editar, clonar, excluir). |
puck-image-field.tsx | Campo de imagem customizado para o editor Puck. |
stock-adjustment-form.tsx | Formulário de ajuste de estoque com motivo e quantidade. |
toggle-client-status-button.tsx | Botão de ativação/desativação de cliente. |
variant-types-client.tsx | Gerenciamento 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 defaultpara o componente principal do arquivo.- Um componente por arquivo (helpers internos são permitidos no mesmo arquivo se pequenos).
- Props tipadas com
interface, nuncatypepara 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:
| Componente | Máscara | Quando usar |
|---|---|---|
CepInput | XXXXX-XXX | Endereço de entrega, calculadora de frete, cadastro de cliente |
CpfInput | XXX.XXX.XXX-XX | Cadastro de cliente PF, emissão de NF-e, identificação fiscal |
CnpjInput | XX.XXX.XXX/XXXX-XX | Configurações da empresa, dados fiscais do admin |
CurrencyInput | R$ X.XXX,XX | Preços de produto, valor mínimo de frete grátis, descontos de cupom |
PhoneInput | (XX) XXXXX-XXXX | Cadastro de cliente, formulário de contato |
MaskedInput | Customizável | Quando 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 defaultpara 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/eadmin/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
sizese 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.