diff --git a/apps/web/app/(app)/[emailAccountId]/automation/Rules.tsx b/apps/web/app/(app)/[emailAccountId]/automation/Rules.tsx
index 461048fbec..09d231f58a 100644
--- a/apps/web/app/(app)/[emailAccountId]/automation/Rules.tsx
+++ b/apps/web/app/(app)/[emailAccountId]/automation/Rules.tsx
@@ -13,6 +13,7 @@ import {
ToggleLeftIcon,
SlidersIcon,
} from "lucide-react";
+import { useMemo } from "react";
import { LoadingContent } from "@/components/LoadingContent";
import { Button } from "@/components/ui/button";
import {
@@ -44,22 +45,25 @@ import { Toggle } from "@/components/Toggle";
import { conditionsToString } from "@/utils/condition";
import { Badge } from "@/components/Badge";
import { getActionColor } from "@/components/PlanBadge";
-import { PremiumAlertWithData } from "@/components/PremiumAlert";
import { toastError, toastSuccess } from "@/components/Toast";
import { Tooltip } from "@/components/Tooltip";
-import type { RiskLevel } from "@/utils/risk";
import { useRules } from "@/hooks/useRules";
-import { ActionType } from "@prisma/client";
+import { ActionType, ColdEmailSetting, LogicalOperator } from "@prisma/client";
import { ThreadsExplanation } from "@/app/(app)/[emailAccountId]/automation/RuleForm";
import { useAction } from "next-safe-action/hooks";
import { useAccount } from "@/providers/EmailAccountProvider";
import { prefixPath } from "@/utils/path";
import { ExpandableText } from "@/components/ExpandableText";
+import { useEmailAccountFull } from "@/hooks/useEmailAccountFull";
+import type { RulesResponse } from "@/app/api/user/rules/route";
+import { inboxZeroLabels } from "@/utils/label";
+import { isDefined } from "@/utils/types";
+
+const COLD_EMAIL_BLOCKER_RULE_ID = "cold-email-blocker-rule";
export function Rules({ size = "md" }: { size?: "sm" | "md" }) {
const { data, isLoading, error, mutate } = useRules();
-
- const hasRules = !!data?.length;
+ const { data: emailAccountData } = useEmailAccountFull();
const { emailAccountId } = useAccount();
const { executeAsync: setRuleRunOnThreads } = useAction(
@@ -72,6 +76,93 @@ export function Rules({ size = "md" }: { size?: "sm" | "md" }) {
deleteRuleAction.bind(null, emailAccountId),
);
+ const baseRules: RulesResponse = useMemo(() => {
+ return (
+ data?.sort((a, b) => (b.enabled ? 1 : 0) - (a.enabled ? 1 : 0)) || []
+ );
+ }, [data]);
+
+ const rules: RulesResponse = useMemo(() => {
+ const enabled: ColdEmailSetting[] = [
+ ColdEmailSetting.LABEL,
+ ColdEmailSetting.ARCHIVE_AND_LABEL,
+ ColdEmailSetting.ARCHIVE_AND_READ_AND_LABEL,
+ ];
+
+ const shouldArchived: ColdEmailSetting[] = [
+ ColdEmailSetting.ARCHIVE_AND_LABEL,
+ ColdEmailSetting.ARCHIVE_AND_READ_AND_LABEL,
+ ];
+
+ const coldEmailBlockerEnabled =
+ emailAccountData?.coldEmailBlocker &&
+ enabled.includes(emailAccountData?.coldEmailBlocker);
+
+ if (!coldEmailBlockerEnabled) return baseRules;
+
+ const showArchiveAction =
+ emailAccountData?.coldEmailBlocker &&
+ shouldArchived.includes(emailAccountData?.coldEmailBlocker);
+
+ // Works differently to rules, but we want to show it in the list for user simplicity
+ const coldEmailBlockerRule: RulesResponse[number] = {
+ id: COLD_EMAIL_BLOCKER_RULE_ID,
+ name: "Block Cold Emails",
+ instructions: emailAccountData?.coldEmailPrompt || null,
+ automate: true,
+ enabled: true,
+ runOnThreads: false,
+ actions: [
+ {
+ id: "cold-email-blocker-label",
+ type: ActionType.LABEL,
+ label: inboxZeroLabels.cold_email.name.split("/")[1],
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ ruleId: COLD_EMAIL_BLOCKER_RULE_ID,
+ to: null,
+ subject: null,
+ content: null,
+ cc: null,
+ bcc: null,
+ url: null,
+ },
+ showArchiveAction
+ ? {
+ id: "cold-email-blocker-archive",
+ type: ActionType.ARCHIVE,
+ label: null,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ ruleId: COLD_EMAIL_BLOCKER_RULE_ID,
+ to: null,
+ subject: null,
+ content: null,
+ cc: null,
+ bcc: null,
+ url: null,
+ }
+ : null,
+ ].filter(isDefined),
+ categoryFilters: [],
+ group: null,
+ emailAccountId: emailAccountId,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ categoryFilterType: null,
+ conditionalOperator: LogicalOperator.OR,
+ groupId: null,
+ systemType: null,
+ to: null,
+ from: null,
+ subject: null,
+ body: null,
+ };
+ return [...(baseRules || []), coldEmailBlockerRule];
+ }, [baseRules, emailAccountData, emailAccountId]);
+
+ const hasRules = !!rules?.length;
+
return (
@@ -97,21 +188,23 @@ export function Rules({ size = "md" }: { size?: "sm" | "md" }) {
- {data
- ?.sort((a, b) => (b.enabled ? 1 : 0) - (a.enabled ? 1 : 0))
- .map((rule) => (
+ {rules.map((rule) => {
+ const isColdEmailBlocker =
+ rule.id === COLD_EMAIL_BLOCKER_RULE_ID;
+ const href = isColdEmailBlocker
+ ? prefixPath(
+ emailAccountId,
+ "/cold-email-blocker?tab=settings",
+ )
+ : prefixPath(emailAccountId, `/automation/rule/${rule.id}`);
+
+ return (
-
+
{!rule.enabled && (
Disabled
@@ -145,6 +238,8 @@ export function Rules({ size = "md" }: { size?: "sm" | "md" }) {
enabled={rule.runOnThreads}
name="runOnThreads"
onChange={async () => {
+ if (isColdEmailBlocker) return;
+
const result = await setRuleRunOnThreads({
ruleId: rule.id,
runOnThreads: !rule.runOnThreads,
@@ -176,10 +271,10 @@ export function Rules({ size = "md" }: { size?: "sm" | "md" }) {
Edit
@@ -187,92 +282,109 @@ export function Rules({ size = "md" }: { size?: "sm" | "md" }) {
History
- {
- const result = await setRuleEnabled({
- ruleId: rule.id,
- enabled: !rule.enabled,
- });
+ {!isColdEmailBlocker && (
+ <>
+ {
+ const result = await setRuleEnabled({
+ ruleId: rule.id,
+ enabled: !rule.enabled,
+ });
- if (result?.serverError) {
- toastError({
- description: `There was an error ${
- rule.enabled ? "disabling" : "enabling"
- } your rule. ${result.serverError || ""}`,
- });
- } else {
- toastSuccess({
- description: `Rule ${
- rule.enabled ? "disabled" : "enabled"
- }!`,
- });
- }
-
- mutate();
- }}
- >
- {rule.enabled ? (
-
- ) : (
-
- )}
- {rule.enabled ? "Disable" : "Enable"}
-
- {
- const yes = confirm(
- "Are you sure you want to delete this rule?",
- );
- if (yes) {
- toast.promise(
- async () => {
- const res = await deleteRule({
- id: rule.id,
+ if (result?.serverError) {
+ toastError({
+ description: `There was an error ${
+ rule.enabled
+ ? "disabling"
+ : "enabling"
+ } your rule. ${result.serverError || ""}`,
});
+ } else {
+ toastSuccess({
+ description: `Rule ${
+ rule.enabled ? "disabled" : "enabled"
+ }!`,
+ });
+ }
- if (
- res?.serverError ||
- res?.validationErrors ||
- res?.bindArgsValidationErrors
- ) {
- throw new Error(
- res?.serverError ||
- "There was an error deleting your rule",
- );
- }
+ mutate();
+ }}
+ >
+ {rule.enabled ? (
+
+ ) : (
+
+ )}
+ {rule.enabled ? "Disable" : "Enable"}
+
+ {
+ const yes = confirm(
+ "Are you sure you want to delete this rule?",
+ );
+ if (yes) {
+ toast.promise(
+ async () => {
+ const res = await deleteRule({
+ id: rule.id,
+ });
- mutate();
- },
- {
- loading: "Deleting rule...",
- success: "Rule deleted",
- error: (error) =>
- `Error deleting rule. ${error.message}`,
- finally: () => {
- mutate();
- },
- },
- );
- }
- }}
- >
-
- Delete
-
+ if (
+ res?.serverError ||
+ res?.validationErrors ||
+ res?.bindArgsValidationErrors
+ ) {
+ throw new Error(
+ res?.serverError ||
+ "There was an error deleting your rule",
+ );
+ }
+
+ mutate();
+ },
+ {
+ loading: "Deleting rule...",
+ success: "Rule deleted",
+ error: (error) =>
+ `Error deleting rule. ${error.message}`,
+ finally: () => {
+ mutate();
+ },
+ },
+ );
+ }
+ }}
+ >
+
+ Delete
+
+ >
+ )}
- ))}
+ );
+ })}
) : (
diff --git a/apps/web/components/SideNav.tsx b/apps/web/components/SideNav.tsx
index 920d26f45c..abce0b3d72 100644
--- a/apps/web/components/SideNav.tsx
+++ b/apps/web/components/SideNav.tsx
@@ -72,7 +72,7 @@ export const useNavigation = () => {
const assistantItems: NavItem[] = useMemo(
() => [
{
- name: "Personal Assistant",
+ name: "Assistant",
href: prefixPath(emailAccountId, "/automation"),
icon: SparklesIcon,
},
@@ -82,7 +82,7 @@ export const useNavigation = () => {
icon: MessageCircleReplyIcon,
},
{
- name: "Cold Email Blocker",
+ name: "Cold Blocker",
href: prefixPath(emailAccountId, "/cold-email-blocker"),
icon: ShieldCheckIcon,
},
@@ -94,7 +94,7 @@ export const useNavigation = () => {
const cleanItems: NavItem[] = useMemo(
() => [
{
- name: "Bulk Unsubscribe",
+ name: "Unsubscribe",
href: prefixPath(emailAccountId, "/bulk-unsubscribe"),
icon: MailsIcon,
},
@@ -264,7 +264,7 @@ export function SideNav({ ...props }: React.ComponentProps) {
{state === "expanded" ? (
-
+
diff --git a/apps/web/components/ui/sidebar.tsx b/apps/web/components/ui/sidebar.tsx
index 9f14366684..558e8359d1 100644
--- a/apps/web/components/ui/sidebar.tsx
+++ b/apps/web/components/ui/sidebar.tsx
@@ -190,7 +190,6 @@ const Sidebar = React.forwardRef<
return (