diff --git a/apps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsx b/apps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsx index 6b30361f37..26485b0a60 100644 --- a/apps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsx +++ b/apps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsx @@ -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(); diff --git a/apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx b/apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx index 725a261e5d..617419a08a 100644 --- a/apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx +++ b/apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx @@ -5,6 +5,7 @@ import { useRouter } from "next/navigation"; import Link from "next/link"; import { type FieldError, + type FieldErrors, type SubmitHandler, useFieldArray, useForm, @@ -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; onCancel?: () => void; }) { @@ -1033,7 +1035,7 @@ function ActionCard({ watch: ReturnType>["watch"]; setValue: ReturnType>["setValue"]; control: ReturnType>["control"]; - errors: any; + errors: FieldErrors; userLabels: EmailLabel[]; isLoading: boolean; mutate: () => void; @@ -1375,7 +1377,10 @@ function ActionCard({ {errors?.actions?.[index]?.delayInMinutes && (
)} diff --git a/apps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsx b/apps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsx index 6c7d61f43b..08f188e456 100644 --- a/apps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsx +++ b/apps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsx @@ -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]); diff --git a/apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsx b/apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsx index d823ed9e6a..3370dcb0af 100644 --- a/apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsx +++ b/apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsx @@ -22,6 +22,7 @@ export function BulkActions({ mutate, }: { selected: Map; + // biome-ignore lint/suspicious/noExplicitAny: lazy mutate: () => Promise; }) { const posthog = usePostHog(); diff --git a/apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx b/apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx index a61babe39a..15a5346be0 100644 --- a/apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx +++ b/apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx @@ -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, diff --git a/apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsx b/apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsx index 372b7845e0..751c695ab2 100644 --- a/apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsx +++ b/apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsx @@ -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"; @@ -69,7 +70,7 @@ export function ActionCell({ item: T; hasUnsubscribeAccess: boolean; mutate: () => Promise; - refetchPremium: () => Promise; + refetchPremium: () => Promise; onOpenNewsletter: (row: T) => void; selected: boolean; labels: EmailLabel[]; @@ -159,7 +160,7 @@ function UnsubscribeButton({ item: T; hasUnsubscribeAccess: boolean; mutate: () => Promise; - refetchPremium: () => Promise; + refetchPremium: () => Promise; posthog: PostHog; emailAccountId: string; }) { @@ -223,7 +224,7 @@ function AutoArchiveButton({ hasUnsubscribeAccess: boolean; mutate: () => Promise; posthog: PostHog; - refetchPremium: () => Promise; + refetchPremium: () => Promise; labels: EmailLabel[]; emailAccountId: string; }) { diff --git a/apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/hooks.ts b/apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/hooks.ts index 1ecddf1f40..fdbeefeeab 100644 --- a/apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/hooks.ts +++ b/apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/hooks.ts @@ -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, @@ -24,7 +25,7 @@ async function unsubscribeAndArchive({ }: { newsletterEmail: string; mutate: () => Promise; - refetchPremium: () => Promise; + refetchPremium: () => Promise; emailAccountId: string; }) { await setNewsletterStatusAction(emailAccountId, { @@ -53,7 +54,7 @@ export function useUnsubscribe({ hasUnsubscribeAccess: boolean; mutate: () => Promise; posthog: PostHog; - refetchPremium: () => Promise; + refetchPremium: () => Promise; }) { const [unsubscribeLoading, setUnsubscribeLoading] = useState(false); @@ -112,9 +113,9 @@ export function useBulkUnsubscribe({ emailAccountId, }: { hasUnsubscribeAccess: boolean; - mutate: () => Promise; + mutate: () => Promise; posthog: PostHog; - refetchPremium: () => Promise; + refetchPremium: () => Promise; emailAccountId: string; }) { const [bulkUnsubscribeLoading, setBulkUnsubscribeLoading] = useState(false); @@ -167,7 +168,7 @@ async function autoArchive({ labelId: string | undefined; labelName: string | undefined; mutate: () => Promise; - refetchPremium: () => Promise; + refetchPremium: () => Promise; emailAccountId: string; }) { await onAutoArchive({ @@ -200,9 +201,9 @@ export function useAutoArchive({ }: { item: T; hasUnsubscribeAccess: boolean; - mutate: () => Promise; + mutate: () => Promise; posthog: PostHog; - refetchPremium: () => Promise; + refetchPremium: () => Promise; emailAccountId: string; }) { const [autoArchiveLoading, setAutoArchiveLoading] = useState(false); @@ -286,8 +287,8 @@ export function useBulkAutoArchive({ emailAccountId, }: { hasUnsubscribeAccess: boolean; - mutate: () => Promise; - refetchPremium: () => Promise; + mutate: () => Promise; + refetchPremium: () => Promise; emailAccountId: string; }) { const [bulkAutoArchiveLoading, setBulkAutoArchiveLoading] = useState(false); @@ -337,7 +338,7 @@ export function useApproveButton({ hasUnsubscribeAccess: true, mutate, posthog, - refetchPremium: () => Promise.resolve(), + refetchPremium: () => Promise.resolve(undefined), emailAccountId, }); @@ -367,7 +368,7 @@ export function useBulkApprove({ posthog, emailAccountId, }: { - mutate: () => Promise; + mutate: () => Promise; posthog: PostHog; emailAccountId: string; }) { @@ -467,7 +468,7 @@ export function useBulkArchive({ posthog, emailAccountId, }: { - mutate: () => Promise; + mutate: () => Promise; posthog: PostHog; emailAccountId: string; }) { @@ -566,7 +567,7 @@ export function useBulkDelete({ posthog, emailAccountId, }: { - mutate: () => Promise; + mutate: () => Promise; posthog: PostHog; emailAccountId: string; }) { @@ -600,8 +601,9 @@ export function useBulkUnsubscribeShortcuts({ selectedRow?: T; setSelectedRow: (row: T) => void; onOpenNewsletter: (row: T) => void; - refetchPremium: () => Promise; + refetchPremium: () => Promise; hasUnsubscribeAccess: boolean; + // biome-ignore lint/suspicious/noExplicitAny: simplest mutate: () => Promise; emailAccountId: string; userEmail: string; diff --git a/apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/types.ts b/apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/types.ts index bce2389ac5..94dc1f8f61 100644 --- a/apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/types.ts +++ b/apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/types.ts @@ -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; @@ -21,12 +22,13 @@ export interface RowProps { onOpenNewsletter: (row: Newsletter) => void; labels: EmailLabel[]; + // biome-ignore lint/suspicious/noExplicitAny: simplest mutate: () => Promise; selected: boolean; onSelectRow: () => void; onDoubleClick: () => void; hasUnsubscribeAccess: boolean; - refetchPremium: () => Promise; + refetchPremium: () => Promise; openPremiumModal: () => void; checked: boolean; onToggleSelect: (id: string) => void; diff --git a/apps/web/scripts/addUsersToResend.ts b/apps/web/scripts/addUsersToResend.ts index 6d9721f299..1bf2353841 100644 --- a/apps/web/scripts/addUsersToResend.ts +++ b/apps/web/scripts/addUsersToResend.ts @@ -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); } } catch (error) { diff --git a/apps/web/utils/email/microsoft.ts b/apps/web/utils/email/microsoft.ts index cdf5e72a7c..9e9971503c 100644 --- a/apps/web/utils/email/microsoft.ts +++ b/apps/web/utils/email/microsoft.ts @@ -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 { @@ -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 || "" }; } } @@ -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", }; } @@ -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", }; } @@ -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) { @@ -468,14 +463,11 @@ export class OutlookProvider implements EmailProvider { from: string; addLabelIds?: string[]; removeLabelIds?: string[]; - }): Promise { + }) { return createFilter({ client: this.client, ...options }); } - async createAutoArchiveFilter(options: { - from: string; - labelName?: string; - }): Promise { + async createAutoArchiveFilter(options: { from: string; labelName?: string }) { return createAutoArchiveFilter({ client: this.client, from: options.from, @@ -483,7 +475,7 @@ export class OutlookProvider implements EmailProvider { }); } - async deleteFilter(id: string): Promise { + async deleteFilter(id: string) { return deleteFilter({ client: this.client, id }); } @@ -810,7 +802,7 @@ export class OutlookProvider implements EmailProvider { messageId: string; }): Promise { try { - const response = await this.client + const response: { value: Message[] } = await this.client .getClient() .api("/me/messages") .filter( @@ -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; diff --git a/apps/web/utils/outlook/attachment.ts b/apps/web/utils/outlook/attachment.ts index 6728f9a4cd..4021231d4d 100644 --- a/apps/web/utils/outlook/attachment.ts +++ b/apps/web/utils/outlook/attachment.ts @@ -1,11 +1,12 @@ import type { OutlookClient } from "@/utils/outlook/client"; +import type { FileAttachment } from "@microsoft/microsoft-graph-types"; export async function getOutlookAttachment( client: OutlookClient, messageId: string, attachmentId: string, ) { - const attachment = await client + const attachment: FileAttachment = await client .getClient() .api(`/me/messages/${messageId}/attachments/${attachmentId}`) .get(); diff --git a/apps/web/utils/outlook/draft.ts b/apps/web/utils/outlook/draft.ts index f0b7ed3c31..0a05ba9875 100644 --- a/apps/web/utils/outlook/draft.ts +++ b/apps/web/utils/outlook/draft.ts @@ -1,3 +1,4 @@ +import type { Message } from "@microsoft/microsoft-graph-types"; import type { OutlookClient } from "@/utils/outlook/client"; import { createScopedLogger } from "@/utils/logger"; import { convertMessage } from "@/utils/outlook/message"; @@ -6,7 +7,7 @@ const logger = createScopedLogger("outlook/draft"); export async function getDraft(draftId: string, client: OutlookClient) { try { - const response = await client + const response: Message = await client .getClient() .api(`/me/messages/${draftId}`) .get(); @@ -22,13 +23,7 @@ export async function getDraft(draftId: string, client: OutlookClient) { export async function deleteDraft(client: OutlookClient, draftId: string) { try { logger.info("Deleting draft", { draftId }); - const response = await client - .getClient() - .api(`/me/messages/${draftId}`) - .delete(); - if (response.status !== 204) { - logger.error("Failed to delete draft", { draftId, response }); - } + await client.getClient().api(`/me/messages/${draftId}`).delete(); logger.info("Successfully deleted draft", { draftId }); } catch (error) { if (error instanceof Error && "code" in error && error.code === 404) { diff --git a/apps/web/utils/outlook/filter.ts b/apps/web/utils/outlook/filter.ts index 339a294774..2aff067ae5 100644 --- a/apps/web/utils/outlook/filter.ts +++ b/apps/web/utils/outlook/filter.ts @@ -1,5 +1,8 @@ import type { OutlookClient } from "@/utils/outlook/client"; -import type { MessageRule } from "@microsoft/microsoft-graph-types"; +import type { + MessageRule, + OutlookCategory, +} from "@microsoft/microsoft-graph-types"; import { createScopedLogger } from "@/utils/logger"; const logger = createScopedLogger("outlook/filter"); @@ -32,7 +35,7 @@ export async function createFilter(options: { }, }; - const response = await client + const response: MessageRule = await client .getClient() .api("/me/mailFolders/inbox/messageRules") .post(rule); @@ -72,7 +75,7 @@ export async function createAutoArchiveFilter({ }, }; - const response = await client + const response: MessageRule = await client .getClient() .api("/me/mailFolders/inbox/messageRules") .post(rule); @@ -94,12 +97,12 @@ export async function deleteFilter(options: { const { client, id } = options; try { - const response = await client + await client .getClient() .api(`/me/mailFolders/inbox/messageRules/${id}`) .delete(); - return { status: 204, data: response }; + return { status: 204 }; } catch (error) { logger.error("Error deleting Outlook filter", { id, error }); throw error; @@ -108,7 +111,7 @@ export async function deleteFilter(options: { export async function getFiltersList(options: { client: OutlookClient }) { try { - const response = await options.client + const response: { value: MessageRule[] } = await options.client .getClient() .api("/me/mailFolders/inbox/messageRules") .get(); @@ -126,6 +129,7 @@ export async function getFiltersList(options: { client: OutlookClient }) { // Helper function to check if a filter already exists function isFilterExistsError(error: unknown) { + // biome-ignore lint/suspicious/noExplicitAny: simplest const errorMessage = (error as any)?.message || ""; return ( errorMessage.includes("already exists") || @@ -147,13 +151,13 @@ export async function createCategoryFilter({ }) { try { // First, ensure the category exists - const categories = await client + const categories: { value: OutlookCategory[] } = await client .getClient() .api("/me/outlook/masterCategories") .get(); let category = categories.value.find( - (cat: { displayName: string }) => cat.displayName === categoryName, + (cat) => cat.displayName === categoryName, ); if (!category) { @@ -174,7 +178,7 @@ export async function createCategoryFilter({ logger.info("Category created for filter", { from, categoryName, - categoryId: category.id, + categoryId: category?.id, }); return { @@ -218,7 +222,7 @@ export async function updateFilter({ }, }; - const response = await client + const response: MessageRule = await client .getClient() .api(`/me/mailFolders/inbox/messageRules/${id}`) .patch(rule); diff --git a/apps/web/utils/outlook/folders.ts b/apps/web/utils/outlook/folders.ts index d6e7e81f80..d551a44758 100644 --- a/apps/web/utils/outlook/folders.ts +++ b/apps/web/utils/outlook/folders.ts @@ -1,3 +1,4 @@ +import type { MailFolder } from "@microsoft/microsoft-graph-types"; import type { OutlookClient } from "./client"; import { createScopedLogger } from "@/utils/logger"; @@ -6,17 +7,27 @@ const logger = createScopedLogger("outlook/folders"); // Should not use a common separator like "/|\>" as it may be used in the folder name. // Using U+2999 as it is unlikely to appear in normal text export const FOLDER_SEPARATOR = " ⦙ "; + export type OutlookFolder = { - id: string; - displayName: string; - childFolders?: OutlookFolder[]; + id: NonNullable; + displayName: NonNullable; + childFolders: OutlookFolder[]; }; +function convertMailFolderToOutlookFolder(folder: MailFolder): OutlookFolder { + return { + id: folder.id ?? "", + displayName: folder.displayName ?? "", + childFolders: + folder.childFolders?.map(convertMailFolderToOutlookFolder) ?? [], + }; +} + export async function getOutlookRootFolders( client: OutlookClient, ): Promise { const fields = "id,displayName"; - const response = await client + const response: { value: MailFolder[] } = await client .getClient() .api("/me/mailFolders") .select(fields) @@ -25,7 +36,7 @@ export async function getOutlookRootFolders( ) .get(); - return response.value; + return response.value.map(convertMailFolderToOutlookFolder); } export async function getOutlookChildFolders( @@ -33,7 +44,7 @@ export async function getOutlookChildFolders( folderId: string, ): Promise { const fields = "id,displayName"; - const response = await client + const response: { value: MailFolder[] } = await client .getClient() .api(`/me/mailFolders/${folderId}/childFolders`) .select(fields) @@ -42,7 +53,7 @@ export async function getOutlookChildFolders( ) .get(); - return response.value; + return response.value.map(convertMailFolderToOutlookFolder); } export async function getOutlookFolderTree( @@ -57,7 +68,7 @@ export async function getOutlookFolderTree( const remainingLevels = expandLevels - 2; for (let currentLevel = 0; currentLevel < remainingLevels; currentLevel++) { - const folderQueue: OutlookFolder[] = [...folders]; + const folderQueue = [...folders]; while (folderQueue.length > 0) { const folder = folderQueue.shift()!; @@ -72,7 +83,9 @@ export async function getOutlookFolderTree( } } if (folder.childFolders) { - folderQueue.push(...folder.childFolders); + folderQueue.push( + ...folder.childFolders.map(convertMailFolderToOutlookFolder), + ); } } } diff --git a/apps/web/utils/outlook/label.ts b/apps/web/utils/outlook/label.ts index 292793141d..b10cb29881 100644 --- a/apps/web/utils/outlook/label.ts +++ b/apps/web/utils/outlook/label.ts @@ -4,16 +4,13 @@ import { publishArchive, type TinybirdEmailAction } from "@inboxzero/tinybird"; import { WELL_KNOWN_FOLDERS } from "./message"; import { inboxZeroLabels, type InboxZeroLabel } from "@/utils/label"; +import type { + OutlookCategory, + Message, +} from "@microsoft/microsoft-graph-types"; const logger = createScopedLogger("outlook/label"); -// Define our own Category type since Microsoft Graph types don't export it -interface OutlookCategory { - id: string; - displayName?: string; - color?: string; -} - // Outlook doesn't have system labels like Gmail, but we map common categories // Using same format as Gmail for consistency export const OutlookLabel = { @@ -57,11 +54,11 @@ export const OUTLOOK_COLOR_MAP = { } as const; export async function getLabels(client: OutlookClient) { - const response = await client + const response: { value: OutlookCategory[] } = await client .getClient() .api("/me/outlook/masterCategories") .get(); - return (response.value as OutlookCategory[]).map((label) => ({ + return response.value.map((label) => ({ ...label, name: label.displayName || label.id, })); @@ -72,11 +69,11 @@ export async function getLabelById(options: { id: string; }) { const { client, id } = options; - const response = await client + const response: OutlookCategory = await client .getClient() .api(`/me/outlook/masterCategories/${id}`) .get(); - return response as OutlookCategory; + return response; } export async function createLabel({ @@ -95,14 +92,14 @@ export async function createLabel({ ? color : OUTLOOK_COLORS[Math.floor(Math.random() * OUTLOOK_COLORS.length)]; - const response = await client + const response: OutlookCategory = await client .getClient() .api("/me/outlook/masterCategories") .post({ displayName: name, color: outlookColor, }); - return response as OutlookCategory; + return response; } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; @@ -217,15 +214,15 @@ export async function labelThread({ // In Outlook, we need to update each message in the thread // Escape single quotes in threadId for the filter const escapedThreadId = threadId.replace(/'/g, "''"); - const messages = await client + const messages: { value: Message[] } = await client .getClient() .api("/me/messages") .filter(`conversationId eq '${escapedThreadId}'`) .get(); await Promise.all( - messages.value.map((message: { id: string }) => - labelMessage({ client, messageId: message.id, categories }), + messages.value.map((message) => + labelMessage({ client, messageId: message.id!, categories }), ), ); } diff --git a/apps/web/utils/outlook/mail.ts b/apps/web/utils/outlook/mail.ts index 0c4e2c4a86..25a8385ebe 100644 --- a/apps/web/utils/outlook/mail.ts +++ b/apps/web/utils/outlook/mail.ts @@ -1,3 +1,4 @@ +import type { Message } from "@microsoft/microsoft-graph-types"; import type { OutlookClient } from "@/utils/outlook/client"; import type { Attachment } from "nodemailer/lib/mailer"; import type { SendEmailBody } from "@/utils/gmail/mail"; @@ -46,7 +47,10 @@ export async function sendEmailWithHtml( message.conversationId = body.replyToEmail.threadId; } - const result = await client.getClient().api("/me/messages").post(message); + const result: Message = await client + .getClient() + .api("/me/messages") + .post(message); return result; } @@ -105,7 +109,7 @@ export async function forwardEmail( if (!options.to.trim()) throw new Error("Recipient address is required"); // Get the original message - const originalMessage = await client + const originalMessage: Message = await client .getClient() .api(`/me/messages/${options.messageId}`) .get(); @@ -202,7 +206,10 @@ export async function draftEmail( isDraft: true, }; - const result = await client.getClient().api("/me/messages").post(draft); + const result: Message = await client + .getClient() + .api("/me/messages") + .post(draft); return result; } diff --git a/apps/web/utils/outlook/message.ts b/apps/web/utils/outlook/message.ts index ba55943c9e..dfe97b7cd7 100644 --- a/apps/web/utils/outlook/message.ts +++ b/apps/web/utils/outlook/message.ts @@ -181,7 +181,8 @@ export async function queryBatchMessages( request = request.skipToken(pageToken); } - const response = await request.get(); + const response: { value: Message[]; "@odata.nextLink"?: string } = + await request.get(); const messages = await convertMessages(response.value, folderIds); // For filter, get next page token from @odata.nextLink @@ -204,10 +205,11 @@ export async function queryBatchMessages( request = request.skipToken(pageToken); } - const response = await request.get(); + const response: { value: Message[]; "@odata.nextLink"?: string } = + await request.get(); // Filter messages to only include inbox and archive folders const filteredMessages = response.value.filter( - (message: Message) => + (message) => message.parentFolderId === inboxFolderId || message.parentFolderId === archiveFolderId, ); @@ -242,7 +244,7 @@ export async function queryBatchMessages( .skip(pageToken ? Number.parseInt(pageToken, 10) : 0) .orderby("receivedDateTime DESC"); - const response = await request.get(); + const response: { value: Message[] } = await request.get(); const messages = await convertMessages(response.value, folderIds); // For non-search, calculate next page token based on message count @@ -316,7 +318,8 @@ export async function getMessages( ); } - const response = await request.get(); + const response: { value: Message[]; "@odata.nextLink"?: string } = + await request.get(); // Get folder IDs to properly map labels const folderIds = await getFolderIds(client); diff --git a/apps/web/utils/outlook/thread.ts b/apps/web/utils/outlook/thread.ts index 1ad6222b50..d1b2e8c406 100644 --- a/apps/web/utils/outlook/thread.ts +++ b/apps/web/utils/outlook/thread.ts @@ -7,7 +7,7 @@ export async function getThread( threadId: string, client: OutlookClient, ): Promise { - const messages = await client + const messages: { value: Message[] } = await client .getClient() .api("/me/messages") .filter(`conversationId eq '${threadId}'`) @@ -25,13 +25,19 @@ export async function getThreads( nextPageToken?: string | null; threads: { id: string; snippet: string }[]; }> { - const response = await client - .getClient() - .api("/me/messages") - .filter(query ? `contains(subject, '${escapeODataString(query)}')` : "") - .top(maxResults) - .select("id,conversationId,subject,bodyPreview") - .get(); + let request = client.getClient().api("/me/messages"); + + if (query) { + request = request.filter( + `contains(subject, '${escapeODataString(query)}')`, + ); + } + + const response: { value: Message[]; "@odata.nextLink"?: string } = + await request + .top(maxResults) + .select("id,conversationId,subject,bodyPreview") + .get(); // Group messages by conversationId to create thread-like structure const threadMap = new Map(); @@ -73,7 +79,8 @@ export async function getThreadsWithNextPageToken({ ); } - const response = await request.get(); + const response: { value: Message[]; "@odata.nextLink"?: string } = + await request.get(); // Group messages by conversationId to create thread-like structure const threadMap = new Map(); @@ -97,7 +104,7 @@ export async function getThreadsFromSender( sender: string, limit: number, ): Promise> { - const response = await client + const response: { value: Message[] } = await client .getClient() .api("/me/messages") .filter(`from/emailAddress/address eq '${escapeODataString(sender)}'`) @@ -124,7 +131,7 @@ export async function getThreadsFromSenderWithSubject( sender: string, limit: number, ): Promise> { - const response = await client + const response: { value: Message[] } = await client .getClient() .api("/me/messages") .filter(`from/emailAddress/address eq '${escapeODataString(sender)}'`) @@ -154,7 +161,7 @@ export async function getThreadMessages( threadId: string, client: OutlookClient, ): Promise { - const messages = await getThread(threadId, client); + const messages: Message[] = await getThread(threadId, client); return messages.map((msg) => ({ id: msg.id || "", diff --git a/biome.json b/biome.json index 451eec35db..ab6d71401d 100644 --- a/biome.json +++ b/biome.json @@ -24,7 +24,7 @@ }, "suspicious": { "noConsole": "warn", - "noExplicitAny": "off", + "noExplicitAny": "warn", "noArrayIndexKey": "off", "noEmptyBlockStatements": "off", "useAwait": "off", diff --git a/packages/resend/src/contacts.ts b/packages/resend/src/contacts.ts index 25e759a608..d85bc8d6d2 100644 --- a/packages/resend/src/contacts.ts +++ b/packages/resend/src/contacts.ts @@ -3,7 +3,7 @@ import { resend } from "./client"; export async function createContact(options: { email: string; audienceId?: string; -}): Promise { +}) { if (!resend) { console.warn("Resend not configured"); return; @@ -16,7 +16,7 @@ export async function createContact(options: { export async function deleteContact(options: { email: string; audienceId?: string; -}): Promise { +}) { if (!resend) { console.warn("Resend not configured"); return; diff --git a/version.txt b/version.txt index c9c156a3a8..bbd0034cee 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -v2.7.3 +v2.7.4