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 @@ -6,7 +6,11 @@ import { useRouter } from "next/navigation";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { useState, useEffect } from "react";
import { updateDigestCategoriesAction } from "@/utils/actions/settings";
import {
updateDigestCategoriesAction,
updateDigestScheduleAction,
ensureDefaultDigestScheduleAction,
} from "@/utils/actions/settings";
import { toastError, toastSuccess } from "@/components/Toast";
import { prefixPath } from "@/utils/path";
import { useAccount } from "@/providers/EmailAccountProvider";
Expand Down Expand Up @@ -53,6 +57,13 @@ export default function DigestFrequencyPage() {
}
}, [digestSettings]);

useEffect(() => {
// Ensure user has a digest schedule entry when they visit this page, otherwise the digest is not sent
const timeOfDay = new Date();
timeOfDay.setHours(11, 0, 0, 0); // 11 AM in user's timezone
ensureDefaultDigestScheduleAction(emailAccountId, { timeOfDay });
}, [emailAccountId]);
Comment on lines +60 to +65
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

Add error handling for the default schedule creation.

The useEffect lacks error handling for ensureDefaultDigestScheduleAction. While this action is designed to be safe, adding error handling would improve robustness.

  useEffect(() => {
    // Ensure user has a digest schedule entry when they visit this page, otherwise the digest is not sent
    const timeOfDay = new Date();
    timeOfDay.setHours(11, 0, 0, 0); // 11 AM in user's timezone
-   ensureDefaultDigestScheduleAction(emailAccountId, { timeOfDay });
+   ensureDefaultDigestScheduleAction(emailAccountId, { timeOfDay }).catch(
+     (error) => {
+       console.error("Failed to ensure default digest schedule:", error);
+       // Non-blocking error - user can still proceed with onboarding
+     }
+   );
  }, [emailAccountId]);
📝 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
useEffect(() => {
// Ensure user has a digest schedule entry when they visit this page, otherwise the digest is not sent
const timeOfDay = new Date();
timeOfDay.setHours(11, 0, 0, 0); // 11 AM in user's timezone
ensureDefaultDigestScheduleAction(emailAccountId, { timeOfDay });
}, [emailAccountId]);
useEffect(() => {
// Ensure user has a digest schedule entry when they visit this page, otherwise the digest is not sent
const timeOfDay = new Date();
timeOfDay.setHours(11, 0, 0, 0); // 11 AM in user's timezone
ensureDefaultDigestScheduleAction(emailAccountId, { timeOfDay }).catch(
(error) => {
console.error("Failed to ensure default digest schedule:", error);
// Non-blocking error - user can still proceed with onboarding
}
);
}, [emailAccountId]);
🤖 Prompt for AI Agents
In
apps/web/app/(app)/[emailAccountId]/assistant/onboarding/digest-frequency/page.tsx
around lines 60 to 65, the useEffect calls ensureDefaultDigestScheduleAction
without error handling. Wrap the call in a try-catch block or handle the promise
rejection to catch any errors during the default schedule creation, and log or
handle the error appropriately to improve robustness.


