import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { request } from './api' // THE PERSONAL MAILBOX. // // A separate module from settings.ts because the audience is different in a way that matters: organization // settings are the owner's and are gated on org.settings.write, while a connected mailbox is one recruiter's // own and is gated on candidate.write. Putting them together would invite a screen that mixes the two, and // the one thing this surface must never do is let one person configure another person's mailbox. export interface MailAccount { imap_host: string imap_port: number imap_username: string mailbox: string sync_sent: boolean enabled: boolean password_set: boolean verified_at?: string last_sync_at?: string last_error?: string lookback_days: number } export interface MailAccountEnvelope { connected: boolean account: MailAccount | null note: string } export interface SyncResult { examined: number matched: number logged: number already_known: number unmatched_and_discarded: number note?: string } export interface MailAccountInput { imap_host: string imap_port: number imap_username: string password?: string mailbox: string lookback_days: number enabled?: boolean } export const mailboxApi = { get: () => request('/mail/account'), save: (v: MailAccountInput) => request<{ saved: boolean; note: string }>('/mail/account', { method: 'PUT', body: JSON.stringify(v) }), disconnect: () => request<{ disconnected: boolean; note: string }>('/mail/account', { method: 'DELETE' }), test: () => request<{ verified: boolean }>('/mail/account/test', { method: 'POST' }), setEnabled: (enabled: boolean) => request<{ enabled: boolean }>('/mail/account/enabled', { method: 'POST', body: JSON.stringify({ enabled }), }), sync: () => request('/mail/sync', { method: 'POST' }), } const KEY = ['mail', 'account'] as const export function useMailAccount() { return useQuery({ queryKey: KEY, queryFn: mailboxApi.get }) } // Every mutation invalidates rather than patching the cache, for the same reason the org settings do: the // server adjusts what was sent. Saving a new host clears the verification and switches sync off, and a hand // written cache update would leave the screen claiming a mailbox is still proven when it is not. export function useSaveMailAccount() { const qc = useQueryClient() return useMutation({ mutationFn: (v: MailAccountInput) => mailboxApi.save(v), onSuccess: () => void qc.invalidateQueries({ queryKey: KEY }), }) } export function useDisconnectMailbox() { const qc = useQueryClient() return useMutation({ mutationFn: () => mailboxApi.disconnect(), onSuccess: () => void qc.invalidateQueries({ queryKey: KEY }), }) } export function useTestMailbox() { const qc = useQueryClient() return useMutation({ mutationFn: () => mailboxApi.test(), // Both outcomes change stored state: success stamps the verification, failure records the error and // switches sync off, so it refetches either way. onSettled: () => void qc.invalidateQueries({ queryKey: KEY }), }) } export function useSetMailboxEnabled() { const qc = useQueryClient() return useMutation({ mutationFn: (enabled: boolean) => mailboxApi.setEnabled(enabled), onSuccess: () => void qc.invalidateQueries({ queryKey: KEY }), }) } export function useSyncMailbox() { const qc = useQueryClient() return useMutation({ mutationFn: () => mailboxApi.sync(), onSuccess: () => { void qc.invalidateQueries({ queryKey: KEY }) // A sync writes activity rows against candidates, so every record's history is stale. void qc.invalidateQueries({ queryKey: ['recruiting', 'activities'] }) void qc.invalidateQueries({ queryKey: ['candidates'] }) }, }) }