Skip to content

Arquitetura do Front-End

Visão Geral

O front-end do Muvien é uma aplicação Next.js 16 (App Router) com React 18, TypeScript e Tailwind CSS. A arquitetura segue um padrão modular orientado a domínio (domain-driven), onde cada módulo de negócio encapsula suas próprias camadas de serviço, hooks, componentes e telas.

┌──────────────────────────────────────────────────────────────────┐
│                        NAVEGADOR                                 │
├──────────────────────────────────────────────────────────────────┤
│  Next.js App Router                                              │
│  /[locale]/[businessID]/(secure|auth)/...                        │
├──────────────────────────────────────────────────────────────────┤
│  Camada de Apresentação                                          │
│  ├── Templates (SecureTemplate, AuthTemplate)                    │
│  ├── Screens (páginas de cada módulo)                            │
│  └── Components (Table, Drawers, Modals, Forms)                  │
├──────────────────────────────────────────────────────────────────┤
│  Camada de Estado                                                │
│  ├── React Query (TanStack) → estado do servidor                 │
│  ├── Zustand → estado global (user, permissions)                 │
│  └── nuqs → estado na URL (paginação, filtros, modais)           │
├──────────────────────────────────────────────────────────────────┤
│  Camada de Dados                                                 │
│  ├── Hooks (useQuery / useMutation)                              │
│  ├── Services (classes que estendem HTTPAdapter)                  │
│  └── Schemas (Zod) + Types (TypeScript)                          │
├──────────────────────────────────────────────────────────────────┤
│  HTTPAdapter (Axios)                                             │
│  ├── Header Tenant-ID (multi-tenancy)                            │
│  ├── JWT (access_token + refresh_token em cookies)               │
│  ├── Interceptors (refresh automático, tradução de erros)        │
│  └── Fila de requisições durante refresh                         │
├──────────────────────────────────────────────────────────────────┤
│                        API REST (Backend)                        │
└──────────────────────────────────────────────────────────────────┘

Stack Tecnológico

CamadaTecnologiaFinalidade
FrameworkNext.js 16 (App Router, Webpack)SSR, roteamento, layouts
UIHeroUI (fork NextUI) + Tailwind CSSComponentes visuais
AnimaçõesFramer MotionTransições e animações
ÍconesLucide ReactIconografia
Estado ServidorTanStack React QueryCache, fetching, mutations
Estado GlobalZustandUser store, Permission store
Estado URLnuqsPaginação, filtros, modais na URL
FormuláriosReact Hook Form + ZodValidação e gerenciamento
HTTPAxios (HTTPAdapter)Requisições com interceptors
i18nnext-intlInternacionalização (pt, en, es)
AutenticaçãoJWT customizado (cookies)Access/refresh tokens
TestesVitest + Testing Library + CypressUnit, integration, E2E

Estrutura de Rotas

A aplicação usa o App Router do Next.js com dois segmentos dinâmicos na raiz:

src/app/[locale]/[businessID]/
├── (auth)/                         # Páginas públicas de autenticação
│   ├── login/
│   ├── register/
│   ├── forgot-password/
│   ├── passwordReset/[token]/
│   ├── verifyEmail/[token]/
│   ├── invites/[token]/
│   └── template.tsx               → AuthTemplate (layout split: form + imagem)

├── (secure)/                       # Páginas protegidas (requer autenticação)
│   ├── dashboard/
│   ├── expedition/
│   ├── (expedition)/cargo-types/
│   ├── (groups)/groups/
│   ├── collection/
│   ├── transport/
│   ├── commercial/
│   ├── calculation/
│   ├── cep/
│   ├── users/
│   ├── freightSimulator/
│   └── template.tsx               → SecureTemplate (sidebar + header + verificação auth)

└── page.tsx                        # Redireciona para /login
  • [locale]: Idioma ativo (pt, en, es). Default: pt.
  • [businessID]: ID numérico do tenant (organização) para multi-tenancy.
  • Route Groups (auth) e (secure): Separam layouts sem afetar a URL.

Arquitetura Modular

Organização de Pastas