const updateDigestCategories = updateDigestCategoriesAction.bind(
null,
emailAccountId,
Expand All @@ -68,19 +79,43 @@ export default function DigestFrequencyPage() {
const handleFinish = async () => {
setIsLoading(true);
try {
// Save digest categories
const result = await updateDigestCategories(settings);

if (result?.serverError) {
toastError({
description: "Failed to save digest settings. Please try again.",
});
} else {
toastSuccess({ description: "Digest settings saved!" });
markOnboardingAsCompleted(ASSISTANT_ONBOARDING_COOKIE);
router.push(
prefixPath(emailAccountId, "/assistant/onboarding/completed"),
);
return;
}

// Ensure a default digest schedule is set if none exists
const updateDigestSchedule = updateDigestScheduleAction.bind(
null,
emailAccountId,
);

const scheduleResult = await updateDigestSchedule({
schedule: {
intervalDays: 7,
daysOfWeek: 1 << (6 - 1), // Monday (1)
timeOfDay: new Date(new Date().setHours(11, 0, 0, 0)), // 11 AM
occurrences: 1,
},
});

if (scheduleResult?.serverError) {
toastError({
description: "Failed to set digest frequency. Please try again.",
});
return;
}

toastSuccess({ description: "Digest settings saved!" });
markOnboardingAsCompleted(ASSISTANT_ONBOARDING_COOKIE);
router.push(
prefixPath(emailAccountId, "/assistant/onboarding/completed"),
);
} catch (error) {
toastError({
description: "Failed to save digest settings. Please try again.",
Expand Down
40 changes: 40 additions & 0 deletions apps/web/utils/actions/settings.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use server";

import { z } from "zod";
import { actionClient } from "@/utils/actions/safe-action";
import {
saveAiSettingsBody,
Expand Down Expand Up @@ -195,3 +196,42 @@ export const updateDigestCategoriesAction = actionClient
return { success: true };
},
);

export const ensureDefaultDigestScheduleAction = actionClient
.metadata({ name: "ensureDefaultDigestSchedule" })
.schema(z.object({ timeOfDay: z.date() }))
.action(async ({ ctx: { emailAccountId }, parsedInput: { timeOfDay } }) => {
try {
// Check if user already has a digest schedule
const existingSchedule = await prisma.schedule.findUnique({
where: { emailAccountId },
select: { id: true },
});

// If no schedule exists, create a default one
if (!existingSchedule) {
const defaultSchedule = {
intervalDays: 7,
daysOfWeek: 1 << (6 - 1), // Monday (bit 5 set = Monday)
timeOfDay: timeOfDay, // Use provided date/time from user to set the accurate users timezone
occurrences: 1,
};

await prisma.schedule.create({
data: {
emailAccountId,
intervalDays: defaultSchedule.intervalDays,
daysOfWeek: defaultSchedule.daysOfWeek,
timeOfDay: defaultSchedule.timeOfDay,
occurrences: defaultSchedule.occurrences,
lastOccurrenceAt: new Date(),
nextOccurrenceAt: calculateNextScheduleDate(defaultSchedule),
},
});
}

return { success: true };
} catch (error) {
throw new SafeError("Failed to ensure digest schedule", 500);
}
});
4 changes: 3 additions & 1 deletion apps/web/utils/ai/choose-rule/ai-choose-rule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ async function getAiResponse(options: GetAiResponseOptions) {
1. Match the email to a SPECIFIC user-defined rule that addresses the email's exact content or purpose.
2. If the email doesn't match any specific rule but the user has a catch-all rule (like "emails that don't match other criteria"), use that catch-all rule.
3. Only set "noMatchFound" to true if no user-defined rule can reasonably apply.
4. Be concise in your reasoning - avoid repetitive explanations.
5. Provide only the exact rule name from the list below.
</priority>

<guidelines>
Expand Down Expand Up @@ -62,7 +64,7 @@ ${
}

<outputFormat>
Respond with a JSON object with the following fields:
Respond with a valid JSON object with the following fields:
"reason" - the reason you chose that rule. Keep it concise.
"ruleName" - the exact name of the rule you want to apply
"noMatchFound" - true if no match was found, false otherwise
Expand Down
10 changes: 5 additions & 5 deletions packages/resend/emails/digest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ export default function DigestEmail(props: DigestEmailProps) {
{category.emoji} {category.name}
</Text>
<div
className={`${colorClasses[category.color as keyof typeof colorClasses].bgAccent} px-[8px] py-[2px] rounded-[12px]`}
className={`${colorClasses[category.color as keyof typeof colorClasses].bgAccent} px-[8px] py-[2px] ml-[8px] rounded-[12px]`}
>
<Text
className={`text-[12px] font-bold ${colorClasses[category.color as keyof typeof colorClasses].text} m-0`}
Expand Down Expand Up @@ -238,7 +238,7 @@ export default function DigestEmail(props: DigestEmailProps) {
{categories[i].emoji} {categories[i].name}
</Text>
<div
className={`${colorClasses[categories[i].color as keyof typeof colorClasses].bgAccent} px-[8px] py-[2px] rounded-[12px]`}
className={`${colorClasses[categories[i].color as keyof typeof colorClasses].bgAccent} px-[8px] py-[2px] ml-[8px] rounded-[12px]`}
>
<Text
className={`text-[12px] font-bold ${colorClasses[categories[i].color as keyof typeof colorClasses].text} m-0`}
Expand All @@ -261,7 +261,7 @@ export default function DigestEmail(props: DigestEmailProps) {
{categories[i + 1].emoji} {categories[i + 1].name}
</Text>
<div
className={`${colorClasses[categories[i + 1].color as keyof typeof colorClasses].bgAccent} px-[8px] py-[2px] rounded-[12px]`}
className={`${colorClasses[categories[i + 1].color as keyof typeof colorClasses].bgAccent} px-[8px] py-[2px] ml-[8px] rounded-[12px]`}
>
<Text
className={`text-[12px] font-bold ${colorClasses[categories[i + 1].color as keyof typeof colorClasses].text} m-0`}
Expand Down Expand Up @@ -296,7 +296,7 @@ export default function DigestEmail(props: DigestEmailProps) {
{categories[i].emoji} {categories[i].name}
</Text>
<div
className={`${colorClasses[categories[i].color as keyof typeof colorClasses].bgAccent} px-[8px] py-[2px] rounded-[12px]`}
className={`${colorClasses[categories[i].color as keyof typeof colorClasses].bgAccent} px-[8px] py-[2px] ml-[8px] rounded-[12px]`}
>
<Text
className={`text-[12px] font-bold ${colorClasses[categories[i].color as keyof typeof colorClasses].text} m-0`}
Expand All @@ -318,7 +318,7 @@ export default function DigestEmail(props: DigestEmailProps) {
{categories[i + 1].emoji} {categories[i + 1].name}
</Text>
<div
className={`${colorClasses[categories[i + 1].color as keyof typeof colorClasses].bgAccent} px-[8px] py-[2px] rounded-[12px]`}
className={`${colorClasses[categories[i + 1].color as keyof typeof colorClasses].bgAccent} px-[8px] py-[2px] ml-[8px] rounded-[12px]`}
>
<Text
className={`text-[12px] font-bold ${colorClasses[categories[i + 1].color as keyof typeof colorClasses].text} m-0`}
Expand Down
Loading