📚 Módulo 07: Frontend - Contexto de Login, Interceptor Axios e Layout Glassmorphic

Neste módulo, conectaremos a segurança de ponta a ponta na nossa aplicação. Desenvolveremos o Contexto de Autenticação Reativa (AuthContext) para manter o estado de login do usuário de forma global e persistente no React, programaremos um Interceptador do Axios para injetar automaticamente as credenciais JWT nas chamadas à API e criaremos um Design System Glassmorphic responsivo e elegante em CSS Puro.


⚡ Fluxo do Interceptador Axios

O interceptador age como um middleware no lado do cliente. Toda vez que você fizer api.get('/produtos'), ele pausa a requisição, anexa o cabeçalho seguro e libera a continuação.

sequenceDiagram
    participant Componente as React Component
    participant Axios as Interceptador Axios
    participant API as NestJS API

    Componente->>Axios: POST /pedidos/checkout (Sem header)
    Axios->>Axios: Lê localStorage ('tecloja_user')
    alt Possui Token
        Axios->>Axios: Anexa Authorization: Bearer <TOKEN>
    end
    Axios->>API: Dispara requisição HTTP
    API-->>Axios: Resposta 200 OK
    Axios-->>Componente: Retorna JSON

🔐 1. O Contexto de Autenticação Reativa (AuthContext)

No React, para compartilhar dados entre múltiplos componentes sem precisar passar propriedades manualmente de pai para filho (Prop Drilling), utilizamos a Context API.

Criaremos o contexto que gerencia a gravação segura e remoção do token JWT armazenado no localStorage. Crie o arquivo em src/contexts/AuthContext.tsx:

// src/contexts/AuthContext.tsx
import React, { createContext, useState, useEffect, useContext } from 'react';
import { UsuarioLogado } from '../types';

interface AuthContextType {
  usuario: UsuarioLogado | null;
  logado: boolean;
  isAdmin: boolean;
  login: (dados: UsuarioLogado) => void;
  logout: () => void;
}

const AuthContext = createContext<AuthContextType>(null!);

