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.
useFeedbackStore
Estado do widget de feedback (reportar bug / sugerir melhoria), que pode ser aberto de qualquer tela — por isso vive no shell, não na página.
useOttoChatStore
Estado do chat do Otto: aberto/fechado, o contexto da tela atual e a ponte para quando o Otto sugere um rascunho de e-mail. Também é acessível de qualquer tela.
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", workspace?.id, dealId]
queryKey: ["contacts", workspace?.id]Invalidação
Mutations invalidam queries relacionadas em onSuccess:
onSuccess: () =>
qc.invalidateQueries({ queryKey: ["deals", workspace?.id] })☠️ Toda query key leva workspace?.id como segundo elemento — inclusive as de item único
(["deal", workspace?.id, id]). Sem isso, trocar de workspace serve dado em cache do workspace
anterior.
O workspace-store é persistido (zustand/middleware), para o modo de impersonação do super
admin sobreviver ao reload. A chave é versionada — se o formato do que é persistido mudar, o
número da versão sobe junto.