Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
dcb6d63
feat: Implement sidebar navigation and user profile section; add skel…
Nov 10, 2024
639d063
chore: Update .gitignore to include .env file
Nov 10, 2024
92fa409
feat: Add labeling feature with CRUD operations for user-defined labe…
Nov 10, 2024
3b266ed
Merge branch 'main' into Dev
CodeWithInferno Nov 10, 2024
ecfa32f
Labels
Nov 11, 2024
228166f
feat: Integrate email processing features with Express server; add em…
Nov 11, 2024
b541d9b
FIX:1
Nov 12, 2024
eb422e6
Merge branch 'main' into Dev
CodeWithInferno Nov 12, 2024
b6378cd
feat: Enhance Labeling and Sidebar components; add new rules link and…
Nov 13, 2024
351e888
Merge branch 'main' into Dev
CodeWithInferno Nov 13, 2024
53ef2f3
fix: Remove duplicate className assignment for priority icon in Dashb…
Nov 13, 2024
2b1ea72
fix: Update callback URLs to use production domain instead of localhost
Nov 13, 2024
1b0ec3a
feat: Update Google OAuth callback URLs to use localhost for developm…
Nov 14, 2024
e5a78f1
feat: Update Google OAuth redirect URIs for development and productio…
Nov 14, 2024
8cc0078
Merge branch 'main' into Dev
CodeWithInferno Nov 14, 2024
81b3815
WIP: Temporary commit to save current progress
Nov 15, 2024
0841016
Merge branch 'Dev' of https://github.com/CodeWithInferno/inboxiq into…
Nov 15, 2024
4d755e3
fix: Remove merge conflict markers in google.js
Nov 15, 2024
07711ab
Merge branch 'main' into Dev
CodeWithInferno Nov 15, 2024
07d3ec5
fix: Correct text content and formatting in Features and TestimonialC…
Nov 15, 2024
9e9c215
Merge branch 'Dev' of https://github.com/CodeWithInferno/inboxiq into…
Nov 15, 2024
839b9f0
fix: Remove unnecessary blank lines in route.js, page.js, and fetchEm…
Nov 15, 2024
e9f45ff
feat: Add GET endpoint to retrieve blocked emails and implement token…
Nov 15, 2024
8093e2b
Merge branch 'main' into Dev
CodeWithInferno Nov 15, 2024
08887ef
refactor: Uncomment and clean up fetchUserEmails function in google.j…
Nov 15, 2024
06b627a
feat: Add email content sanitization utility and update related compo…
Nov 17, 2024
e143a95
Merge branch 'main' into Dev
CodeWithInferno Nov 17, 2024
bc32d57
refactor: Clean up unused code and simplify the prompt section in Rul…
Nov 17, 2024
b619276
feat: Add Redis integration and update image handling in components; …
Nov 24, 2024
1b79b28
Merge branch 'main' into Dev
CodeWithInferno Nov 24, 2024
2e845b7
feat: Implement Google Calendar and iCalendar event import functional…
Nov 25, 2024
d59cb06
Merge branch 'main' into Inferno-Patch
CodeWithInferno Nov 25, 2024
40ad998
feat: Enhance UI components and styles; add sidebar for AI assistant,…
Dec 7, 2024
5e4c47b
Merge branch 'main' into Inferno-Patch
CodeWithInferno Dec 7, 2024
9a03a83
feat: Add HTML editor, template generation popup, advanced filters, a…
Dec 18, 2024
8cd1954
Merge branch 'main' into Inferno-Patch
CodeWithInferno Dec 18, 2024
9f85148
refactor: Update HtmlEditor and GenerateTemplatePopup components; rem…
Dec 19, 2024
6a5843c
Merge branch 'Inferno-Patch' of https://github.com/CodeWithInferno/in…
Dec 19, 2024
44520d5
feat: Add compose components including SubjectField, ActionButtons, A…
Dec 19, 2024
556ba4b
Feat
Dec 19, 2024
f0b04dd
Merge branch 'main' into inferno-patch-v2
CodeWithInferno Dec 19, 2024
48f8c8a
feat: Implement new functionality for enhanced user experience
Dec 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions src/app/api/drafts/fetch/route.js
Original file line number Diff line number Diff line change
@@ -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' } }
);
}
}
107 changes: 107 additions & 0 deletions src/app/api/drafts/save/route.js
Original file line number Diff line number Diff line change
@@ -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' } }
);
}
}
104 changes: 104 additions & 0 deletions src/app/dashboard/[slug]/components/Drafts.js
Original file line number Diff line number Diff line change
@@ -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 <p>Loading drafts...</p>;
// if (drafts.length === 0) return <p>No drafts found.</p>;

// return (
// <div className="drafts-container">
// <h2 className="text-xl font-semibold">Drafts</h2>
// <ul className="space-y-4">
// {drafts.map((draft) => (
// <li key={draft.id} className="bg-white p-4 rounded shadow">
// <h3 className="font-bold">{draft.subject || '(No Subject)'}</h3>
// <p className="text-gray-600">To: {draft.to || '(Not specified)'}</p>
// <p className="text-gray-800 truncate">{draft.snippet}</p>
// </li>
// ))}
// </ul>
// </div>
// );
// };

// 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 <p>Loading drafts...</p>;
if (drafts.length === 0) return <p>No drafts found.</p>;

return (
<div className="drafts-container">
<ul className="space-y-4">
{drafts.map((draft) => (
<li
key={draft.id}
className="bg-white p-4 rounded shadow cursor-pointer hover:bg-gray-100"
onClick={() => onDraftClick(draft)} // Pass the draft data on click
>
<h3 className="font-bold">{draft.subject || '(No Subject)'}</h3>
<p className="text-gray-600">To: {draft.to || '(Not specified)'}</p>
<p className="text-gray-800 truncate">{draft.snippet}</p>
</li>
))}
</ul>
</div>
);
};

export default Drafts;
Loading