src/
├── app/                    # Rotas Next.js (App Router)
├── data/
│   ├── services/api/       # HTTPAdapter (cliente Axios base)
│   ├── states/zustand/     # Stores globais (user, permission)
│   └── queryClient/        # Configuração do React Query
├── locales/                # Arquivos de tradução (pt.json, en.json, es.json)
├── modules/                # Módulos de domínio
│   ├── auth/
│   ├── organizations/
│   ├── groups-management/
│   ├── users-management/
│   ├── operacional/
│   ├── transport/
│   ├── commercial/
│   ├── freight-simulator/
│   ├── collection/
│   ├── cep/
│   └── external/
└── shared/                 # Código compartilhado entre módulos
    ├── components/         # Componentes reutilizáveis
    ├── hooks/              # Hooks compartilhados
    ├── modules/            # Sub-módulos compartilhados (permissions, operacional)
    ├── services/           # Serviços compartilhados
    ├── types/              # Tipos globais
    ├── constants/          # Constantes
    ├── schemas/            # Schemas Zod compartilhados
    ├── utils/              # Utilitários
    └── misc/               # Helpers e regex

Padrão de um Módulo

Cada módulo segue uma estrutura consistente com 3 camadas:

modules/[domínio]/
├── data/
│   ├── services/               # Classe de serviço (API)
│   │   ├── index.ts            # Classe que estende HTTPAdapter
│   │   ├── @types/             # Tipos de request/response
│   │   ├── schemas/            # Schemas Zod (validação)
│   │   └── responses/          # Tipos de resposta
│   ├── hooks/
│   │   ├── use[Feature]/       # Agregador de hooks
│   │   │   ├── index.ts        # Exporta objeto com queries e mutations
│   │   │   ├── queries/        # Hooks useQuery (list, get)
│   │   │   └── mutations/      # Hooks useMutation (create, update, delete)
│   │   ├── use[Feature]Logic/  # Hook de lógica (paginação, filtros, busca)
│   │   └── use[Feature]Drawers/ # Hook de estado dos modais/drawers
│   └── static/                 # Dados estáticos (configs de colunas)
├── screens/
│   └── [FeatureName]/
│       └── page.tsx            # Tela principal
└── components/
    └── [feature]/
        ├── drawers/            # Drawers (create, edit)
        ├── modals/             # Modais (delete, confirm)
        ├── forms/              # Formulários
        └── table/              # Configurações de tabela

Módulos com Sub-módulos

Módulos maiores como operacional, transport, commercial e cep possuem uma pasta modules/ interna:

MóduloSub-módulos
operacionalcalendar, loading-bay, modalities, cargo-type, warehouse, config-docks
transportcarrier, carrier-service, delivery-grid, calculation-rule
commercialplatform, product-category, channels
cepmap-cep, reference-base
users-managementmembers, invitations

Camada de Serviço (API)

HTTPAdapter

O HTTPAdapter (src/data/services/api/index.ts) é a classe base que encapsula o Axios e fornece:

  • Multi-tenancy: Header Tenant-ID em todas as requisições
  • Autenticação JWT: Interceptor que anexa o Bearer token
  • Refresh automático: Na resposta 401, tenta renovar o access_token usando o refresh_token
  • Fila de requisições: Requisições concorrentes durante o refresh são enfileiradas e reprocessadas após sucesso
  • Tradução de erros: Mapeia códigos de erro da API para mensagens em português
  • Redirecionamento 403: Redireciona para o dashboard quando sem permissão

Padrão de Service

Cada módulo cria uma classe que estende HTTPAdapter:

typescript
class CarrierService extends HTTPAdapter {
  constructor(tenantID: string) {
    super();
    this.setTenant(tenantID);
  }

  list(params: IPaginationSchema) {
    return this.api.get<Paginated<Carrier>>('/carriers/', { params });
  }

  get(id: number) {
    return this.api.get<Carrier>(`/carriers/${id}/`);
  }

  create(data: ICreateCarrierSchema) {
    return this.api.post<Carrier>('/carriers/', data);
  }

  update(id: number, data: IUpdateCarrierSchema) {
    return this.api.patch<Carrier>(`/carriers/${id}/`, data);
  }

  remove(id: number) {
    return this.api.delete(`/carriers/${id}/`);
  }
}

Camada de Hooks (React Query)

Hook Agregador

Cada módulo expõe um objeto que agrupa todos os hooks de query e mutation:

typescript
export const useCarrier = {
  list: useCarrierListQuery,
  get: useGetCarrierQuery,
  create: useCreateCarrierMutation,
  update: useUpdateCarrierMutation,
  delete: useDeleteCarrierMutation,
};

Query Hook

typescript
export const useCarrierListQuery = ({ tenantId, ...params }: Props) => {
  return useQuery({
    queryKey: ['carriers', tenantId, params],
    queryFn: async () => {
      const service = new CarrierService(tenantId);
      service.authenticate();
      const { data } = await service.list(params);
      return data;
    },
  });
};

