Skip to main content

Stack tecnológica

CamadaTecnologia
FrameworkNext.js 16.0.7 (App Router)
LinguagemTypeScript 5.x
UI / EstilosTailwind CSS v4.1, Radix UI, shadcn/ui pattern
ÍconesLucide React
AnimaçõesFramer Motion, tw-animate-css
GráficosRecharts
NotificaçõesSonner (toast)
Estado globalZustand v5
Estado do servidorTanStack Query v5 (React Query)
HTTP ClientAxios
FormuláriosReact Hook Form + Zod + @hookform/resolvers

Estrutura de pastas

src/
├── app/                        # Roteamento Next.js (App Router)
│   ├── (public)/               # Rotas públicas (login, landing)
│   ├── (protected)/            # Rotas autenticadas
│   │   ├── layout.tsx          # Layout raiz protegido — auth guard + sidebar
│   │   ├── dashboard/
│   │   ├── products/
│   │   ├── inventory/
│   │   ├── orders/
│   │   ├── listings/
│   │   ├── integrations/
│   │   ├── imports/
│   │   └── ...                 # Demais módulos
│   ├── layout.tsx              # Root layout (providers globais)
│   └── globals.css

├── components/                 # Componentes compartilhados
│   ├── ui/                     # Design system (Button, Input, Dialog, etc.)
│   ├── layout/                 # Header, Sidebar, SidebarContext
│   ├── auth/                   # RefreshTokenOverlay
│   └── integrations/

├── features/                   # Lógica de negócio por domínio
│   ├── auth/
│   ├── catalog/
│   ├── inventory/
│   ├── listings/
│   ├── orders/
│   ├── imports/
│   ├── integrations/
│   ├── tenant/
│   └── ...                     # ~27 features no total

└── shared/                     # Utilitários transversais
    ├── lib/
    │   ├── axios.ts            # Instância Axios com interceptors
    │   └── utils.ts
    ├── providers/
    │   ├── auth-provider.tsx   # Silent refresh ao carregar a app
    │   └── query-provider.tsx  # QueryClient global
    ├── hooks/                  # (reservado para hooks globais futuros)
    └── types/                  # Tipos TypeScript globais

Padrão de Feature

Cada domínio em src/features/ segue a mesma estrutura interna:
src/features/<nome>/
├── components/     # Componentes exclusivos desta feature (formulários, tabelas, cards)
├── hooks/          # Custom hooks — chamam os services e expõem para componentes
├── services/       # Chamadas Axios à API (única camada que usa HTTP)
├── store/          # Zustand store (se o domínio tiver estado global)
├── types/          # Interfaces e tipos TypeScript do domínio
└── schemas.ts      # Schemas Zod de validação de formulários
Regra de ouro: componentes e hooks nunca chamam Axios. Só services/ faz chamadas HTTP.

Autenticação e Sessão

Fluxo de tokens

Usuário faz login


POST /auth/login

      ├── accessToken (JWT, memória — Zustand)
      └── refreshToken (HttpOnly cookie — automático)
O accessToken nunca é salvo em localStorage. Fica exclusivamente no Zustand (useAuthStore) em memória.

Silent Refresh (ao recarregar a página)

Quando o usuário recarrega (F5), o Zustand é reiniciado e o accessToken é perdido. O AuthProvider e o ProtectedLayout detectam esse estado e automaticamente chamam POST /auth/refresh usando o cookie HttpOnly:
F5 / Recarregar


ProtectedLayout.initAuth()

      ├── authApi.refresh()  →  POST /auth/refresh (cookie enviado automaticamente)
      │         │
      │         ├── Sucesso: login() no Zustand + loadTenant()
      │         └── Falha: logout() + redirect para /login?redirect=<pathname>

      └── Mostra overlay de loading durante a hidratação

Interceptor Axios (src/shared/lib/axios.ts)

Toda requisição passa pelo interceptor que:
  1. Injeta Authorization: Bearer <accessToken> no header.
  2. Injeta x-tenant-id: <tenantId> no header.
  3. Se receber 401, tenta renovar o token automaticamente via /auth/refresh.
  4. Enquanto renova, coloca requisições paralelas em fila e as reexecuta após sucesso.
  5. Se o refresh falhar, chama logout() e a fila de requisições é rejeitada.

Proteção de rotas

Todas as rotas em app/(protected)/** requerem autenticação. O ProtectedLayout redireciona para /login se o usuário não estiver autenticado após a tentativa de silent refresh.

Estado Global (Zustand)

StoreLocalizaçãoResponsabilidade
useAuthStorefeatures/auth/store/auth-storeuser, accessToken, isRefreshing, logout
useTenantStorefeatures/tenant/store/tenant-storeDados do tenant ativo, loadTenant()
Os stores são inicializados sem persistência em localStorage. A persistência é feita via cookie do refresh token.

Fetching de dados (TanStack Query)

O QueryProvider está no root layout. Toda busca de dados de servidor usa useQuery e mutações usam useMutation, sempre encapsulados em custom hooks dentro de features/<nome>/hooks/. Convenção:
// features/products/hooks/useProducts.ts
export function useProducts(filters: ProductFilters) {
  return useQuery({
    queryKey: ['products', filters],
    queryFn: () => productService.list(filters),
  })
}
Paginação: Todos os endpoints usam cursor-based pagination. Nunca implemente offset.

Formulários e Validação

  • React Hook Form gerencia o estado dos formulários.
  • Zod valida os dados (schemas em features/<nome>/schemas.ts).
  • A validação no frontend é limitada a UX e campos obrigatórios. Regras de negócio vêm do backend via resposta da API.

Convenções de código

O queConvenção
Diretórioskebab-case
ComponentesPascalCase.tsx
Hooks / ServicescamelCase.ts (ex: useProducts.ts, productService.ts)
TiposPascalCase (ex: Product, ProductResponse)
'use client'Apenas em leaf components (formulários, tabelas, interatividade)
Axios direto❌ Nunca em componentes ou hooks. Apenas em services/
Estado global ad-hoc❌ Nunca use window, localStorage, ou singletons implícitos

Módulos do frontend

Os 27 domínios em src/features/ correspondem às seguintes áreas do sistema:
FeatureDescrição
authLogin, logout, sessão
catalogCatálogo de produtos
productsGestão de produtos
products-dashboardDashboard de produtos
inventoryControle de estoque
listingsAnúncios dos marketplaces
integrationsConexões OAuth com marketplaces
importsImportação de NF-e
ordersGestão de pedidos
orders-dashboardDashboard de pedidos
financialMódulo financeiro
fiscalMódulo fiscal
logisticsLogística
dispatchExpedição
returnsDevoluções
suppliersFornecedores
partnersParceiros
brandsMarcas
registriesCadastros gerais
tenantDados e configurações do tenant
organizationOrganização / empresa
subscription/subscriptionsPlanos e assinaturas
plansPlanos disponíveis
social-catalogsCatálogos sociais
social-catalogs-dashboardDashboard de catálogos sociais

Variáveis de ambiente

VariávelObrigatóriaDescrição
NEXT_PUBLIC_API_URLURL base da API backend (ex: https://api.omnidom.com)
NEXT_PUBLIC_APP_NAMENome exibido na interface (padrão: Hub Marketplace)
Configure em .env.local para desenvolvimento local. Em produção, configure no serviço de hospedagem (Vercel, etc.).