Files
SRCmail/hooks/use-tag-drop.ts
T

116 lines
3.4 KiB
TypeScript

"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,
};
}