diff --git a/pages/api/email.ts b/pages/api/email.ts index 2b060688..bf5d57af 100644 --- a/pages/api/email.ts +++ b/pages/api/email.ts @@ -2,15 +2,21 @@ import { NextApiRequest, NextApiResponse } from 'next'; import { mailOptions, transporter } from '../../src/config/nodemailer'; type BodyEmail = { - email: string, - subject: string, - message: string, + email: string; + subject: string; + message: string; + fileName: string; + fileContent: string; + userName: string; } const CONTACT_MESSAGE_FIELDS: BodyEmail = { email: 'Email', subject: 'Subject', message: 'Message', + fileName: 'FileName', + fileContent: 'FileContent', + userName: 'UserName', }; const generateEmailContent = (data: BodyEmail): { text: string, html: string } => { @@ -20,11 +26,52 @@ const generateEmailContent = (data: BodyEmail): { text: string, html: string } = } return str; }, ''); - const htmlData = Object.entries(data).reduce((str, [key, val]) => (`${str}

${CONTACT_MESSAGE_FIELDS[key as keyof BodyEmail]}

${val}

`), ''); return { text: stringData, - html: `

New Contact Message

${htmlData}
`, + html: ` + + + + + Email Template + + + +
+

Hello,

+

${data.userName} sent you + ${data.message ? 'the following message:' : 'a file without a message.'} +

+ ${data.message ? `

${data.message}

` : ''} +
+ + +`, }; }; @@ -37,6 +84,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) try { mailOptions.to = data.email; + if (data.fileContent) { + mailOptions.attachments = [ + { + filename: data.fileName, + content: data.fileContent, + }, + ]; + } await transporter.sendMail({ ...mailOptions, ...generateEmailContent(data), diff --git a/src/components/file/SendEmailFile.tsx b/src/components/file/SendEmailFile.tsx index a606ab7d..96688f31 100644 --- a/src/components/file/SendEmailFile.tsx +++ b/src/components/file/SendEmailFile.tsx @@ -20,6 +20,7 @@ import Button from '../Button'; import Modal from '../Modal'; import { textColorMode } from '../../config/colorMode'; import sendContactForm from '../../lib/email'; +import { useUserContext } from '../../contexts/user'; type SendEmailFileProps = { file: IPCFile; @@ -30,14 +31,18 @@ type EmailElements = { email: string; subject: string; message: string; + fileName: string; + fileContent: string; + userName: string; } -const initValues: EmailElements = { email: '', subject: '', message: '' }; +const initValues: EmailElements = { email: '', subject: '', message: '', fileContent: '', fileName: '', userName: '' }; const initState = { isLoading: false, error: '', values: initValues }; -const SendEmailFile = ({ onClosePopover }: SendEmailFileProps): JSX.Element => { +const SendEmailFile = ({ file, onClosePopover }: SendEmailFileProps): JSX.Element => { + const { user } = useUserContext(); const isDrawer = useBreakpointValue({ base: true, sm: false }) || false; const { isOpen, onOpen, onClose } = useDisclosure(); const { colorMode } = useColorMode(); @@ -52,9 +57,18 @@ const SendEmailFile = ({ onClosePopover }: SendEmailFileProps): JSX.Element => { const { isLoading, error, values } = state; + const arrayBufferToStringAndBase64 = async () => { + const data = await user.drive.getContentFile(file); + const uint8Array = new Uint8Array(data); + + const decoder = new TextDecoder('utf-8'); + return decoder.decode(uint8Array); + }; + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - const onBlur = ({ target }) => + const onBlur = async ({ target }) => setTouched((prev) => ({ ...prev, [target.name]: true })); const sendEmail = async () => { @@ -63,6 +77,9 @@ const SendEmailFile = ({ onClosePopover }: SendEmailFileProps): JSX.Element => { isLoading: true, })); try { + values.fileContent = await arrayBufferToStringAndBase64(); + values.fileName = file.name; + values.userName = user?.fullContact.contact.username; await sendContactForm(values); setTouched({ email: false, subject: false, message: false }); setState(initState); @@ -141,7 +158,8 @@ const SendEmailFile = ({ onClosePopover }: SendEmailFileProps): JSX.Element => { isLoading={isLoading} id='ipc-dashboard-update-filename-button' disabled={!values.email} - onClickCapture={sendEmail} + // onClickCapture={sendEmail} + onClickCapture={arrayBufferToStringAndBase64} > Send diff --git a/src/components/file/detailsFile/DrawerDetailsFile.tsx b/src/components/file/detailsFile/DrawerDetailsFile.tsx index d495d891..7c9f1c36 100644 --- a/src/components/file/detailsFile/DrawerDetailsFile.tsx +++ b/src/components/file/detailsFile/DrawerDetailsFile.tsx @@ -26,10 +26,10 @@ import colors from 'theme/foundations/colors'; import { textColorMode } from 'config/colorMode'; const DrawerDetailsFile = ({ - file, - isOpen, - onClose, -}: { + file, + isOpen, + onClose, + }: { file: IPCFile; isOpen: boolean; onClose: () => void; @@ -59,65 +59,65 @@ const DrawerDetailsFile = ({ > {!isDrawer && } - - - + + + {' '} {file.name}{' '} - + - + Created at{' '} {formatDate(file.createdAt)} - + Size of: {' '} {formatFileSize(file.size)} - + IPFS hash: {' '} {file.hash} - + Type: {' '} {getFileType(file.name)} - - + + - + This file is shared with: {` ${list.length} ${list.length > 1 ? 'contacts' : 'contact'}`} {showShared ? ( list.map((item) => ( - - {item.name} - {item.address} + + {item.name} + {item.address} )) ) : ( @@ -125,18 +125,18 @@ const DrawerDetailsFile = ({ )} - + - + History:{' '} {file.logs.map((log) => ( - + {log.action} {formatDate(log.date)} diff --git a/src/config/nodemailer.ts b/src/config/nodemailer.ts index 43f9ef30..25ee12d7 100644 --- a/src/config/nodemailer.ts +++ b/src/config/nodemailer.ts @@ -14,4 +14,8 @@ export const transporter = nodemailer.createTransport({ export const mailOptions = { from: email, to: '', + attachments: [{ + filename: '', + content: '', + }], }; diff --git a/src/lib/drive.ts b/src/lib/drive.ts index d250f4f9..0ade50cc 100644 --- a/src/lib/drive.ts +++ b/src/lib/drive.ts @@ -186,6 +186,35 @@ class Drive { return { success: false, message: 'Failed to download the file' }; } } + + public async getContentFile(file: IPCFile) { + try { + const storeFile = await store.Get({ fileHash: file.hash }); + + const decryptedKey = await this.account.decrypt(Buffer.from(file.encryptInfos.key, 'hex')); + const decryptedIv = await this.account.decrypt(Buffer.from(file.encryptInfos.iv, 'hex')); + + return await crypto.subtle.decrypt( + { + name: 'AES-GCM', + iv: decryptedIv, + }, + await crypto.subtle.importKey( + 'raw', + decryptedKey, + { + name: 'AES-GCM', + length: 256, + }, + true, + ['encrypt', 'decrypt'], + ), + Buffer.from(storeFile), + ); + } catch (err) { + throw new Error('Couldn\'t get file content'); + } + } } export default Drive;