From 50c10dac8996b32c957e5ab9382d5d27a7653a57 Mon Sep 17 00:00:00 2001
From: AnjanyKumarJaiswal
<136046942+AnjanyKumarJaiswal@users.noreply.github.com>
Date: Sun, 29 Jun 2025 12:35:52 +0530
Subject: [PATCH 1/6] fix: fixed conflict issues and drafts featurs are done
---
apps/mail/components/create/create-email.tsx | 13 +-
.../mail/components/create/email-composer.tsx | 145 +++++++++++++++---
apps/mail/components/ui/draftnotification.tsx | 142 +++++++++++++++++
apps/server/src/lib/driver/google.ts | 124 ++++++++++++---
apps/server/src/lib/driver/microsoft.ts | 88 ++++++++---
apps/server/src/lib/driver/types.ts | 6 +
apps/server/src/routes/chat.ts | 22 +++
apps/server/src/trpc/routes/drafts.ts | 24 ++-
8 files changed, 485 insertions(+), 79 deletions(-)
create mode 100644 apps/mail/components/ui/draftnotification.tsx
diff --git a/apps/mail/components/create/create-email.tsx b/apps/mail/components/create/create-email.tsx
index 74a992226e..b415f20cee 100644
--- a/apps/mail/components/create/create-email.tsx
+++ b/apps/mail/components/create/create-email.tsx
@@ -61,6 +61,7 @@ export function CreateEmail({
data: draft,
isLoading: isDraftLoading,
error: draftError,
+ refetch: refetchDraft
} = useDraft(draftId ?? propDraftId ?? null);
const t = useTranslations();
const [, setIsDraftFailed] = useState(false);
@@ -165,14 +166,6 @@ export function CreateEmail({
- {isDraftLoading ? (
-
- ) : (
{
+ refetchDraft();
+ }}
initialSubject={typedDraft?.subject || initialSubject}
autofocus={false}
settingsLoading={settingsLoading}
/>
- )}
>
diff --git a/apps/mail/components/create/email-composer.tsx b/apps/mail/components/create/email-composer.tsx
index fbc0571c04..7ce84042ec 100644
--- a/apps/mail/components/create/email-composer.tsx
+++ b/apps/mail/components/create/email-composer.tsx
@@ -13,7 +13,7 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
-import { Check, Command, Loader, Paperclip, Plus, X as XIcon } from 'lucide-react';
+import { Check, Command, Loader, Paperclip, Plus, Trash, X as XIcon } from 'lucide-react';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { TextEffect } from '@/components/motion-primitives/text-effect';
import { useActiveConnection } from '@/hooks/use-connections';
@@ -25,7 +25,7 @@ import { AnimatePresence, motion } from 'motion/react';
import { zodResolver } from '@hookform/resolvers/zod';
import { Avatar, AvatarFallback } from '../ui/avatar';
import { useTRPC } from '@/providers/query-provider';
-import { useMutation } from '@tanstack/react-query';
+import { useMutation, useQuery } from '@tanstack/react-query';
import { useSettings } from '@/hooks/use-settings';
import { cn, formatFileSize } from '@/lib/utils';
import { useThread } from '@/hooks/use-threads';
@@ -42,6 +42,7 @@ import { ImageCompressionSettings } from './image-compression-settings';
import { compressImages } from '@/lib/image-compression';
import type { ImageQuality } from '@/lib/image-compression';
import { useIsMobile } from '@/hooks/use-mobile';
+import { DraftNotification, useDraftNotification } from '../ui/draftnotification';
type ThreadContent = {
from: string;
@@ -69,6 +70,7 @@ interface EmailComposerProps {
fromEmail?: string;
}) => Promise;
onClose?: () => void;
+ onDraftUpdate?: () => void;
className?: string;
autofocus?: boolean;
settingsLoading?: boolean;
@@ -101,6 +103,7 @@ export function EmailComposer({
initialAttachments = [],
onSendEmail,
onClose,
+ onDraftUpdate,
className,
autofocus = false,
settingsLoading = false,
@@ -114,6 +117,7 @@ export function EmailComposer({
const [showBcc, setShowBcc] = useState(initialBcc.length > 0);
const [isLoading, setIsLoading] = useState(false);
const [isSavingDraft, setIsSavingDraft] = useState(false);
+ const [isDeleteDraft, setIsDeleteDraft] = useState(false);
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
const [messageLength, setMessageLength] = useState(0);
const fileInputRef = useRef(null);
@@ -206,6 +210,8 @@ export function EmailComposer({
}
}
};
+ // const [showDraftMessage, setshowDraftMessage] = useState("");
+ const { notification, showSaveNotification, hideNotification } = useDraftNotification();
// Add this function to handle clicks outside the input fields
useEffect(() => {
@@ -240,6 +246,8 @@ export function EmailComposer({
const trpc = useTRPC();
const { mutateAsync: aiCompose } = useMutation(trpc.ai.compose.mutationOptions());
const { mutateAsync: createDraft } = useMutation(trpc.drafts.create.mutationOptions());
+ const {mutateAsync: updateDraft} = useMutation(trpc.drafts.update.mutationOptions());
+ const {mutateAsync: deleteDraft} = useMutation(trpc.drafts.delete.mutationOptions());
const { mutateAsync: generateEmailSubject } = useMutation(
trpc.ai.generateEmailSubject.mutationOptions(),
);
@@ -538,6 +546,8 @@ export function EmailComposer({
}
};
+
+ //this whole method is for saving draft and its called when the useeffect is triggered
const saveDraft = async () => {
const values = getValues();
@@ -571,11 +581,29 @@ export function EmailComposer({
fromEmail: values.fromEmail ? values.fromEmail : null,
};
+ if(draftId){
+ const response = await updateDraft(draftData);
+ if(response?.id){
+ setDraftId(response?.id);
+ onDraftUpdate?.();
+ showSaveNotification('Your Draft has been Successfully Saved');
+ }
+ else{
+ const response = await createDraft(draftData);
+ if(response?.id){
+ setDraftId(response?.id);
+ showSaveNotification('Your Draft has been Successfully Saved');
+ }
+ console.error("Failed Setting up Draft Id")
+ toast.error("Failed Setting up Draft Id")
+ }
+ } else {
const response = await createDraft(draftData);
-
- if (response?.id && response.id !== draftId) {
- setDraftId(response.id);
+ if(response?.id){
+ setDraftId(response?.id);
+ showSaveNotification('Your Draft has been Successfully Saved');
}
+ }
} catch (error) {
console.error('Error saving draft:', error);
toast.error('Failed to save draft');
@@ -587,6 +615,76 @@ export function EmailComposer({
}
};
+ useEffect(() => {
+ if (!hasUnsavedChanges) return;
+
+ const autoSaveTimer = setTimeout(() => {
+ console.log('Draft Save TimeOut');
+ saveDraft();
+ }, 3000);
+
+ return () => clearTimeout(autoSaveTimer);
+ }, [hasUnsavedChanges, saveDraft]);
+
+ // this handleSaveclick for saving drafts might be used in future when save button is introduced
+ // const handleSaveClick = () =>{
+ // const hasContent = editor?.getText()?.trim().length > 0;
+ // if(hasContent){
+ // onDraftUpdate?.();
+ // showSaveNotification('Your Draft has been Successfully Saved');
+ // }
+ // }
+
+ //ths function is going to be used to delete drafts
+ const handleDeleteDraft = async () => {
+ const values = getValues();
+ if (!draftId) {
+ toast.error('No draft Id available to delete any Draft.');
+ return;
+ }
+ try {
+ const draftData = {
+ to: values.to.join(', '),
+ cc: values.cc?.join(', '),
+ bcc: values.bcc?.join(', '),
+ subject: values.subject,
+ message: editor.getHTML(),
+ attachments: await serializeFiles(values.attachments ?? []),
+ id: draftId,
+ threadId: threadId ? threadId : null,
+ fromEmail: values.fromEmail ? values.fromEmail : null,
+ };
+
+ if(draftId){
+ const response = await deleteDraft(draftData);
+ if(response === ''){
+ setDraftId(null);
+ setIsComposeOpen(null);
+ setTimeout(() => {
+ const currentUrl = new URL(window.location.href);
+ window.location.href = currentUrl.toString();
+ }, 500);
+ }
+ }
+ } catch (error) {
+ console.error('Failed to delete draft:', error);
+ toast.error('Failed to delete draft.');
+ } finally {
+ setIsDeleteDraft(false);
+ }
+ };
+
+ // this handleclose button triggeres to auto-save draft upon close
+ const handleClose = () => {
+ const hasContent = editor?.getText()?.trim().length > 0;
+ if (hasContent) {
+ saveDraft();
+ setShowLeaveConfirmation(true);
+ } else {
+ onClose?.();
+ }
+ };
+
const handleGenerateSubject = async () => {
try {
setIsGeneratingSubject(true);
@@ -608,14 +706,6 @@ export function EmailComposer({
}
};
- const handleClose = () => {
- const hasContent = editor?.getText()?.trim().length > 0;
- if (hasContent) {
- setShowLeaveConfirmation(true);
- } else {
- onClose?.();
- }
- };
const confirmLeave = () => {
setShowLeaveConfirmation(false);
@@ -639,16 +729,6 @@ export function EmailComposer({
};
}, [editor, showLeaveConfirmation]);
- useEffect(() => {
- if (!hasUnsavedChanges) return;
-
- const autoSaveTimer = setTimeout(() => {
- console.log('timeout set');
- saveDraft();
- }, 3000);
-
- return () => clearTimeout(autoSaveTimer);
- }, [hasUnsavedChanges, saveDraft]);
useEffect(() => {
const handlePasteFiles = (event: ClipboardEvent) => {
@@ -901,7 +981,7 @@ export function EmailComposer({
tabIndex={-1}
className="flex h-full items-center gap-2 text-sm font-medium text-[#8C8C8C] hover:text-[#A8A8A8]"
onClick={handleClose}
- >
+ >
)}
@@ -1428,7 +1508,13 @@ export function EmailComposer({
)}
-
+
+
{aiGeneratedMessage !== null ? (
@@ -1562,6 +1648,15 @@ export function EmailComposer({
+ {notification && (
+
+ )}
);
}
diff --git a/apps/mail/components/ui/draftnotification.tsx b/apps/mail/components/ui/draftnotification.tsx
new file mode 100644
index 0000000000..c784163909
--- /dev/null
+++ b/apps/mail/components/ui/draftnotification.tsx
@@ -0,0 +1,142 @@
+import React, { useState, useEffect } from 'react';
+import { CheckCircle, Trash, X} from 'lucide-react';
+
+interface DraftNotificationProps {
+ message: string;
+ type: 'save' | 'delete';
+ isVisible: boolean;
+ onClose: () => void;
+ duration?: number;
+}
+
+export const DraftNotification: React.FC
= ({
+ message,
+ type,
+ isVisible,
+ onClose,
+ duration = 3000
+}) => {
+ const [progress, setProgress] = useState(100);
+ const [isClosing, setIsClosing] = useState(false);
+
+ useEffect(() => {
+ if (!isVisible) {
+ setProgress(100);
+ setIsClosing(false);
+ return;
+ }
+
+ const interval = setInterval(() => {
+ setProgress((prev) => {
+ const newProgress = prev - (100 / (duration / 100));
+ if (newProgress <= 0) {
+ clearInterval(interval);
+ handleClose();
+ return 0;
+ }
+ return newProgress;
+ });
+ }, 100);
+
+ return () => clearInterval(interval);
+ }, [isVisible, duration]);
+
+ const handleClose = () => {
+ setIsClosing(true);
+ setTimeout(() => {
+ onClose();
+ }, 300);
+ };
+
+ if (!isVisible) return null;
+
+ const isSave = type === 'save';
+
+ return (
+
+
+
+
+
+
+ {isSave ? (
+
+ ) : (
+
+ )}
+
+
+ {/* Message */}
+
+
+ {/* Close button */}
+
+
+
+ {/* Bottom accent line */}
+
+
+
+ );
+};
+
+// Hook for managing notifications
+export const useDraftNotification = () => {
+ const [notification, setNotification] = useState<{
+ type: 'save' | 'delete';
+ message: string;
+ isVisible: boolean;
+ } | null>(null);
+
+ const showSaveNotification = (message: string = 'Your Draft has been Successfully Saved', onComplete?: () => void) => {
+ setNotification({
+ type: 'save',
+ message,
+ isVisible: true
+ });
+
+ // If onComplete callback is provided, execute it after the duration
+ if (onComplete) {
+ setTimeout(onComplete, 5000);
+ }
+ };
+
+ const hideNotification = () => {
+ setNotification(null);
+ };
+
+ return {
+ notification,
+ showSaveNotification,
+ hideNotification
+ };
+};
\ No newline at end of file
diff --git a/apps/server/src/lib/driver/google.ts b/apps/server/src/lib/driver/google.ts
index 8fcc4d5406..b484ae2d36 100644
--- a/apps/server/src/lib/driver/google.ts
+++ b/apps/server/src/lib/driver/google.ts
@@ -562,7 +562,7 @@ export class GoogleMailManager implements MailManager {
const { html: message, inlineImages } = await sanitizeTipTapHtml(data.message);
const msg = createMimeMessage();
msg.setSender('me');
- // name
+
const to = data.to.split(', ').map((recipient: string) => {
if (recipient.includes('<')) {
const [name, email] = recipient.split('<');
@@ -572,12 +572,10 @@ export class GoogleMailManager implements MailManager {
});
msg.setTo(to);
- if (data.cc)
- msg.setCc(data.cc?.split(', ').map((recipient: string) => ({ addr: recipient })));
- if (data.bcc)
- msg.setBcc(data.bcc?.split(', ').map((recipient: string) => ({ addr: recipient })));
-
+ if (data.cc) msg.setCc(data.cc.split(', ').map((addr) => ({ addr })));
+ if (data.bcc) msg.setBcc(data.bcc.split(', ').map((addr) => ({ addr })));
msg.setSubject(data.subject);
+
msg.addMessage({
contentType: 'text/html',
data: message || '',
@@ -598,9 +596,11 @@ export class GoogleMailManager implements MailManager {
}
}
- if (data.attachments && data.attachments?.length > 0) {
+ if (data.attachments?.length) {
for (const attachment of data.attachments) {
- const base64Data = attachment.base64;
+ // const arrayBuffer = await attachment.arrayBuffer();
+ // const base64Data = Buffer.from(arrayBuffer).toString('base64');
+ const base64Data = await attachment.base64;
msg.addAttachment({
filename: attachment.name,
contentType: attachment.type,
@@ -623,24 +623,106 @@ export class GoogleMailManager implements MailManager {
},
};
- let res;
+ const res = await this.gmail.users.drafts.create({
+ userId: 'me',
+ requestBody,
+ });
- if (data.id) {
- res = await this.gmail.users.drafts.update({
- userId: 'me',
- id: data.id,
- requestBody,
- });
- } else {
- res = await this.gmail.users.drafts.create({
- userId: 'me',
- requestBody,
- });
+ return res.data;
+ },
+ { data }
+ );
+ }
+ public updateDraft(data: CreateDraftData) {
+ return this.withErrorHandler(
+ 'updateDraft',
+ async () => {
+ if (!data.id) throw new Error('Missing draft ID for update');
+
+ const { html: message, inlineImages } = await sanitizeTipTapHtml(data.message);
+ const msg = createMimeMessage();
+ msg.setSender('me');
+
+ const to = data.to.split(', ').map((recipient: string) => {
+ if (recipient.includes('<')) {
+ const [name, email] = recipient.split('<');
+ return { addr: email.replace('>', ''), name: name.replace('>', '') };
+ }
+ return { addr: recipient };
+ });
+
+ msg.setTo(to);
+ if (data.cc) msg.setCc(data.cc.split(', ').map((addr) => ({ addr })));
+ if (data.bcc) msg.setBcc(data.bcc.split(', ').map((addr) => ({ addr })));
+ msg.setSubject(data.subject);
+
+ msg.addMessage({
+ contentType: 'text/html',
+ data: message || '',
+ });
+ if (inlineImages.length > 0) {
+ for (const image of inlineImages) {
+ msg.addAttachment({
+ inline: true,
+ filename: `${image.cid}`,
+ contentType: image.mimeType,
+ data: image.data,
+ headers: {
+ 'Content-ID': `<${image.cid}>`,
+ 'Content-Disposition': 'inline',
+ },
+ });
+ }
}
+ if (data.attachments?.length) {
+ for (const attachment of data.attachments) {
+ const base64Data = attachment.base64;
+ msg.addAttachment({
+ filename: attachment.name,
+ contentType: attachment.type,
+ data: base64Data,
+ });
+ }
+ }
+
+ const mimeMessage = msg.asRaw();
+ const encodedMessage = Buffer.from(mimeMessage)
+ .toString('base64')
+ .replace(/\+/g, '-')
+ .replace(/\//g, '_')
+ .replace(/=+$/, '');
+
+ const requestBody = {
+ message: {
+ raw: encodedMessage,
+ threadId: data.threadId,
+ },
+ };
+
+ const res = await this.gmail.users.drafts.update({
+ userId: 'me',
+ id: data.id,
+ requestBody,
+ });
+
return res.data;
},
- { data },
+ { data }
+ );
+ }
+ public deleteDraft(data: CreateDraftData){
+ return this.withErrorHandler(
+ 'deleteDraft',
+ async () =>{
+ if (!data.id) throw new Error('Missing draft ID to delete');
+
+ const res = await this.gmail.users.drafts.delete({
+ userId: 'me',
+ id: data.id,
+ })
+ return res.data;
+ } , {data}
);
}
public async getUserLabels() {
diff --git a/apps/server/src/lib/driver/microsoft.ts b/apps/server/src/lib/driver/microsoft.ts
index b6c3b857e6..599ef2bec1 100644
--- a/apps/server/src/lib/driver/microsoft.ts
+++ b/apps/server/src/lib/driver/microsoft.ts
@@ -636,7 +636,6 @@ export class OutlookMailManager implements MailManager {
const { html: message, inlineImages } = await sanitizeTipTapHtml(data.message);
const toRecipients = Array.isArray(data.to) ? data.to : data.to.split(', ');
-
const outlookMessage: Message = {
subject: data.subject,
body: {
@@ -707,35 +706,84 @@ export class OutlookMailManager implements MailManager {
if (allAttachments.length > 0) {
outlookMessage.attachments = allAttachments;
}
+ const res = await this.graphClient
+ .api('/me/mailfolders/drafts/messages')
+ .post(outlookMessage);
- let res;
+ return res;
+ },
+ { data },
+ );
+ }
+ public updateDraft(data: CreateDraftData) {
+ return this.withErrorHandler(
+ 'updateDraft',
+ async () => {
+ if (!data.id) throw new Error('Draft ID is required to update a draft');
- if (data.id) {
- try {
- res = await this.graphClient
- .api(`/me/mailfolders/drafts/messages/${data.id}`)
- .patch(outlookMessage);
- } catch (error) {
- console.warn(`Failed to update draft ${data.id}, creating a new one`, error);
- try {
- await this.graphClient.api(`/me/mailfolders/drafts/messages/${data.id}`).delete();
- } catch (deleteError) {
- console.error(`Failed to delete draft ${data.id}`, deleteError);
- }
+ const message = await sanitizeTipTapHtml(data.message);
- res = await this.graphClient
- .api('/me/mailfolders/drafts/messages')
- .post(outlookMessage);
- }
- } else {
- res = await this.graphClient.api('/me/mailfolders/drafts/messages').post(outlookMessage);
+ const toRecipients = Array.isArray(data.to) ? data.to : data.to.split(', ');
+ const outlookMessage: Message = {
+ subject: data.subject,
+ body: {
+ contentType: 'html',
+ content: message || '',
+ },
+ toRecipients: toRecipients.map((recipient) => ({
+ emailAddress: {
+ address: typeof recipient === 'string' ? recipient : recipient.email,
+ name: typeof recipient === 'string' ? undefined : recipient.name || undefined,
+ },
+ })),
+ };
+
+ if (data.cc) {
+ const ccRecipients = Array.isArray(data.cc) ? data.cc : data.cc.split(', ');
+ outlookMessage.ccRecipients = ccRecipients.map((recipient) => ({
+ emailAddress: {
+ address: typeof recipient === 'string' ? recipient : recipient.email,
+ name: typeof recipient === 'string' ? undefined : recipient.name || undefined,
+ },
+ }));
+ }
+
+ if (data.bcc) {
+ const bccRecipients = Array.isArray(data.bcc) ? data.bcc : data.bcc.split(', ');
+ outlookMessage.bccRecipients = bccRecipients.map((recipient) => ({
+ emailAddress: {
+ address: typeof recipient === 'string' ? recipient : recipient.email,
+ name: typeof recipient === 'string' ? undefined : recipient.name || undefined,
+ },
+ }));
}
+ if (data.attachments && data.attachments.length > 0) {
+ outlookMessage.attachments = await Promise.all(
+ data.attachments.map(async (file) => {
+ const arrayBuffer = await file.arrayBuffer();
+ const buffer = Buffer.from(arrayBuffer);
+ const base64Content = buffer.toString('base64');
+
+ return {
+ '@odata.type': '#microsoft.graph.fileAttachment',
+ name: file.name,
+ contentType: file.type || 'application/octet-stream',
+ contentBytes: base64Content,
+ };
+ }),
+ );
+ }
+ const res = await this.graphClient
+ .api(`/me/mailfolders/drafts/messages/${data.id}`)
+ .patch(outlookMessage);
+
return res;
},
{ data },
);
}
+
public async getUserLabels() {
try {
// Get root mail folders
diff --git a/apps/server/src/lib/driver/types.ts b/apps/server/src/lib/driver/types.ts
index 0e442833bf..4b8dd3a80c 100644
--- a/apps/server/src/lib/driver/types.ts
+++ b/apps/server/src/lib/driver/types.ts
@@ -55,6 +55,12 @@ export interface MailManager {
createDraft(
data: CreateDraftData,
): Promise<{ id?: string | null; success?: boolean; error?: string }>;
+ updateDraft(
+ data: CreateDraftData,
+ ) : Promise<{id?: string | null, success?: boolean, error?: string}>;
+ deleteDraft(
+ data: CreateDraftData
+ ) : Promise;
getDraft(id: string): Promise;
listDrafts(params: { q?: string; maxResults?: number; pageToken?: string }): Promise<{
threads: { id: string; historyId: string | null; $raw: unknown }[];
diff --git a/apps/server/src/routes/chat.ts b/apps/server/src/routes/chat.ts
index 35d36bd758..be8adeee49 100644
--- a/apps/server/src/routes/chat.ts
+++ b/apps/server/src/routes/chat.ts
@@ -209,6 +209,14 @@ export class AgentRpcDO extends RpcTarget {
return await this.mainDo.createDraft(draftData);
}
+ async updateDraft(draftData: CreateDraftData){
+ return await this.mainDo.updateDraft(draftData);
+ }
+
+ async deleteDraft(draftData: CreateDraftData){
+ return await this.mainDo.deleteDraft(draftData);
+ }
+
async getDraft(id: string) {
return await this.mainDo.getDraft(id);
}
@@ -720,6 +728,20 @@ export class ZeroAgent extends AIChatAgent {
return await this.driver.createDraft(draftData);
}
+ async updateDraft(draftData: CreateDraftData){
+ if(!this.driver){
+ throw new Error("No driver available")
+ }
+ return await this.driver.updateDraft(draftData);
+ }
+
+ async deleteDraft(draftData: CreateDraftData){
+ if(!this.driver){
+ throw new Error("No driver available")
+ }
+ return await this.driver.deleteDraft(draftData);
+ }
+
async getDraft(id: string) {
if (!this.driver) {
throw new Error('No driver available');
diff --git a/apps/server/src/trpc/routes/drafts.ts b/apps/server/src/trpc/routes/drafts.ts
index d3c1775a4f..74ccfe948a 100644
--- a/apps/server/src/trpc/routes/drafts.ts
+++ b/apps/server/src/trpc/routes/drafts.ts
@@ -10,11 +10,28 @@ export const draftsRouter = router({
const agent = await getZeroAgent(activeConnection.id);
return agent.createDraft(input);
}),
+ update: activeDriverProcedure
+ .input(createDraftData)
+ .mutation(async ({input, ctx}) =>{
+ const {activeConnection} = ctx;
+ const agent = await getZeroAgent(activeConnection.id);
+ const res = agent.updateDraft(input)
+ return res;
+ }),
+ delete: activeDriverProcedure
+ .input(createDraftData)
+ .mutation(async({input,ctx})=>{
+ const {activeConnection} = ctx;
+ const agent = await getZeroAgent(activeConnection.id);
+ const res = agent.deleteDraft(input);
+ return res;
+ }),
get: activeDriverProcedure.input(z.object({ id: z.string() })).query(async ({ input, ctx }) => {
const { activeConnection } = ctx;
const agent = await getZeroAgent(activeConnection.id);
const { id } = input;
- return agent.getDraft(id) as Awaited>;
+ const res = agent.getDraft(id) as Awaited>
+ return res;
}),
list: activeDriverProcedure
.input(
@@ -28,8 +45,7 @@ export const draftsRouter = router({
const { activeConnection } = ctx;
const agent = await getZeroAgent(activeConnection.id);
const { q, max, pageToken } = input;
- return agent.listDrafts({ q, maxResults: max, pageToken }) as Awaited<
- ReturnType
- >;
+ const res = agent.listDrafts({ q, maxResults: max, pageToken }) as Awaited>;
+ return res;
}),
});
From 0028ed945b0bd337f89965013ee0e4eba07347a9 Mon Sep 17 00:00:00 2001
From: AnjanyKumarJaiswal
<136046942+AnjanyKumarJaiswal@users.noreply.github.com>
Date: Fri, 11 Jul 2025 22:22:54 +0530
Subject: [PATCH 2/6] fix: fixed conflicts
---
apps/mail/components/create/create-email.tsx | 2 ++
apps/mail/components/create/email-composer.tsx | 10 +++++++---
2 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/apps/mail/components/create/create-email.tsx b/apps/mail/components/create/create-email.tsx
index d8e5400254..91c490da80 100644
--- a/apps/mail/components/create/create-email.tsx
+++ b/apps/mail/components/create/create-email.tsx
@@ -157,6 +157,8 @@ export function CreateEmail({
}
};
+
+
const base64ToFile = (base64: string, filename: string, mimeType: string): File | null => {
try {
const byteString = atob(base64);
diff --git a/apps/mail/components/create/email-composer.tsx b/apps/mail/components/create/email-composer.tsx
index c8b58a83a7..596c70fa52 100644
--- a/apps/mail/components/create/email-composer.tsx
+++ b/apps/mail/components/create/email-composer.tsx
@@ -13,6 +13,8 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
+
+import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/tooltip';
import { Check, Command, Loader, Paperclip, Plus, X as XIcon } from 'lucide-react';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { TextEffect } from '@/components/motion-primitives/text-effect';
@@ -72,6 +74,7 @@ interface EmailComposerProps {
}) => Promise;
onClose?: () => void;
onDraftUpdate?: () => void;
+ // onDeleteDrafts?: () => void;
className?: string;
autofocus?: boolean;
settingsLoading?: boolean;
@@ -110,6 +113,7 @@ export function EmailComposer({
onSendEmail,
onClose,
onDraftUpdate,
+ // onDeleteDrafts,
className,
autofocus = false,
settingsLoading = false,
@@ -643,7 +647,7 @@ export function EmailComposer({
// }
//ths function is going to be used to delete drafts
- const handleDeleteDraft = async () => {
+ const DeleteDraft = async () => {
const values = getValues();
if (!draftId) {
toast.error('No draft Id available to delete any Draft.');
@@ -1552,12 +1556,12 @@ export function EmailComposer({
-
+
Discard */}
{aiGeneratedMessage !== null ? (
From 3551c1963d05a7e62cf5465821e072828632e1fb Mon Sep 17 00:00:00 2001
From: AnjanyKumarJaiswal
<136046942+AnjanyKumarJaiswal@users.noreply.github.com>
Date: Sat, 12 Jul 2025 02:04:36 +0530
Subject: [PATCH 3/6] feat: finally done with Auto-Save , Update and Delete
Drafts
---
apps/mail/components/create/create-email.tsx | 4 +-
.../mail/components/create/email-composer.tsx | 16 +-
apps/mail/components/ui/draftnotification.tsx | 142 ------------------
3 files changed, 13 insertions(+), 149 deletions(-)
delete mode 100644 apps/mail/components/ui/draftnotification.tsx
diff --git a/apps/mail/components/create/create-email.tsx b/apps/mail/components/create/create-email.tsx
index f147dbe365..01c853c9cd 100644
--- a/apps/mail/components/create/create-email.tsx
+++ b/apps/mail/components/create/create-email.tsx
@@ -10,6 +10,7 @@ import { EmailComposer } from './email-composer';
import { useSession } from '@/lib/auth-client';
import { serializeFiles } from '@/lib/schemas';
import { useDraft } from '@/hooks/use-drafts';
+import { useThreads } from '@/hooks/use-threads';
import { useEffect, useState } from 'react';
import type { Attachment } from '@/types';
@@ -62,6 +63,7 @@ export function CreateEmail({
const { mutateAsync: sendEmail } = useMutation(trpc.mail.send.mutationOptions());
const [isComposeOpen, setIsComposeOpen] = useQueryState('isComposeOpen');
const [, setThreadId] = useQueryState('threadId');
+ const [{ isFetching, refetch: refetchThreads }] = useThreads();
const [, setActiveReplyId] = useQueryState('activeReplyId');
const { data: activeConnection } = useActiveConnection();
const { data: settings, isLoading: settingsLoading } = useSettings();
@@ -172,7 +174,7 @@ export function CreateEmail({
-