email syncyng

This commit is contained in:
2026-02-26 01:39:38 +00:00
parent 5d4f0114cd
commit 42abb97d7b
22 changed files with 1422 additions and 348 deletions
@@ -12,7 +12,7 @@ type GoogleStatus = {
configured: boolean;
};
const formatTime = (ts: number) => {
const formatTime = (ts: number | string) => {
const d = new Date(ts);
return d.toLocaleString(undefined, { dateStyle: 'medium', timeStyle: 'short' });
};
@@ -21,6 +21,7 @@ export const GoogleAccount = () => {
const client = useClient();
const [isLoading, setIsLoading] = useState(true);
const [status, setStatus] = useState<GoogleStatus>({ connected: false, email: null, picture: null, configured: false });
const [lastSyncAt, setLastSyncAt] = useState<string | null>(null);
const { jobs, createJob, refetch } = useJobs({ type: 'gmail-sync' });
const activeJob = jobs.find((j) => j.status === 'queued' || j.status === 'running');
const lastJob = jobs[0];
@@ -31,6 +32,10 @@ export const GoogleAccount = () => {
.then(setStatus)
.catch(() => {})
.finally(() => setIsLoading(false));
client
.get<{ lastSyncAt: string | null }>('/email/sync-status')
.then((res) => setLastSyncAt(res.lastSyncAt))
.catch(() => {});
};
useEffect(() => {
@@ -47,6 +52,13 @@ export const GoogleAccount = () => {
}
}, []);
// Refresh sync status from DB when a job finishes
useEffect(() => {
if (!activeJob && lastJob?.status === 'completed') {
client.get<{ lastSyncAt: string | null }>('/email/sync-status').then((res) => setLastSyncAt(res.lastSyncAt)).catch(() => {});
}
}, [activeJob, lastJob?.status]);
const handleSync = async (year?: number) => {
try {
await createJob({ lane: 'google-api', type: 'gmail-sync', meta: year ? { year } : undefined });
@@ -131,40 +143,28 @@ export const GoogleAccount = () => {
</div>
</div>
)}
{!activeJob && lastJob?.status === 'completed' && (
<div className="flex items-center gap-2 text-xs text-duck-dark/50 dark:text-foreground/50">
<CheckCircle2 className="h-3.5 w-3.5 text-green-500 shrink-0" />
Last sync completed {formatTime(lastJob.completedAt!)}
</div>
)}
{!activeJob && lastJob?.status === 'failed' && (
<div className="flex items-center gap-2 text-xs text-red-500">
<XCircle className="h-3.5 w-3.5 shrink-0" />
Last sync failed{lastJob.error ? `: ${lastJob.error}` : ''}
</div>
)}
<div className="flex gap-2">
<Button
type="button"
variant="outline"
disabled={!!activeJob}
onClick={() => handleSync()}
className="flex-1 h-11 cursor-pointer gap-2"
>
<RefreshCw className="h-4 w-4" />
Sync Gmail Inbox
</Button>
<Button
type="button"
variant="outline"
disabled={!!activeJob}
onClick={() => handleSync(2026)}
className="flex-1 h-11 cursor-pointer gap-2"
>
<RefreshCw className="h-4 w-4" />
Test Sync (2026)
</Button>
</div>
{!activeJob && lastSyncAt && (
<div className="flex items-center gap-2 text-xs text-duck-dark/50 dark:text-foreground/50">
<CheckCircle2 className="h-3.5 w-3.5 text-green-500 shrink-0" />
Last sync completed {formatTime(lastSyncAt)}
</div>
)}
<Button
type="button"
variant="outline"
disabled={!!activeJob}
onClick={() => handleSync()}
className="w-full h-11 cursor-pointer gap-2"
>
<RefreshCw className="h-4 w-4" />
Sync Gmail Inbox
</Button>
<Button
type="button"
variant="outline"