Mutation Hook

typescript
export const useCreateCarrierMutation = ({ tenantId }: Props) => {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: async ({ data }: MutationParams) => {
      const service = new CarrierService(tenantId);
      service.authenticate();
      const { data: response } = await service.create(data);
      return response;
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['carriers', tenantId] });
    },
  });
};

Hook de Lógica (use[Feature]Logic)

Combina dados do servidor com estado de UI (paginação, filtros, busca):

typescript
export const useCarrierLogic = () => {
  const [params, setParams] = useQueryStates({
    page: parseAsInteger.withDefault(1),
    page_size: parseAsInteger.withDefault(10),
    search: parseAsString,
    is_active: parseAsBoolean,
  });

  const debouncedParams = useDebounce(params, 500);
  const { data, isLoading } = useCarrier.list(debouncedParams);

  return { data, isLoading, filter: { ... }, pagination: { ... } };
};

Hook de Drawers (use[Feature]Drawers)

Gerencia o estado de modais e drawers via URL (nuqs):

typescript
export const useCarrierDrawers = () => {
  const [drawerParams, setDrawerParams] = useQueryStates({
    modal: parseAsString,  // 'create' | 'edit' | 'delete' | null
    id: parseAsInteger,
  });

  return {
    modal: drawerParams.modal,
    id: drawerParams.id,
    openCreateModal: () => setDrawerParams({ modal: 'create', id: null }),
    openEditModal: (id: number) => setDrawerParams({ modal: 'edit', id }),
    close: () => setDrawerParams({ modal: null, id: null }),
  };
};

Camada de Apresentação

Padrão de Screen (Tela)

typescript
'use client';

export const CarriersScreen = () => {
  const t = useTranslations('modules.carrier');
  const drawers = useCarrierDrawers();
  const data = useCarrierLogic();

  return (
    <div>
      <SectionActions
        onCreate={drawers.openCreateModal}
        onSearch={data.filter.search}
      />
      <CarrierDrawers modal={drawers.modal} id={drawers.id} onClose={drawers.close} />
      <DataTable.Root columns={columns} data={data.data?.items ?? []} isLoading={data.isLoading}>
        {/* Colunas renderizadas */}
      </DataTable.Root>
    </div>
  );
};

Padrão de Drawer (Formulário)

typescript
export const CreateCarrierDrawer = ({ isOpen, onClose }: Props) => {
  const form = useForm<ICreateSchema>({
    resolver: zodResolver(createSchema),
  });

  const mutation = useCarrier.create();

  const onSubmit = (data: ICreateSchema) => {
    toast.promise(mutation.mutateAsync(data), {
      loading: <b>Criando...</b>,
      success: () => { form.reset(); onClose(); return <b>Criado com sucesso!</b>; },
      error: <b>Erro ao criar</b>,
    });
  };

  return (
    <DrawerTemplate isOpen={isOpen} onClose={onClose}>
      <form onSubmit={form.handleSubmit(onSubmit)}>
        {/* campos do formulário */}
      </form>
    </DrawerTemplate>
  );
};

Autenticação e Multi-Tenancy

Fluxo de Autenticação

1. Usuário faz login em /[locale]/[businessID]/login
2. AuthService.login() → API retorna access_token + refresh_token
3. Tokens armazenados em cookies (httpOnly para refresh)
4. Redirecionamento para rota (secure)
5. SecureTemplate verifica tokens e carrega:
   - userStore.fetch() → GET /auth/me
   - permissionStore.fetch(businessID) → GET /permissions/
6. Se tokens expirados/ausentes → redirect para /login

Refresh de Token

Requisição falha (401)
  → HTTPAdapter intercepta
  → Enfileira requisições pendentes
  → POST /auth/refresh com refresh_token
  → Sucesso: atualiza access_token, reprocessa fila
  → Falha: limpa cookies, redireciona para /login

Sincronização Entre Abas

O hook useAuthSync usa a BroadcastChannel API (com fallback para localStorage) para sincronizar logout/login entre abas do navegador. Ao detectar logout em uma aba, todas as outras limpam os stores e redirecionam para /login.

Multi-Tenancy

Todas as requisições incluem o header Tenant-ID correspondente ao businessID da URL. Cada serviço recebe o tenantID no construtor e configura o header via setTenant().

Sistema de Permissões

Permission Store (Zustand)

