inline email resync instead of job queue, refresh list on completion

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-22 17:07:06 +00:00
co-authored by Claude Opus 4.6
parent ea8113e61d
commit b3f1d10bc1
7 changed files with 367 additions and 83 deletions
@@ -73,18 +73,28 @@ export const EmailList = () => {
setPage(1);
};
const [syncing, setSyncing] = useState(false);
const handleSync = async () => {
if (!syncableAccount) return;
setSyncing(true);
try {
await client.post(`/email/accounts/${syncableAccount.id}/sync`, {});
toast.success('Sync started');
const result = await client.post<{ ok: boolean; saved?: number }>(`/email/accounts/${syncableAccount.id}/sync`, {});
if (result.saved !== undefined) {
toast.success(result.saved > 0 ? `${result.saved} new emails` : 'No new emails');
queryClient.invalidateQueries({ queryKey: ['email-messages'] });
} else {
toast.success('Sync started');
}
refetchAccounts();
} catch (err) {
toast.error(err instanceof Error ? err.message : 'Failed to start sync');
toast.error(err instanceof Error ? err.message : 'Failed to sync');
} finally {
setSyncing(false);
}
};
// Poll accounts while syncing, refresh email list when done
// Poll accounts while first sync is running (job-based), refresh email list when done
const prevSyncing = useRef(false);
useEffect(() => {
if (isSyncing) {
@@ -149,8 +159,8 @@ export const EmailList = () => {
) : syncableAccount ? (
<div className="flex flex-col items-center gap-2">
<span>No emails synced yet</span>
<Button variant="outline" size="sm" onClick={handleSync} disabled={isSyncing}>
{isSyncing ? <Loader2 className="mr-2 h-3.5 w-3.5 animate-spin" /> : <RefreshCw className="mr-2 h-3.5 w-3.5" />}
<Button variant="outline" size="sm" onClick={handleSync} disabled={syncing || isSyncing}>
{syncing || isSyncing ? <Loader2 className="mr-2 h-3.5 w-3.5 animate-spin" /> : <RefreshCw className="mr-2 h-3.5 w-3.5" />}
Sync Now
</Button>
</div>
@@ -189,7 +199,7 @@ export const EmailList = () => {
})}
</div>
<span className="text-xs opacity-50">{total}</span>
{isSyncing ? (
{syncing || isSyncing ? (
<Loader2 className="h-3.5 w-3.5 animate-spin shrink-0 opacity-50" title="Syncing..." />
) : syncableAccount ? (
<button
@@ -158,12 +158,16 @@ export const EmailAccounts = () => {
const handleSync = async (id: number) => {
setSyncing(id);
try {
await client.post(`/email/accounts/${id}/sync`, {});
toast.success('Sync started');
// Update local state immediately
setAccounts((prev) => prev.map((a) => (a.id === id ? { ...a, status: 'queued' } : a)));
const result = await client.post<{ ok: boolean; saved?: number }>(`/email/accounts/${id}/sync`, {});
if (result.saved !== undefined) {
toast.success(result.saved > 0 ? `${result.saved} new emails` : 'No new emails');
} else {
toast.success('Sync started');
setAccounts((prev) => prev.map((a) => (a.id === id ? { ...a, status: 'queued' } : a)));
}
fetchAccounts();
} catch (err) {
toast.error(err instanceof Error ? err.message : 'Failed to start sync');
toast.error(err instanceof Error ? err.message : 'Failed to sync');
} finally {
setSyncing(null);
}