diff --git a/src/app/api/drafts/fetch/route.js b/src/app/api/drafts/fetch/route.js
new file mode 100644
index 0000000..1fb7798
--- /dev/null
+++ b/src/app/api/drafts/fetch/route.js
@@ -0,0 +1,108 @@
+import { google } from 'googleapis';
+import { getSession } from '@auth0/nextjs-auth0';
+import getUserTokens from '@/lib/getUserTokens';
+
+export async function GET(req) {
+ const { searchParams } = new URL(req.url);
+ const email = searchParams.get('email');
+
+ if (!email) {
+ return new Response(JSON.stringify({ message: 'Email is required' }), {
+ status: 400,
+ headers: { 'Content-Type': 'application/json' },
+ });
+ }
+
+ try {
+ const session = await getSession(req);
+ const user = session?.user;
+
+ if (!user) {
+ return new Response(JSON.stringify({ message: 'User not authenticated' }), {
+ status: 401,
+ headers: { 'Content-Type': 'application/json' },
+ });
+ }
+
+ const userTokens = await getUserTokens(email);
+ if (!userTokens || !userTokens.access_token || !userTokens.refresh_token) {
+ return new Response(JSON.stringify({ message: 'User tokens not found or incomplete' }), {
+ status: 401,
+ headers: { 'Content-Type': 'application/json' },
+ });
+ }
+
+ const oauth2Client = new google.auth.OAuth2(
+ process.env.GOOGLE_CLIENT_ID,
+ process.env.GOOGLE_CLIENT_SECRET
+ );
+ oauth2Client.setCredentials({
+ access_token: userTokens.access_token,
+ refresh_token: userTokens.refresh_token,
+ });
+
+ const gmail = google.gmail({ version: 'v1', auth: oauth2Client });
+
+ const draftsResponse = await gmail.users.drafts.list({
+ userId: 'me',
+ maxResults: 10,
+ });
+
+ if (!draftsResponse.data.drafts) {
+ return new Response(JSON.stringify({ drafts: [] }), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' },
+ });
+ }
+
+ const drafts = await Promise.all(
+ draftsResponse.data.drafts.map(async (draft) => {
+ const draftData = await gmail.users.drafts.get({
+ userId: 'me',
+ id: draft.id,
+ });
+
+ const decodeBase64 = (data) => Buffer.from(data || '', 'base64').toString('utf-8');
+
+ const getDraftBody = (msg) => {
+ let body = '';
+ const parts = msg.payload.parts || [msg.payload];
+ for (const part of parts) {
+ if (part.mimeType === 'text/html' && part.body?.data) {
+ body = decodeBase64(part.body.data);
+ break;
+ } else if (part.mimeType === 'text/plain' && part.body?.data) {
+ body = decodeBase64(part.body.data);
+ }
+ }
+ return body || 'No content available';
+ };
+
+ const body = getDraftBody(draftData.data.message);
+
+ return {
+ id: draftData.data.id,
+ subject: draftData.data.message.payload.headers.find((h) => h.name === 'Subject')?.value || '(No Subject)',
+ to: draftData.data.message.payload.headers.find((h) => h.name === 'To')?.value || '',
+ body,
+ snippet: draftData.data.message.snippet,
+ timestamp: new Date(parseInt(draftData.data.message.internalDate || Date.now())),
+ };
+ })
+ );
+
+ return new Response(JSON.stringify({ drafts }), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' },
+ });
+ } catch (error) {
+ console.error('Error fetching drafts:', error);
+ return new Response(
+ JSON.stringify({
+ message: 'Internal server error',
+ details: error.message,
+ }),
+ { status: 500, headers: { 'Content-Type': 'application/json' } }
+ );
+ }
+}
diff --git a/src/app/api/drafts/save/route.js b/src/app/api/drafts/save/route.js
new file mode 100644
index 0000000..134ab4f
--- /dev/null
+++ b/src/app/api/drafts/save/route.js
@@ -0,0 +1,107 @@
+import { google } from 'googleapis';
+import { getSession } from '@auth0/nextjs-auth0';
+import getUserTokens from '@/lib/getUserTokens';
+
+export async function POST(req) {
+ try {
+ const body = await req.json(); // Parse the request body
+ const { to, subject, body: emailBody, userEmail, draftId } = body;
+
+ if (!userEmail || !to || !subject || !emailBody) {
+ return new Response(
+ JSON.stringify({ message: 'Missing required fields' }),
+ { status: 400, headers: { 'Content-Type': 'application/json' } }
+ );
+ }
+
+ // Get user session
+ const session = await getSession(req);
+ if (!session || !session.user) {
+ return new Response(
+ JSON.stringify({ message: 'User not authenticated' }),
+ { status: 401, headers: { 'Content-Type': 'application/json' } }
+ );
+ }
+
+ // Fetch user's tokens from the database
+ const userTokens = await getUserTokens(userEmail);
+ if (!userTokens || !userTokens.access_token || !userTokens.refresh_token) {
+ return new Response(
+ JSON.stringify({ message: 'User tokens not found or incomplete' }),
+ { status: 401, headers: { 'Content-Type': 'application/json' } }
+ );
+ }
+
+ // Initialize OAuth2 client
+ const oauth2Client = new google.auth.OAuth2(
+ process.env.GOOGLE_CLIENT_ID,
+ process.env.GOOGLE_CLIENT_SECRET
+ );
+ oauth2Client.setCredentials({
+ access_token: userTokens.access_token,
+ refresh_token: userTokens.refresh_token,
+ });
+
+ const gmail = google.gmail({ version: 'v1', auth: oauth2Client });
+
+ // Format email for draft
+ const rawEmail = [
+ `To: ${to}`,
+ `Subject: ${subject}`,
+ 'Content-Type: text/html; charset=UTF-8',
+ '',
+ emailBody,
+ ].join('\n');
+
+ const encodedEmail = Buffer.from(rawEmail)
+ .toString('base64')
+ .replace(/\+/g, '-')
+ .replace(/\//g, '_')
+ .replace(/=+$/, '');
+
+ if (draftId) {
+ // Update existing draft
+ const response = await gmail.users.drafts.update({
+ userId: 'me',
+ id: draftId,
+ requestBody: {
+ message: {
+ raw: encodedEmail,
+ },
+ },
+ });
+
+ return new Response(
+ JSON.stringify({
+ message: 'Draft updated successfully',
+ draftId: response.data.id,
+ }),
+ { status: 200, headers: { 'Content-Type': 'application/json' } }
+ );
+ } else {
+ // Create new draft
+ const response = await gmail.users.drafts.create({
+ userId: 'me',
+ requestBody: {
+ message: {
+ raw: encodedEmail,
+ },
+ },
+ });
+
+ return new Response(
+ JSON.stringify({
+ message: 'Draft saved successfully',
+ draftId: response.data.id,
+ }),
+ { status: 200, headers: { 'Content-Type': 'application/json' } }
+ );
+ }
+ } catch (error) {
+ console.error('Error saving draft to Gmail:', error);
+ return new Response(
+ JSON.stringify({ message: 'Internal server error', error: error.message }),
+ { status: 500, headers: { 'Content-Type': 'application/json' } }
+ );
+ }
+}
diff --git a/src/app/dashboard/[slug]/components/Drafts.js b/src/app/dashboard/[slug]/components/Drafts.js
new file mode 100644
index 0000000..2614c81
--- /dev/null
+++ b/src/app/dashboard/[slug]/components/Drafts.js
@@ -0,0 +1,104 @@
+// 'use client';
+
+// import { useEffect, useState } from 'react';
+
+// const Drafts = ({ email }) => {
+// const [drafts, setDrafts] = useState([]);
+// const [loading, setLoading] = useState(true);
+
+// const fetchDrafts = async () => {
+// try {
+// const response = await fetch(`/api/drafts/fetch?email=${encodeURIComponent(email)}`);
+// const data = await response.json();
+// setDrafts(data.drafts || []);
+// } catch (error) {
+// console.error('Error fetching drafts:', error.message);
+// } finally {
+// setLoading(false);
+// }
+// };
+
+// useEffect(() => {
+// if (email) {
+// fetchDrafts();
+// }
+// }, [email]);
+
+// if (loading) return
Loading drafts...
;
+// if (drafts.length === 0) return No drafts found.
;
+
+// return (
+//
+// );
+// };
+
+// export default Drafts;
+
+
+
+
+
+
+
+
+
+'use client';
+
+import { useEffect, useState } from 'react';
+
+const Drafts = ({ email, onDraftClick }) => {
+ const [drafts, setDrafts] = useState([]);
+ const [loading, setLoading] = useState(true);
+
+ const fetchDrafts = async () => {
+ try {
+ const response = await fetch(`/api/drafts/fetch?email=${encodeURIComponent(email)}`);
+ const data = await response.json();
+ setDrafts(data.drafts || []);
+ } catch (error) {
+ console.error('Error fetching drafts:', error.message);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ if (email) {
+ fetchDrafts();
+ }
+ }, [email]);
+
+ if (loading) return Loading drafts...
;
+ if (drafts.length === 0) return No drafts found.
;
+
+ return (
+
+ );
+};
+
+export default Drafts;
diff --git a/src/app/dashboard/[slug]/page.js b/src/app/dashboard/[slug]/page.js
index 2ce53d2..301292b 100644
--- a/src/app/dashboard/[slug]/page.js
+++ b/src/app/dashboard/[slug]/page.js
@@ -8,8 +8,7 @@ import MessageDetails from '../components/MessageDetails';
import Compose from '../components/Compose';
import { FaExclamationCircle, FaSearch } from 'react-icons/fa';
import Search from '../components/search';
-
-
+import Drafts from './components/Drafts';
const DashboardPage = () => {
const router = useRouter();
@@ -25,6 +24,7 @@ const DashboardPage = () => {
const [isComposeOpen, setIsComposeOpen] = useState(false);
const [isClassifying, setIsClassifying] = useState(false);
const [isBlocking, setIsBlocking] = useState(false);
+ const [composeData, setComposeData] = useState(null); // State to hold draft data for Compose
useEffect(() => {
if (!slug) {
@@ -69,12 +69,6 @@ const DashboardPage = () => {
}
};
-
-
-
-
-
-
const classifyEmails = async (emails) => {
const classifiedEmails = [];
@@ -85,7 +79,6 @@ const DashboardPage = () => {
body: truncateContent(email.snippet || '', 800),
};
- // Log the email content for inspection
console.log('Sending email for classification:', emailContent);
const response = await fetch('/api/ai/email/classifyEmail', {
@@ -114,16 +107,10 @@ const DashboardPage = () => {
return classifiedEmails;
};
-
- // Utility function to truncate text to avoid token limit issues
const truncateContent = (text, limit) => {
return text.length > limit ? text.slice(0, limit - 1) + '…' : text;
};
-
-
-
- // Email labeling function reintroduced from the backup code
const labelEmails = async () => {
return await Promise.all(
emails.map(async (email) => {
@@ -176,14 +163,12 @@ const DashboardPage = () => {
const handleOpenMessage = async (message) => {
if (message && message.threadId) {
-
try {
- // Mark the message as read if it hasn't been already
if (!message.isRead) {
await markAsRead(message.id);
}
- setSelectedMessage(message); // Set the selected message for display
- await fetchThread(message.threadId); // Fetch the thread for the selected message
+ setSelectedMessage(message);
+ await fetchThread(message.threadId);
} catch (error) {
console.error('Error opening message:', error.message);
}
@@ -226,11 +211,7 @@ const DashboardPage = () => {
setThreadMessages([]);
};
-
-
const handleBackToInbox = () => {
- setSearchMode(false);
- setSearchQuery('');
setEmails([]);
const label = getGmailLabel('inbox');
fetchEmails(label, user.email);
@@ -241,9 +222,15 @@ const DashboardPage = () => {
};
const closeComposeModal = () => {
+ setComposeData(null); // Clear draft data
setIsComposeOpen(false);
};
+ const handleDraftClick = (draft) => {
+ setComposeData(draft); // Pass draft data to Compose
+ setIsComposeOpen(true); // Open the Compose modal
+ };
+
const handleLoadMore = () => {
const label = getGmailLabel(slug);
if (user?.email && nextPageToken) {
@@ -265,28 +252,24 @@ const DashboardPage = () => {
);
}
+
const handleSearchSubmit = async (query) => {
- setEmails([]); // Clear existing emails
- setLoading(true); // Show loading state
- const label = getGmailLabel(slug); // Use the appropriate label
+ setEmails([]);
+ setLoading(true);
+ const label = getGmailLabel(slug);
try {
- // Make API call to fetch emails based on the query
const url = `/api/auth/google/fetchEmails?label=${label}&email=${encodeURIComponent(user?.email)}&query=${encodeURIComponent(query)}`;
const response = await fetch(url);
if (!response.ok) throw new Error('Error fetching emails');
const data = await response.json();
- setEmails(data.messages || []); // Update email list
+ setEmails(data.messages || []);
} catch (error) {
console.error('Error fetching emails:', error.message);
} finally {
- setLoading(false); // Hide loading state
+ setLoading(false);
}
};
-
-
-
-
return (
@@ -295,20 +278,15 @@ const DashboardPage = () => {
{!selectedMessage && (
<>
- {/* Add the Search Component */}
{
- console.log('Search query submitted:', query); // Debug search query
- const label = getGmailLabel(slug); // Dynamically determine the label
- fetchEmails(label, user?.email, query); // Pass the query
+ console.log('Search query submitted:', query);
+ const label = getGmailLabel(slug);
+ fetchEmails(label, user?.email, query);
}}
/>
-
-
-
-
-
+
);
};
diff --git a/src/app/dashboard/components/Compose.js b/src/app/dashboard/components/Compose.js
index d1c7e5a..90fc4e4 100644
--- a/src/app/dashboard/components/Compose.js
+++ b/src/app/dashboard/components/Compose.js
@@ -1,1057 +1,164 @@
-// "use client";
+ "use client";
-// import React, { useState, useRef, useEffect } from 'react';
-// import { FaBold, FaItalic, FaUnderline, FaListUl, FaListOl, FaAlignCenter, FaAlignLeft, FaAlignRight, FaHeading } from "react-icons/fa";
-// import DOMPurify from 'dompurify';
-
-// const Compose = ({ isOpen, onClose, userEmail }) => {
-// const [message, setMessage] = useState({ to: '', cc: '', bcc: '', subject: '' });
-// const [htmlInput, setHtmlInput] = useState('');
-// const [showHtmlEditor, setShowHtmlEditor] = useState(false);
-// const editorRef = useRef(null);
-
-// const handleChange = (e) => {
-// const { name, value } = e.target;
-// setMessage((prevMessage) => ({ ...prevMessage, [name]: value }));
-// };
-
-// const validateEmailList = (emails) => {
-// if (!emails) return true;
-// const emailList = emails.split(',').map((email) => email.trim());
-// const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
-// return emailList.every((email) => emailRegex.test(email));
-// };
-
-// const decodeHtmlEntities = (str) => {
-// const textArea = document.createElement("textarea");
-// textArea.innerHTML = str;
-// return textArea.value;
-// };
-
-// const encodeHtmlEntities = (str) => {
-// const textArea = document.createElement("textarea");
-// textArea.textContent = str;
-// return textArea.innerHTML;
-// };
-
-// const handleHtmlInput = (e) => {
-// const rawHtml = e.target.value;
-// setHtmlInput(rawHtml);
-// if (editorRef.current) {
-// editorRef.current.innerHTML = DOMPurify.sanitize(decodeHtmlEntities(rawHtml));
-// }
-// };
-
-// useEffect(() => {
-// if (editorRef.current) {
-// editorRef.current.innerHTML = DOMPurify.sanitize(decodeHtmlEntities(htmlInput));
-// }
-// }, [htmlInput]);
-
-// const handleEditorChange = () => {
-// if (editorRef.current) {
-// const rawHtml = editorRef.current.innerHTML;
-// setHtmlInput(encodeHtmlEntities(rawHtml));
-// }
-// };
-
-// const applyFormat = (command, value = null) => {
-// document.execCommand(command, false, value);
-// };
-
-// const handleSubmit = async (e) => {
-// e.preventDefault();
-// const body = editorRef.current.innerHTML;
-
-// if (!validateEmailList(message.to) || !validateEmailList(message.cc) || !validateEmailList(message.bcc)) {
-// alert('Please provide valid email addresses.');
-// return;
-// }
-
-// console.log("Email Body:", body);
-// alert('Email sent successfully!');
-// };
-
-// if (!isOpen) return null;
-
-// return (
-//
-//
-//
Compose a Message
-//
-//
-//
-// );
-// };
-
-// export default Compose;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-// "use client";
-
-// import React, { useState, useRef } from 'react';
-// import dynamic from 'next/dynamic';
-// import { FaAngleDown, FaPaperclip } from "react-icons/fa";
-// import FilePreview from './FilePreview';
-
-// const QuillNoSSRWrapper = dynamic(() => import('react-quill'), { ssr: false });
-// import 'react-quill/dist/quill.snow.css';
-
-// const Compose = ({ isOpen, onClose, userEmail }) => {
-// const [message, setMessage] = useState({ to: '', cc: '', bcc: '', subject: '' });
-// const [showCcBcc, setShowCcBcc] = useState(false);
-// const [body, setBody] = useState('');
-// const [attachments, setAttachments] = useState([]);
-// const [isSending, setIsSending] = useState(false);
-// const dropRef = useRef(null);
-
-// const handleChange = (e) => {
-// const { name, value } = e.target;
-// setMessage((prevMessage) => ({ ...prevMessage, [name]: value }));
-// };
-
-// const handleFileChange = (e) => {
-// const files = Array.from(e.target.files);
-// processFiles(files);
-// };
-
-// const processFiles = (files) => {
-// const filePromises = files.map((file) => {
-// return new Promise((resolve) => {
-// const reader = new FileReader();
-// reader.onload = () => resolve({
-// name: file.name,
-// content: reader.result.split(',')[1],
-// type: file.type
-// });
-// reader.readAsDataURL(file);
-// });
-// });
-
-// Promise.all(filePromises).then((filesData) => {
-// setAttachments((prev) => [...prev, ...filesData]);
-// });
-// };
-
-// const handleRemoveAttachment = (index) => {
-// setAttachments((prev) => prev.filter((_, i) => i !== index));
-// };
-
-// const handleDragOver = (e) => {
-// e.preventDefault();
-// e.stopPropagation();
-// dropRef.current.classList.add("border-dashed", "border-blue-500");
-// };
-
-// const handleDragLeave = () => {
-// dropRef.current.classList.remove("border-dashed", "border-blue-500");
-// };
-
-// const handleDrop = (e) => {
-// e.preventDefault();
-// e.stopPropagation();
-// dropRef.current.classList.remove("border-dashed", "border-blue-500");
-
-// const files = Array.from(e.dataTransfer.files);
-// processFiles(files);
-// };
-
-// const handleSubmit = async (e) => {
-// e.preventDefault();
-
-// if (!message.to || !message.subject || !body) {
-// alert('Please provide recipient, subject, and body.');
-// return;
-// }
-
-// setIsSending(true);
-
-// try {
-// const response = await fetch('/api/messages/sendMail', {
-// method: 'POST',
-// headers: {
-// 'Content-Type': 'application/json',
-// },
-// body: JSON.stringify({
-// to: message.to,
-// subject: message.subject,
-// body: body,
-// userEmail: userEmail,
-// attachments: attachments,
-// }),
-// });
-
-// const data = await response.json();
-
-// if (!response.ok) {
-// throw new Error(data.message || 'Error sending email');
-// }
-
-// alert('Email sent successfully!');
-// setMessage({ to: '', cc: '', bcc: '', subject: '' });
-// setBody('');
-// setAttachments([]);
-// onClose();
-// } catch (error) {
-// console.error('Error:', error.message);
-// alert('Failed to send email. Please try again.');
-// } finally {
-// setIsSending(false);
-// }
-// };
-
-// const quillModules = {
-// toolbar: [
-// ['bold', 'italic', 'underline', 'strike'], // toggled buttons
-// ['blockquote', 'code-block'],
-
-// [{ 'header': 1 }, { 'header': 2 }], // custom button values
-// [{ 'list': 'ordered'}, { 'list': 'bullet' }],
-// [{ 'script': 'sub'}, { 'script': 'super' }], // superscript/subscript
-// [{ 'indent': '-1'}, { 'indent': '+1' }], // outdent/indent
-// [{ 'direction': 'rtl' }], // text direction
-
-// [{ 'size': ['small', false, 'large', 'huge'] }], // custom dropdown
-// [{ 'header': [1, 2, 3, 4, 5, 6, false] }],
-
-// [{ 'color': [] }, { 'background': [] }], // dropdown with defaults from theme
-// [{ 'font': [] }],
-// [{ 'align': [] }],
-
-// ['clean'], // remove formatting button
-// ['link', 'image', 'video'] // link and image, video
-// ]
-// };
-
-// if (!isOpen) return null;
-
-// return (
-//
-// );
-// };
-
-// export default Compose;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-// "use client";
-
-// import React, { useState, useRef } from "react";
-// import dynamic from "next/dynamic";
-// import { FaAngleDown, FaPaperclip } from "react-icons/fa";
-// import FilePreview from "./FilePreview";
-
-// const QuillNoSSRWrapper = dynamic(() => import("react-quill"), { ssr: false });
-// import "react-quill/dist/quill.snow.css";
-
-// const Compose = ({ isOpen, onClose, userEmail }) => {
-// const [message, setMessage] = useState({ to: "", cc: "", bcc: "", subject: "" });
-// const [showCcBcc, setShowCcBcc] = useState(false);
-// const [body, setBody] = useState("");
-// const [attachments, setAttachments] = useState([]);
-// const [isSending, setIsSending] = useState(false);
-// const dropRef = useRef(null);
-
-// const handleChange = (e) => {
-// const { name, value } = e.target;
-// setMessage((prevMessage) => ({ ...prevMessage, [name]: value }));
-// };
-
-// const handleFileChange = (e) => {
-// const files = Array.from(e.target.files);
-// processFiles(files);
-// };
-
-// const processFiles = (files) => {
-// const filePromises = files.map((file) => {
-// return new Promise((resolve) => {
-// const reader = new FileReader();
-// reader.onload = () =>
-// resolve({
-// name: file.name,
-// content: reader.result.split(",")[1], // Base64 content
-// type: file.type,
-// });
-// reader.readAsDataURL(file);
-// });
-// });
-
-// Promise.all(filePromises).then((filesData) => {
-// setAttachments((prev) => [...prev, ...filesData]);
-// });
-// };
-
-// const handleRemoveAttachment = (index) => {
-// setAttachments((prev) => prev.filter((_, i) => i !== index));
-// };
-
-// const handleDragOver = (e) => {
-// e.preventDefault();
-// e.stopPropagation();
-// dropRef.current.classList.add("border-dashed", "border-blue-500");
-// };
-
-// const handleDragLeave = () => {
-// dropRef.current.classList.remove("border-dashed", "border-blue-500");
-// };
-
-// const handleDrop = (e) => {
-// e.preventDefault();
-// e.stopPropagation();
-// dropRef.current.classList.remove("border-dashed", "border-blue-500");
-
-// const files = Array.from(e.dataTransfer.files);
-// processFiles(files);
-// };
-
-// const handleSubmit = async (e) => {
-// e.preventDefault();
-
-// if (!message.to || !message.subject || !body) {
-// alert("Please provide recipient, subject, and body.");
-// return;
-// }
-
-// setIsSending(true);
-
-// try {
-// const response = await fetch("/api/messages/sendMail", {
-// method: "POST",
-// headers: {
-// "Content-Type": "application/json",
-// },
-// body: JSON.stringify({
-// to: message.to,
-// subject: message.subject,
-// body: body, // Quill content
-// userEmail: userEmail,
-// attachments: attachments, // Pass attachments to the backend
-// }),
-// });
-
-// const data = await response.json();
-
-// if (!response.ok) {
-// throw new Error(data.message || "Error sending email");
-// }
-
-// alert("Email sent successfully!");
-// setMessage({ to: "", cc: "", bcc: "", subject: "" });
-// setBody("");
-// setAttachments([]);
-// onClose();
-// } catch (error) {
-// console.error("Error:", error.message);
-// alert("Failed to send email. Please try again.");
-// } finally {
-// setIsSending(false);
-// }
-// };
-
-// const quillModules = {
-// toolbar: [
-// ["bold", "italic", "underline", "strike"],
-// ["blockquote", "code-block"],
-// [{ header: 1 }, { header: 2 }],
-// [{ list: "ordered" }, { list: "bullet" }],
-// [{ script: "sub" }, { script: "super" }],
-// [{ indent: "-1" }, { indent: "+1" }],
-// [{ direction: "rtl" }],
-// [{ size: ["small", false, "large", "huge"] }],
-// [{ header: [1, 2, 3, 4, 5, 6, false] }],
-// [{ color: [] }, { background: [] }],
-// [{ font: [] }],
-// [{ align: [] }],
-// ["clean"],
-// ["link", "image", "video"],
-// ],
-// };
-
-// if (!isOpen) return null;
-
-// return (
-//
-// );
-// };
-
-// export default Compose;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-"use client";
-
-import React, { useState, useRef } from "react";
+import React, { useState, useRef, useEffect, useCallback } from "react";
import dynamic from "next/dynamic";
-import { FaAngleDown, FaPaperclip } from "react-icons/fa";
-import FilePreview from "./FilePreview";
-import {
- ContextMenu,
- ContextMenuContent,
- ContextMenuItem,
- ContextMenuTrigger,
-} from "@/components/ui/context-menu";
-
-const QuillNoSSRWrapper = dynamic(() => import("react-quill"), { ssr: false });
+import RecipientFields from "./compose/RecipientFields";
+import SubjectField from "./compose/SubjectField";
+import BodyEditor from "./compose/BodyEditor";
+import Attachments from "./compose/Attachments";
+import ActionButtons from "./compose/ActionButtons";
+import AIModal from "./compose/AIModal";
import "react-quill/dist/quill.snow.css";
-const Compose = ({ isOpen, onClose, userEmail }) => {
- const [message, setMessage] = useState({ to: "", cc: "", bcc: "", subject: "" });
- const [showCcBcc, setShowCcBcc] = useState(false);
+const Compose = ({ isOpen, onClose, userEmail, draftData }) => {
+ const [message, setMessage] = useState({
+ to: "",
+ cc: "",
+ bcc: "",
+ subject: "",
+ });
const [body, setBody] = useState("");
const [attachments, setAttachments] = useState([]);
- const [isSending, setIsSending] = useState(false);
- const dropRef = useRef(null);
+ const [isSaving, setIsSaving] = useState(false);
const [isAiModalOpen, setIsAiModalOpen] = useState(false);
const [aiInput, setAiInput] = useState("");
-
-
- const handleChange = (e) => {
- const { name, value } = e.target;
- setMessage((prevMessage) => ({ ...prevMessage, [name]: value }));
- };
-
- const handleFileChange = (e) => {
- const files = Array.from(e.target.files);
- processFiles(files);
- };
-
- const processFiles = (files) => {
- const filePromises = files.map((file) => {
- return new Promise((resolve) => {
- const reader = new FileReader();
- reader.onload = () =>
- resolve({
- name: file.name,
- content: reader.result.split(",")[1], // Base64 content
- type: file.type,
- });
- reader.readAsDataURL(file);
+ const saveTimeout = useRef(null); // To track debounce timeout
+
+ // Effect to populate the form when draftData changes
+ useEffect(() => {
+ if (draftData) {
+ setMessage({
+ to: draftData.to || "",
+ cc: draftData.cc || "",
+ bcc: draftData.bcc || "",
+ subject: draftData.subject || "",
});
- });
-
- Promise.all(filePromises).then((filesData) => {
- setAttachments((prev) => [...prev, ...filesData]);
- });
- };
-
- const handleRemoveAttachment = (index) => {
- setAttachments((prev) => prev.filter((_, i) => i !== index));
- };
-
- const handleDragOver = (e) => {
- e.preventDefault();
- e.stopPropagation();
- dropRef.current.classList.add("border-dashed", "border-blue-500");
- };
-
- const handleDragLeave = () => {
- dropRef.current.classList.remove("border-dashed", "border-blue-500");
- };
-
- const handleDrop = (e) => {
- e.preventDefault();
- e.stopPropagation();
- dropRef.current.classList.remove("border-dashed", "border-blue-500");
-
- const files = Array.from(e.dataTransfer.files);
- processFiles(files);
- };
-
- const handleSubmit = async (e) => {
- e.preventDefault();
-
- if (!message.to || !message.subject || !body) {
- alert("Please provide recipient, subject, and body.");
- return;
+ setBody(draftData.body || "");
+ setAttachments(draftData.attachments || []);
}
+ }, [draftData]);
- setIsSending(true);
-
+ // Auto-save draft API call
+ const saveDraft = useCallback(async () => {
+ if (!userEmail) return;
+ setIsSaving(true);
try {
- const response = await fetch("/api/messages/sendMail", {
+ const response = await fetch("/api/drafts/save", {
method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
+ headers: { "Content-Type": "application/json" },
body: JSON.stringify({
to: message.to,
+ cc: message.cc,
+ bcc: message.bcc,
subject: message.subject,
- body: body, // Quill content
- userEmail: userEmail,
- attachments: attachments, // Pass attachments to the backend
+ body,
+ attachments,
+ userEmail,
+ draftId: draftData?.id || null, // Pass draft ID if editing an existing draft
}),
});
-
- const data = await response.json();
-
if (!response.ok) {
- throw new Error(data.message || "Error sending email");
+ throw new Error("Failed to save draft");
}
-
- alert("Email sent successfully!");
- setMessage({ to: "", cc: "", bcc: "", subject: "" });
- setBody("");
- setAttachments([]);
- onClose();
+ console.log("Draft saved successfully");
} catch (error) {
- console.error("Error:", error.message);
- alert("Failed to send email. Please try again.");
+ console.error("Error saving draft:", error.message);
} finally {
- setIsSending(false);
+ setIsSaving(false);
}
+ }, [message, body, attachments, userEmail, draftData?.id]);
+
+ // Debounce function to delay saveDraft calls
+ const handleAutoSave = () => {
+ if (saveTimeout.current) clearTimeout(saveTimeout.current); // Clear previous timeout
+ saveTimeout.current = setTimeout(() => {
+ saveDraft();
+ }, 500); // Save after 500ms of inactivity
};
- const handleContextCopy = () => {
- const plainText = body.replace(/<\/?[^>]+(>|$)/g, ""); // Remove HTML tags
- navigator.clipboard.writeText(plainText).then(
- () => alert("Copied to clipboard!"),
- (err) => console.error("Failed to copy text: ", err)
- );
+ // Update message state and trigger auto-save
+ const handleChange = (e) => {
+ const { name, value } = e.target;
+ setMessage((prevMessage) => ({ ...prevMessage, [name]: value }));
+ handleAutoSave();
};
- const handleContextPaste = () => {
- navigator.clipboard.readText().then(
- (text) => setBody((prev) => `${prev}${text}
`),
- (err) => console.error("Failed to paste text: ", err)
- );
+ // Update body and trigger auto-save
+ const handleBodyChange = (value) => {
+ setBody(value);
+ handleAutoSave();
};
- const handleContextCut = () => {
- const plainText = body.replace(/<\/?[^>]+(>|$)/g, ""); // Remove HTML tags
- navigator.clipboard.writeText(plainText).then(() => setBody(""), (err) => console.error(err));
+ const handleFileChange = (e) => {
+ const files = Array.from(e.target.files);
+ setAttachments((prev) => [...prev, ...files]);
+ handleAutoSave();
};
- const handleAiGenerate = async (templateDescription) => {
- if (!templateDescription) return;
+ const handleRemoveAttachment = (index) => {
+ setAttachments((prev) => prev.filter((_, i) => i !== index));
+ handleAutoSave();
+ };
+ const handleAiGenerate = async () => {
+ if (!aiInput) return;
try {
- const response = await fetch("/api/ai/compose/emailTemplates", {
+ const response = await fetch("/api/ai/generate", {
method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({ templateType: templateDescription, userEmail }),
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ input: aiInput, userEmail }),
});
-
const data = await response.json();
-
- if (response.ok) {
- setMessage((prev) => ({ ...prev, subject: data.subject }));
- setBody(data.body);
- } else {
- alert(data.message || "Error generating email template");
+ if (!response.ok) {
+ throw new Error(data.message || "Failed to generate content");
}
+ setBody((prevBody) => `${prevBody}\n${data.generatedContent}`);
+ setAiInput("");
} catch (error) {
- console.error("Error fetching AI-generated template:", error.message);
- alert("Failed to generate template. Please try again.");
+ console.error("Error generating content:", error.message);
+ } finally {
+ setIsAiModalOpen(false);
}
};
- const quillModules = {
- toolbar: [
- ["bold", "italic", "underline", "strike"],
- ["blockquote", "code-block"],
- [{ header: 1 }, { header: 2 }],
- [{ list: "ordered" }, { list: "bullet" }],
- [{ script: "sub" }, { script: "super" }],
- [{ indent: "-1" }, { indent: "+1" }],
- [{ direction: "rtl" }],
- [{ size: ["small", false, "large", "huge"] }],
- [{ header: [1, 2, 3, 4, 5, 6, false] }],
- [{ color: [] }, { background: [] }],
- [{ font: [] }],
- [{ align: [] }],
- ["clean"],
- ["link", "image", "video"],
- ],
- };
+ useEffect(() => {
+ // Cleanup timeout on component unmount
+ return () => {
+ if (saveTimeout.current) clearTimeout(saveTimeout.current);
+ };
+ }, []);
if (!isOpen) return null;
return (
-
+
- {isAiModalOpen && (
-
-
-
AI Email Template
-
-
- )}
-
);
};
diff --git a/src/app/dashboard/components/Compose/AIModal.jsx b/src/app/dashboard/components/Compose/AIModal.jsx
new file mode 100644
index 0000000..593314f
--- /dev/null
+++ b/src/app/dashboard/components/Compose/AIModal.jsx
@@ -0,0 +1,38 @@
+'use client';
+
+const AIModal = ({ isOpen, setIsOpen, aiInput, setAiInput, handleAiGenerate }) => {
+ if (!isOpen) return null;
+
+ return (
+
+
+
AI Email Template
+
+
+ );
+};
+
+export default AIModal;
diff --git a/src/app/dashboard/components/Compose/ActionButtons.jsx b/src/app/dashboard/components/Compose/ActionButtons.jsx
new file mode 100644
index 0000000..a22e35c
--- /dev/null
+++ b/src/app/dashboard/components/Compose/ActionButtons.jsx
@@ -0,0 +1,25 @@
+'use client';
+
+const ActionButtons = ({ isSending, onClose, handleSubmit }) => {
+ return (
+
+
+ Cancel
+
+
+ {isSending ? "Sending..." : "Send"}
+
+
+ );
+};
+
+export default ActionButtons;
diff --git a/src/app/dashboard/components/Compose/Attachments.jsx b/src/app/dashboard/components/Compose/Attachments.jsx
new file mode 100644
index 0000000..f849d88
--- /dev/null
+++ b/src/app/dashboard/components/Compose/Attachments.jsx
@@ -0,0 +1,19 @@
+'use client';
+
+import { FaPaperclip } from "react-icons/fa";
+import FilePreview from "./FilePreview";
+
+const Attachments = ({ attachments, handleFileChange, handleRemoveAttachment }) => {
+ return (
+
+
+
+
+ );
+};
+
+export default Attachments;
diff --git a/src/app/dashboard/components/Compose/BodyEditor.jsx b/src/app/dashboard/components/Compose/BodyEditor.jsx
new file mode 100644
index 0000000..ac94aca
--- /dev/null
+++ b/src/app/dashboard/components/Compose/BodyEditor.jsx
@@ -0,0 +1,40 @@
+'use client';
+
+import dynamic from "next/dynamic";
+import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from "@/components/ui/context-menu";
+
+const QuillNoSSRWrapper = dynamic(() => import("react-quill"), { ssr: false });
+import "react-quill/dist/quill.snow.css";
+
+const BodyEditor = ({ body, setBody, handleContextCopy, handleContextPaste, handleContextCut }) => {
+ const quillModules = {
+ toolbar: [
+ ["bold", "italic", "underline"],
+ [{ list: "ordered" }, { list: "bullet" }],
+ ["link", "image"],
+ ],
+ };
+
+ return (
+
+
+
+
+
+
+
+ Copy
+ Paste
+ Cut
+
+
+ );
+};
+
+export default BodyEditor;
diff --git a/src/app/dashboard/components/Compose/FilePreview.js b/src/app/dashboard/components/Compose/FilePreview.js
new file mode 100644
index 0000000..9569e76
--- /dev/null
+++ b/src/app/dashboard/components/Compose/FilePreview.js
@@ -0,0 +1,266 @@
+// "use client";
+
+// import React, { useState } from "react";
+// import { Dialog, DialogTrigger, DialogContent, DialogClose } from "@/components/ui/dialog";
+
+// const FilePreview = ({ files, onRemove }) => {
+// const [selectedFile, setSelectedFile] = useState(null);
+
+// const renderFilePreview = (file) => {
+// if (file.type.startsWith("image/")) {
+// return (
+//

setSelectedFile(file)}
+// />
+// );
+// } else if (file.type === "application/pdf") {
+// return (
+//
setSelectedFile(file)}
+// >
+// View PDF
+//
+// );
+// } else if (file.type.startsWith("text/")) {
+// return (
+//
setSelectedFile(file)}
+// >
+// {atob(file.content).slice(0, 20)}...
+//
+// );
+// } else {
+// return
Unsupported File;
+// }
+// };
+
+// return (
+//
+// {files.map((file, index) => (
+//
setSelectedFile(file)}
+// >
+//
+// {renderFilePreview(file)}
+//
+// {file.name}
+//
+//
+//
{
+// e.stopPropagation();
+// onRemove(index);
+// }}
+// className="text-red-500 hover:text-red-700 text-xs"
+// >
+// Remove
+//
+//
+// ))}
+
+// {/* Full-Screen Centered File Popup */}
+// {selectedFile && (
+//
+// )}
+//
+// );
+// };
+
+// export default FilePreview;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+"use client";
+
+import React, { useState } from "react";
+import { Dialog } from "@headlessui/react";
+
+const FilePreview = ({ files, onRemove }) => {
+ const [selectedFile, setSelectedFile] = useState(null);
+
+ // Renders file preview (image, PDF, text)
+ const renderFilePreview = (file) => {
+ if (file.type.startsWith("image/")) {
+ return (
+

setSelectedFile(file)}
+ />
+ );
+ } else if (file.type === "application/pdf") {
+ return (
+
setSelectedFile(file)}
+ className="text-blue-500 underline text-sm"
+ >
+ View PDF
+
+ );
+ } else if (file.type.startsWith("text/")) {
+ return (
+
setSelectedFile(file)}
+ className="text-gray-600 truncate text-sm"
+ >
+ {atob(file.content).slice(0, 20)}...
+
+ );
+ }
+ return
Unsupported File;
+ };
+
+ return (
+
+ {/* File List */}
+ {files.map((file, index) => (
+
+
+ {renderFilePreview(file)}
+
+ {file.name}
+
+
+
onRemove(index)}
+ className="text-red-500 hover:text-red-700 text-xs"
+ >
+ Remove
+
+
+ ))}
+
+ {/* Full-Screen Popup */}
+ {selectedFile && (
+
+ )}
+
+ );
+};
+
+export default FilePreview;
diff --git a/src/app/dashboard/components/Compose/RecipientFields.jsx b/src/app/dashboard/components/Compose/RecipientFields.jsx
new file mode 100644
index 0000000..2c0b272
--- /dev/null
+++ b/src/app/dashboard/components/Compose/RecipientFields.jsx
@@ -0,0 +1,49 @@
+'use client';
+
+const RecipientFields = ({ message, handleChange, showCcBcc, setShowCcBcc }) => {
+ return (
+
+ );
+};
+
+export default RecipientFields;
diff --git a/src/app/dashboard/components/Compose/SubjectField.jsx b/src/app/dashboard/components/Compose/SubjectField.jsx
new file mode 100644
index 0000000..a2be7bb
--- /dev/null
+++ b/src/app/dashboard/components/Compose/SubjectField.jsx
@@ -0,0 +1,17 @@
+'use client';
+
+const SubjectField = ({ message, handleChange }) => {
+ return (
+
+ );
+};
+
+export default SubjectField;