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 @@ -5,33 +5,17 @@ import { useRouter } from "next/navigation";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import type { ControllerRenderProps } from "react-hook-form";
import {
Mail,
Newspaper,
Megaphone,
Calendar,
Receipt,
Bell,
Users,
} from "lucide-react";
import { TypographyH3, TypographyP } from "@/components/Typography";
import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
} from "@/components/ui/form";
import { Form, FormControl, FormField, FormItem } from "@/components/ui/form";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox";
import { createRulesOnboardingAction } from "@/utils/actions/rule";
import {
createRulesOnboardingBody,
Expand All @@ -43,13 +27,12 @@ import {
markOnboardingAsCompleted,
} from "@/utils/cookies";
import { prefixPath } from "@/utils/path";
import { useDigestEnabled } from "@/hooks/useFeatureFlags";
import { ClientOnly } from "@/components/ClientOnly";
import Image from "next/image";
import {
ExampleDialog,
SeeExampleDialogButton,
} from "@/app/(app)/[emailAccountId]/assistant/onboarding/ExampleDialog";
import { categoryConfig } from "@/utils/category-config";

const NEXT_URL = "/assistant/onboarding/draft-replies";

Expand All @@ -69,31 +52,24 @@ export function CategoriesSetup({
defaultValues: {
toReply: {
action: defaultValues?.toReply?.action || "label",
hasDigest: defaultValues?.toReply?.hasDigest || false,
},
newsletter: {
action: defaultValues?.newsletter?.action || "label",
hasDigest: defaultValues?.newsletter?.hasDigest || false,
},
marketing: {
action: defaultValues?.marketing?.action || "label_archive",
hasDigest: defaultValues?.marketing?.hasDigest || false,
},
calendar: {
action: defaultValues?.calendar?.action || "label",
hasDigest: defaultValues?.calendar?.hasDigest || false,
},
receipt: {
action: defaultValues?.receipt?.action || "label",
hasDigest: defaultValues?.receipt?.hasDigest || false,
},
notification: {
action: defaultValues?.notification?.action || "label",
hasDigest: defaultValues?.notification?.hasDigest || false,
},
coldEmail: {
action: defaultValues?.coldEmail?.action || "label_archive",
hasDigest: defaultValues?.coldEmail?.hasDigest || false,
},
},
});
Expand Down Expand Up @@ -139,55 +115,16 @@ export function CategoriesSetup({
/>

<div className="mt-4 grid grid-cols-1 gap-4">
<CategoryCard
id="toReply"
label="To Reply"
tooltipText="Emails you need to reply to and those where you're awaiting a reply. The label will update automatically as the conversation progresses"
icon={<Mail className="h-5 w-5 text-blue-500" />}
form={form}
/>
<CategoryCard
id="newsletter"
label="Newsletter"
tooltipText="Newsletters, blogs, and publications"
icon={<Newspaper className="h-5 w-5 text-purple-500" />}
form={form}
/>
<CategoryCard
id="marketing"
label="Marketing"
tooltipText="Promotional emails about sales and offers"
icon={<Megaphone className="h-5 w-5 text-green-500" />}
form={form}
/>
<CategoryCard
id="calendar"
label="Calendar"
tooltipText="Events, appointments, and reminders"
icon={<Calendar className="h-5 w-5 text-yellow-500" />}
form={form}
/>
<CategoryCard
id="receipt"
label="Receipt"
tooltipText="Invoices, receipts, and payments"
icon={<Receipt className="h-5 w-5 text-orange-500" />}
form={form}
/>
<CategoryCard
id="notification"
label="Notification"
tooltipText="Alerts, status updates, and system messages"
icon={<Bell className="h-5 w-5 text-red-500" />}
form={form}
/>
<CategoryCard
id="coldEmail"
label="Cold Email"
tooltipText="Unsolicited sales pitches and cold emails. We'll never block someone that's emailed you before"
icon={<Users className="h-5 w-5 text-indigo-500" />}
form={form}
/>
{categoryConfig.map((category) => (
<CategoryCard
key={category.key}
id={category.key}
label={category.label}
tooltipText={category.tooltipText}
icon={category.icon}
form={form}
/>
))}
</div>

<div className="mt-6 flex flex-col gap-2">
Expand Down Expand Up @@ -239,9 +176,6 @@ function CategoryCard({
)}
</div>
<div className="ml-auto flex items-center gap-4">
<ClientOnly>
<DigestCheckbox form={form} id={id} />
</ClientOnly>
<FormField
control={form.control}
name={id}
Expand Down Expand Up @@ -284,45 +218,3 @@ function CategoryCard({
</Card>
);
}

function DigestCheckbox({
form,
id,
}: {
form: ReturnType<typeof useForm<CreateRulesOnboardingBody>>;
id: keyof CreateRulesOnboardingBody;
}) {
const digestEnabled = useDigestEnabled();

if (!digestEnabled) return null;

return (
<FormField
control={form.control}
name={id}
render={({
field,
}: {
field: ControllerRenderProps<
CreateRulesOnboardingBody,
keyof CreateRulesOnboardingBody
>;
}) => (
<FormItem className="flex flex-row items-center space-x-2 space-y-0">
<FormControl>
<Checkbox
checked={!!field.value?.hasDigest}
onCheckedChange={(checked) => {
field.onChange({
...field.value,
hasDigest: checked,
});
}}
/>
</FormControl>
<FormLabel className="text-sm font-normal">Digest</FormLabel>
</FormItem>
)}
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
"use client";

import { useState } from "react";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { SchedulePicker } from "@/app/(app)/[emailAccountId]/settings/SchedulePicker";
import { updateDigestScheduleAction } from "@/utils/actions/settings";
import { toastError, toastSuccess } from "@/components/Toast";
import type { SaveDigestScheduleBody } from "@/utils/actions/settings.validation";
import { useAccount } from "@/providers/EmailAccountProvider";

interface DigestFrequencyDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
}

export function DigestFrequencyDialog({
open,
onOpenChange,
}: DigestFrequencyDialogProps) {
const { emailAccountId } = useAccount();
const [isLoading, setIsLoading] = useState(false);
const [digestScheduleValue, setDigestScheduleValue] = useState<
SaveDigestScheduleBody["schedule"]
>({
intervalDays: 7,
daysOfWeek: 1 << (6 - 1), // Monday (1)
timeOfDay: new Date(new Date().setHours(11, 0, 0, 0)), // 11 AM
occurrences: 1,
});

const updateDigestSchedule = updateDigestScheduleAction.bind(
null,
emailAccountId,
);

const handleSave = async () => {
if (!digestScheduleValue) return;

setIsLoading(true);
try {
const result = await updateDigestSchedule({
schedule: digestScheduleValue,
});

if (result?.serverError) {
toastError({
description: "Failed to save digest frequency. Please try again.",
});
} else {
toastSuccess({ description: "Digest frequency saved!" });
onOpenChange(false);
}
} catch (error) {
toastError({
description: "Failed to save digest frequency. Please try again.",
});
} finally {
setIsLoading(false);
}
};

return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Digest Email Frequency</DialogTitle>
<DialogDescription>
Choose how often you want to receive your digest emails. These
emails will include a summary of the actions taken on your behalf.
</DialogDescription>
</DialogHeader>
<div className="py-4">
<SchedulePicker onChange={setDigestScheduleValue} />
</div>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}>
Cancel
</Button>
<Button onClick={handleSave} loading={isLoading}>
Save
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
Loading
Loading