export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const [usuario, setUsuario] = useState<UsuarioLogado | null>(null);

  // Carregar token gravado no início da aplicação (Persistência)
  useEffect(() => {
    const usuarioSalvo = localStorage.getItem('tecloja_user');
    if (usuarioSalvo) {
      setUsuario(JSON.parse(usuarioSalvo));
    }
  }, []);

  const login = (dados: UsuarioLogado) => {
    localStorage.setItem('tecloja_user', JSON.stringify(dados));
    setUsuario(dados);
  };

  const logout = () => {
    localStorage.removeItem('tecloja_user');
    setUsuario(null);
  };

  const logado = !!usuario;
  const isAdmin = usuario?.role === 'ADMIN';

  return (
    <AuthContext.Provider value={{ usuario, logado, isAdmin, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
};

// Hook customizado para facilitar o acesso às informações de segurança
export const useAuth = () => useContext(AuthContext);

⚡ 2. Injeção Criptográfica Automatizada (Axios Request Interceptor)

Para que o desenvolvedor não precise incluir manualmente o cabeçalho HTTP de autorização (Authorization: Bearer <TOKEN>) em cada método de consulta ou salvamento da API, configuraremos um Interceptador do Axios.

Ele inspecionará todas as requisições enviadas ao servidor. Se houver um token ativo no localStorage, ele injetará o cabeçalho Bearer de forma transparente.

Crie o arquivo em src/services/api.ts:

// src/services/api.ts
import axios from 'axios';

const api = axios.create({
  baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000',
  timeout: 10000,
});

// Interceptor de Requisição do Axios
api.interceptors.request.use(
  (config) => {
    const usuarioSalvo = localStorage.getItem('tecloja_user');
    
    if (usuarioSalvo) {
      const { token } = JSON.parse(usuarioSalvo);
      if (token && config.headers) {
        // Injeta automaticamente o token JWT
        config.headers.Authorization = `Bearer ${token}`;
      }
    }
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

// Interceptor de Resposta (captura erros de Token expirado / 401 Unauthorized)
api.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response && error.response.status === 401) {
      console.warn('⚠️ Token expirado ou não autorizado. Redirecionando para logout.');
      localStorage.removeItem('tecloja_user');
      window.location.href = '/login';
    }
    return Promise.reject(error);
  }
);

export default api;

🎨 3. O Design System Responsivo Glassmorphic em CSS Puro

Para proporcionar uma experiência visual premium de tirar o fôlego baseada nas tendências modernas de design de interfaces web, usaremos efeitos estéticos de transparência com desfoque de fundo (glassmorphism), gradientes harmônicos e micro-animações.

Crie o arquivo de estilos em src/assets/styles.css:

/* src/assets/styles.css */
@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap');

:root {
  --font-principal: 'Outfit', sans-serif;
  --bg-gradient: linear-gradient(135deg, #0b0f19 0%, #111827 100%);
  --glass-bg: rgba(255, 255, 255, 0.03);
  --glass-border: rgba(255, 255, 255, 0.07);
  --glass-shadow: rgba(0, 0, 0, 0.3);
  --glass-blur: blur(12px);
  --primaria: #61dafb;
  --primaria-hover: #4fa8c7;
  --texto: #f3f4f6;
  --texto-secundario: #9ca3af;
  --sucesso: #10b981;
  --perigo: #ef4444;
}

body {
  margin: 0;
  font-family: var(--font-principal);
  background: var(--bg-gradient);
  color: var(--texto);
  min-height: 100vh;
  overflow-x: hidden;
}

/* Efeito Premium de Vidro (Glassmorphism) */
.card-glass {
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-border);
  box-shadow: 0 8px 32px 0 var(--glass-shadow);
  border-radius: 16px;
  padding: 24px;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

.card-glass:hover {
  border-color: rgba(97, 218, 251, 0.3);
  transform: translateY(-4px);
  box-shadow: 0 12px 40px 0 rgba(97, 218, 251, 0.1);
}

/* Estilização dos Formulários Reativos */
.form-grupo {
  margin-bottom: 20px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.form-campo {
  background: rgba(0, 0, 0, 0.3);
  border: 1px solid var(--glass-border);
  border-radius: 8px;
  padding: 12px 16px;
  color: var(--texto);
  font-family: var(--font-principal);
  font-size: 1rem;
  transition: all 0.3s ease;
}

.form-campo:focus {
  outline: none;
  border-color: var(--primaria);
  box-shadow: 0 0 0 2px rgba(97, 218, 251, 0.2);
}

/* Botões Reativos Premium */
.btn-premium {
  font-family: var(--font-principal);
  font-weight: 600;
  font-size: 1rem;
  padding: 12px 24px;
  border-radius: 8px;
  border: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  transition: all 0.3s ease;
}

.btn-primario {
  background: linear-gradient(135deg, #61dafb 0%, #3b82f6 100%);
  color: #0b0f19;
}

.btn-primario:hover {
  transform: scale(1.02);
  box-shadow: 0 0 20px rgba(97, 218, 251, 0.4);
}

.btn-perigo {
  background: var(--perigo);
  color: #fff;
}

.btn-perigo:hover {
  background: #dc2626;
  box-shadow: 0 0 15px rgba(239, 68, 68, 0.4);
}

✅ Pré-Requisitos deste Módulo

Antes de passar para a criação dos hooks do carrinho de compras e formulários CRUD, certifique-se de que:


🤔 Por que fizemos assim?


🔍 Checkpoint

  1. Acoplamento do Provider: Garanta que o componente <AuthProvider> foi declarado em src/main.tsx envolvendo o componente raiz <App />.
  2. Injeção de Cabeçalho: Logue na aplicação e verifique no console de rede do navegador (Aba Network) se as chamadas de API feitas pela instância api contêm o cabeçalho Authorization: Bearer ....
  3. Redirecionamento Automático: Exclua manualmente o token do localStorage via aba Application do navegador durante o uso e tente acessar um endpoint privado. Valide se a aplicação redireciona você instantaneamente para a tela /login.

⚠️ Erros Comuns

Erro Causa Solução
TypeError: Cannot read properties of undefined (reading 'usuario') ao chamar useAuth O componente que consome o hook useAuth() está renderizado fora do escopo físico do provedor <AuthProvider>. Certifique-se de que o <AuthProvider> foi registrado no topo da hierarquia de componentes da aplicação, idealmente envolvendo o componente <App /> em src/main.tsx.
O token JWT não é enviado nas requisições HTTP do frontend O desenvolvedor realizou a importação do axios padrão (import axios from 'axios') em vez da nossa instância configurada. Garanta o uso exclusivo da instância do axios customizada para conexões privadas: import api from '../services/api'.
Loop infinito de redirecionamento para /login no navegador O interceptador de resposta redireciona o usuário para /login e a própria rota tenta disparar chamadas REST que resultam em novas falhas 401. Certifique-se de que rotas e consultas públicas (como login e consulta inicial do catálogo) não disparem interceptadores de falhas críticas ou limpe as credenciais antes de realizar a navegação.

🏁 Conclusão

Com o contexto de autenticação global estruturado, interceptadores criptográficos anexando tokens JWT automaticamente e o sistema de design responsivo glassmorphic definido, nossa infraestrutura de frontend está pronta!

No Módulo 08, focaremos no desenvolvimento das interações do usuário. Programaremos o Carrinho de Compras Reativo usando Custom Hooks para gerenciar compras e recalcular totais de forma instantânea, e criaremos os formulários administrativos de CRUD de Produtos completos com feedback e validação instantânea visual de campos.


Voltar para o Sumário