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
16 changes: 16 additions & 0 deletions apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,22 @@ export function Rules({ size = "md" }: { size?: "sm" | "md" }) {
url: null,
}
: null,
emailAccountData?.coldEmailDigest
? {
id: "cold-email-blocker-digest",
type: ActionType.DIGEST,
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,
Expand Down
88 changes: 69 additions & 19 deletions apps/web/app/(app)/[emailAccountId]/settings/DigestItemsForm.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useCallback, useEffect } from "react";
import { useForm, type SubmitHandler } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import useSWR from "swr";
import { Label } from "@/components/ui/label";
import { Button } from "@/components/ui/button";
import { toastError, toastSuccess } from "@/components/Toast";
Expand All @@ -14,10 +15,26 @@ import {
} from "@/utils/actions/settings.validation";
import { ActionType } from "@prisma/client";
import { useAccount } from "@/providers/EmailAccountProvider";
import type { GetDigestSettingsResponse } from "@/app/api/user/digest-settings/route";
import { Skeleton } from "@/components/ui/skeleton";

export function DigestItemsForm() {
const { emailAccountId } = useAccount();
const { data: rules, isLoading, error, mutate } = useRules();
const {
data: rules,
isLoading: rulesLoading,
error: rulesError,
mutate: mutateRules,
} = useRules();
const {
data: digestSettings,
isLoading: digestLoading,
error: digestError,
mutate: mutateDigestSettings,
} = useSWR<GetDigestSettingsResponse>("/api/user/digest-settings");

const isLoading = rulesLoading || digestLoading;
const error = rulesError || digestError;

const {
handleSubmit,
Expand All @@ -29,14 +46,16 @@ export function DigestItemsForm() {
resolver: zodResolver(updateDigestItemsBody),
defaultValues: {
ruleDigestPreferences: {},
coldEmailDigest: false,
},
});

const ruleDigestPreferences = watch("ruleDigestPreferences");
const coldEmailDigest = watch("coldEmailDigest");

// Initialize preferences from rules data
// Initialize preferences from rules and digest settings data
useEffect(() => {
if (rules) {
if (rules && digestSettings) {
const preferences: Record<string, boolean> = {};
rules.forEach((rule) => {
preferences[rule.id] = rule.actions.some(
Expand All @@ -45,9 +64,10 @@ export function DigestItemsForm() {
});
reset({
ruleDigestPreferences: preferences,
coldEmailDigest: digestSettings.coldEmail || false,
});
}
}, [rules, reset]);
}, [rules, digestSettings, reset]);

const handleRuleDigestToggle = useCallback(
(ruleId: string, enabled: boolean) => {
Expand All @@ -56,6 +76,13 @@ export function DigestItemsForm() {
[setValue],
);

const handleColdEmailDigestToggle = useCallback(
(enabled: boolean) => {
setValue("coldEmailDigest", enabled);
},
[setValue],
);

const onSubmit: SubmitHandler<UpdateDigestItemsBody> = useCallback(
async (data) => {
const result = await updateDigestItemsAction(emailAccountId, data);
Expand All @@ -67,33 +94,37 @@ export function DigestItemsForm() {
});
} else {
toastSuccess({ description: "Your digest items have been updated!" });
mutate();
mutateRules();
mutateDigestSettings();
}
},
[mutate, emailAccountId],
[mutateRules, mutateDigestSettings, emailAccountId],
);

return (
<LoadingContent loading={isLoading} error={error}>
<LoadingContent
loading={isLoading}
error={error}
loadingComponent={<Skeleton className="min-h-[500px] w-full" />}
>
<form onSubmit={handleSubmit(onSubmit)}>
<Label>Choose what to include in the digest:</Label>

<div className="mt-2 space-y-2">
{rules?.map((rule) => (
<div
<DigestItem
key={rule.id}
className="flex items-center gap-4 rounded-lg border p-4"
>
<div className="flex flex-1 items-center gap-2">
<span className="font-medium">{rule.name}</span>
</div>
<Toggle
name={`rule-${rule.id}`}
enabled={ruleDigestPreferences[rule.id] ?? false}
onChange={(enabled) => handleRuleDigestToggle(rule.id, enabled)}
/>
</div>
label={rule.name}
enabled={ruleDigestPreferences[rule.id] ?? false}
onChange={(enabled) => handleRuleDigestToggle(rule.id, enabled)}
/>
))}

<DigestItem
label="Cold Emails"
enabled={coldEmailDigest ?? false}
onChange={handleColdEmailDigestToggle}
/>
</div>

<Button type="submit" loading={isSubmitting} className="mt-4">
Expand All @@ -103,3 +134,22 @@ export function DigestItemsForm() {
</LoadingContent>
);
}

function DigestItem({
label,
enabled,
onChange,
}: {
label: string;
enabled: boolean;
onChange: (enabled: boolean) => void;
}) {
return (
<div className="flex items-center gap-4 rounded-lg border p-4">
<div className="flex flex-1 items-center gap-2">
<span className="font-medium">{label}</span>
</div>
<Toggle name={label} enabled={enabled} onChange={onChange} />
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import type { GetDigestScheduleResponse } from "@/app/api/user/digest-schedule/r
import { LoadingContent } from "@/components/LoadingContent";
import { ErrorMessage } from "@/components/Input";
import { zodResolver } from "@hookform/resolvers/zod";
import { Skeleton } from "@/components/ui/skeleton";

const digestScheduleFormSchema = z.object({
schedule: z.string().min(1, "Please select a frequency"),
Expand Down Expand Up @@ -73,7 +74,11 @@ export function DigestScheduleForm() {
);

return (
<LoadingContent loading={isLoading} error={error}>
<LoadingContent
loading={isLoading}
error={error}
loadingComponent={<Skeleton className="min-h-[200px] w-full" />}
>
<DigestScheduleFormInner data={data} mutate={mutate} />
</LoadingContent>
);
Expand Down
14 changes: 13 additions & 1 deletion apps/web/utils/actions/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export const updateDigestItemsAction = actionClient
.action(
async ({
ctx: { emailAccountId },
parsedInput: { ruleDigestPreferences },
parsedInput: { ruleDigestPreferences, coldEmailDigest },
}) => {
const logger = createScopedLogger("updateDigestItems").with({
emailAccountId,
Expand Down Expand Up @@ -130,6 +130,18 @@ export const updateDigestItemsAction = actionClient
},
);

// Handle cold email digest setting separately
if (coldEmailDigest !== undefined) {
promises.push(
prisma.emailAccount
.update({
where: { id: emailAccountId },
data: { coldEmailDigest },
})
.then(() => {}),
);
}

await Promise.all(promises);
return { success: true };
},
Expand Down
1 change: 1 addition & 0 deletions apps/web/utils/actions/settings.validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,6 @@ export type SaveAiSettingsBody = z.infer<typeof saveAiSettingsBody>;

export const updateDigestItemsBody = z.object({
ruleDigestPreferences: z.record(z.string(), z.boolean()),
coldEmailDigest: z.boolean().optional(),
});
export type UpdateDigestItemsBody = z.infer<typeof updateDigestItemsBody>;
2 changes: 1 addition & 1 deletion version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v1.9.7
v1.9.8
Loading