Appearance
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
| Camada | Tecnologia | Finalidade |
|---|---|---|
| Framework | Next.js 16 (App Router, Webpack) | SSR, roteamento, layouts |
| UI | HeroUI (fork NextUI) + Tailwind CSS | Componentes visuais |
| Animações | Framer Motion | Transições e animações |
| Ícones | Lucide React | Iconografia |
| Estado Servidor | TanStack React Query | Cache, fetching, mutations |
| Estado Global | Zustand | User store, Permission store |
| Estado URL | nuqs | Paginação, filtros, modais na URL |
| Formulários | React Hook Form + Zod | Validação e gerenciamento |
| HTTP | Axios (HTTPAdapter) | Requisições com interceptors |
| i18n | next-intl | Internacionalização (pt, en, es) |
| Autenticação | JWT customizado (cookies) | Access/refresh tokens |
| Testes | Vitest + Testing Library + Cypress | Unit, 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 regexPadrã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 tabelaMódulos com Sub-módulos
Módulos maiores como operacional, transport, commercial e cep possuem uma pasta modules/ interna:
| Módulo | Sub-módulos |
|---|---|
| operacional | calendar, loading-bay, modalities, cargo-type, warehouse, config-docks |
| transport | carrier, carrier-service, delivery-grid, calculation-rule |
| commercial | platform, product-category, channels |
| cep | map-cep, reference-base |
| users-management | members, 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-IDem todas as requisições - Autenticação JWT: Interceptor que anexa o
Bearertoken - Refresh automático: Na resposta 401, tenta renovar o
access_tokenusando orefresh_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 /loginRefresh 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 /loginSincronizaçã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.tscomgetRequestConfig - 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
| Template | Localização | Finalidade |
|---|---|---|
| SecureTemplate | shared/components/templates/SecureTemplate/ | Layout principal com sidebar, header, verificação de auth e permissões |
| AuthTemplate | shared/components/templates/authTemplate/ | Layout de login/registro (split: formulário + imagem) |
| ModalTemplate | shared/components/templates/ModalTemplate/ | Wrapper de modal com logo Muvien |
| DrawerTemplate | shared/components/common/Drawers/ | Drawer reutilizável (modos create/edit com cores distintas) |
| ReusableTabsTemplate | shared/components/templates/ReusableTabsTemplate/ | Interface com abas filtradas por permissão, estado via URL |
Componentes Comuns
| Componente | Finalidade |
|---|---|
| DataTable | Tabela genérica com tipagem (Root + Column) |
| CheckboxTable | Tabela com seleção, busca fuzzy (Fuse.js), agrupamento por categoria |
| SectionActions | Toolbar com busca, filtros de status e botão de criar (com permissão) |
| Page | Compound component de layout (Root, Title, Content, Actions) |
| TimeInput | Input formatado HH:MM com validação |
| InputFile | Upload drag-and-drop com validação de formato/tamanho |
| Conditional | Renderização condicional declarativa |
| DraftPublishedChip | Badge de status (Rascunho/Publicado) |
| LocalesPicker | Seletor de idioma (pt, en, es) |
Hooks Compartilhados
| Hook | Finalidade |
|---|---|
| useDebounce | Debounce genérico para valores |
| useAuthSync | Sincronização de auth entre abas (BroadcastChannel) |
| useApiErrorHandler | Tratamento de erros de API em formulários (react-hook-form) |
| useTranslateApiCode | Tradução de códigos de erro da API |
| useTabQueryParam | Gerenciamento de aba ativa via query param |
| useMembersSummary | Query hook para listagem de membros |
Utilitários (shared/utils/)
| Utilitário | Finalidade |
|---|---|
| formatMoney | Formatação BRL (Intl.NumberFormat) |
| formatDuration | Duração em "1m 30s" |
| convertDimension/Weight/Volume | Conversão de unidades (mm/cm/m, g/kg/t, cm3/l/m3) |
| parseISO8601 | Parse de duração ISO 8601 para minutos |
| useCNPJMask / useCEPMask | Máscaras de input (CNPJ, CEP) |
| noEmoji | Validaçã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: 1Variáveis de Ambiente
env
NEXT_PUBLIC_API_URL=http://localhost:8080/api/v1/
NEXT_PUBLIC_TENANT_ID=1234567890Diagrama 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 BackendMó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ódulo | Responsabilidade |
|---|---|
| config-docks | Configuração de docas de carga/descarga |
| loading-bay | Gerenciamento de baias de carregamento |
| warehouse | Armazéns com regras de classificação |
| modalities | Modalidades de transporte |
| cargo-type | Tipos de carga |
| calendar | Agendas e eventos operacionais |
Transport (modules/transport/)
Módulo de transporte com 4 sub-módulos:
| Sub-módulo | Responsabilidade |
|---|---|
| carrier | Cadastro de transportadoras |
| carrier-service | Serviços de cada transportadora |
| delivery-grid | Grade de entrega (prazos e regiões) |
| calculation-rule | Regras de cálculo de frete |
Commercial (modules/commercial/)
Módulo comercial com 3 sub-módulos:
| Sub-módulo | Responsabilidade |
|---|---|
| platform | Plataformas de venda |
| product-category | Categorias de produto |
| channels | Canais 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.