Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,10 @@ async function onRun(
q,
};
const res = await fetchWithAccount({
url: `/api/threads?${new URLSearchParams(query as any).toString()}`,
url: `/api/threads?${
// biome-ignore lint/suspicious/noExplicitAny: simplest
new URLSearchParams(query as any).toString()
}`,
emailAccountId,
});
const data: ThreadsResponse = await res.json();
Expand Down
9 changes: 7 additions & 2 deletions apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useRouter } from "next/navigation";
import Link from "next/link";
import {
type FieldError,
type FieldErrors,
type SubmitHandler,
useFieldArray,
useForm,
Expand Down Expand Up @@ -132,6 +133,7 @@ export function RuleForm({
alwaysEditMode?: boolean;
onSuccess?: () => void;
isDialog?: boolean;
// biome-ignore lint/suspicious/noExplicitAny: lazy
mutate?: (data?: any, options?: any) => void;
Comment on lines +136 to 137
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Eliminate noExplicitAny on mutate by using the hook’s actual type

Use the mutate type from useRule instead of any.

-  // biome-ignore lint/suspicious/noExplicitAny: lazy
-  mutate?: (data?: any, options?: any) => void;
+  mutate?: ReturnType<typeof useRule>["mutate"];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// biome-ignore lint/suspicious/noExplicitAny: lazy
mutate?: (data?: any, options?: any) => void;
- // biome-ignore lint/suspicious/noExplicitAny: lazy
mutate?: ReturnType<typeof useRule>["mutate"];
🤖 Prompt for AI Agents
In apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx around lines
136-137, the prop type for mutate is declared as any with a biome-ignore
comment; replace this with the actual mutate type exported/returned by the
useRule hook (import or derive the type from useRule's return type), update the
mutate prop signature to use that specific type (e.g., mutate?:
ReturnType<typeof useRule>["mutate"] or an exported type alias from useRule),
and remove the biome-ignore comment so noExplicitAny is no longer needed.

onCancel?: () => void;
}) {
Expand Down Expand Up @@ -1033,7 +1035,7 @@ function ActionCard({
watch: ReturnType<typeof useForm<CreateRuleBody>>["watch"];
setValue: ReturnType<typeof useForm<CreateRuleBody>>["setValue"];
control: ReturnType<typeof useForm<CreateRuleBody>>["control"];
errors: any;
errors: FieldErrors<CreateRuleBody>;
userLabels: EmailLabel[];
isLoading: boolean;
mutate: () => void;
Expand Down Expand Up @@ -1375,7 +1377,10 @@ function ActionCard({
{errors?.actions?.[index]?.delayInMinutes && (
<div className="mt-2">
<ErrorMessage
message={errors.actions?.[index]?.delayInMinutes?.message}
message={
errors.actions?.[index]?.delayInMinutes?.message ||
"Invalid delay value"
}
/>
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import * as Sentry from "@sentry/nextjs";
import { useEffect } from "react";
import { ErrorDisplay } from "@/components/ErrorDisplay";

export default function ErrorBoundary({ error }: any) {
export default function ErrorBoundary({
error,
}: {
error: Error & { digest?: string };
}) {
useEffect(() => {
Sentry.captureException(error);
}, [error]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export function BulkActions({
mutate,
}: {
selected: Map<string, boolean>;
// biome-ignore lint/suspicious/noExplicitAny: lazy
mutate: () => Promise<any>;
}) {
const posthog = usePostHog();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export function BulkUnsubscribe() {
includeMissingUnsubscribe: true,
...getDateRangeParams(dateRange),
};
// biome-ignore lint/suspicious/noExplicitAny: simplest
const urlParams = new URLSearchParams(params as any);
const { data, isLoading, error, mutate } = useSWR<
NewsletterStatsResponse,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
TrashIcon,
} from "lucide-react";
import { type PostHog, usePostHog } from "posthog-js/react";
import type { UserResponse } from "@/app/api/user/me/route";
import { Button } from "@/components/ui/button";
import { ButtonLoader } from "@/components/Loading";
import { Tooltip } from "@/components/Tooltip";
Expand Down Expand Up @@ -69,7 +70,7 @@ export function ActionCell<T extends Row>({
item: T;
hasUnsubscribeAccess: boolean;
mutate: () => Promise<void>;
refetchPremium: () => Promise<any>;
refetchPremium: () => Promise<UserResponse | null | undefined>;
onOpenNewsletter: (row: T) => void;
selected: boolean;
labels: EmailLabel[];
Expand Down Expand Up @@ -159,7 +160,7 @@ function UnsubscribeButton<T extends Row>({
item: T;
hasUnsubscribeAccess: boolean;
mutate: () => Promise<void>;
refetchPremium: () => Promise<any>;
refetchPremium: () => Promise<UserResponse | null | undefined>;
posthog: PostHog;
emailAccountId: string;
}) {
Expand Down Expand Up @@ -223,7 +224,7 @@ function AutoArchiveButton<T extends Row>({
hasUnsubscribeAccess: boolean;
mutate: () => Promise<void>;
posthog: PostHog;
refetchPremium: () => Promise<any>;
refetchPremium: () => Promise<UserResponse | null | undefined>;
labels: EmailLabel[];
emailAccountId: string;
}) {
Expand Down
30 changes: 16 additions & 14 deletions apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type { Row } from "@/app/(app)/[emailAccountId]/bulk-unsubscribe/types";
import type { GetThreadsResponse } from "@/app/api/threads/basic/route";
import { isDefined } from "@/utils/types";
import { fetchWithAccount } from "@/utils/fetch";
import type { UserResponse } from "@/app/api/user/me/route";

async function unsubscribeAndArchive({
newsletterEmail,
Expand All @@ -24,7 +25,7 @@ async function unsubscribeAndArchive({
}: {
newsletterEmail: string;
mutate: () => Promise<void>;
refetchPremium: () => Promise<any>;
refetchPremium: () => Promise<UserResponse | null | undefined>;
emailAccountId: string;
}) {
await setNewsletterStatusAction(emailAccountId, {
Expand Down Expand Up @@ -53,7 +54,7 @@ export function useUnsubscribe<T extends Row>({
hasUnsubscribeAccess: boolean;
mutate: () => Promise<void>;
posthog: PostHog;
refetchPremium: () => Promise<any>;
refetchPremium: () => Promise<UserResponse | null | undefined>;
}) {
const [unsubscribeLoading, setUnsubscribeLoading] = useState(false);

Expand Down Expand Up @@ -112,9 +113,9 @@ export function useBulkUnsubscribe<T extends Row>({
emailAccountId,
}: {
hasUnsubscribeAccess: boolean;
mutate: () => Promise<any>;
mutate: () => Promise<void>;
posthog: PostHog;
refetchPremium: () => Promise<any>;
refetchPremium: () => Promise<UserResponse | null | undefined>;
emailAccountId: string;
}) {
const [bulkUnsubscribeLoading, setBulkUnsubscribeLoading] = useState(false);
Expand Down Expand Up @@ -167,7 +168,7 @@ async function autoArchive({
labelId: string | undefined;
labelName: string | undefined;
mutate: () => Promise<void>;
refetchPremium: () => Promise<any>;
refetchPremium: () => Promise<UserResponse | null | undefined>;
emailAccountId: string;
}) {
await onAutoArchive({
Expand Down Expand Up @@ -200,9 +201,9 @@ export function useAutoArchive<T extends Row>({
}: {
item: T;
hasUnsubscribeAccess: boolean;
mutate: () => Promise<any>;
mutate: () => Promise<void>;
posthog: PostHog;
refetchPremium: () => Promise<any>;
refetchPremium: () => Promise<UserResponse | null | undefined>;
emailAccountId: string;
}) {
const [autoArchiveLoading, setAutoArchiveLoading] = useState(false);
Expand Down Expand Up @@ -286,8 +287,8 @@ export function useBulkAutoArchive<T extends Row>({
emailAccountId,
}: {
hasUnsubscribeAccess: boolean;
mutate: () => Promise<any>;
refetchPremium: () => Promise<any>;
mutate: () => Promise<void>;
refetchPremium: () => Promise<UserResponse | null | undefined>;
emailAccountId: string;
}) {
const [bulkAutoArchiveLoading, setBulkAutoArchiveLoading] = useState(false);
Expand Down Expand Up @@ -337,7 +338,7 @@ export function useApproveButton<T extends Row>({
hasUnsubscribeAccess: true,
mutate,
posthog,
refetchPremium: () => Promise.resolve(),
refetchPremium: () => Promise.resolve(undefined),
emailAccountId,
});

Expand Down Expand Up @@ -367,7 +368,7 @@ export function useBulkApprove<T extends Row>({
posthog,
emailAccountId,
}: {
mutate: () => Promise<any>;
mutate: () => Promise<void>;
posthog: PostHog;
emailAccountId: string;
}) {
Expand Down Expand Up @@ -467,7 +468,7 @@ export function useBulkArchive<T extends Row>({
posthog,
emailAccountId,
}: {
mutate: () => Promise<any>;
mutate: () => Promise<void>;
posthog: PostHog;
emailAccountId: string;
}) {
Expand Down Expand Up @@ -566,7 +567,7 @@ export function useBulkDelete<T extends Row>({
posthog,
emailAccountId,
}: {
mutate: () => Promise<any>;
mutate: () => Promise<void>;
posthog: PostHog;
emailAccountId: string;
}) {
Expand Down Expand Up @@ -600,8 +601,9 @@ export function useBulkUnsubscribeShortcuts<T extends Row>({
selectedRow?: T;
setSelectedRow: (row: T) => void;
onOpenNewsletter: (row: T) => void;
refetchPremium: () => Promise<any>;
refetchPremium: () => Promise<UserResponse | null | undefined>;
hasUnsubscribeAccess: boolean;
// biome-ignore lint/suspicious/noExplicitAny: simplest
mutate: () => Promise<any>;
emailAccountId: string;
userEmail: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { NewsletterStatsResponse } from "@/app/api/user/stats/newsletters/route";
import type { NewsletterStatus } from "@prisma/client";
import type { EmailLabel } from "@/providers/EmailProvider";
import type { UserResponse } from "@/app/api/user/me/route";

export type Row = {
name: string;
Expand All @@ -21,12 +22,13 @@ export interface RowProps {

onOpenNewsletter: (row: Newsletter) => void;
labels: EmailLabel[];
// biome-ignore lint/suspicious/noExplicitAny: simplest
mutate: () => Promise<any>;
selected: boolean;
onSelectRow: () => void;
onDoubleClick: () => void;
hasUnsubscribeAccess: boolean;
refetchPremium: () => Promise<any>;
refetchPremium: () => Promise<UserResponse | null | undefined>;
openPremiumModal: () => void;
checked: boolean;
onToggleSelect: (id: string) => void;
Expand Down
3 changes: 2 additions & 1 deletion apps/web/scripts/addUsersToResend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ async function main() {
try {
if (user.email) {
console.log("Adding user", user.email);
const { error } = await createContact({ email: user.email });
const result = await createContact({ email: user.email });
const error = result && "error" in result ? result.error : undefined;
if (error) console.error(error);
Comment on lines +15 to 17
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Strengthen the runtime type guard; also replace console with scoped logger and use shared Prisma util

  • The "error" in result check is a good move. Add an typeof result === "object" guard to avoid using in on non-objects.
  • Per guidelines, avoid console in backend TS; use createScopedLogger.
  • Prefer the shared Prisma singleton (import prisma from "@/utils/prisma";) over instantiating new PrismaClient().
  • Optionally await disconnect in finally.

Apply these diffs:

-import { PrismaClient } from "@prisma/client";
+import prisma from "@/utils/prisma";
+import { createScopedLogger } from "@/utils/logger";

-const prisma = new PrismaClient();
+const log = createScopedLogger("scripts/addUsersToResend");

@@
-        console.log("Adding user", user.email);
+        log.info("Adding user", { email: user.email });
-        const result = await createContact({ email: user.email });
-        const error = result && "error" in result ? result.error : undefined;
-        if (error) console.error(error);
+        const result = await createContact({ email: user.email });
+        const error =
+          result && typeof result === "object" && "error" in result
+            ? // narrow for TS; adjust the shape if you type `createContact`
+              (result as { error?: unknown }).error
+            : undefined;
+        if (error) log.error("Resend createContact error", { email: user.email, error });

@@
-    } catch (error) {
-      console.error("Error creating contact for user: ", user.email, error);
+    } catch (error) {
+      log.error("Error creating contact for user", { email: user.email, error });
     }
@@
-main().finally(() => {
-  prisma.$disconnect();
-});
+main().finally(async () => {
+  await prisma.$disconnect();
+});

If createContact is updated to always throw on failures (as suggested in contacts.ts), you can simplify to:

await createContact({ email: user.email });
// only catch/LOG exceptions; no need to inspect `result.error`
🤖 Prompt for AI Agents
In apps/web/scripts/addUsersToResend.ts around lines 15 to 17, strengthen the
runtime guard and replace console and Prisma usage: ensure you check typeof
result === "object" before using "error" in result (or, if createContact now
throws on failure, simplify to just await createContact(...) inside a
try/catch), replace console.error with a createScopedLogger instance (import and
use createScopedLogger for this script), import the shared Prisma singleton
(import prisma from "@/utils/prisma") instead of instantiating new
PrismaClient(), and if you keep a manually-managed client ensure you await
client.$disconnect() in a finally block.

}
} catch (error) {
Expand Down
62 changes: 27 additions & 35 deletions apps/web/utils/email/microsoft.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Message } from "@microsoft/microsoft-graph-types";
import type { OutlookClient } from "@/utils/outlook/client";
import type { ParsedMessage } from "@/utils/types";
import {
Expand Down Expand Up @@ -294,10 +295,10 @@ export class OutlookProvider implements EmailProvider {
logger,
}),
]);
return { draftId: result.id };
return { draftId: result.id || "" };
} else {
const result = await draftEmail(this.client, email, args);
return { draftId: result.id };
return { draftId: result.id || "" };
}
}

Expand Down Expand Up @@ -402,8 +403,8 @@ export class OutlookProvider implements EmailProvider {
});

return {
id: label.id,
name: label.displayName || label.id,
id: label.id || "",
name: label.displayName || label.id || "",
type: "user",
};
}
Expand All @@ -414,8 +415,8 @@ export class OutlookProvider implements EmailProvider {
key,
});
return {
id: label.id,
name: label.displayName || label.id,
id: label.id || "",
name: label.displayName || label.id || "",
type: "user",
};
}
Expand All @@ -435,27 +436,21 @@ export class OutlookProvider implements EmailProvider {
try {
const response = await getFiltersList({ client: this.client });

const mappedFilters = (response.value || []).map(
(filter: {
id: string;
conditions: { senderContains: string[] };
actions: { applyCategories: string[]; moveToFolder: string };
}) => {
const mappedFilter = {
id: filter.id || "",
criteria: {
from: filter.conditions?.senderContains?.[0] || undefined,
},
action: {
addLabelIds: filter.actions?.applyCategories || undefined,
removeLabelIds: filter.actions?.moveToFolder
? ["INBOX"]
: undefined,
},
};
return mappedFilter;
},
);
const mappedFilters = (response.value || []).map((filter) => {
const mappedFilter = {
id: filter.id || "",
criteria: {
from: filter.conditions?.senderContains?.[0] || undefined,
},
action: {
addLabelIds: filter.actions?.assignCategories || undefined,
removeLabelIds: filter.actions?.moveToFolder
? ["INBOX"]
: undefined,
},
};
return mappedFilter;
});

return mappedFilters;
} catch (error) {
Expand All @@ -468,22 +463,19 @@ export class OutlookProvider implements EmailProvider {
from: string;
addLabelIds?: string[];
removeLabelIds?: string[];
}): Promise<any> {
}) {
return createFilter({ client: this.client, ...options });
}

async createAutoArchiveFilter(options: {
from: string;
labelName?: string;
}): Promise<any> {
async createAutoArchiveFilter(options: { from: string; labelName?: string }) {
return createAutoArchiveFilter({
client: this.client,
from: options.from,
labelName: options.labelName,
});
}

async deleteFilter(id: string): Promise<any> {
async deleteFilter(id: string) {
return deleteFilter({ client: this.client, id });
}

Expand Down Expand Up @@ -810,7 +802,7 @@ export class OutlookProvider implements EmailProvider {
messageId: string;
}): Promise<boolean> {
try {
const response = await this.client
const response: { value: Message[] } = await this.client
.getClient()
.api("/me/messages")
.filter(
Expand All @@ -823,7 +815,7 @@ export class OutlookProvider implements EmailProvider {
// Check if there are any messages from this sender before the current date
// and exclude the current message
const hasPreviousEmail = response.value.some(
(message: { id: string }) => message.id !== options.messageId,
(message) => message.id !== options.messageId,
);

return hasPreviousEmail;
Expand Down
Loading
Loading