O permissionStore armazena as permissões do usuário em um Set para lookup O(1):

Verificação de permissão (hasPermission):
  1. Checa '*:*' (acesso total)
  2. Checa '{resource}:*' (wildcard no recurso)
  3. Checa '{resource}:{action}' (match exato)
  4. Checa '*:{action}' (wildcard na ação)

Componente <Can>

Renderização condicional baseada em permissões:

typescript
<Can resource="hub" action="read">
  <Button>Editar</Button>
</Can>

Constantes de Permissão

Definidas em shared/modules/permissions/constants/permission.ts:

typescript
PERMISSIONS.HUB.READ       // { resource: 'hub', action: 'read' }
PERMISSIONS.MEMBER.INVITE  // { resource: 'member', action: 'invite' }
PERMISSIONS.ADMIN.FULL_ACCESS // { resource: '*', action: '*' }

Recursos disponíveis: agenda, additional_cost, additional_time, cargo_type, hub, invitation, loading_bay, member, modal, modality, order, organization, permission, pickup_point, product, role, sales_channel, sales_platform, product_category.

Internacionalização (i18n)

  • Biblioteca: next-intl
  • Idiomas suportados: Português (pt), English (en), Español (es)
  • Arquivos de tradução: src/locales/{pt,en,es}.json
  • Configuração: src/locales/i18n.ts com getRequestConfig
  • Uso: useTranslations('namespace.key') nos componentes client
  • Validação: Schemas Zod usam chaves de tradução com prefixo @ (ex: @MIN_CHARACTERS, @REQUIRED_PERMISSIONS)

Validação com Zod

Schemas de validação ficam em data/services/[feature]/schemas/:

typescript
const createCarrierSchema = z.object({
  name: z.string().min(1, '@REQUIRED').max(128),
  cnpj: z.string().min(14, '@MIN_CHARACTERS'),
  is_active: z.boolean().default(false),
});

export type ICreateCarrierSchema = z.infer<typeof createCarrierSchema>;

A integração com React Hook Form é feita via zodResolver:

typescript
const form = useForm<ICreateCarrierSchema>({
  resolver: zodResolver(createCarrierSchema),
});

Componentes Compartilhados (shared)

Templates

TemplateLocalizaçãoFinalidade
SecureTemplateshared/components/templates/SecureTemplate/Layout principal com sidebar, header, verificação de auth e permissões
AuthTemplateshared/components/templates/authTemplate/Layout de login/registro (split: formulário + imagem)
ModalTemplateshared/components/templates/ModalTemplate/Wrapper de modal com logo Muvien
DrawerTemplateshared/components/common/Drawers/Drawer reutilizável (modos create/edit com cores distintas)
ReusableTabsTemplateshared/components/templates/ReusableTabsTemplate/Interface com abas filtradas por permissão, estado via URL

Componentes Comuns

ComponenteFinalidade
DataTableTabela genérica com tipagem (Root + Column)
CheckboxTableTabela com seleção, busca fuzzy (Fuse.js), agrupamento por categoria
SectionActionsToolbar com busca, filtros de status e botão de criar (com permissão)
PageCompound component de layout (Root, Title, Content, Actions)
TimeInputInput formatado HH:MM com validação
InputFileUpload drag-and-drop com validação de formato/tamanho
ConditionalRenderização condicional declarativa
DraftPublishedChipBadge de status (Rascunho/Publicado)
LocalesPickerSeletor de idioma (pt, en, es)

Hooks Compartilhados

HookFinalidade
useDebounceDebounce genérico para valores
useAuthSyncSincronização de auth entre abas (BroadcastChannel)
useApiErrorHandlerTratamento de erros de API em formulários (react-hook-form)
useTranslateApiCodeTradução de códigos de erro da API
useTabQueryParamGerenciamento de aba ativa via query param
useMembersSummaryQuery hook para listagem de membros

Utilitários (shared/utils/)

UtilitárioFinalidade
formatMoneyFormatação BRL (Intl.NumberFormat)
formatDurationDuração em "1m 30s"
convertDimension/Weight/VolumeConversão de unidades (mm/cm/m, g/kg/t, cm3/l/m3)
parseISO8601Parse de duração ISO 8601 para minutos
useCNPJMask / useCEPMaskMáscaras de input (CNPJ, CEP)
noEmojiValidação contra emojis

Providers e Configuração

Stack de Providers

