Fixed Quick Actions & IMAP
X-Lovable-Edit-ID: edt-531abf5e-7e18-47b9-8f49-ce9176a1bfc6 Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
@@ -55,29 +55,44 @@ export const Route = createFileRoute("/hooks/poll-imap")({
|
||||
const sinceUid = (settings.last_uid ?? 0) + 1;
|
||||
const range = `${sinceUid}:*`;
|
||||
|
||||
// First pass: fetch lightweight envelopes only to learn the
|
||||
// size of each message. Then fetch full source one-by-one,
|
||||
// skipping anything over the size cap.
|
||||
const candidates: Array<{ uid: number; size: number }> = [];
|
||||
for await (const meta of client.fetch(
|
||||
range,
|
||||
{ uid: true, size: true },
|
||||
// First pass: collect just UIDs via search (no FETCH stream).
|
||||
// This avoids any large per-message data crossing the socket
|
||||
// before we know which messages to skip.
|
||||
const uidList = (await (client as any).search(
|
||||
{ uid: range },
|
||||
{ uid: true },
|
||||
)) {
|
||||
if (meta.uid <= (settings.last_uid ?? 0)) continue;
|
||||
candidates.push({ uid: meta.uid, size: meta.size ?? 0 });
|
||||
if (candidates.length >= MAX_MESSAGES_PER_RUN * 4) break;
|
||||
}
|
||||
)) as number[] | undefined;
|
||||
const candidateUids = (uidList ?? [])
|
||||
.filter((u: number) => u > (settings.last_uid ?? 0))
|
||||
.sort((a: number, b: number) => a - b)
|
||||
.slice(0, MAX_MESSAGES_PER_RUN * 4);
|
||||
|
||||
for (const cand of candidates) {
|
||||
for (const uid of candidateUids) {
|
||||
if (imported >= MAX_MESSAGES_PER_RUN) break;
|
||||
|
||||
// Always advance highestUid so we don't re-attempt skipped messages.
|
||||
if (cand.uid > highestUid) highestUid = cand.uid;
|
||||
if (uid > highestUid) highestUid = uid;
|
||||
|
||||
if (cand.size > MAX_MESSAGE_SIZE_BYTES) {
|
||||
// Per-message size probe so we can skip oversize messages
|
||||
// before downloading the full source.
|
||||
let probedSize = 0;
|
||||
try {
|
||||
for await (const meta of client.fetch(
|
||||
String(uid),
|
||||
{ uid: true, size: true },
|
||||
{ uid: true },
|
||||
)) {
|
||||
probedSize = meta.size ?? 0;
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Size probe failed uid", uid, e);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (probedSize > MAX_MESSAGE_SIZE_BYTES) {
|
||||
console.warn(
|
||||
`Skipping IMAP uid ${cand.uid} (${cand.size} bytes > ${MAX_MESSAGE_SIZE_BYTES})`,
|
||||
`Skipping IMAP uid ${uid} (${probedSize} bytes > ${MAX_MESSAGE_SIZE_BYTES})`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
@@ -85,7 +100,7 @@ export const Route = createFileRoute("/hooks/poll-imap")({
|
||||
let msg: { uid: number; source: Buffer; size?: number } | null = null;
|
||||
try {
|
||||
for await (const m of client.fetch(
|
||||
String(cand.uid),
|
||||
String(uid),
|
||||
{ uid: true, source: true, size: true },
|
||||
{ uid: true },
|
||||
)) {
|
||||
@@ -93,7 +108,7 @@ export const Route = createFileRoute("/hooks/poll-imap")({
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Per-message fetch failed uid", cand.uid, e);
|
||||
console.error("Per-message fetch failed uid", uid, e);
|
||||
continue;
|
||||
}
|
||||
if (!msg) continue;
|
||||
|
||||
@@ -13,6 +13,8 @@ import {
|
||||
} from "lucide-react";
|
||||
import { formatCurrency, formatDate, statusBadgeClass } from "@/lib/format";
|
||||
import { format, startOfDay, startOfMonth, endOfMonth, subDays } from "date-fns";
|
||||
import { QuickAddTime, QuickAddExpense } from "@/components/quick-add/quick-add";
|
||||
import { NewTaskDialog } from "@/components/tasks/new-task-dialog";
|
||||
|
||||
export const Route = createFileRoute("/")({
|
||||
component: () => (
|
||||
@@ -48,6 +50,7 @@ function Dashboard() {
|
||||
const [recentCases, setRecentCases] = useState<any[]>([]);
|
||||
const [recentInvoices, setRecentInvoices] = useState<any[]>([]);
|
||||
const [recentPayments, setRecentPayments] = useState<any[]>([]);
|
||||
const [taskDialogOpen, setTaskDialogOpen] = useState(false);
|
||||
const [upcomingReminders, setUpcomingReminders] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
@@ -150,12 +153,9 @@ function Dashboard() {
|
||||
}, [user?.id]);
|
||||
|
||||
const quickActions = useMemo(() => [
|
||||
{ label: "Add task", icon: CheckSquare, to: "/tasks" },
|
||||
{ label: "Add case", icon: Briefcase, to: "/cases/new" },
|
||||
{ label: "Add contact", icon: UserPlus, to: "/contacts" },
|
||||
{ label: "Create invoice", icon: Receipt, to: "/invoices" },
|
||||
{ label: "Add time", icon: Clock, to: "/tasks" },
|
||||
{ label: "Add expense", icon: DollarSign, to: "/cases" },
|
||||
], []);
|
||||
|
||||
const financialCards = [
|
||||
@@ -174,11 +174,17 @@ function Dashboard() {
|
||||
<CardContent className="p-4">
|
||||
<div className="text-xs uppercase tracking-wider text-muted-foreground mb-3">Quick actions</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button variant="outline" size="sm" onClick={() => setTaskDialogOpen(true)}>
|
||||
<CheckSquare className="h-3.5 w-3.5 mr-1.5" />Add task
|
||||
</Button>
|
||||
<NewTaskDialog open={taskDialogOpen} onOpenChange={setTaskDialogOpen} />
|
||||
{quickActions.map((a) => (
|
||||
<Button key={a.label} asChild variant="outline" size="sm">
|
||||
<Link to={a.to}><a.icon className="h-3.5 w-3.5 mr-1.5" />{a.label}</Link>
|
||||
</Button>
|
||||
))}
|
||||
<QuickAddTime />
|
||||
<QuickAddExpense />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user