feat(frontend): Setup Next.js with Zustand stores and API client - Phase 2 initialization

This commit is contained in:
Erik Silva
2025-12-01 01:43:21 -03:00
parent 5ff1a4a004
commit 888e4e4d60
7 changed files with 990 additions and 20 deletions

View File

@@ -0,0 +1,220 @@
/**
* 🔐 Auth Store (Zustand)
* Gerenciador de estado de autenticação
*/
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import apiClient, { handleApiError } from '../api';
import {
AuthState,
AuthUser,
SignupPayload,
LoginPayload,
AuthResponse,
} from '../types';
interface AuthStore extends AuthState {
// ações
signup: (payload: SignupPayload) => Promise<void>;
login: (payload: LoginPayload) => Promise<void>;
logout: () => Promise<void>;
getProfile: () => Promise<AuthUser | null>;
setError: (error: string | null) => void;
setLoading: (loading: boolean) => void;
}
/**
* Decodificar JWT para pegar o userId
*/
const decodeToken = (token: string): { userId: string } | null => {
try {
const base64Url = token.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
const jsonPayload = decodeURIComponent(
atob(base64)
.split('')
.map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
.join(''),
);
return JSON.parse(jsonPayload);
} catch {
return null;
}
};
/**
* Criar store de autenticação
*/
export const useAuthStore = create<AuthStore>()(
persist(
(set, get) => ({
// Estado inicial
user: null,
token: null,
isAuthenticated: false,
isLoading: false,
error: null,
// ============================================================================
// AÇÕES
// ============================================================================
/**
* Registrar novo usuário
*/
signup: async (payload: SignupPayload) => {
try {
set({ isLoading: true, error: null });
const response = await apiClient.post<AuthResponse>(
'/auth/signup',
payload,
);
const { access_token, user } = response.data;
set({
token: access_token,
user,
isAuthenticated: true,
isLoading: false,
});
// Salvar token no localStorage
localStorage.setItem('auth_token', access_token);
localStorage.setItem('auth_user', JSON.stringify(user));
} catch (error) {
const message = handleApiError(error);
set({ error: message, isLoading: false });
throw new Error(message);
}
},
/**
* Fazer login
*/
login: async (payload: LoginPayload) => {
try {
set({ isLoading: true, error: null });
const response = await apiClient.post<AuthResponse>(
'/auth/login',
payload,
);
const { access_token, user } = response.data;
set({
token: access_token,
user,
isAuthenticated: true,
isLoading: false,
});
// Salvar no localStorage
localStorage.setItem('auth_token', access_token);
localStorage.setItem('auth_user', JSON.stringify(user));
} catch (error) {
const message = handleApiError(error);
set({ error: message, isLoading: false });
throw new Error(message);
}
},
/**
* Fazer logout
*/
logout: async () => {
try {
set({ isLoading: true });
// Chamar endpoint de logout (opcional)
await apiClient.post('/auth/logout');
} catch (error) {
console.error('Erro ao fazer logout:', error);
} finally {
// Limpar estado e localStorage
set({
user: null,
token: null,
isAuthenticated: false,
isLoading: false,
error: null,
});
localStorage.removeItem('auth_token');
localStorage.removeItem('auth_user');
localStorage.removeItem('auth_store');
}
},
/**
* Obter perfil do usuário
*/
getProfile: async () => {
try {
const response = await apiClient.get<AuthUser>('/auth/me');
const user = response.data;
set({ user, isAuthenticated: true });
localStorage.setItem('auth_user', JSON.stringify(user));
return user;
} catch (error) {
const message = handleApiError(error);
set({ error: message, isAuthenticated: false });
return null;
}
},
/**
* Setar erro
*/
setError: (error: string | null) => {
set({ error });
},
/**
* Setar loading
*/
setLoading: (isLoading: boolean) => {
set({ isLoading });
},
}),
// Configurar persistência
{
name: 'auth_store',
partialize: (state) => ({
token: state.token,
user: state.user,
isAuthenticated: state.isAuthenticated,
}),
// Validar token ao recuperar do storage
onRehydrateStorage: () => (state) => {
if (state?.token) {
const decoded = decodeToken(state.token);
if (!decoded) {
state.token = null;
state.isAuthenticated = false;
}
}
},
},
),
);
// Exportar hook separado para debugging
export const useAuth = () => {
const store = useAuthStore();
return {
user: store.user,
token: store.token,
isAuthenticated: store.isAuthenticated,
isLoading: store.isLoading,
error: store.error,
login: store.login,
signup: store.signup,
logout: store.logout,
getProfile: store.getProfile,
};
};