Skip to content

Commit

Permalink
Merge pull request #304 from elie222/email-side-panel
Browse files Browse the repository at this point in the history
View emails in side panel
  • Loading branch information
elie222 authored Jan 11, 2025
2 parents 53fc47c + 5915174 commit 00f9065
Show file tree
Hide file tree
Showing 22 changed files with 299 additions and 163 deletions.
19 changes: 12 additions & 7 deletions apps/web/app/(app)/automation/ExecutedRulesTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,35 @@ import { conditionsToString, conditionTypesToString } from "@/utils/condition";
import { MessageText } from "@/components/Typography";
import { ReportMistake } from "@/app/(app)/automation/ReportMistake";
import type { ParsedMessage } from "@/utils/types";
import { useDisplayedEmail } from "@/hooks/useDisplayedEmail";
import { ViewEmailButton } from "@/components/ViewEmailButton";

export function EmailCell({
from,
subject,
snippet,
threadId,
messageId,
userEmail,
}: {
from: string;
subject: string;
snippet: string;
threadId: string;
messageId: string;
userEmail: string;
}) {
// use regex to find first letter
const firstLetter = from.match(/[a-zA-Z]/)?.[0] || "-";

const { showEmail } = useDisplayedEmail();

return (
<div className="flex items-center gap-4">
<Avatar>
<AvatarFallback>{firstLetter}</AvatarFallback>
</Avatar>
<div className="flex flex-col justify-center">
<div className="flex flex-1 flex-col justify-center">
<div className="font-semibold">{from}</div>
<div className="mt-1 flex items-center font-medium">
{subject}{" "}
Expand All @@ -46,6 +52,7 @@ export function EmailCell({
{decodeSnippet(snippet)}
</div>
</div>
<ViewEmailButton threadId={threadId} messageId={messageId} />
</div>
);
}
Expand Down Expand Up @@ -136,14 +143,12 @@ function OpenInGmailButton({
userEmail: string;
}) {
return (
<button
type="button"
<Link
href={getGmailUrl(messageId, userEmail)}
target="_blank"
className="ml-2 text-gray-700 hover:text-gray-900"
onClick={() => {
window.open(getGmailUrl(messageId, userEmail), "_blank");
}}
>
<ExternalLinkIcon className="h-4 w-4" />
</button>
</Link>
);
}
2 changes: 2 additions & 0 deletions apps/web/app/(app)/automation/History.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
import { TablePagination } from "@/components/TablePagination";
import { Badge } from "@/components/Badge";
import { RulesSelect } from "@/app/(app)/automation/RulesSelect";
import { useDisplayedEmail } from "@/hooks/useDisplayedEmail";

export function History() {
const [page] = useQueryState("page", parseAsInteger.withDefault(1));
Expand Down Expand Up @@ -95,6 +96,7 @@ function HistoryTable({
from={p.message.headers.from}
subject={p.message.headers.subject}
snippet={p.message.snippet}
threadId={p.message.threadId}
messageId={p.message.id}
userEmail={userEmail}
/>
Expand Down
3 changes: 2 additions & 1 deletion apps/web/app/(app)/automation/Pending.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { RulesSelect } from "@/app/(app)/automation/RulesSelect";
export function Pending() {
const [page] = useQueryState("page", parseAsInteger.withDefault(1));
const [ruleId, setRuleId] = useQueryState(
"ruleId",
"rule-id",
parseAsString.withDefault("all"),
);

Expand Down Expand Up @@ -179,6 +179,7 @@ function PendingTable({
from={p.message.headers.from}
subject={p.message.headers.subject}
snippet={p.message.snippet}
threadId={p.message.threadId}
messageId={p.message.id}
userEmail={userEmail}
/>
Expand Down
1 change: 1 addition & 0 deletions apps/web/app/(app)/automation/ProcessRules.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ function ProcessRulesRow({
subject={message.headers.subject}
snippet={message.snippet?.trim() || ""}
userEmail={userEmail}
threadId={message.threadId}
messageId={message.id}
/>
<div className="ml-4 flex items-center gap-1">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { useCallback } from "react";
import { type SubmitHandler, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { PenIcon } from "lucide-react";
import { Modal, useModal } from "@/components/Modal";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/Input";
import {
Expand All @@ -15,39 +13,7 @@ import { DEFAULT_COLD_EMAIL_PROMPT } from "@/app/api/ai/cold-email/prompt";
import { toastError, toastSuccess } from "@/components/Toast";
import { isErrorMessage } from "@/utils/error";

export function ColdEmailPromptModal(props: {
coldEmailPrompt?: string | null;
refetch: () => void;
}) {
const { isModalOpen, openModal, closeModal } = useModal();
const { refetch } = props;

const onSuccess = useCallback(() => {
refetch();
closeModal();
}, [closeModal, refetch]);

return (
<>
<Button onClick={openModal} type="button" variant="outline">
<PenIcon className="mr-2 h-4 w-4" />
Edit Prompt
</Button>
<Modal
isOpen={isModalOpen}
hideModal={closeModal}
title="Edit Cold Email Prompt"
>
<ColdEmailPromptForm
coldEmailPrompt={props.coldEmailPrompt}
onSuccess={onSuccess}
/>
</Modal>
</>
);
}

function ColdEmailPromptForm(props: {
export function ColdEmailPromptForm(props: {
coldEmailPrompt?: string | null;
onSuccess: () => void;
}) {
Expand Down Expand Up @@ -95,10 +61,10 @@ function ColdEmailPromptForm(props: {
autosizeTextarea
rows={10}
name="coldEmailPrompt"
label="Prompt to classify cold emails."
label="Prompt to classify cold emails"
registerProps={register("coldEmailPrompt")}
error={errors.coldEmailPrompt}
explainText=" The default prompt we use is shown above if none set. Use a similar style for best results. Delete your prompt to revert to the default prompt."
explainText="Adjust to your needs.Use a similar style for best results. Delete your prompt to revert to the default prompt."
/>

<div className="mt-2">
Expand Down
4 changes: 2 additions & 2 deletions apps/web/app/(app)/cold-email-blocker/ColdEmailRejected.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,8 @@ function NoRejectedColdEmails() {
return (
<div className="p-2">
<AlertBasic
title="You have not marked any emails as 'Not Cold'"
description=""
title="No emails marked as 'Not a cold email'"
description="When you mark an AI-detected cold email as 'Not a cold email', it will appear here."
/>
</div>
);
Expand Down
12 changes: 6 additions & 6 deletions apps/web/app/(app)/cold-email-blocker/ColdEmailSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
updateColdEmailSettingsBody,
} from "@/app/api/user/settings/cold-email/validation";
import { TestRules } from "@/app/(app)/cold-email-blocker/TestRules";
import { ColdEmailPromptModal } from "@/app/(app)/cold-email-blocker/ColdEmailPromptModal";
import { ColdEmailPromptForm } from "@/app/(app)/cold-email-blocker/ColdEmailPromptForm";
import { RadioGroup } from "@/components/RadioGroup";
import { useUser } from "@/hooks/useUser";

Expand All @@ -26,12 +26,12 @@ export function ColdEmailSettings() {
<LoadingContent loading={isLoading} error={error}>
{data && (
<>
<ColdEmailForm coldEmailBlocker={data.coldEmailBlocker} />
<div className="mt-2 flex items-center gap-2">
<TestRules />
<ColdEmailPromptModal
<TestRules />
<div className="mt-2">
<ColdEmailForm coldEmailBlocker={data.coldEmailBlocker} />
<ColdEmailPromptForm
coldEmailPrompt={data.coldEmailPrompt}
refetch={mutate}
onSuccess={mutate}
/>
</div>
</>
Expand Down
3 changes: 2 additions & 1 deletion apps/web/app/(app)/cold-email-blocker/TestRules.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function TestRules() {
description="Test which emails are flagged as cold emails. We also check if the sender has emailed you before and if it includes unsubscribe links."
content={<TestRulesContent />}
>
<Button type="button" variant="outline">
<Button type="button">
<BookOpenCheckIcon className="mr-2 h-4 w-4" />
Test
</Button>
Expand Down Expand Up @@ -175,6 +175,7 @@ function TestRulesContentRow(props: {
subject={message.headers.subject}
snippet={decodeSnippet(message.snippet)}
userEmail={props.userEmail}
threadId={message.threadId}
messageId={message.id}
/>
<div className="ml-4">
Expand Down
9 changes: 9 additions & 0 deletions apps/web/app/(app)/cold-email-blocker/TestRulesMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,21 @@ import Link from "next/link";
import { MessageText } from "@/components/Typography";
import { getGmailUrl } from "@/utils/url";
import { decodeSnippet } from "@/utils/gmail/decode";
import { ViewEmailButton } from "@/components/ViewEmailButton";

export function TestRulesMessage({
from,
userEmail,
subject,
snippet,
threadId,
messageId,
}: {
from: string;
userEmail: string;
subject: string;
snippet: string;
threadId: string;
messageId: string;
}) {
return (
Expand All @@ -30,6 +33,12 @@ export function TestRulesMessage({
>
<ExternalLinkIcon className="h-4 w-4" />
</Link>
<ViewEmailButton
threadId={threadId}
messageId={messageId}
size="xs"
className="ml-1.5"
/>
</MessageText>
<MessageText className="mt-1 font-bold">{subject}</MessageText>
<MessageText className="mt-1">
Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/(app)/cold-email-blocker/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default function ColdEmailBlockerPage() {
<div className="content-container flex shrink-0 flex-col items-center justify-between gap-x-4 space-y-2 border-b border-gray-200 bg-white pb-2 shadow-sm md:flex-row md:gap-x-6 md:space-y-0">
<TabsList>
<TabsTrigger value="cold-emails">Cold Emails</TabsTrigger>
<TabsTrigger value="rejected">Not Cold</TabsTrigger>
<TabsTrigger value="rejected">Marked Not Cold</TabsTrigger>
<TabsTrigger value="settings">Settings</TabsTrigger>
</TabsList>
</div>
Expand Down
2 changes: 2 additions & 0 deletions apps/web/app/(app)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { SentryIdentify } from "@/app/(app)/sentry-identify";
import { ErrorMessages } from "@/app/(app)/ErrorMessages";
import { QueueInitializer } from "@/store/QueueInitializer";
import { ErrorBoundary } from "@/components/ErrorBoundary";
import { EmailViewer } from "@/components/EmailViewer";

export const viewport = {
themeColor: "#FFF",
Expand Down Expand Up @@ -43,6 +44,7 @@ export default async function AppLayout({
<ErrorMessages />
{children}
</SideNavWithTopNav>
<EmailViewer />
<ErrorBoundary extra={{ component: "AppLayout" }}>
<PostHogIdentify />
<TokenCheck />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export function SetUpCategories({
const [isCreating, setIsCreating] = useState(false);
const router = useRouter();
const [selectedCategoryName, setSelectedCategoryName] =
useQueryState("categoryName");
useQueryState("category-name");

const combinedCategories = uniqBy(
[
Expand Down
17 changes: 9 additions & 8 deletions apps/web/components/CommandK.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import * as React from "react";
import { useRouter } from "next/navigation";
import { ArchiveIcon, PenLineIcon } from "lucide-react";
import { useAtom, useAtomValue } from "jotai";
import { useAtomValue } from "jotai";
import {
CommandDialog,
CommandEmpty,
Expand All @@ -15,28 +15,29 @@ import {
} from "@/components/ui/command";
import { useNavigation } from "@/components/SideNav";
import { useComposeModal } from "@/providers/ComposeModalProvider";
import { refetchEmailListAtom, selectedEmailAtom } from "@/store/email";
import { refetchEmailListAtom } from "@/store/email";
import { archiveEmails } from "@/store/archive-queue";
import { useDisplayedEmail } from "@/hooks/useDisplayedEmail";

export function CommandK() {
const [open, setOpen] = React.useState(false);

const router = useRouter();

const [selectedEmail, setSelectedEmail] = useAtom(selectedEmailAtom);
const { threadId, showEmail } = useDisplayedEmail();
const refreshEmailList = useAtomValue(refetchEmailListAtom);

const { onOpen: onOpenComposeModal } = useComposeModal();

const onArchive = React.useCallback(() => {
if (selectedEmail) {
const threadIds = [selectedEmail];
if (threadId) {
const threadIds = [threadId];
archiveEmails(threadIds, undefined, () => {
return refreshEmailList?.refetch({ removedThreadIds: threadIds });
});
setSelectedEmail(undefined);
showEmail(null);
}
}, [refreshEmailList, selectedEmail, setSelectedEmail]);
}, [refreshEmailList, threadId, showEmail]);

React.useEffect(() => {
const down = (e: KeyboardEvent) => {
Expand Down Expand Up @@ -89,7 +90,7 @@ export function CommandK() {
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Actions">
{selectedEmail && (
{threadId && (
<CommandItem
onSelect={() => {
onArchive();
Expand Down
46 changes: 46 additions & 0 deletions apps/web/components/EmailViewer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"use client";

import { useCallback } from "react";
import { Sheet, SheetContent } from "@/components/ui/sheet";
import { useDisplayedEmail } from "@/hooks/useDisplayedEmail";
import { EmailThread } from "@/components/email-list/EmailPanel";
import { useThread } from "@/hooks/useThread";
import { LoadingContent } from "@/components/LoadingContent";
import { ErrorBoundary } from "@/components/ErrorBoundary";

export function EmailViewer() {
const { threadId, showEmail } = useDisplayedEmail();

const hideEmail = useCallback(() => showEmail(null), [showEmail]);

return (
<Sheet open={!!threadId} onOpenChange={hideEmail}>
<SheetContent
side="right"
size="5xl"
className="overflow-y-auto p-0"
overlay="transparent"
>
{threadId && <EmailContent threadId={threadId} />}
</SheetContent>
</Sheet>
);
}

function EmailContent({ threadId }: { threadId: string }) {
const { data, isLoading, error, mutate } = useThread({ id: threadId });

return (
<ErrorBoundary extra={{ component: "EmailContent", threadId }}>
<LoadingContent loading={isLoading} error={error}>
{data && (
<EmailThread
messages={data.thread.messages}
refetch={mutate}
showReplyButton={false}
/>
)}
</LoadingContent>
</ErrorBoundary>
);
}
Loading

0 comments on commit 00f9065

Please sign in to comment.