diff --git a/apps/web/app/(app)/[emailAccountId]/assistant/ResultDisplay.tsx b/apps/web/app/(app)/[emailAccountId]/assistant/ResultDisplay.tsx index 7602e21f49..198793228a 100644 --- a/apps/web/app/(app)/[emailAccountId]/assistant/ResultDisplay.tsx +++ b/apps/web/app/(app)/[emailAccountId]/assistant/ResultDisplay.tsx @@ -213,7 +213,10 @@ function Actions({ {fields.length > 0 && (
{fields.map((field) => ( -
+
{field.key}:{" "} {field.value}
diff --git a/apps/web/app/(app)/[emailAccountId]/debug/page.tsx b/apps/web/app/(app)/[emailAccountId]/debug/page.tsx index 2dfba75499..22a39db1ca 100644 --- a/apps/web/app/(app)/[emailAccountId]/debug/page.tsx +++ b/apps/web/app/(app)/[emailAccountId]/debug/page.tsx @@ -14,6 +14,9 @@ export default async function DebugPage(props: { Debug
+ diff --git a/apps/web/app/(app)/[emailAccountId]/debug/rules/page.tsx b/apps/web/app/(app)/[emailAccountId]/debug/rules/page.tsx new file mode 100644 index 0000000000..cc5e58de64 --- /dev/null +++ b/apps/web/app/(app)/[emailAccountId]/debug/rules/page.tsx @@ -0,0 +1,96 @@ +"use client"; + +import { useCallback, useState } from "react"; +import useSWR from "swr"; +import { useAction } from "next-safe-action/hooks"; +import { CopyIcon, CheckIcon } from "lucide-react"; +import { PageHeading } from "@/components/Typography"; +import { PageWrapper } from "@/components/PageWrapper"; +import { LoadingContent } from "@/components/LoadingContent"; +import { Button } from "@/components/ui/button"; +import { Switch } from "@/components/ui/switch"; +import { Label } from "@/components/ui/label"; +import { toastSuccess, toastError } from "@/components/Toast"; +import { toggleAllRulesAction } from "@/utils/actions/rule"; +import type { DebugRulesResponse } from "@/app/api/user/debug/rules/route"; +import { useAccount } from "@/providers/EmailAccountProvider"; + +export default function DebugRulesPage() { + const { emailAccountId } = useAccount(); + const { data, isLoading, error, mutate } = useSWR( + "/api/user/debug/rules", + ); + const [copied, setCopied] = useState(false); + const allRulesEnabled = data?.every((rule) => rule.enabled) ?? false; + const someRulesEnabled = data?.some((rule) => rule.enabled) ?? false; + const { execute, isExecuting } = useAction( + toggleAllRulesAction.bind(null, emailAccountId), + { + onSuccess: () => { + toastSuccess({ description: "Rules updated successfully" }); + mutate(); + }, + onError: (result) => { + toastError({ + title: "Failed to update rules", + description: result.error.serverError || "Unknown error", + }); + }, + }, + ); + + const handleCopy = useCallback(() => { + if (!data) return; + navigator.clipboard.writeText(JSON.stringify(data, null, 2)); + setCopied(true); + toastSuccess({ description: "Copied to clipboard" }); + setTimeout(() => setCopied(false), 2000); + }, [data]); + + return ( + + Rules + + +
+
+
+ execute({ enabled })} + disabled={isExecuting} + /> + +
+ +
+ +
+
+              {data ? JSON.stringify(data, null, 2) : "Loading..."}
+            
+
+
+
+
+ ); +} diff --git a/apps/web/app/api/user/debug/rules/route.ts b/apps/web/app/api/user/debug/rules/route.ts new file mode 100644 index 0000000000..26afffd98a --- /dev/null +++ b/apps/web/app/api/user/debug/rules/route.ts @@ -0,0 +1,64 @@ +import { NextResponse } from "next/server"; +import { withEmailAccount } from "@/utils/middleware"; +import prisma from "@/utils/prisma"; + +export type DebugRulesResponse = Awaited>; + +export const GET = withEmailAccount("user/debug/rules", async (request) => { + const emailAccountId = request.auth.emailAccountId; + const result = await getDebugRules({ emailAccountId }); + return NextResponse.json(result); +}); + +async function getDebugRules({ emailAccountId }: { emailAccountId: string }) { + const rules = await prisma.rule.findMany({ + where: { emailAccountId }, + include: { + actions: true, + group: { + select: { + id: true, + name: true, + _count: { + select: { items: true }, + }, + }, + }, + }, + orderBy: { createdAt: "asc" }, + }); + + return rules.map((rule) => ({ + id: rule.id, + name: rule.name, + enabled: rule.enabled, + automate: rule.automate, + runOnThreads: rule.runOnThreads, + conditionalOperator: rule.conditionalOperator, + systemType: rule.systemType, + instructions: rule.instructions, + from: rule.from, + to: rule.to, + subject: rule.subject, + body: rule.body, + promptText: rule.promptText, + actions: rule.actions.map((action) => ({ + id: action.id, + type: action.type, + label: action.label, + labelId: action.labelId, + subject: action.subject, + content: action.content, + to: action.to, + cc: action.cc, + bcc: action.bcc, + url: action.url, + folderName: action.folderName, + folderId: action.folderId, + delayInMinutes: action.delayInMinutes, + })), + learnedPatternsCount: rule.group?._count.items ?? 0, + createdAt: rule.createdAt, + updatedAt: rule.updatedAt, + })); +} diff --git a/apps/web/components/HoverCard.tsx b/apps/web/components/HoverCard.tsx index 0081254ca9..20458a305a 100644 --- a/apps/web/components/HoverCard.tsx +++ b/apps/web/components/HoverCard.tsx @@ -3,6 +3,7 @@ import { HoverCardContent, HoverCardTrigger, } from "@/components/ui/hover-card"; +import { cn } from "@/utils"; export function HoverCard(props: { children: React.ReactNode; @@ -12,7 +13,11 @@ export function HoverCard(props: { return ( {props.children} - + {props.content} diff --git a/apps/web/utils/actions/rule.ts b/apps/web/utils/actions/rule.ts index e15d400e10..c224bade20 100644 --- a/apps/web/utils/actions/rule.ts +++ b/apps/web/utils/actions/rule.ts @@ -14,6 +14,7 @@ import { type CategoryConfig, type CategoryAction, toggleRuleBody, + toggleAllRulesBody, } from "@/utils/actions/rule.validation"; import prisma from "@/utils/prisma"; import { isDuplicateError, isNotFoundError } from "@/utils/prisma-helpers"; @@ -443,6 +444,18 @@ export const toggleRuleAction = actionClient }, ); +export const toggleAllRulesAction = actionClient + .metadata({ name: "toggleAllRules" }) + .inputSchema(toggleAllRulesBody) + .action(async ({ ctx: { emailAccountId }, parsedInput: { enabled } }) => { + await prisma.rule.updateMany({ + where: { emailAccountId }, + data: { enabled }, + }); + + return { success: true }; + }); + async function toggleRule({ ruleId, systemType, diff --git a/apps/web/utils/actions/rule.validation.ts b/apps/web/utils/actions/rule.validation.ts index 0691b4bada..d88f87e24a 100644 --- a/apps/web/utils/actions/rule.validation.ts +++ b/apps/web/utils/actions/rule.validation.ts @@ -266,3 +266,8 @@ export const toggleRuleBody = z .refine((data) => data.ruleId || data.systemType, { message: "Either ruleId or systemType must be provided", }); + +export const toggleAllRulesBody = z.object({ + enabled: z.boolean(), +}); +export type ToggleAllRulesBody = z.infer; diff --git a/version.txt b/version.txt index fb7994a2e3..21348d8e16 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -v2.23.5 +v2.23.6