<html lang={locale}>
  <NextIntlClientProvider>          <!-- i18n -->
    <NuqsAdapter>                   <!-- Estado na URL -->
      <HeroUIProvider>              <!-- Componentes UI -->
        <QueryClientProvider>       <!-- React Query -->
          <Toaster />               <!-- Notificações -->
          {children}
          <ReactQueryDevtools />
        </QueryClientProvider>
      </HeroUIProvider>
    </NuqsAdapter>
  </NextIntlClientProvider>
</html>

React Query Config

typescript
// src/data/queryClient/index.ts
staleTime: 5 min
gcTime: 10 min
refetchOnWindowFocus: false
retry: 1

Variáveis de Ambiente

env
NEXT_PUBLIC_API_URL=http://localhost:8080/api/v1/
NEXT_PUBLIC_TENANT_ID=1234567890

Diagrama de Fluxo de Dados

┌─────────────────────────────────────────────────────────────────┐
│                       SCREEN (page.tsx)                         │
│   Usa: use[Feature]Logic() + use[Feature]Drawers()             │
│   Renderiza: DataTable, Forms, Modals, Drawers                 │
└──────────────┬───────────────────────┬──────────────────────────┘
               │                       │
   ┌───────────▼──────────┐   ┌───────▼────────────┐
   │  use[Feature]Logic   │   │ use[Feature]Drawers │
   │                      │   │                     │
   │  - Paginação (nuqs)  │   │ - Estado modal/     │
   │  - Filtros (nuqs)    │   │   drawer (nuqs)     │
   │  - Busca debounced   │   │ - open/close        │
   └───────────┬──────────┘   └─────────────────────┘

   ┌───────────▼──────────┐
   │   use[Feature]       │
   │   (agregador)        │
   │                      │
   │  .list() → useQuery  │
   │  .get()  → useQuery  │
   │  .create → useMutation│
   │  .update → useMutation│
   │  .delete → useMutation│
   └───────────┬──────────┘

   ┌───────────▼──────────┐
   │  [Feature]Service    │
   │  extends HTTPAdapter │
   │                      │
   │  - setTenant()       │
   │  - authenticate()    │
   │  - CRUD methods      │
   └───────────┬──────────┘

   ┌───────────▼──────────┐
   │    HTTPAdapter       │
   │    (Axios)           │
   │                      │
   │  - Tenant-ID header  │
   │  - JWT Bearer token  │
   │  - Token refresh     │
   │  - Error translation │
   └───────────┬──────────┘


         API REST Backend

Módulos do Sistema

Auth (modules/auth/)

Gerencia login, registro, verificação de email, reset de senha e aceite de convites. Contém o AuthService que se comunica com endpoints /auth/*.

Organizations (modules/organizations/)

CRUD de organizações (tenants). Módulo mais simples, apenas data/services e data/hooks sem telas próprias (dados consumidos pelo SecureTemplate).

Groups Management (modules/groups-management/)

Gerenciamento de grupos/roles de permissão. Permite criar, editar e excluir grupos, e associar permissões a eles. Usa CheckboxTable para seleção de permissões.

Users Management (modules/users-management/)

Dois sub-módulos:

  • members: Listagem e gerenciamento de membros da organização
  • invitations: Envio e gerenciamento de convites

Operacional (modules/operacional/)

Módulo operacional com 6 sub-módulos:

Sub-móduloResponsabilidade
config-docksConfiguração de docas de carga/descarga
loading-bayGerenciamento de baias de carregamento
warehouseArmazéns com regras de classificação
modalitiesModalidades de transporte
cargo-typeTipos de carga
calendarAgendas e eventos operacionais

Transport (modules/transport/)

Módulo de transporte com 4 sub-módulos:

Sub-móduloResponsabilidade
carrierCadastro de transportadoras
carrier-serviceServiços de cada transportadora
delivery-gridGrade de entrega (prazos e regiões)
calculation-ruleRegras de cálculo de frete

Commercial (modules/commercial/)

Módulo comercial com 3 sub-módulos:

Sub-móduloResponsabilidade
platformPlataformas de venda
product-categoryCategorias de produto
channelsCanais de venda

Freight Simulator (modules/freight-simulator/)

Simulação de custos de frete com base nos parâmetros configurados nos módulos de transporte.

Collection (modules/collection/)

Cálculo de coleta.

CEP (modules/cep/)

Dois sub-módulos:

  • map-cep: Mapeamento de CEPs para regiões
  • reference-base: Base de referência de CEPs

External (modules/external/)

Integrações com serviços externos.

Documentação interna · Muvien