feat: implement tagging functionality for emails with drag-and-drop support
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useState, DragEvent } from "react";
|
||||
import { useEmailStore } from "@/stores/email-store";
|
||||
import { useAuthStore } from "@/stores/auth-store";
|
||||
import { useDragDropContext } from "@/contexts/drag-drop-context";
|
||||
|
||||
interface UseTagDropOptions {
|
||||
tagId: string;
|
||||
onSuccess?: (count: number, tagLabel: string) => void;
|
||||
onError?: (error: string) => void;
|
||||
}
|
||||
|
||||
interface UseTagDropReturn {
|
||||
dropHandlers: {
|
||||
onDragOver: (e: DragEvent<HTMLDivElement>) => void;
|
||||
onDragEnter: (e: DragEvent<HTMLDivElement>) => void;
|
||||
onDragLeave: (e: DragEvent<HTMLDivElement>) => void;
|
||||
onDrop: (e: DragEvent<HTMLDivElement>) => void;
|
||||
};
|
||||
isDropTarget: boolean;
|
||||
isValidDropTarget: boolean;
|
||||
}
|
||||
|
||||
export function useTagDrop({ tagId, onSuccess, onError }: UseTagDropOptions): UseTagDropReturn {
|
||||
const [isOver, setIsOver] = useState(false);
|
||||
const { client } = useAuthStore();
|
||||
const { fetchEmails, selectedMailbox } = useEmailStore();
|
||||
const { isDragging, endDrag } = useDragDropContext();
|
||||
|
||||
const handleDragOver = useCallback((e: DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
if (isDragging) {
|
||||
e.dataTransfer.dropEffect = "copy";
|
||||
} else {
|
||||
e.dataTransfer.dropEffect = "none";
|
||||
}
|
||||
}, [isDragging]);
|
||||
|
||||
const handleDragEnter = useCallback((e: DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsOver(true);
|
||||
}, []);
|
||||
|
||||
const handleDragLeave = useCallback((e: DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const relatedTarget = e.relatedTarget as Node | null;
|
||||
if (!e.currentTarget.contains(relatedTarget)) {
|
||||
setIsOver(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleDrop = useCallback(async (e: DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsOver(false);
|
||||
|
||||
if (!client || !isDragging) {
|
||||
endDrag();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const emailIdsJson = e.dataTransfer.getData("application/x-email-ids");
|
||||
if (!emailIdsJson) {
|
||||
endDrag();
|
||||
return;
|
||||
}
|
||||
|
||||
const emailIds: string[] = JSON.parse(emailIdsJson);
|
||||
|
||||
for (const emailId of emailIds) {
|
||||
// Read fresh state to avoid stale closures
|
||||
const currentEmails = useEmailStore.getState().emails;
|
||||
const email = currentEmails.find(em => em.id === emailId);
|
||||
const keywords = { ...(email?.keywords || {}) };
|
||||
|
||||
// Remove old label/color keywords
|
||||
Object.keys(keywords).forEach(key => {
|
||||
if (key.startsWith("$label:") || key.startsWith("$color:")) {
|
||||
keywords[key] = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Add the new tag
|
||||
keywords[`$label:${tagId}`] = true;
|
||||
|
||||
await client.updateEmailKeywords(emailId, keywords);
|
||||
}
|
||||
|
||||
// Refresh the email list
|
||||
await fetchEmails(client, selectedMailbox);
|
||||
|
||||
onSuccess?.(emailIds.length, tagId);
|
||||
} catch (error) {
|
||||
console.error("Failed to tag emails:", error);
|
||||
onError?.(error instanceof Error ? error.message : "Unknown error");
|
||||
} finally {
|
||||
endDrag();
|
||||
}
|
||||
}, [client, isDragging, tagId, fetchEmails, selectedMailbox, endDrag, onSuccess, onError]);
|
||||
|
||||
return {
|
||||
dropHandlers: {
|
||||
onDragOver: handleDragOver,
|
||||
onDragEnter: handleDragEnter,
|
||||
onDragLeave: handleDragLeave,
|
||||
onDrop: handleDrop,
|
||||
},
|
||||
isDropTarget: isOver && isDragging,
|
||||
isValidDropTarget: isOver && isDragging,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user