Frontend
Estado
Zustand stores e TanStack Query
O app separa estado em duas camadas:
- Zustand — Estado global do cliente (auth, workspace, UI)
- TanStack Query — Estado do servidor (dados do Supabase)
Zustand Stores
Localizados em src/stores/:
useAuthStore
interface AuthState {
user: User | null;
session: Session | null;
loading: boolean;
setAuth: (user: User | null, session: Session | null) => void;
setLoading: (loading: boolean) => void;
}Gerencia o estado de autenticação do Supabase.
useWorkspaceStore
interface WorkspaceState {
workspace: Workspace | null;
member: Member | null;
members: Member[];
impersonatedWorkspaceId: string | null;
setWorkspace: (workspace, member, members) => void;
enterAsAdmin: (target, syntheticMember, targetMembers) => void;
exitImpersonation: () => void;
clear: () => void;
}Armazena o workspace ativo, o membro atual e a lista de membros.
Quando o super admin entra em um workspace como suporte, impersonatedWorkspaceId armazena o ID do workspace alvo. O store mantém o workspace ativo como se fosse um admin daquele cliente. exitImpersonation() restaura o estado anterior.
useUiStore
Estado de UI global (sidebar, modais, etc.).
TanStack Query
Toda interação com o Supabase usa useQuery para leitura e useMutation para escrita.
// Leitura
const { data, isLoading } = useDeals(pipelineId);
// Escrita
const createDeal = useCreateDeal();
createDeal.mutate({ name: "Novo negócio", ... });Query keys
Padrão: ["entidade", workspaceId, ...filtros]
queryKey: ["deals", workspace?.id, pipelineId]
queryKey: ["deal", dealId]
queryKey: ["contacts", workspace?.id]Invalidação
Mutations invalidam queries relacionadas em onSuccess:
onSuccess: () =>
qc.invalidateQueries({ queryKey: ["deals", workspace?.id] })