-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
feat: finally done with Auto-Save , Update and Delete Drafts #1732
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
AnjanyKumarJaiswal
wants to merge
16
commits into
Mail-0:staging
from
AnjanyKumarJaiswal:feat/autoSave_Email_drafts
Closed
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
50c10da
fix: fixed conflict issues and drafts featurs are done
AnjanyKumarJaiswal f023d02
Merge branch 'Mail-0:staging' into feat/autoSave_Email_drafts
AnjanyKumarJaiswal 1cbb104
fix: fixing conflicts and rebasing with new changes
AnjanyKumarJaiswal 0028ed9
fix: fixed conflicts
AnjanyKumarJaiswal 1c068b6
fix: fixed conflicts with new fast codebase
AnjanyKumarJaiswal 3551c19
feat: finally done with Auto-Save , Update and Delete Drafts
AnjanyKumarJaiswal 50d99eb
Merge branch 'feat/autoSave_Email_drafts' of https://github.com/Anjan…
AnjanyKumarJaiswal 5bbe1d6
Merge branch 'staging' into feat/autoSave_Email_drafts
AnjanyKumarJaiswal 307660f
Merge branch 'staging' into feat/autoSave_Email_drafts
AnjanyKumarJaiswal f669ca3
fix: applied AI recommendations and implemented to code structure
AnjanyKumarJaiswal 9c1b4a5
Merge branch 'feat/autoSave_Email_drafts' of https://github.com/Anjan…
AnjanyKumarJaiswal a8919cb
Merge branch 'staging' into feat/autoSave_Email_drafts
AnjanyKumarJaiswal b5ef607
fix: implmented AI considerations
AnjanyKumarJaiswal 33c8eef
Merge branch 'feat/autoSave_Email_drafts' of https://github.com/Anjan…
AnjanyKumarJaiswal c3790a3
Merge branch 'staging' into feat/autoSave_Email_drafts
AnjanyKumarJaiswal 6599c07
feat: updated condition logic for drafts
AnjanyKumarJaiswal File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,7 +15,7 @@ import { | |
| } from '@/components/ui/select'; | ||
|
|
||
| import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/tooltip'; | ||
| import { Check, Command, Loader, Paperclip, Plus, Type, X as XIcon } from 'lucide-react'; | ||
| import { Check, Command, Loader, Paperclip, Trash, Plus, Type, X as XIcon } from 'lucide-react'; | ||
| import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; | ||
| import { TextEffect } from '@/components/motion-primitives/text-effect'; | ||
| import { ImageCompressionSettings } from './image-compression-settings'; | ||
|
|
@@ -31,11 +31,11 @@ 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'; | ||
AnjanyKumarJaiswal marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| import { useSettings } from '@/hooks/use-settings'; | ||
|
|
||
| import { cn, formatFileSize } from '@/lib/utils'; | ||
| import { useThread } from '@/hooks/use-threads'; | ||
| import { useThread, useThreads } from '@/hooks/use-threads'; | ||
| import { serializeFiles } from '@/lib/schemas'; | ||
| import { Input } from '@/components/ui/input'; | ||
| import { EditorContent } from '@tiptap/react'; | ||
|
|
@@ -74,6 +74,7 @@ interface EmailComposerProps { | |
| fromEmail?: string; | ||
| }) => Promise<void>; | ||
| onClose?: () => void; | ||
| onDraftUpdate?: () => void; | ||
| className?: string; | ||
| autofocus?: boolean; | ||
| settingsLoading?: boolean; | ||
|
|
@@ -111,6 +112,8 @@ export function EmailComposer({ | |
| initialAttachments = [], | ||
| onSendEmail, | ||
| onClose, | ||
| onDraftUpdate, | ||
| // onDeleteDrafts, | ||
| className, | ||
| autofocus = false, | ||
| settingsLoading = false, | ||
|
|
@@ -122,6 +125,8 @@ 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); | ||
AnjanyKumarJaiswal marked this conversation as resolved.
Show resolved
Hide resolved
AnjanyKumarJaiswal marked this conversation as resolved.
Show resolved
Hide resolved
AnjanyKumarJaiswal marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| const [{ isFetching, refetch: refetchThreads }] = useThreads(); | ||
| const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); | ||
| const [messageLength, setMessageLength] = useState(0); | ||
| const fileInputRef = useRef<HTMLInputElement>(null); | ||
|
|
@@ -251,6 +256,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(), | ||
| ); | ||
|
|
@@ -551,6 +558,8 @@ export function EmailComposer({ | |
| } | ||
| }; | ||
|
|
||
|
|
||
|
|
||
| const saveDraft = async () => { | ||
| const values = getValues(); | ||
|
|
||
|
|
@@ -576,11 +585,34 @@ export function EmailComposer({ | |
| fromEmail: values.fromEmail ? values.fromEmail : null, | ||
| }; | ||
|
|
||
| if(draftId){ | ||
| try { | ||
| const response = await updateDraft(draftData); | ||
| if(response?.id){ | ||
| setDraftId(response.id); | ||
| onDraftUpdate?.(); | ||
| toast.success("Your Draft has been Successfully Saved") | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no toast please
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. but why shouldnt we show a message for confirmation that the user draft is saved? |
||
| } | ||
| } catch (error) { | ||
| console.error("Failed to update draft:", error); | ||
| toast.error("Failed to update draft"); | ||
| } | ||
| } else { | ||
| const response = await createDraft(draftData); | ||
| if(response?.id){ | ||
| setDraftId(response.id); | ||
| toast.success("Your Draft has been Successfully Saved") | ||
| } else { | ||
| console.error("Failed Setting up Draft Id") | ||
| toast.error("Failed Setting up Draft Id") | ||
| } | ||
| } | ||
| const response = await createDraft(draftData); | ||
|
|
||
| if (response?.id && response.id !== draftId) { | ||
| setDraftId(response.id); | ||
| if(response?.id){ | ||
| setDraftId(response?.id); | ||
| toast.success("Your Draft has been Successfully Saved") | ||
| } | ||
| } | ||
AnjanyKumarJaiswal marked this conversation as resolved.
Show resolved
Hide resolved
AnjanyKumarJaiswal marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } catch (error) { | ||
| console.error('Error saving draft:', error); | ||
| toast.error('Failed to save draft'); | ||
|
|
@@ -592,6 +624,53 @@ export function EmailComposer({ | |
| } | ||
| }; | ||
|
|
||
| useEffect(() => { | ||
| if (!hasUnsavedChanges) return; | ||
|
|
||
| const autoSaveTimer = setTimeout(() => { | ||
| console.log('Draft Save TimeOut'); | ||
| saveDraft(); | ||
AnjanyKumarJaiswal marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }, 3000); | ||
|
|
||
| return () => clearTimeout(autoSaveTimer); | ||
| }, [hasUnsavedChanges, saveDraft]); | ||
|
|
||
AnjanyKumarJaiswal marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| //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 response = await deleteDraft({id: draftId}); | ||
| if(response === ''){ | ||
| setDraftId(null); | ||
| setIsComposeOpen(null); | ||
| setTimeout(() => { | ||
| toast.success("Successfully Deleted Draft"); | ||
| refetchThreads(); | ||
| }, 500); | ||
| } | ||
| } catch (error) { | ||
| console.error('Failed to delete draft:', error); | ||
| toast.error('Failed to delete draft.'); | ||
| } finally { | ||
| setIsDeleteDraft(false); | ||
| } | ||
| }; | ||
|
|
||
AnjanyKumarJaiswal marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // this handleclose button triggeres to auto-save draft upon close | ||
| const handleClose = () => { | ||
| const hasContent = editor?.getText()?.trim().length > 0; | ||
| if (hasContent) { | ||
| saveDraft(); | ||
AnjanyKumarJaiswal marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| setShowLeaveConfirmation(true); | ||
| } else { | ||
| onClose?.(); | ||
| } | ||
| }; | ||
|
|
||
AnjanyKumarJaiswal marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| const handleGenerateSubject = async () => { | ||
| try { | ||
| setIsGeneratingSubject(true); | ||
|
|
@@ -613,17 +692,10 @@ export function EmailComposer({ | |
| } | ||
| }; | ||
|
|
||
| const handleClose = () => { | ||
| const hasContent = editor?.getText()?.trim().length > 0; | ||
| if (hasContent) { | ||
| setShowLeaveConfirmation(true); | ||
| } else { | ||
| onClose?.(); | ||
| } | ||
| }; | ||
|
|
||
| const confirmLeave = () => { | ||
| setShowLeaveConfirmation(false); | ||
| handledeleteDraft(); | ||
| onClose?.(); | ||
| }; | ||
|
|
||
|
|
@@ -644,16 +716,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) => { | ||
|
|
@@ -921,7 +983,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} | ||
| > | ||
| > | ||
| <X className="h-3.5 w-3.5 fill-[#9A9A9A]" /> | ||
| </button> | ||
| )} | ||
|
|
@@ -1467,7 +1529,13 @@ export function EmailComposer({ | |
| </TooltipProvider> | ||
| </div> | ||
| </div> | ||
| <div className="flex items-start justify-start gap-2"> | ||
| <div className="flex items-start justify-start gap-4"> | ||
| {/* <Button | ||
| className='flex p-2 hover:text-black hover:bg-white max-h-[35px] h-screen bg-black text-center text-zinc-300 text-sm' | ||
| onClick={()=>{setShowLeaveConfirmation(true)}} | ||
| disabled = {editor.getText().trim().length < 1} | ||
| > | ||
| <Trash className='w-5 h-5 rounded-md'/>Discard</Button> */} | ||
| <div className="relative"> | ||
| <AnimatePresence> | ||
| {aiGeneratedMessage !== null ? ( | ||
|
|
@@ -1566,7 +1634,7 @@ export function EmailComposer({ | |
| Stay | ||
| </Button> | ||
| <Button variant="destructive" onClick={confirmLeave}> | ||
| Leave | ||
| <Trash className='w-5 h-5 rounded-md'/>Discard | ||
| </Button> | ||
| </DialogFooter> | ||
| </DialogContent> | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to refetch threads?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually why i did it because when the user clicks the esc button the emails are not refreshed automatically though
it needs to be done manually