Last active
August 29, 2025 21:51
-
-
Save wellington1993/43b27c18632be8bdc8943243493bb40d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| '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