Skip to content

Instantly share code, notes, and snippets.

@wellington1993
Last active August 29, 2025 21:51
Show Gist options
  • Select an option

  • Save wellington1993/43b27c18632be8bdc8943243493bb40d to your computer and use it in GitHub Desktop.

Select an option

Save wellington1993/43b27c18632be8bdc8943243493bb40d to your computer and use it in GitHub Desktop.
'use client';
import { usePathname, useRouter } from 'next/navigation';
import { useEffect, useState, useRef, useCallback } from 'react';
import { useToast } from '@/hooks/useToast';
import { api } from '@/services/Api/api';
// --- Tipagem para maior segurança ---
interface User {
id?: string;
name?: string;
// Adicione outros campos do usuário conforme necessário
}
interface LoginCredentials {
username?: string;
password?: string;
// Adicione outros campos, se necessário
}
// --- Constantes para evitar "magic strings" ---
const TOKEN_KEY = 'token';
const REFRESH_TOKEN_KEY = 'refreshToken';
const useAuth = () => {
const router = useRouter();
const pathname = usePathname();
const toast = useToast();
const [isAuth, setIsAuth] = useState<boolean>(false);
const [loading, setLoading] = useState<boolean>(true);
const [user, setUser] = useState<User>({});
const [error, setError] = useState<unknown>(null);
// Ref para garantir que os interceptors sejam anexados apenas uma vez
const interceptorsAttached = useRef(false);
// --- Função de Logout Centralizada ---
// useCallback garante que a função não seja recriada a cada renderização
const handleLogout = useCallback(() => {
console.log('🚪 [handleLogout] Executando logout centralizado...');
setUser({});
setIsAuth(false);
localStorage.removeItem(TOKEN_KEY);
localStorage.removeItem(REFRESH_TOKEN_KEY);
delete api.defaults.headers.common['Authorization'];
// Redireciona para o login de forma segura
if (typeof window !== 'undefined' && window.location.pathname !== '/login') {
router.push('/login');
}
}, [router]);
// --- Efeito para anexar interceptors (Executa apenas uma vez) ---
useEffect(() => {
// Se os interceptors já foram anexados, não faz nada.
if (interceptorsAttached.current) {
return;
}
const requestInterceptor = api.interceptors.request.use(
(config) => {
if (config?.url?.includes('reset-senha')) {
return config;
}
const token = localStorage.getItem(TOKEN_KEY);
if (token && token !== 'undefined') {
try {
const parsedToken = JSON.parse(token);
config.headers.Authorization = `Bearer ${parsedToken}`;
} catch (e) {
console.error("Erro ao parsear token, deslogando.", e);
handleLogout();
}
}
return config;
},
(error) => Promise.reject(error)
);
const responseInterceptor = api.interceptors.response.use(
(response) => response,
async (err) => {
const originalRequest = err.config;
// Lógica de Refresh Token (status 401 ou 403 são comuns para token expirado)
if (err.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
console.log('🔄 [Interceptor] Token expirado. Tentando renovar...');
try {
const refreshToken = localStorage.getItem(REFRESH_TOKEN_KEY);
if (!refreshToken || refreshToken === 'undefined') throw new Error("Refresh token não encontrado");
const parsedRefreshToken = JSON.parse(refreshToken);
const { headers } = await api.post('/app/auth/token/refresh', {}, {
headers: { 'Refresh-Token': `Bearer ${parsedRefreshToken}` }
});
const newAccessToken = headers.access_token;
localStorage.setItem(TOKEN_KEY, JSON.stringify(newAccessToken));
api.defaults.headers.common['Authorization'] = `Bearer ${newAccessToken}`;
console.log('✅ [Interceptor] Token renovado com sucesso. Reenviando requisição original...');
return api(originalRequest);
} catch (refreshError) {
console.error('❌ [Interceptor] Falha CRÍTICA ao renovar o token. Deslogando.', refreshError);
handleLogout(); // Usa a função centralizada de logout
return Promise.reject(refreshError);
}
}
return Promise.reject(err);
}
);
interceptorsAttached.current = true;
// Função de limpeza: remove os interceptors se o componente for desmontado
// Isso é importante em cenários de SPA mais complexos
return () => {
api.interceptors.request.eject(requestInterceptor);
api.interceptors.response.eject(responseInterceptor);
interceptorsAttached.current = false;
};
}, [handleLogout]); // handleLogout está no array de dependências
// --- Efeito para validar a sessão ao carregar a página ---
useEffect(() => {
const validateSession = async () => {
const token = localStorage.getItem(TOKEN_KEY);
if (!token || token === 'undefined') {
setIsAuth(false);
setLoading(false);
if(!pathname.includes('/login') && !pathname.includes('reset-password')) {
router.push('/login');
}
return;
}
try {
console.log('🔄 [useEffect] Validando sessão com chamada de usuário...');
const { data } = await api.get<User>('/usuario/atual');
setUser(data);
setIsAuth(true);
} catch (err) {
console.error('❌ [useEffect] Falha ao validar sessão inicial.', err);
// O interceptor já deve ter tentado o refresh e chamado o handleLogout em caso de falha.
// Se chegar aqui, a sessão é inválida.
handleLogout();
} finally {
setLoading(false);
}
};
validateSession();
}, [pathname, router, handleLogout]); // Adicionado handleLogout
// --- Função de Login ---
const handleLogin = async (credentials: LoginCredentials) => {
setLoading(true);
setError(null);
try {
// Este trecho assume que a API de login retorna os tokens no CORPO da resposta
// Se for nos headers, a abordagem com proxy continua sendo a única solução sem alterar o backend
const response = await api.post('/app/auth/login', credentials, {
headers: { 'content-type': 'application/x-www-form-urlencoded' },
});
// Assumindo que os tokens vêm no corpo da resposta
const { accessToken, refreshToken } = response.data;
if (!accessToken || !refreshToken) {
throw new Error("Tokens não recebidos na resposta de login");
}
localStorage.setItem(TOKEN_KEY, JSON.stringify(accessToken));
localStorage.setItem(REFRESH_TOKEN_KEY, JSON.stringify(refreshToken));
api.defaults.headers.common['Authorization'] = `Bearer ${accessToken}`;
const { data: userData } = await api.get<User>('/usuario/atual');
setUser(userData);
setIsAuth(true);
router.push('/dashboard');
} catch (err) {
console.error('❌ [handleLogin] Erro ao fazer login:', err);
setError(err); // Armazena o erro no estado
toast({
title: 'Erro',
type: 'error',
message: 'Credenciais de acesso inválidas',
});
} finally {
setLoading(false);
}
};
return { isAuth, user, loading, handleLogin, handleLogout, error };
};
export default useAuth;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment