O Objetivo
O Nuuvallace foi construído como um showcase de frontend e-commerce — um dos tipos de aplicação mais comuns em vagas front-end, mas que poucos candidatos demonstram domínio real. O projeto vai além de uma "lista de produtos": tem carrinho com persistência em localStorage, filtros multi-critério, buscas, e uma arquitetura de componentes pensada para escala.
A escolha da Nuuvem como inspiração foi intencional — é um produto real brasileiro que o público tech conhece, o que facilita a contextualização durante uma entrevista. "Você conhece a Nuuvem? Fiz um clone técnico dela."
Arquitetura & Decisões
UI Layer
- • Next.js (App Router)
- • TypeScript strict
- • CSS Modules
- • Componentes atômicos
- • Responsivo mobile-first
State
- • React Context API
- • useReducer pattern
- • localStorage persist
- • Otimistic updates
- • Cart count badge
Features
- • Busca por nome
- • Filtro por categoria
- • Ordenação por preço
- • Favoritos
- • Detalhes do produto
CSS Modules — Por quê?
Escolhi CSS Modules ao invés de Tailwind para este projeto por uma razão específica: demonstrar que sei CSS de verdade. Qualquer dev usa Tailwind — mas escrever CSS escalável, sem conflitos de escopo, com naming semântico e variáveis CSS é uma habilidade rara. CSS Modules garante escopo local por padrão, sem poluir o global.
Context vs Redux
Para o carrinho, optei por Context API + useReducer ao invés de Redux. A justificativa é clara: a ferramenta certa para o escopo certo. Um marketplace com estado de carrinho não precisa do overhead do Redux. O padrão Reducer traz previsibilidade sem complexidade desnecessária — e demonstra que entendo quando usar cada abordagem.
type CartAction =
| { type: 'ADD_ITEM'; payload: Game }
| { type: 'REMOVE_ITEM'; payload: string }
| { type: 'CLEAR_CART' };
function cartReducer(state: CartState, action: CartAction) {
switch (action.type) {
case 'ADD_ITEM':
const exists = state.items.find(i => i.id === action.payload.id);
if (exists) return state; // idempotente
return { ...state, items: [...state.items, action.payload] };
case 'REMOVE_ITEM':
return { ...state, items: state.items.filter(i => i.id !== action.payload) };
default: return state;
}
}
Desafio: Filtros Combinados
Filtros por categoria + busca por nome + ordenação por preço precisavam ser compostos de forma declarativa, não imperativa. Um chain de ifs aninhados criaria código frágil. O desafio foi manter cada filtro isolado e combinável.
Solução: Pipeline Funcional
Implementei um pipeline de filtros via Array.reduce() — cada critério de filtro é uma função pura que recebe e retorna o array. Eles são compostos em sequência. Adicionar um novo filtro significa apenas adicionar uma função pura ao pipeline, sem tocar no código existente (Open/Closed Principle).
Aprendizados
CSS Modules força a pensar em escopo e naming desde o início. A experiência reforçou que CSS bem estruturado é tão importante quanto JS bem estruturado.
Context + Reducer é subestimado. Para muitos casos de uso, ele é suficiente e muito mais simples que Redux, que deve ser reservado para estados verdadeiramente complexos.
Microtransições (hover nos cards, feedback do carrinho) fazem diferença desproporcional na percepção de qualidade do produto. Vale o investimento.