DEV Community

thinkThroo
thinkThroo

Posted on

How Documenso, an open-source Docusign alternative, handles pdf file upload in Next.js?

In this article, we analyse how Documenso, an open-source Docusign alternative, handles pdf file upload in Next.js app router.

But first, where is the code that takes care of upload? To find that out, we first need to know where in the user interface on Documenso we upload pdf files.

Where is the file upload related code?

When you visit /documents, you see a widget shown below that lets you

upload pdf files in the Documenso dashboard.

Image description

Since Documenso uses Next.js app router and the url ends with /documents, we are looking for a folder named documents in the Documenso source code. You will find this in the (dashboard)/documents/page.tsx, but straight away you don’t see anything that says “upload”.

export default async function DocumentsPage({ searchParams = {} }: DocumentsPageProps) {
  await setupI18nSSR();

  const { user } = await getRequiredServerComponentSession();

  return (
    <>
      <UpcomingProfileClaimTeaser user={user} />
      <DocumentsPageView searchParams={searchParams} />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

This means, we now have to search for “upload” in DocumentsPageView and UpcomingProfileClaimTeaser. Looking at the imports used in documents/upcoming-profile-claim-teaser.tsx, there’s nothing related to “upload”, that means, it has to be in DocumentsPageView.

In the documents-page-view.tsx, you will find this below shown import at line 27.

import { UploadDocument } from './upload-document';
Enter fullscreen mode Exit fullscreen mode

and this Component is the first in order in this documents-page-view.tsx, as shown below:

<div className="mx-auto w-full max-w-screen-xl px-4 md:px-8">
      <UploadDocument team={currentTeam} />

      <div className="mt-12 flex flex-wrap items-center justify-between gap-x-4 gap-y-8">
        <div className="flex flex-row items-center">
          {team && (
Enter fullscreen mode Exit fullscreen mode

Hence the reason why you see the Upload widget followed by list of documents that you may have uploaded before.

Image description

DocumentDropzone

You will find this below code at line 133 in the upload-document.tsx,

that is imported from document-dropzone.tsx

<DocumentDropzone
 className="h-[min(400px,50vh)]"
 disabled={remaining.documents === 0 || !session?.user.emailVerified}
 disabledMessage={disabledMessage}
 onDrop={onFileDrop}
 onDropRejected={onFileDropRejected}
/>
Enter fullscreen mode Exit fullscreen mode

we are interested in onFileDrop function. This function has 50+ lines of code. Let’s focus only on core functionality that does the upload.

const { type, data } = await putPdfFile(file);

const { id: documentDataId } = await createDocumentData({
  type,
  data,
});

const { id } = await createDocument({
  title: file.name,
  documentDataId,
  teamId: team?.id,
});
Enter fullscreen mode Exit fullscreen mode

This code is picked from onFileDrop function. putPdfFile is a function imported from lib/universal/upload/put-file.ts

putPdfFile

/**
 * Uploads a document file to the appropriate storage location and creates
 * a document data record.
 */
export const putPdfFile = async (file: File) => {
  const isEncryptedDocumentsAllowed = await getFlag('app_allow_encrypted_documents').catch(
    () => false,
  );

  // This will prevent uploading encrypted PDFs or anything that can't be opened.
  if (!isEncryptedDocumentsAllowed) {
    await PDFDocument.load(await file.arrayBuffer()).catch((e) => {
      console.error(`PDF upload parse error: ${e.message}`);

      throw new AppError('INVALID_DOCUMENT_FILE');
    });
  }

  if (!file.name.endsWith('.pdf')) {
    file.name = `${file.name}.pdf`;
  }

  const { type, data } = await putFile(file);

  return await createDocumentData({ type, data });
};
Enter fullscreen mode Exit fullscreen mode

This function above has some checks against uploading encrypted document.

putFile

This below code is picked from upload/put-file.ts

/**
 * Uploads a file to the appropriate storage location.
 */
export const putFile = async (file: File) => {
  const NEXT_PUBLIC_UPLOAD_TRANSPORT = env('NEXT_PUBLIC_UPLOAD_TRANSPORT');

  return await match(NEXT_PUBLIC_UPLOAD_TRANSPORT)
    .with('s3', async () => putFileInS3(file))
    .otherwise(async () => putFileInDatabase(file));
};
Enter fullscreen mode Exit fullscreen mode

so this function supports file uploads to either S3 or upload to database. Interesting.

About us:

At Thinkthroo, we study large open source projects and provide architectural guides. We have developed reusable Components, built with tailwind, that you can use in your project.

We offer Next.js, React and Node development services.

Book a meeting with us to discuss your project.

Image description

References:

  1. https://github.com/documenso/documenso/tree/main

  2. https://github.com/documenso/documenso/blob/main/apps/web/src/app/(dashboard)/documents/page.tsx

  3. https://github.com/documenso/documenso/blob/main/apps/web/src/app/(dashboard)/documents/upcoming-profile-claim-teaser.tsx

  4. https://github.com/documenso/documenso/blob/main/apps/web/src/app/(dashboard)/documents/documents-page-view.tsx#L41

  5. https://github.com/documenso/documenso/blob/main/apps/web/src/app/(dashboard)/documents/upload-document.tsx#L65

  6. https://github.com/documenso/documenso/blob/main/packages/lib/universal/upload/put-file.ts#L22

Top comments (0)