Compare commits

...

17 Commits

Author SHA1 Message Date
Mythie
c4b4e8e664 Merge branch 'main' into release 2023-11-16 16:48:50 +11:00
Timur Ercan
e838a07bf9 chore: remove share button from top level, texts (#653) 2023-11-16 16:43:50 +11:00
Lucas Smith
8722e4de74 chore: marketing site updates (#654)
Change "Open" links to "Open Startup" to reduce confusion about how to open the web app.

Additionally add the OSS Friends link to the footer since it was dropped during some changes.
2023-11-16 13:39:22 +11:00
Nafees Nazik
f7d8ebb9de feat: enable resend email menu (#496) 2023-11-16 13:08:31 +11:00
Ephraim Atta-Duncan
67f3b2de45 fix: fetch the correct number of open issues using the github search api (#495) 2023-11-15 21:56:09 +11:00
Tanay
0da080aa41 fix: hovering on tabslist items at dashboard (#645) 2023-11-15 18:18:11 +11:00
Nafees Nazik
fe25239a4e feat: cache getServerComponentSession calls (#644) 2023-11-15 17:42:27 +11:00
SAHIL SIRAJALI KAZI
5ea4a16e36 fix: safari pdf overflow issue fixed (#466) 2023-11-15 17:35:57 +11:00
Nafees Nazik
5002a475d1 feat: limit document upload size (#347) 2023-11-15 13:13:31 +11:00
Nafees Nazik
ca9c0d7bf0 chore: add some eslint rules (#344) 2023-11-15 13:10:17 +11:00
Ephraim Atta-Duncan
3f0341c7d4 feat: add dialog to confirm signing (#342) 2023-11-14 19:01:00 +11:00
Lucas Smith
c3fe98b05f fix: update docker compose entrypoint (#650) 2023-11-14 13:11:57 +11:00
neo773
608a4eaaa6 feat: refactor og image generation (#639) 2023-11-14 13:08:14 +11:00
Nafees Nazik
d6ae0b44e6 feat: use nextjs.js standalone output for improvised docker image (#338) 2023-11-12 13:15:42 +11:00
Mythie
6e596ce7f5 Merge branch 'main' into release 2023-11-09 15:52:12 +11:00
Lucas Smith
b41b026e95 Merge pull request #222 from documenso/main
Release 0.9.2
2023-07-08 19:10:17 +10:00
Lucas Smith
f4e6aca4cf Merge pull request #218 from documenso/main
Release 0.9.1
2023-06-28 08:21:16 +10:00
149 changed files with 58214 additions and 776 deletions

16
.prettierignore Normal file
View File

@@ -0,0 +1,16 @@
node_modules
.next
public
**/**/node_modules
**/**/.next
**/**/public
*.lock
*.log
*.test.ts
.gitignore
.npmignore
.prettierignore
.DS_Store
.eslintignore

View File

@@ -1,51 +1,51 @@
---
title: Announcing Documenso
description: Launching an open-source document signing tool because trusted-based products should be built on openness. The first release will be in 2023. Sign up at documenso.com to be on board.
authorName: 'Timur Ercan'
authorImage: '/blog/blog-author-timur.jpeg'
authorRole: 'Co-Founder'
date: 2022-12-29
tags:
- Announcement
---
<figure>
<MdxNextImage
src="/blog/blog-banner-announcing-documenso.webp"
width="1400"
height="884"
alt="Documenso announcement blog banner"
/>
<figcaption className="text-center">Documenso — The Open Source DocuSign Alternative.</figcaption>
</figure>
## TL; DR;
I'm launching an open source document signing tool because trust-based products should be built on openness. The first release will be in 2023. Sign up at <a href="https://documenso.com" target="_blank">documenso.com</a> and get on board.
## Lets build the worlds most trusted document-signing tool.
Today I'm excited to announce my new project Documenso. Documenso is an open source document signing tool you can host yourself and freely build upon because it's, you know, open source. Before I get more into the details of what and when will be launched I want to take a moment and talk about why.
## Digital signing is great
Signing Documents digitally has countless benefits: Less struggle with printing, less wasting paper, faster request delivery, easier changes, easier coordination of people far away, verifiable document integrity, and verifiable signer identity (this is a vast topic, will write more on soon), easier storage and search of signed documents, the list goes on. Digital Signatures take something very old and very trusted like personally signing documents into the digital space, adding the benefits listed above. It also introduces a new party to every signing transaction, the signing tool providers. What was peer to peer transaction before, now goes through an intermediary. While this isn't a problem in itself, it should make us think about how we want these providers of trust to work.
## How do we build trusted systems?
While doing research for Documenso I came upon a quote that expresses the current state of document signing pretty well:
> Document signing is NOT a technical problem. [Editors Note: Because it was solved technically a long time ago] Its a legal acceptance problem — and everyone KNOWS DocuSign and friends and understands how theyre admissible. Anything else would have to compete with that and people would be suspicious of it for a long time.
While this may sound like a hurdle at first, it immediately gave me a sense of validation for a more open approach to signing. People will and should be suspicious of their tools and demand a high bar when it comes to trust. And the way to earn this trust is by being open. Trusted tools should be the result of thoughtful discussion and reviews. They should be the result of the needs and will of its community. They should be transparent, adaptable, and empowering while using. Open Source embodies these values very well for software, which makes it a perfect fit for this space and creating a high-trust tool.
## Next Steps
So, what can you expect from here on out? I've started to build Documenso 0.1 which is scheduled to release in “early” 2023. If you're interested in helping make this happen, let me know via [hi@documenso.com](mailto:hi@documenso.com). Getting working code into the hands of the perspective Documenso community is currently the #1 goal. Other than that I'll be releasing several articles about document signing and what something like Documenso should look like, in my humble opinion. So stay tuned!
If you think Documenso is worthy of support, please share <a href="https://documenso.com" target="_blank">documenso.com</a> with anyone interested, and sign up to be among the first to try out version 0.1 as soon as it launches.
Cheers from Hamburg
Timur
---
title: Announcing Documenso
description: Launching an open-source document signing tool because trusted-based products should be built on openness. The first release will be in 2023. Sign up at documenso.com to be on board.
authorName: 'Timur Ercan'
authorImage: '/blog/blog-author-timur.jpeg'
authorRole: 'Co-Founder'
date: 2022-12-29
tags:
- Announcement
---
<figure>
<MdxNextImage
src="/blog/blog-banner-announcing-documenso.webp"
width="1400"
height="884"
alt="Documenso announcement blog banner"
/>
<figcaption className="text-center">Documenso — The Open Source DocuSign Alternative.</figcaption>
</figure>
## TL; DR;
I'm launching an open source document signing tool because trust-based products should be built on openness. The first release will be in 2023. Sign up at <a href="https://documenso.com" target="_blank">documenso.com</a> and get on board.
## Lets build the worlds most trusted document-signing tool.
Today I'm excited to announce my new project Documenso. Documenso is an open source document signing tool you can host yourself and freely build upon because it's, you know, open source. Before I get more into the details of what and when will be launched I want to take a moment and talk about why.
## Digital signing is great
Signing Documents digitally has countless benefits: Less struggle with printing, less wasting paper, faster request delivery, easier changes, easier coordination of people far away, verifiable document integrity, and verifiable signer identity (this is a vast topic, will write more on soon), easier storage and search of signed documents, the list goes on. Digital Signatures take something very old and very trusted like personally signing documents into the digital space, adding the benefits listed above. It also introduces a new party to every signing transaction, the signing tool providers. What was peer to peer transaction before, now goes through an intermediary. While this isn't a problem in itself, it should make us think about how we want these providers of trust to work.
## How do we build trusted systems?
While doing research for Documenso I came upon a quote that expresses the current state of document signing pretty well:
> Document signing is NOT a technical problem. [Editors Note: Because it was solved technically a long time ago] Its a legal acceptance problem — and everyone KNOWS DocuSign and friends and understands how theyre admissible. Anything else would have to compete with that and people would be suspicious of it for a long time.
While this may sound like a hurdle at first, it immediately gave me a sense of validation for a more open approach to signing. People will and should be suspicious of their tools and demand a high bar when it comes to trust. And the way to earn this trust is by being open. Trusted tools should be the result of thoughtful discussion and reviews. They should be the result of the needs and will of its community. They should be transparent, adaptable, and empowering while using. Open Source embodies these values very well for software, which makes it a perfect fit for this space and creating a high-trust tool.
## Next Steps
So, what can you expect from here on out? I've started to build Documenso 0.1 which is scheduled to release in “early” 2023. If you're interested in helping make this happen, let me know via [hi@documenso.com](mailto:hi@documenso.com). Getting working code into the hands of the perspective Documenso community is currently the #1 goal. Other than that I'll be releasing several articles about document signing and what something like Documenso should look like, in my humble opinion. So stay tuned!
If you think Documenso is worthy of support, please share <a href="https://documenso.com" target="_blank">documenso.com</a> with anyone interested, and sign up to be among the first to try out version 0.1 as soon as it launches.
Cheers from Hamburg
Timur

View File

@@ -1,29 +1,29 @@
---
title: The Documenso Manifest
description: Signing documents is a fundamental building block of private, economic, and government interactions. Access to easy and secure signing to participate in society should therefore be a fundamental right for everyone. The technology to enable this should be accessible and widespread.
authorName: 'Timur Ercan'
authorImage: '/blog/blog-author-timur.jpeg'
authorRole: 'Co-Founder'
date: 2023-07-13
tags:
- Manifesto
---
<figure>
<MdxNextImage
src="/blog/blog-banner-manifest.jpeg"
width="1260"
height="630"
alt="The Documenso Manifest blog banner"
/>
<figcaption className="text-center">
Documenso — The Open Source DocuSign Alternative.
</figcaption>
</figure>
Signing documents is a fundamental building block of private, economic, and government interactions. Access to easy and secure signing to participate in society should therefore be a fundamental right for everyone. The technology to enable this should be accessible and widespread.
We know that open source is the key to solving this need once and for all to benefit all humankind. Using open source kickstarts innovation by putting the open sharing of ideas and solutions first. With Documenso, we will create an open and globally accessible signing platform to empower users, customers, and developers to fulfill their needs. Documenso is built by and for the global community, listening and implementing what is needed. Being transparent with the code and the processes that use it brings trust and security to the platform.
We build Documenso for longevity and scale by embracing the capital efficiency and inclusiveness of the Commercial Open Source (COSS) movement. We are building a global commodity for the world.
---
title: The Documenso Manifest
description: Signing documents is a fundamental building block of private, economic, and government interactions. Access to easy and secure signing to participate in society should therefore be a fundamental right for everyone. The technology to enable this should be accessible and widespread.
authorName: 'Timur Ercan'
authorImage: '/blog/blog-author-timur.jpeg'
authorRole: 'Co-Founder'
date: 2023-07-13
tags:
- Manifesto
---
<figure>
<MdxNextImage
src="/blog/blog-banner-manifest.jpeg"
width="1260"
height="630"
alt="The Documenso Manifest blog banner"
/>
<figcaption className="text-center">
Documenso — The Open Source DocuSign Alternative.
</figcaption>
</figure>
Signing documents is a fundamental building block of private, economic, and government interactions. Access to easy and secure signing to participate in society should therefore be a fundamental right for everyone. The technology to enable this should be accessible and widespread.
We know that open source is the key to solving this need once and for all to benefit all humankind. Using open source kickstarts innovation by putting the open sharing of ideas and solutions first. With Documenso, we will create an open and globally accessible signing platform to empower users, customers, and developers to fulfill their needs. Documenso is built by and for the global community, listening and implementing what is needed. Being transparent with the code and the processes that use it brings trust and security to the platform.
We build Documenso for longevity and scale by embracing the capital efficiency and inclusiveness of the Commercial Open Source (COSS) movement. We are building a global commodity for the world.

View File

@@ -11,26 +11,28 @@ tags:
- Open Startup
---
Today I'm happy to announce that we closed a \$1.25M Pre-Seed round for Documenso, bringing our total funding to \$1.54M. The round actually closed last month, we just were sneaky about it.
Today I'm happy to announce that we closed a \$1.25M Pre-Seed round for Documenso, bringing our total funding to \$1.54M. The round actually closed last month, we just were sneaky about it.
## Two more for the road (to open signing)
We're ecstatic to welcome [OSS Capital](https://twitter.com/osscapital) and especially [Joseph Jacks](https://twitter.com/JosephJacks_) to the inner circle of the open signing revolution. We're also fortunate to be joined by Orrick's very own [John Harrison](https://www.linkedin.com/in/john-harrison-a1213b9/) and his legal experience. For those who are wondering, yes, the round was, of course, signed using Documenso.
## Open Source, Open Metrics
If you follow us, you know we're firmly committed to the open source values of openness and transparency. For us, this includes not only the code side of things but also the business. As we aim to build trust among our investors, customers, and partners, we want to be open about what's going on. We also want to allow everyone to learn from our data and choices, just as we did from so many other COSS (Commercial Open Source) startups. The term "Open Startup" isn't precisely defined (and probably will never be, just like startup). There is however a [great write-up](https://cal.com/blog/open-startup) about the basics by the founder of our favorite open source scheduling tool Cal.com.
If you follow us, you know we're firmly committed to the open source values of openness and transparency. For us, this includes not only the code side of things but also the business. As we aim to build trust among our investors, customers, and partners, we want to be open about what's going on. We also want to allow everyone to learn from our data and choices, just as we did from so many other COSS (Commercial Open Source) startups. The term "Open Startup" isn't precisely defined (and probably will never be, just like startup). There is however a [great write-up](https://cal.com/blog/open-startup) about the basics by the founder of our favorite open source scheduling tool Cal.com.
The two main takeaways are:
- "Any Startup that shares its metrics as open as technically and operationally possible is an Open Startup."
- "Why should I care? Frankly speaking, Open Startups have a tough time screwing you over."
The more open the culture, the less shady stuff is going on. While this may sound trivial, the implications are profound. A new generation of organizations, operating more ethically and responsibly simply because everything is out in the open.
The more open the culture, the less shady stuff is going on. While this may sound trivial, the implications are profound. A new generation of organizations, operating more ethically and responsibly simply because everything is out in the open.
For us, there are two sides to being an Open Startup:
- The company side: Sharing Financial KPIs like growth, funding, team structure, salary, internal processes, and tools.
- The product side: Sharing insights and data like usage, reach, and GitHub activity.
Both sides aim to contribute to the global knowledge base of how startups work, specifically COSS startups. As we see more and more COSS, best practices and business insights should be broadly available to let the space mature. As we contribute code to the global community, we also contribute our business knowledge to help bring about even more COSS.
Both sides aim to contribute to the global knowledge base of how startups work, specifically COSS startups. As we see more and more COSS, best practices and business insights should be broadly available to let the space mature. As we contribute code to the global community, we also contribute our business knowledge to help bring about even more COSS.
Starting today, we're releasing a lot of our data as part of the Open Startup movement. You can find the juicy details on our funding and more here: [documen.so/open](https://documen.so/open)

View File

@@ -24,9 +24,9 @@ Were an open-source project and focus on building a great developer experienc
So, were switching all conversations, team and community-wide, to Discord.
In this post, we wont debate *why* were switching — Slack vs. Discord is a long-lasting debate with pros and cons, and fans on both sides. There are great [stories](https://blog.meilisearch.com/from-slack-to-discord-our-migration/) and [threads](https://twitter.com/McPizza0/status/1655519558600470528) on the topic. We just dont want to write yet another story here.
In this post, we wont debate _why_ were switching — Slack vs. Discord is a long-lasting debate with pros and cons, and fans on both sides. There are great [stories](https://blog.meilisearch.com/from-slack-to-discord-our-migration/) and [threads](https://twitter.com/McPizza0/status/1655519558600470528) on the topic. We just dont want to write yet another story here.
Instead, well focus on *how* we plan to make the switch.
Instead, well focus on _how_ we plan to make the switch.
## Who is this story for?
@@ -38,7 +38,7 @@ For founders and makers who would like to switch too, in one way or another, thi
## Switching to Discord
Were switching to Discord, step by step. First, were moving team conversations, then were moving the community with a 15-day buffering.
Were switching to Discord, step by step. First, were moving team conversations, then were moving the community with a 15-day buffering.
The detailed plan goes like this:
@@ -46,90 +46,91 @@ The detailed plan goes like this:
- 2023-07-26 `t+1`: The team switches to Discord. The objective is to get used to the product and to customize it to feel at home and, when were ready to welcome the community, to make new members feel at home, too.
- 2023-08-02 `t+8`: We announce to the community the upcoming changes in the different channels — GitHub, Twitter, and Slack.
- **GitHub**
- Create new Pull Request
- Add story to the blog
- Update link to the community
```
https://documen.so/discord
```
- Start a new Discussion
```markdown
Happy Wednesday!
TL,DR: Were switching to Discord. [Join the fun!](https://documen.so/discord)
We want to build a beautiful, open-source DocuSign alternative. As we're growing (reached 2.3K Stars), we feel the need to have a more community- and developer-friendly environment to share ideas, support, and memes.
Make sure to join the server to keep up to date on all things Documenso.
Oh and, spoiler alert, there may be some swag there 👀
See you there!
Flo
```
- **Twitter**
- [Tweet the announcement](https://twitter.com/documenso/status/1686719482096766977)
- Pin Tweet
- Update link in bio
```
The Open Source DocuSign Alternative.
http://documen.so/github
http://documen.so/discord
http://documen.so/manifest
```
- **Slack**
- Post message in `#general`
```markdown
Happy Wednesday!
TL,DR: Were switching to Discord. [Join the fun!](https://documen.so/discord)
We want to build a beautiful, open-source DocuSign alternative. As we're growing (reached 2.3K Stars), we feel the need to have a more community- and developer-friendly environment to share ideas, support, and memes.
Make sure to [join the server](https://documen.so/discord) to keep up to date on all things Documenso.
Oh and, spoiler alert, there may be some swag there 👀
See you there!
Flo
```
- Pin post
- Set topic and description
```
We're switching to Discord. Join the fun: https://documen.so/discord
```
- Archive channels: `#code-review` `#how-to` `#meet-and-greet` `#random-memes` `#self-hosting` `#support`
- **GitHub**
- Create new Pull Request
- Add story to the blog
- Update link to the community
```
https://documen.so/discord
```
- Start a new Discussion
```markdown
Happy Wednesday!
TL,DR: Were switching to Discord. [Join the fun!](https://documen.so/discord)
We want to build a beautiful, open-source DocuSign alternative. As we're growing (reached 2.3K Stars), we feel the need to have a more community- and developer-friendly environment to share ideas, support, and memes.
Make sure to join the server to keep up to date on all things Documenso.
Oh and, spoiler alert, there may be some swag there 👀
See you there!
Flo
```
- **Twitter**
- [Tweet the announcement](https://twitter.com/documenso/status/1686719482096766977)
- Pin Tweet
- Update link in bio
```
The Open Source DocuSign Alternative.
http://documen.so/github
http://documen.so/discord
http://documen.so/manifest
```
- **Slack**
- Post message in `#general`
```markdown
Happy Wednesday!
TL,DR: Were switching to Discord. [Join the fun!](https://documen.so/discord)
We want to build a beautiful, open-source DocuSign alternative. As we're growing (reached 2.3K Stars), we feel the need to have a more community- and developer-friendly environment to share ideas, support, and memes.
Make sure to [join the server](https://documen.so/discord) to keep up to date on all things Documenso.
Oh and, spoiler alert, there may be some swag there 👀
See you there!
Flo
```
- Pin post
- Set topic and description
```
We're switching to Discord. Join the fun: https://documen.so/discord
```
- Archive channels: `#code-review` `#how-to` `#meet-and-greet` `#random-memes` `#self-hosting` `#support`
- 2023-08-09 `t+15`: 7 days later, we send a reminder on Slack.
- **Slack**
- Schedule reminder in `#general`
```
Friendly reminder: we're switching to Discord and will soon disconnect this Slack workspace.
Join the fun! https://documen.so/discord
```
- **Slack**
- Schedule reminder in `#general`
```
Friendly reminder: we're switching to Discord and will soon disconnect this Slack workspace.
Join the fun! https://documen.so/discord
```
- 2023-08-16 `t+22`: 15 days later, were making the final edits to the Slack workspace.
- **Slack**
- [Edit posting permissions](https://app.slack.com/slackhelp/en-US/360004635551) in `#general`
- Disconnect Slack
- **Slack**
- [Edit posting permissions](https://app.slack.com/slackhelp/en-US/360004635551) in `#general`
- Disconnect Slack
## Final thoughts
- Were at the very, early stage on our journey to building a beautiful, open-source DocuSign alternative. We want to build a great developer experience with the open-source community and, switching to Discord, we want to set up the foundations of an open, safe place for community members to get in touch, brainstorm ideas, and have fun.
- It doesnt mean we wont ever switch back to Slack. The tools of today arent the ones of tomorrow. We dont delete the Slack workspace, we archive it, and keep the `documenso` handle. May it be just an *au revoir?*
- It doesnt mean we wont ever switch back to Slack. The tools of today arent the ones of tomorrow. We dont delete the Slack workspace, we archive it, and keep the `documenso` handle. May it be just an _au revoir?_
- For now, were pushing forward and are eager to welcome you on Discord. Make sure to [join the server](https://documen.so/discord) in order to keep up to date on all things Documenso. See you there!

View File

@@ -1,256 +1,256 @@
---
title: Privacy Policy
---
# Privacy Policy
Effective date: 05/28/2023
### 1\. Introduction
Welcome to **Documenso Inc.**
Documenso Inc. (“us”, “we”, or “our”) operates [https://documenso.com](https://documenso.com) (hereinafter referred to as “ **Service**”).
Our Privacy Policy governs your visit to [https://documenso.com](https://documenso.com), and explains how we collect, safeguard and disclose information that results from your use of our Service.
We use your data to provide and improve Service. By using Service, you agree to the collection and use of information in accordance with this policy. Unless otherwise defined in this Privacy Policy, the terms used in this Privacy Policy have the same meanings as in our Terms and Conditions.
Our Terms and Conditions (“**Terms**”) govern all use of our Service and together with the Privacy Policy constitutes your agreement with us (“ **agreement**”).
### 2\. Definitions
**SERVICE** means the https://documenso.com website operated by Documenso Inc.
**PERSONAL DATA** means data about a living individual who can be identified from those data (or from those and other information either in our possession or likely to come into our possession).
**USAGE DATA** is data collected automatically either generated by the use of Service or from Service infrastructure itself (for example, the duration of a page visit).
**COOKIES** are small files stored on your device (computer or mobile device).
**DATA CONTROLLER** means a natural or legal person who (either alone or jointly or in common with other persons) determines the purposes for which and the manner in which any personal data are, or are to be, processed. For the purpose of this Privacy Policy, we are a Data Controller of your data.
**DATA PROCESSORS (OR SERVICE PROVIDERS)** means any natural or legal person who processes the data on behalf of the Data Controller. We may use the services of various Service Providers in order to process your data more effectively.
**DATA SUBJECT** is any living individual who is the subject of Personal Data.
**THE USER** is the individual using our Service. The User corresponds to the Data Subject, who is the subject of Personal Data.
### 3\. Information Collection and Use
We collect several different types of information for various purposes to provide and improve our Service to you.
### 4\. Types of Data Collected
**Personal Data**
While using our Service, we may ask you to provide us with certain personally identifiable information that can be used to contact or identify you (“**Personal Data**”). Personally identifiable information may include, but is not limited to:
1. Email address
2. First name and last name
3. Cookies and Usage Data
We may use your Personal Data to contact you with newsletters, marketing or promotional materials and other information that may be of interest to you. You may opt out of receiving any, or all, of these communications from us by following the unsubscribe link.
**Usage Data**
We may also collect information that your browser sends whenever you visit our Service or when you access Service by or through a mobile device (“**Usage Data**”).
This Usage Data may include information such as your computer's Internet Protocol address (e.g. IP address), browser type, browser version, the pages of our Service that you visit, the time and date of your visit, the time spent on those pages, unique device identifiers and other diagnostic data.
When you access Service with a mobile device, this Usage Data may include information such as the type of mobile device you use, your mobile device unique ID, the IP address of your mobile device, your mobile operating system, the type of mobile Internet browser you use, unique device identifiers and other diagnostic data.
**Tracking Cookies Data**
We use cookies and similar tracking technologies to track the activity on our Service and we hold certain information.
Cookies are files with a small amount of data which may include an anonymous unique identifier. Cookies are sent to your browser from a website and stored on your device. Other tracking technologies are also used such as beacons, tags and scripts to collect and track information and to improve and analyze our Service.
You can instruct your browser to refuse all cookies or to indicate when a cookie is being sent. However, if you do not accept cookies, you may not be able to use some portions of our Service.
Examples of Cookies we use:
1. **Session Cookies:** We use Session Cookies to operate our Service.
2. **Preference Cookies:** We use Preference Cookies to remember your preferences and various settings.
3. **Security Cookies:** We use Security Cookies for security purposes.
4. **Advertising Cookies:** Advertising Cookies are used to serve you with advertisements that may be relevant to you and your interests.
### 5\. Use of Data
Documenso Inc. uses the collected data for various purposes:
1. to provide and maintain our Service;
2. to notify you about changes to our Service;
3. to allow you to participate in interactive features of our Service when you choose to do so;
4. to provide customer support;
5. to gather analysis or valuable information so that we can improve our Service;
6. to monitor the usage of our Service;
7. to detect, prevent and address technical issues;
8. to fulfill any other purpose for which you provide it;
9. to carry out our obligations and enforce our rights arising from any contracts entered into between you and us, including for billing and collection;
10. to provide you with notices about your account and/or subscription, including expiration and renewal notices, email-instructions, etc.;
11. to provide you with news, special offers and general information about other goods, services and events which we offer that are similar to those that you have already purchased or enquired about unless you have opted not to receive such information;
12. in any other way we may describe when you provide the information;
13. for any other purpose with your consent.
### 6\. Retention of Data
We will retain your Personal Data only for as long as is necessary for the purposes set out in this Privacy Policy. We will retain and use your Personal Data to the extent necessary to comply with our legal obligations (for example, if we are required to retain your data to comply with applicable laws), resolve disputes, and enforce our legal agreements and policies.
We will also retain Usage Data for internal analysis purposes. Usage Data is generally retained for a shorter period, except when this data is used to strengthen the security or to improve the functionality of our Service, or we are legally obligated to retain this data for longer time periods.
### 7\. Transfer of Data
Your information, including Personal Data, may be transferred to and maintained on computers located outside of your state, province, country or other governmental jurisdiction where the data protection laws may differ from those of your jurisdiction.
If you are located outside United States and choose to provide information to us, please note that we transfer the data, including Personal Data, to United States and process it there.
Your consent to this Privacy Policy followed by your submission of such information represents your agreement to that transfer.
Documenso Inc. will take all the steps reasonably necessary to ensure that your data is treated securely and in accordance with this Privacy Policy and no transfer of your Personal Data will take place to an organisation or a country unless there are adequate controls in place including the security of your data and other personal information.
### 8\. Disclosure of Data
We may disclose personal information that we collect, or you provide:
1. **Disclosure for Law Enforcement.**
2. Under certain circumstances, we may be required to disclose your Personal Data if required to do so by law or in response to valid requests by public authorities.
3. **Business Transaction.**
4. If we or our subsidiaries are involved in a merger, acquisition or asset sale, your Personal Data may be transferred.
5. **Other cases. We may disclose your information also:**
1. to our subsidiaries and affiliates;
2. to contractors, service providers, and other third parties we use to support our business;
3. to fulfill the purpose for which you provide it;
### 9\. Security of Data
The security of your data is important to us but remember that no method of transmission over the Internet or method of electronic storage is 100% secure. While we strive to use commercially acceptable means to protect your Personal Data, we cannot guarantee its absolute security.
### 10\. Your Data Protection Rights Under General Data Protection Regulation (GDPR)
If you are a resident of the European Union (EU) and European Economic Area (EEA), you have certain data protection rights, covered by GDPR. See more at [https://eur-lex.europa.eu/eli/reg/2016/679/oj](https://eur-lex.europa.eu/eli/reg/2016/679/oj)
We aim to take reasonable steps to allow you to correct, amend, delete, or limit the use of your Personal Data.
If you wish to be informed what Personal Data we hold about you and if you want it to be removed from our systems, please email us at hi@documenso.com.
In certain circumstances, you have the following data protection rights:
1. the right to access, update or to delete the information we have on you;
2. the right of rectification. You have the right to have your information rectified if that information is inaccurate or incomplete;
3. the right to object. You have the right to object to our processing of your Personal Data;
4. the right of restriction. You have the right to request that we restrict the processing of your personal information;
5. the right to data portability. You have the right to be provided with a copy of your Personal Data in a structured, machine-readable and commonly used format;
6. the right to withdraw consent. You also have the right to withdraw your consent at any time where we rely on your consent to process your personal information;
Please note that we may ask you to verify your identity before responding to such requests. Please note, we may not able to provide Service without some necessary data.
You have the right to complain to a Data Protection Authority about our collection and use of your Personal Data. For more information, please contact your local data protection authority in the European Economic Area (EEA).
### 11\. Your Data Protection Rights under the California Privacy Protection Act (CalOPPA)
CalOPPA is the first state law in the nation to require commercial websites and online services to post a privacy policy. The laws reach stretches well beyond California to require a person or company in the United States (and conceivable the world) that operates websites collecting personally identifiable information from California consumers to post a conspicuous privacy policy on its website stating exactly the information being collected and those individuals with whom it is being shared, and to comply with this policy. See more at: [https://consumercal.org/about-cfc/cfc-education-foundation/california-online-privacy-protection-act-caloppa-3/](https://consumercal.org/about-cfc/cfc-education-foundation/california-online-privacy-protection-act-caloppa-3/)
According to CalOPPA we agree to the following:
1. users can visit our site anonymously;
2. our Privacy Policy link includes the word “Privacy”, and can easily be found on the page specified above on the home page of our website;
3. users will be notified of any privacy policy changes on our Privacy Policy Page;
4. users are able to change their personal information by emailing us at hi@documenso.com.
Our Policy on “Do Not Track” Signals:
We honor Do Not Track signals and do not track, plant cookies, or use advertising when a Do Not Track browser mechanism is in place. Do Not Track is a preference you can set in your web browser to inform websites that you do not want to be tracked.
You can enable or disable Do Not Track by visiting the Preferences or Settings page of your web browser.
### 12\. Your Data Protection Rights under the California Consumer Privacy Act (CCPA)
If you are a California resident, you are entitled to learn what data we collect about you, ask to delete your data and not to sell (share) it. To exercise your data protection rights, you can make certain requests and ask us:
1. **What personal information we have about you**. If you make this request, we will return to you:
1. The categories of personal information we have collected about you.
2. The categories of sources from which we collect your personal information.
3. The business or commercial purpose for collecting or selling your personal information.
4. The categories of third parties with whom we share personal information.
5. The specific pieces of personal information we have collected about you.
6. A list of categories of personal information that we have sold, along with the category of any other company we sold it to. If we have not sold your personal information, we will inform you of that fact.
7. A list of categories of personal information that we have disclosed for a business purpose, along with the category of any other company we shared it with.
Please note, you are entitled to ask us to provide you with this information up to two times in a rolling twelve-month period. When you make this request, the information provided may be limited to the personal information we collected about you in the previous 12 months.
2. **To delete your personal information**. If you make this request, we will delete the personal information we hold about you as of the date of your request from our records and direct any service providers to do the same. In some cases, deletion may be accomplished through de-identification of the information. If you choose to delete your personal information, you may not be able to use certain functions that require your personal information to operate.
3. **To stop selling your personal information**. We don't sell or rent your personal information to any third parties for any purpose. You are the only owner of your Personal Data and can request disclosure or deletion at any time.
Please note, if you ask us to delete or stop selling your data, it may impact your experience with us, and you may not be able to participate in certain programs or membership services which require the usage of your personal information to function. But in no circumstances, we will discriminate against you for exercising your rights.
To exercise your California data protection rights described above, please send your request(s) by one of the following means:
By email: hi@documenso.com
Your data protection rights, described above, are covered by the CCPA, short for the California Consumer Privacy Act. To find out more, visit the official [California Legislative Information website](https://leginfo.legislature.ca.gov/faces/billTextClient.xhtml?bill_id=201720180AB375). The CCPA took effect on 01/01/2020.
### 13\. Service Providers
We may employ third party companies and individuals to facilitate our Service (“ **Service Providers**”), provide Service on our behalf, perform Service-related services or assist us in analysing how our Service is used.
These third parties have access to your Personal Data only to perform these tasks on our behalf and are obligated not to disclose or use it for any other purpose.
### 14\. Analytics
We may use third-party Service Providers to monitor and analyze the use of our Service.
**Plausible Analytics**
Plausible Analytics is an analytics service provided by Conva Ventures Inc. You can find their Privacy Policy here: [https://plausible.io/privacy](https://plausible.io/privacy)
### 15\. CI/CD tools
We may use third-party Service Providers to automate the development process of our Service.
**GitHub**
GitHub is provided by GitHub, Inc.
GitHub is a development platform to host and review code, manage projects, and build software.
For more information on what data GitHub collects for what purpose and how the protection of the data is ensured, please visit GitHub Privacy Policy page: [https://help.github.com/en/articles/github-privacy-statement](https://help.github.com/en/articles/github-privacy-statement) .
### 16\. Payments
We may provide paid products and/or services within Service. In that case, we use third-party services for payment processing (e.g. payment processors).
We will not store or collect your payment card details. That information is provided directly to our third-party payment processors whose use of your personal information is governed by their Privacy Policy. These payment processors adhere to the standards set by PCI-DSS as managed by the PCI Security Standards Council, which is a joint effort of brands like Visa, Mastercard, American Express and Discover. PCI-DSS requirements help ensure the secure handling of payment information.
The payment processors we work with are:
**Stripe:**
Their Privacy Policy can be viewed at: [https://stripe.com/us/privacy](https://stripe.com/us/privacy)
### 17\. Links to Other Sites
Our Service may contain links to other sites that are not operated by us. If you click a third party link, you will be directed to that third party's site. We strongly advise you to review the Privacy Policy of every site you visit.
We have no control over and assume no responsibility for the content, privacy policies or practices of any third party sites or services.
### 18\. Children's Privacy
Our Services are not intended for use by children under the age of 18 (“ **Child**” or “**Children**”).
We do not knowingly collect personally identifiable information from Children under 18. If you become aware that a Child has provided us with Personal Data, please contact us. If we become aware that we have collected Personal Data from Children without verification of parental consent, we take steps to remove that information from our servers.
### 19\. Changes to This Privacy Policy
We may update our Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page.
We will let you know via email and/or a prominent notice on our Service, prior to the change becoming effective and update “effective date” at the top of this Privacy Policy.
You are advised to review this Privacy Policy periodically for any changes. Changes to this Privacy Policy are effective when they are posted on this page.
### 20\. Contact Us
If you have any questions about this Privacy Policy, please contact us:
By email: hi@documenso.com.
---
title: Privacy Policy
---
# Privacy Policy
Effective date: 05/28/2023
### 1\. Introduction
Welcome to **Documenso Inc.**
Documenso Inc. (“us”, “we”, or “our”) operates [https://documenso.com](https://documenso.com) (hereinafter referred to as “ **Service**”).
Our Privacy Policy governs your visit to [https://documenso.com](https://documenso.com), and explains how we collect, safeguard and disclose information that results from your use of our Service.
We use your data to provide and improve Service. By using Service, you agree to the collection and use of information in accordance with this policy. Unless otherwise defined in this Privacy Policy, the terms used in this Privacy Policy have the same meanings as in our Terms and Conditions.
Our Terms and Conditions (“**Terms**”) govern all use of our Service and together with the Privacy Policy constitutes your agreement with us (“ **agreement**”).
### 2\. Definitions
**SERVICE** means the https://documenso.com website operated by Documenso Inc.
**PERSONAL DATA** means data about a living individual who can be identified from those data (or from those and other information either in our possession or likely to come into our possession).
**USAGE DATA** is data collected automatically either generated by the use of Service or from Service infrastructure itself (for example, the duration of a page visit).
**COOKIES** are small files stored on your device (computer or mobile device).
**DATA CONTROLLER** means a natural or legal person who (either alone or jointly or in common with other persons) determines the purposes for which and the manner in which any personal data are, or are to be, processed. For the purpose of this Privacy Policy, we are a Data Controller of your data.
**DATA PROCESSORS (OR SERVICE PROVIDERS)** means any natural or legal person who processes the data on behalf of the Data Controller. We may use the services of various Service Providers in order to process your data more effectively.
**DATA SUBJECT** is any living individual who is the subject of Personal Data.
**THE USER** is the individual using our Service. The User corresponds to the Data Subject, who is the subject of Personal Data.
### 3\. Information Collection and Use
We collect several different types of information for various purposes to provide and improve our Service to you.
### 4\. Types of Data Collected
**Personal Data**
While using our Service, we may ask you to provide us with certain personally identifiable information that can be used to contact or identify you (“**Personal Data**”). Personally identifiable information may include, but is not limited to:
1. Email address
2. First name and last name
3. Cookies and Usage Data
We may use your Personal Data to contact you with newsletters, marketing or promotional materials and other information that may be of interest to you. You may opt out of receiving any, or all, of these communications from us by following the unsubscribe link.
**Usage Data**
We may also collect information that your browser sends whenever you visit our Service or when you access Service by or through a mobile device (“**Usage Data**”).
This Usage Data may include information such as your computer's Internet Protocol address (e.g. IP address), browser type, browser version, the pages of our Service that you visit, the time and date of your visit, the time spent on those pages, unique device identifiers and other diagnostic data.
When you access Service with a mobile device, this Usage Data may include information such as the type of mobile device you use, your mobile device unique ID, the IP address of your mobile device, your mobile operating system, the type of mobile Internet browser you use, unique device identifiers and other diagnostic data.
**Tracking Cookies Data**
We use cookies and similar tracking technologies to track the activity on our Service and we hold certain information.
Cookies are files with a small amount of data which may include an anonymous unique identifier. Cookies are sent to your browser from a website and stored on your device. Other tracking technologies are also used such as beacons, tags and scripts to collect and track information and to improve and analyze our Service.
You can instruct your browser to refuse all cookies or to indicate when a cookie is being sent. However, if you do not accept cookies, you may not be able to use some portions of our Service.
Examples of Cookies we use:
1. **Session Cookies:** We use Session Cookies to operate our Service.
2. **Preference Cookies:** We use Preference Cookies to remember your preferences and various settings.
3. **Security Cookies:** We use Security Cookies for security purposes.
4. **Advertising Cookies:** Advertising Cookies are used to serve you with advertisements that may be relevant to you and your interests.
### 5\. Use of Data
Documenso Inc. uses the collected data for various purposes:
1. to provide and maintain our Service;
2. to notify you about changes to our Service;
3. to allow you to participate in interactive features of our Service when you choose to do so;
4. to provide customer support;
5. to gather analysis or valuable information so that we can improve our Service;
6. to monitor the usage of our Service;
7. to detect, prevent and address technical issues;
8. to fulfill any other purpose for which you provide it;
9. to carry out our obligations and enforce our rights arising from any contracts entered into between you and us, including for billing and collection;
10. to provide you with notices about your account and/or subscription, including expiration and renewal notices, email-instructions, etc.;
11. to provide you with news, special offers and general information about other goods, services and events which we offer that are similar to those that you have already purchased or enquired about unless you have opted not to receive such information;
12. in any other way we may describe when you provide the information;
13. for any other purpose with your consent.
### 6\. Retention of Data
We will retain your Personal Data only for as long as is necessary for the purposes set out in this Privacy Policy. We will retain and use your Personal Data to the extent necessary to comply with our legal obligations (for example, if we are required to retain your data to comply with applicable laws), resolve disputes, and enforce our legal agreements and policies.
We will also retain Usage Data for internal analysis purposes. Usage Data is generally retained for a shorter period, except when this data is used to strengthen the security or to improve the functionality of our Service, or we are legally obligated to retain this data for longer time periods.
### 7\. Transfer of Data
Your information, including Personal Data, may be transferred to and maintained on computers located outside of your state, province, country or other governmental jurisdiction where the data protection laws may differ from those of your jurisdiction.
If you are located outside United States and choose to provide information to us, please note that we transfer the data, including Personal Data, to United States and process it there.
Your consent to this Privacy Policy followed by your submission of such information represents your agreement to that transfer.
Documenso Inc. will take all the steps reasonably necessary to ensure that your data is treated securely and in accordance with this Privacy Policy and no transfer of your Personal Data will take place to an organisation or a country unless there are adequate controls in place including the security of your data and other personal information.
### 8\. Disclosure of Data
We may disclose personal information that we collect, or you provide:
1. **Disclosure for Law Enforcement.**
2. Under certain circumstances, we may be required to disclose your Personal Data if required to do so by law or in response to valid requests by public authorities.
3. **Business Transaction.**
4. If we or our subsidiaries are involved in a merger, acquisition or asset sale, your Personal Data may be transferred.
5. **Other cases. We may disclose your information also:**
1. to our subsidiaries and affiliates;
2. to contractors, service providers, and other third parties we use to support our business;
3. to fulfill the purpose for which you provide it;
### 9\. Security of Data
The security of your data is important to us but remember that no method of transmission over the Internet or method of electronic storage is 100% secure. While we strive to use commercially acceptable means to protect your Personal Data, we cannot guarantee its absolute security.
### 10\. Your Data Protection Rights Under General Data Protection Regulation (GDPR)
If you are a resident of the European Union (EU) and European Economic Area (EEA), you have certain data protection rights, covered by GDPR. See more at [https://eur-lex.europa.eu/eli/reg/2016/679/oj](https://eur-lex.europa.eu/eli/reg/2016/679/oj)
We aim to take reasonable steps to allow you to correct, amend, delete, or limit the use of your Personal Data.
If you wish to be informed what Personal Data we hold about you and if you want it to be removed from our systems, please email us at hi@documenso.com.
In certain circumstances, you have the following data protection rights:
1. the right to access, update or to delete the information we have on you;
2. the right of rectification. You have the right to have your information rectified if that information is inaccurate or incomplete;
3. the right to object. You have the right to object to our processing of your Personal Data;
4. the right of restriction. You have the right to request that we restrict the processing of your personal information;
5. the right to data portability. You have the right to be provided with a copy of your Personal Data in a structured, machine-readable and commonly used format;
6. the right to withdraw consent. You also have the right to withdraw your consent at any time where we rely on your consent to process your personal information;
Please note that we may ask you to verify your identity before responding to such requests. Please note, we may not able to provide Service without some necessary data.
You have the right to complain to a Data Protection Authority about our collection and use of your Personal Data. For more information, please contact your local data protection authority in the European Economic Area (EEA).
### 11\. Your Data Protection Rights under the California Privacy Protection Act (CalOPPA)
CalOPPA is the first state law in the nation to require commercial websites and online services to post a privacy policy. The laws reach stretches well beyond California to require a person or company in the United States (and conceivable the world) that operates websites collecting personally identifiable information from California consumers to post a conspicuous privacy policy on its website stating exactly the information being collected and those individuals with whom it is being shared, and to comply with this policy. See more at: [https://consumercal.org/about-cfc/cfc-education-foundation/california-online-privacy-protection-act-caloppa-3/](https://consumercal.org/about-cfc/cfc-education-foundation/california-online-privacy-protection-act-caloppa-3/)
According to CalOPPA we agree to the following:
1. users can visit our site anonymously;
2. our Privacy Policy link includes the word “Privacy”, and can easily be found on the page specified above on the home page of our website;
3. users will be notified of any privacy policy changes on our Privacy Policy Page;
4. users are able to change their personal information by emailing us at hi@documenso.com.
Our Policy on “Do Not Track” Signals:
We honor Do Not Track signals and do not track, plant cookies, or use advertising when a Do Not Track browser mechanism is in place. Do Not Track is a preference you can set in your web browser to inform websites that you do not want to be tracked.
You can enable or disable Do Not Track by visiting the Preferences or Settings page of your web browser.
### 12\. Your Data Protection Rights under the California Consumer Privacy Act (CCPA)
If you are a California resident, you are entitled to learn what data we collect about you, ask to delete your data and not to sell (share) it. To exercise your data protection rights, you can make certain requests and ask us:
1. **What personal information we have about you**. If you make this request, we will return to you:
1. The categories of personal information we have collected about you.
2. The categories of sources from which we collect your personal information.
3. The business or commercial purpose for collecting or selling your personal information.
4. The categories of third parties with whom we share personal information.
5. The specific pieces of personal information we have collected about you.
6. A list of categories of personal information that we have sold, along with the category of any other company we sold it to. If we have not sold your personal information, we will inform you of that fact.
7. A list of categories of personal information that we have disclosed for a business purpose, along with the category of any other company we shared it with.
Please note, you are entitled to ask us to provide you with this information up to two times in a rolling twelve-month period. When you make this request, the information provided may be limited to the personal information we collected about you in the previous 12 months.
2. **To delete your personal information**. If you make this request, we will delete the personal information we hold about you as of the date of your request from our records and direct any service providers to do the same. In some cases, deletion may be accomplished through de-identification of the information. If you choose to delete your personal information, you may not be able to use certain functions that require your personal information to operate.
3. **To stop selling your personal information**. We don't sell or rent your personal information to any third parties for any purpose. You are the only owner of your Personal Data and can request disclosure or deletion at any time.
Please note, if you ask us to delete or stop selling your data, it may impact your experience with us, and you may not be able to participate in certain programs or membership services which require the usage of your personal information to function. But in no circumstances, we will discriminate against you for exercising your rights.
To exercise your California data protection rights described above, please send your request(s) by one of the following means:
By email: hi@documenso.com
Your data protection rights, described above, are covered by the CCPA, short for the California Consumer Privacy Act. To find out more, visit the official [California Legislative Information website](https://leginfo.legislature.ca.gov/faces/billTextClient.xhtml?bill_id=201720180AB375). The CCPA took effect on 01/01/2020.
### 13\. Service Providers
We may employ third party companies and individuals to facilitate our Service (“ **Service Providers**”), provide Service on our behalf, perform Service-related services or assist us in analysing how our Service is used.
These third parties have access to your Personal Data only to perform these tasks on our behalf and are obligated not to disclose or use it for any other purpose.
### 14\. Analytics
We may use third-party Service Providers to monitor and analyze the use of our Service.
**Plausible Analytics**
Plausible Analytics is an analytics service provided by Conva Ventures Inc. You can find their Privacy Policy here: [https://plausible.io/privacy](https://plausible.io/privacy)
### 15\. CI/CD tools
We may use third-party Service Providers to automate the development process of our Service.
**GitHub**
GitHub is provided by GitHub, Inc.
GitHub is a development platform to host and review code, manage projects, and build software.
For more information on what data GitHub collects for what purpose and how the protection of the data is ensured, please visit GitHub Privacy Policy page: [https://help.github.com/en/articles/github-privacy-statement](https://help.github.com/en/articles/github-privacy-statement) .
### 16\. Payments
We may provide paid products and/or services within Service. In that case, we use third-party services for payment processing (e.g. payment processors).
We will not store or collect your payment card details. That information is provided directly to our third-party payment processors whose use of your personal information is governed by their Privacy Policy. These payment processors adhere to the standards set by PCI-DSS as managed by the PCI Security Standards Council, which is a joint effort of brands like Visa, Mastercard, American Express and Discover. PCI-DSS requirements help ensure the secure handling of payment information.
The payment processors we work with are:
**Stripe:**
Their Privacy Policy can be viewed at: [https://stripe.com/us/privacy](https://stripe.com/us/privacy)
### 17\. Links to Other Sites
Our Service may contain links to other sites that are not operated by us. If you click a third party link, you will be directed to that third party's site. We strongly advise you to review the Privacy Policy of every site you visit.
We have no control over and assume no responsibility for the content, privacy policies or practices of any third party sites or services.
### 18\. Children's Privacy
Our Services are not intended for use by children under the age of 18 (“ **Child**” or “**Children**”).
We do not knowingly collect personally identifiable information from Children under 18. If you become aware that a Child has provided us with Personal Data, please contact us. If we become aware that we have collected Personal Data from Children without verification of parental consent, we take steps to remove that information from our servers.
### 19\. Changes to This Privacy Policy
We may update our Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page.
We will let you know via email and/or a prominent notice on our Service, prior to the change becoming effective and update “effective date” at the top of this Privacy Policy.
You are advised to review this Privacy Policy periodically for any changes. Changes to this Privacy Policy are effective when they are posted on this page.
### 20\. Contact Us
If you have any questions about this Privacy Policy, please contact us:
By email: hi@documenso.com.

View File

@@ -14,6 +14,7 @@ ENV_FILES.forEach((file) => {
const config = {
experimental: {
serverActionsBodySizeLimit: '10mb',
outputFileTracingRoot: path.join(__dirname, '../../'),
},
reactStrictMode: true,
transpilePackages: ['@documenso/lib', '@documenso/prisma', '@documenso/trpc', '@documenso/ui'],

View File

@@ -8,6 +8,7 @@
"build": "next build",
"start": "next start -p 3001",
"lint": "next lint",
"lint:fix": "next lint --fix",
"clean": "rimraf .next && rimraf node_modules",
"copy:pdfjs": "node ../../scripts/copy-pdfjs.cjs"
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@@ -4,13 +4,13 @@ import { allBlogPosts } from 'contentlayer/generated';
export const runtime = 'edge';
export const size = {
export const contentType = 'image/png';
export const IMAGE_SIZE = {
width: 1200,
height: 630,
};
export const contentType = 'image/png';
type BlogPostOpenGraphImageProps = {
params: { post: string };
};
@@ -25,16 +25,16 @@ export default async function BlogPostOpenGraphImage({ params }: BlogPostOpenGra
// The long urls are needed for a compiler optimisation on the Next.js side, lifting this up
// to a constant will break og image generation.
const [interBold, interRegular, backgroundImage, logoImage] = await Promise.all([
fetch(new URL('./../../../../assets/inter-bold.ttf', import.meta.url)).then(async (res) =>
fetch(new URL('@documenso/assets/fonts/inter-bold.ttf', import.meta.url)).then(async (res) =>
res.arrayBuffer(),
),
fetch(new URL('./../../../../assets/inter-regular.ttf', import.meta.url)).then(async (res) =>
fetch(new URL('@documenso/assets/fonts/inter-regular.ttf', import.meta.url)).then(async (res) =>
res.arrayBuffer(),
),
fetch(new URL('./../../../../assets/background-blog-og.png', import.meta.url)).then(
fetch(new URL('@documenso/assets/images/background-blog-og.png', import.meta.url)).then(
async (res) => res.arrayBuffer(),
),
fetch(new URL('./../../../../../public/logo.png', import.meta.url)).then(async (res) =>
fetch(new URL('@documenso/assets/logo.png', import.meta.url)).then(async (res) =>
res.arrayBuffer(),
),
]);
@@ -56,7 +56,7 @@ export default async function BlogPostOpenGraphImage({ params }: BlogPostOpenGra
</div>
),
{
...size,
...IMAGE_SIZE,
fonts: [
{
name: 'Inter',

View File

@@ -18,6 +18,14 @@ export const revalidate = 3600;
export const dynamic = 'force-dynamic';
const GITHUB_HEADERS: Record<string, string> = {
accept: 'application/vnd.github.v3+json',
};
if (process.env.NEXT_PRIVATE_GITHUB_TOKEN) {
GITHUB_HEADERS.authorization = `Bearer ${process.env.NEXT_PRIVATE_GITHUB_TOKEN}`;
}
const ZGithubStatsResponse = z.object({
stargazers_count: z.number(),
forks_count: z.number(),
@@ -28,6 +36,10 @@ const ZMergedPullRequestsResponse = z.object({
total_count: z.number(),
});
const ZOpenIssuesResponse = z.object({
total_count: z.number(),
});
const ZStargazersLiveResponse = z.record(
z.object({
stars: z.number(),
@@ -48,49 +60,76 @@ const ZEarlyAdoptersResponse = z.record(
export type StargazersType = z.infer<typeof ZStargazersLiveResponse>;
export type EarlyAdoptersType = z.infer<typeof ZEarlyAdoptersResponse>;
export default async function OpenPage() {
const GITHUB_HEADERS: Record<string, string> = {
accept: 'application/vnd.github.v3+json',
};
if (process.env.NEXT_PRIVATE_GITHUB_TOKEN) {
GITHUB_HEADERS.authorization = `Bearer ${process.env.NEXT_PRIVATE_GITHUB_TOKEN}`;
}
const {
forks_count: forksCount,
open_issues: openIssues,
stargazers_count: stargazersCount,
} = await fetch('https://api.github.com/repos/documenso/documenso', {
headers: GITHUB_HEADERS,
const fetchGithubStats = async () => {
return await fetch('https://api.github.com/repos/documenso/documenso', {
headers: {
...GITHUB_HEADERS,
},
})
.then(async (res) => res.json())
.then((res) => ZGithubStatsResponse.parse(res));
};
const { total_count: mergedPullRequests } = await fetch(
const fetchOpenIssues = async () => {
return await fetch(
'https://api.github.com/search/issues?q=repo:documenso/documenso+type:issue+state:open&page=0&per_page=1',
{
headers: {
...GITHUB_HEADERS,
},
},
)
.then(async (res) => res.json())
.then((res) => ZOpenIssuesResponse.parse(res));
};
const fetchMergedPullRequests = async () => {
return await fetch(
'https://api.github.com/search/issues?q=repo:documenso/documenso/+is:pr+merged:>=2010-01-01&page=0&per_page=1',
{
headers: GITHUB_HEADERS,
headers: {
...GITHUB_HEADERS,
},
},
)
.then(async (res) => res.json())
.then((res) => ZMergedPullRequestsResponse.parse(res));
};
const STARGAZERS_DATA = await fetch('https://stargrazer-live.onrender.com/api/stats', {
const fetchStargazers = async () => {
return await fetch('https://stargrazer-live.onrender.com/api/stats', {
headers: {
accept: 'application/json',
},
})
.then(async (res) => res.json())
.then((res) => ZStargazersLiveResponse.parse(res));
};
const EARLY_ADOPTERS_DATA = await fetch('https://stargrazer-live.onrender.com/api/stats/stripe', {
const fetchEarlyAdopters = async () => {
return await fetch('https://stargrazer-live.onrender.com/api/stats/stripe', {
headers: {
accept: 'application/json',
},
})
.then(async (res) => res.json())
.then((res) => ZEarlyAdoptersResponse.parse(res));
};
export default async function OpenPage() {
const [
{ forks_count: forksCount, stargazers_count: stargazersCount },
{ total_count: openIssues },
{ total_count: mergedPullRequests },
STARGAZERS_DATA,
EARLY_ADOPTERS_DATA,
] = await Promise.all([
fetchGithubStats(),
fetchOpenIssues(),
fetchMergedPullRequests(),
fetchStargazers(),
fetchEarlyAdopters(),
]);
const MONTHLY_USERS = await getUserMonthlyGrowth();

View File

@@ -2,7 +2,7 @@ import Image from 'next/image';
import { z } from 'zod';
import backgroundPattern from '~/assets/background-pattern.png';
import backgroundPattern from '@documenso/assets/images/background-pattern.png';
import { OSSFriendsContainer } from './container';
import { TOSSFriendsSchema, ZOSSFriendsSchema } from './schema';

View File

@@ -7,11 +7,10 @@ import { useRouter } from 'next/navigation';
import { motion } from 'framer-motion';
import { ChevronLeft } from 'lucide-react';
import backgroundPattern from '@documenso/assets/images/background-pattern.png';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import backgroundPattern from '~/assets/background-pattern.png';
export default function NotFound() {
const router = useRouter();

View File

@@ -2,14 +2,13 @@ import { HTMLAttributes } from 'react';
import Image from 'next/image';
import backgroundPattern from '@documenso/assets/images/background-pattern.png';
import cardBeautifulFigure from '@documenso/assets/images/card-beautiful-figure.png';
import cardFastFigure from '@documenso/assets/images/card-fast-figure.png';
import cardSmartFigure from '@documenso/assets/images/card-smart-figure.png';
import { cn } from '@documenso/ui/lib/utils';
import { Card, CardContent } from '@documenso/ui/primitives/card';
import backgroundPattern from '~/assets/background-pattern.png';
import cardBeautifulFigure from '~/assets/card-beautiful-figure.png';
import cardFastFigure from '~/assets/card-fast-figure.png';
import cardSmartFigure from '~/assets/card-smart-figure.png';
export type FasterSmarterBeautifulBentoProps = HTMLAttributes<HTMLDivElement>;
export const FasterSmarterBeautifulBento = ({

View File

@@ -1,6 +1,6 @@
'use client';
import { HTMLAttributes } from 'react';
import type { HTMLAttributes } from 'react';
import Image from 'next/image';
import Link from 'next/link';
@@ -11,6 +11,7 @@ import { FaXTwitter } from 'react-icons/fa6';
import { LiaDiscord } from 'react-icons/lia';
import { LuGithub } from 'react-icons/lu';
import LogoImage from '@documenso/assets/logo.png';
import { cn } from '@documenso/ui/lib/utils';
export type FooterProps = HTMLAttributes<HTMLDivElement>;
@@ -26,10 +27,11 @@ const FOOTER_LINKS = [
{ href: '/singleplayer', text: 'Singleplayer' },
{ href: '/blog', text: 'Blog' },
{ href: '/design-system', text: 'Design' },
{ href: '/open', text: 'Open' },
{ href: '/open', text: 'Open Startup' },
{ href: 'https://shop.documenso.com', text: 'Shop', target: '_blank' },
{ href: 'https://status.documenso.com', text: 'Status', target: '_blank' },
{ href: 'mailto:support@documenso.com', text: 'Support', target: '_blank' },
{ href: '/oss-friends', text: 'OSS Friends' },
{ href: '/privacy', text: 'Privacy' },
];
@@ -42,7 +44,7 @@ export const Footer = ({ className, ...props }: FooterProps) => {
<div>
<Link href="/">
<Image
src="/logo.png"
src={LogoImage}
alt="Documenso Logo"
className="dark:invert"
width={170}

View File

@@ -1,10 +1,12 @@
'use client';
import { HTMLAttributes, useState } from 'react';
import type { HTMLAttributes } from 'react';
import { useState } from 'react';
import Image from 'next/image';
import Link from 'next/link';
import LogoImage from '@documenso/assets/logo.png';
import { useFeatureFlags } from '@documenso/lib/client-only/providers/feature-flag';
import { cn } from '@documenso/ui/lib/utils';
@@ -25,7 +27,7 @@ export const Header = ({ className, ...props }: HeaderProps) => {
<div className="flex items-center space-x-4">
<Link href="/" className="z-10" onClick={() => setIsHamburgerMenuOpen(false)}>
<Image
src="/logo.png"
src={LogoImage}
alt="Documenso Logo"
className="dark:invert"
width={170}
@@ -62,7 +64,7 @@ export const Header = ({ className, ...props }: HeaderProps) => {
href="/open"
className="text-muted-foreground hover:text-muted-foreground/80 text-sm font-semibold"
>
Open
Open Startup
</Link>
<Link

View File

@@ -8,12 +8,11 @@ import { usePlausible } from 'next-plausible';
import { LuGithub } from 'react-icons/lu';
import { match } from 'ts-pattern';
import backgroundPattern from '@documenso/assets/images/background-pattern.png';
import { useFeatureFlags } from '@documenso/lib/client-only/providers/feature-flag';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import backgroundPattern from '~/assets/background-pattern.png';
import { Widget } from './widget';
export type HeroProps = {

View File

@@ -8,6 +8,7 @@ import { FaXTwitter } from 'react-icons/fa6';
import { LiaDiscord } from 'react-icons/lia';
import { LuGithub } from 'react-icons/lu';
import LogoImage from '@documenso/assets/logo.png';
import { Sheet, SheetContent } from '@documenso/ui/primitives/sheet';
export type MobileNavigationProps = {
@@ -30,7 +31,7 @@ export const MENU_NAVIGATION_LINKS = [
},
{
href: '/open',
text: 'Open',
text: 'Open Startup',
},
{
href: 'https://status.documenso.com',
@@ -63,7 +64,7 @@ export const MobileNavigation = ({ isMenuOpen, onMenuOpenChange }: MobileNavigat
<SheetContent className="w-full max-w-[400px]">
<Link href="/" className="z-10" onClick={handleMenuItemClick}>
<Image
src="/logo.png"
src={LogoImage}
alt="Documenso Logo"
className="dark:invert"
width={170}

View File

@@ -2,14 +2,13 @@ import { HTMLAttributes } from 'react';
import Image from 'next/image';
import backgroundPattern from '@documenso/assets/images/background-pattern.png';
import cardBuildFigure from '@documenso/assets/images/card-build-figure.png';
import cardOpenFigure from '@documenso/assets/images/card-open-figure.png';
import cardTemplateFigure from '@documenso/assets/images/card-template-figure.png';
import { cn } from '@documenso/ui/lib/utils';
import { Card, CardContent } from '@documenso/ui/primitives/card';
import backgroundPattern from '~/assets/background-pattern.png';
import cardBuildFigure from '~/assets/card-build-figure.png';
import cardOpenFigure from '~/assets/card-open-figure.png';
import cardTemplateFigure from '~/assets/card-template-figure.png';
export type OpenBuildTemplateBentoProps = HTMLAttributes<HTMLDivElement>;
export const OpenBuildTemplateBento = ({ className, ...props }: OpenBuildTemplateBentoProps) => {

View File

@@ -2,15 +2,14 @@ import { HTMLAttributes } from 'react';
import Image from 'next/image';
import backgroundPattern from '@documenso/assets/images/background-pattern.png';
import cardConnectionsFigure from '@documenso/assets/images/card-connections-figure.png';
import cardPaidFigure from '@documenso/assets/images/card-paid-figure.png';
import cardSharingFigure from '@documenso/assets/images/card-sharing-figure.png';
import cardWidgetFigure from '@documenso/assets/images/card-widget-figure.png';
import { cn } from '@documenso/ui/lib/utils';
import { Card, CardContent } from '@documenso/ui/primitives/card';
import backgroundPattern from '~/assets/background-pattern.png';
import cardConnectionsFigure from '~/assets/card-connections-figure.png';
import cardPaidFigure from '~/assets/card-paid-figure.png';
import cardSharingFigure from '~/assets/card-sharing-figure.png';
import cardWidgetFigure from '~/assets/card-widget-figure.png';
export type ShareConnectPaidWidgetBentoProps = HTMLAttributes<HTMLDivElement>;
export const ShareConnectPaidWidgetBento = ({

View File

@@ -4,6 +4,7 @@ import { useEffect, useState } from 'react';
import Link from 'next/link';
import signingCelebration from '@documenso/assets/images/signing-celebration.png';
import { useFeatureFlags } from '@documenso/lib/client-only/providers/feature-flag';
import { DocumentStatus, Signature } from '@documenso/prisma/client';
import { DocumentWithRecipient } from '@documenso/prisma/types/document-with-recipient';
@@ -14,7 +15,6 @@ import { SigningCard3D } from '@documenso/ui/components/signing-card';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import signingCelebration from '~/assets/signing-celebration.png';
import { ConfettiScreen } from '~/components/(marketing)/confetti-screen';
interface SinglePlayerModeSuccessProps {

View File

@@ -1,7 +1,6 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { randomBytes } from 'crypto';
import { readFileSync } from 'fs';
import { buffer } from 'micro';
import { insertImageInPDF } from '@documenso/lib/server-only/pdf/insert-image-in-pdf';
@@ -88,7 +87,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const now = new Date();
const bytes64 = readFileSync('./public/documenso-supporter-pledge.pdf').toString('base64');
const bytes64 = await fetch(
new URL('@documenso/assets/documenso-supporter-pledge.pdf', import.meta.url),
)
.then(async (res) => res.arrayBuffer())
.then((buffer) => Buffer.from(buffer).toString('base64'));
const { id: documentDataId } = await prisma.documentData.create({
data: {

View File

@@ -12,7 +12,9 @@ ENV_FILES.forEach((file) => {
/** @type {import('next').NextConfig} */
const config = {
output: process.env.DOCKER_OUTPUT ? 'standalone' : undefined,
experimental: {
outputFileTracingRoot: path.join(__dirname, '../../'),
serverActionsBodySizeLimit: '50mb',
},
reactStrictMode: true,

View File

@@ -8,6 +8,7 @@
"build": "next build",
"start": "next start",
"lint": "next lint",
"lint:fix": "next lint --fix",
"clean": "rimraf .next && rimraf node_modules",
"copy:pdfjs": "node ../../scripts/copy-pdfjs.cjs"
},

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { redirect } from 'next/navigation';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { isAdmin } from '@documenso/lib/next-auth/guards/is-admin';
import { AdminNav } from './nav';

View File

@@ -4,7 +4,7 @@ import { useRouter } from 'next/navigation';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import type { z } from 'zod';
import { trpc } from '@documenso/trpc/react';
import { ZUpdateProfileMutationByAdminSchema } from '@documenso/trpc/server/admin-router/schema';

View File

@@ -3,7 +3,7 @@ import { redirect } from 'next/navigation';
import { ChevronLeft, Users2 } from 'lucide-react';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
import { getFieldsForDocument } from '@documenso/lib/server-only/field/get-fields-for-document';
import { getRecipientsForDocument } from '@documenso/lib/server-only/recipient/get-recipients-for-document';

View File

@@ -0,0 +1,185 @@
'use client';
import { useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { History } from 'lucide-react';
import { useForm } from 'react-hook-form';
import * as z from 'zod';
import { getRecipientType } from '@documenso/lib/client-only/recipient-type';
import { recipientAbbreviation } from '@documenso/lib/utils/recipient-formatter';
import { type Document, type Recipient, SigningStatus } from '@documenso/prisma/client';
import { trpc as trpcReact } from '@documenso/trpc/react';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import { Checkbox } from '@documenso/ui/primitives/checkbox';
import {
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import { DropdownMenuItem } from '@documenso/ui/primitives/dropdown-menu';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
} from '@documenso/ui/primitives/form/form';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { StackAvatar } from '~/components/(dashboard)/avatar/stack-avatar';
const FORM_ID = 'resend-email';
export type ResendDocumentActionItemProps = {
document: Document;
recipients: Recipient[];
};
export const ZResendDocumentFormSchema = z.object({
recipients: z.array(z.number()).min(1, {
message: 'You must select at least one item.',
}),
});
export type TResendDocumentFormSchema = z.infer<typeof ZResendDocumentFormSchema>;
export const ResendDocumentActionItem = ({
document,
recipients,
}: ResendDocumentActionItemProps) => {
const { toast } = useToast();
const [isOpen, setIsOpen] = useState(false);
const isDisabled =
document.status !== 'PENDING' ||
!recipients.some((r) => r.signingStatus === SigningStatus.NOT_SIGNED);
const { mutateAsync: resendDocument } = trpcReact.document.resendDocument.useMutation();
const form = useForm<TResendDocumentFormSchema>({
resolver: zodResolver(ZResendDocumentFormSchema),
defaultValues: {
recipients: [],
},
});
const {
handleSubmit,
formState: { isSubmitting },
} = form;
const onFormSubmit = async ({ recipients }: TResendDocumentFormSchema) => {
try {
await resendDocument({ documentId: document.id, recipients });
toast({
title: 'Document re-sent',
description: 'Your document has been re-sent successfully.',
duration: 5000,
});
setIsOpen(false);
} catch (err) {
toast({
title: 'Something went wrong',
description: 'This document could not be re-sent at this time. Please try again.',
variant: 'destructive',
duration: 7500,
});
}
};
return (
<>
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<DropdownMenuItem disabled={isDisabled} onSelect={(e) => e.preventDefault()}>
<History className="mr-2 h-4 w-4" />
Resend
</DropdownMenuItem>
</DialogTrigger>
<DialogContent className="sm:max-w-sm" hideClose>
<DialogHeader>
<DialogTitle>
<h1 className="text-center text-xl">Who do you want to remind?</h1>
</DialogTitle>
</DialogHeader>
<Form {...form}>
<form id={FORM_ID} onSubmit={handleSubmit(onFormSubmit)} className="px-3">
<FormField
control={form.control}
name="recipients"
render={({ field: { value, onChange } }) => (
<>
{recipients.map((recipient) => (
<FormItem
key={recipient.id}
className="flex flex-row items-center justify-between gap-x-3"
>
<FormLabel
className={cn('my-2 flex items-center gap-2 font-normal', {
'opacity-50': !value.includes(recipient.id),
})}
>
<StackAvatar
key={recipient.id}
type={getRecipientType(recipient)}
fallbackText={recipientAbbreviation(recipient)}
/>
{recipient.email}
</FormLabel>
<FormControl>
<Checkbox
className="h-5 w-5 rounded-full data-[state=checked]:border-black data-[state=checked]:bg-black "
checkClassName="text-white"
value={recipient.id}
checked={value.includes(recipient.id)}
onCheckedChange={(checked: boolean) =>
checked
? onChange([...value, recipient.id])
: onChange(value.filter((v) => v !== recipient.id))
}
/>
</FormControl>
</FormItem>
))}
</>
)}
/>
</form>
</Form>
<DialogFooter>
<div className="flex w-full flex-1 flex-nowrap gap-4">
<DialogClose asChild>
<Button
type="button"
className="dark:bg-muted dark:hover:bg-muted/80 flex-1 bg-black/5 hover:bg-black/10"
variant="secondary"
disabled={isSubmitting}
>
Cancel
</Button>
</DialogClose>
<Button className="flex-1" loading={isSubmitting} type="submit" form={FORM_ID}>
Send reminder
</Button>
</div>
</DialogFooter>
</DialogContent>
</Dialog>
</>
);
};

View File

@@ -2,12 +2,15 @@
import Link from 'next/link';
import { Edit, Pencil, Share } from 'lucide-react';
import { Download, Edit, Pencil } from 'lucide-react';
import { useSession } from 'next-auth/react';
import { match } from 'ts-pattern';
import { Document, DocumentStatus, Recipient, SigningStatus, User } from '@documenso/prisma/client';
import { DocumentShareButton } from '@documenso/ui/components/document/document-share-button';
import { getFile } from '@documenso/lib/universal/upload/get-file';
import type { Document, Recipient, User } from '@documenso/prisma/client';
import { DocumentStatus, SigningStatus } from '@documenso/prisma/client';
import type { DocumentWithData } from '@documenso/prisma/types/document-with-data';
import { trpc as trpcClient } from '@documenso/trpc/client';
import { Button } from '@documenso/ui/primitives/button';
export type DataTableActionButtonProps = {
@@ -33,6 +36,41 @@ export const DataTableActionButton = ({ row }: DataTableActionButtonProps) => {
const isComplete = row.status === DocumentStatus.COMPLETED;
const isSigned = recipient?.signingStatus === SigningStatus.SIGNED;
const onDownloadClick = async () => {
let document: DocumentWithData | null = null;
if (!recipient) {
document = await trpcClient.document.getDocumentById.query({
id: row.id,
});
} else {
document = await trpcClient.document.getDocumentByToken.query({
token: recipient.token,
});
}
const documentData = document?.documentData;
if (!documentData) {
return;
}
const documentBytes = await getFile(documentData);
const blob = new Blob([documentBytes], {
type: 'application/pdf',
});
const link = window.document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = row.title || 'document.pdf';
link.click();
window.URL.revokeObjectURL(link.href);
};
return match({
isOwner,
isRecipient,
@@ -42,7 +80,7 @@ export const DataTableActionButton = ({ row }: DataTableActionButtonProps) => {
isSigned,
})
.with({ isOwner: true, isDraft: true }, () => (
<Button className="w-24" asChild>
<Button className="w-32" asChild>
<Link href={`/documents/${row.id}`}>
<Edit className="-ml-1 mr-2 h-4 w-4" />
Edit
@@ -50,23 +88,24 @@ export const DataTableActionButton = ({ row }: DataTableActionButtonProps) => {
</Button>
))
.with({ isRecipient: true, isPending: true, isSigned: false }, () => (
<Button className="w-24" asChild>
<Button className="w-32" asChild>
<Link href={`/sign/${recipient?.token}`}>
<Pencil className="-ml-1 mr-2 h-4 w-4" />
Sign
</Link>
</Button>
))
.otherwise(() => (
<DocumentShareButton
documentId={row.id}
token={recipient?.token}
trigger={({ loading }) => (
<Button className="w-24" loading={loading}>
{!loading && <Share className="-ml-1 mr-2 h-4 w-4" />}
Share
</Button>
)}
/>
));
.with({ isPending: true, isSigned: true }, () => (
<Button className="w-32" disabled={true}>
<Pencil className="-ml-1 mr-2 inline h-4 w-4" />
Sign
</Button>
))
.with({ isComplete: true }, () => (
<Button className="w-32" onClick={onDownloadClick}>
<Download className="-ml-1 mr-2 inline h-4 w-4" />
Download
</Button>
))
.otherwise(() => <div></div>);
};

View File

@@ -8,7 +8,6 @@ import {
Copy,
Download,
Edit,
History,
Loader,
MoreHorizontal,
Pencil,
@@ -19,8 +18,9 @@ import {
import { useSession } from 'next-auth/react';
import { getFile } from '@documenso/lib/universal/upload/get-file';
import { Document, DocumentStatus, Recipient, User } from '@documenso/prisma/client';
import { DocumentWithData } from '@documenso/prisma/types/document-with-data';
import type { Document, Recipient, User } from '@documenso/prisma/client';
import { DocumentStatus } from '@documenso/prisma/client';
import type { DocumentWithData } from '@documenso/prisma/types/document-with-data';
import { trpc as trpcClient } from '@documenso/trpc/client';
import { DocumentShareButton } from '@documenso/ui/components/document/document-share-button';
import {
@@ -31,6 +31,7 @@ import {
DropdownMenuTrigger,
} from '@documenso/ui/primitives/dropdown-menu';
import { ResendDocumentActionItem } from './_action-items/resend-document';
import { DeleteDraftDocumentDialog } from './delete-draft-document-dialog';
import { DuplicateDocumentDialog } from './duplicate-document-dialog';
@@ -96,6 +97,7 @@ export const DataTableActionDropdown = ({ row }: DataTableActionDropdownProps) =
window.URL.revokeObjectURL(link.href);
};
const nonSignedRecipients = row.Recipient.filter((item) => item.signingStatus !== 'SIGNED');
return (
<DropdownMenu>
<DropdownMenuTrigger>
@@ -141,19 +143,16 @@ export const DataTableActionDropdown = ({ row }: DataTableActionDropdownProps) =
<DropdownMenuLabel>Share</DropdownMenuLabel>
<DropdownMenuItem disabled>
<History className="mr-2 h-4 w-4" />
Resend
</DropdownMenuItem>
<ResendDocumentActionItem document={row} recipients={nonSignedRecipients} />
<DocumentShareButton
documentId={row.id}
token={recipient?.token}
token={isOwner ? undefined : recipient?.token}
trigger={({ loading, disabled }) => (
<DropdownMenuItem disabled={disabled || isDraft} onSelect={(e) => e.preventDefault()}>
<div className="flex items-center">
{loading ? <Loader className="mr-2 h-4 w-4" /> : <Share className="mr-2 h-4 w-4" />}
Share
Share Signing Card
</div>
</DropdownMenuItem>
)}

View File

@@ -26,14 +26,14 @@ export const DuplicateDocumentDialog = ({
const router = useRouter();
const { toast } = useToast();
const { data, isLoading } = trpcReact.document.getDocumentById.useQuery({
const { data: document, isLoading } = trpcReact.document.getDocumentById.useQuery({
id,
});
const documentData = data?.documentData
const documentData = document?.documentData
? {
...data.documentData,
data: data.documentData.initialData,
...document.documentData,
data: document.documentData.initialData,
}
: undefined;
@@ -78,7 +78,7 @@ export const DuplicateDocumentDialog = ({
</div>
) : (
<div className="p-2 [&>div]:h-[50vh] [&>div]:overflow-y-scroll ">
<LazyPDFViewer key={data?.id} documentData={documentData} />
<LazyPDFViewer key={document?.id} documentData={documentData} />
</div>
)}

View File

@@ -1,6 +1,6 @@
import Link from 'next/link';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { findDocuments } from '@documenso/lib/server-only/document/find-documents';
import { getStats } from '@documenso/lib/server-only/document/get-stats';
import { isExtendedDocumentStatus } from '@documenso/prisma/guards/is-extended-document-status';
@@ -8,10 +8,8 @@ import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-documen
import { Tabs, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs';
import { PeriodSelector } from '~/components/(dashboard)/period-selector/period-selector';
import {
PeriodSelectorValue,
isPeriodSelectorValue,
} from '~/components/(dashboard)/period-selector/types';
import type { PeriodSelectorValue } from '~/components/(dashboard)/period-selector/types';
import { isPeriodSelectorValue } from '~/components/(dashboard)/period-selector/types';
import { DocumentStatus } from '~/components/formatter/document-status';
import { DocumentsDataTable } from './data-table';
@@ -80,7 +78,12 @@ export default async function DocumentsPage({ searchParams = {} }: DocumentsPage
ExtendedDocumentStatus.DRAFT,
ExtendedDocumentStatus.ALL,
].map((value) => (
<TabsTrigger key={value} className="min-w-[60px]" value={value} asChild>
<TabsTrigger
key={value}
className="hover:text-foreground min-w-[60px]"
value={value}
asChild
>
<Link href={getTabHref(value)} scroll={false}>
<DocumentStatus status={value} />

View File

@@ -6,7 +6,7 @@ import { getServerSession } from 'next-auth';
import { LimitsProvider } from '@documenso/ee/server-only/limits/provider/server';
import { NEXT_AUTH_OPTIONS } from '@documenso/lib/next-auth/auth-options';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { Header } from '~/components/(dashboard)/layout/header';
import { RefreshOnFocus } from '~/components/(dashboard)/refresh-on-focus/refresh-on-focus';

View File

@@ -5,8 +5,9 @@ import {
getStripeCustomerById,
} from '@documenso/ee/server-only/stripe/get-customer';
import { getPortalSession } from '@documenso/ee/server-only/stripe/get-portal-session';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
import { Stripe, stripe } from '@documenso/lib/server-only/stripe';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import type { Stripe } from '@documenso/lib/server-only/stripe';
import { stripe } from '@documenso/lib/server-only/stripe';
import { getSubscriptionByUserId } from '@documenso/lib/server-only/subscription/get-subscription-by-user-id';
export const createBillingPortal = async () => {

View File

@@ -7,8 +7,8 @@ import {
getStripeCustomerById,
} from '@documenso/ee/server-only/stripe/get-customer';
import { getPortalSession } from '@documenso/ee/server-only/stripe/get-portal-session';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
import { Stripe } from '@documenso/lib/server-only/stripe';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import type { Stripe } from '@documenso/lib/server-only/stripe';
import { getSubscriptionByUserId } from '@documenso/lib/server-only/subscription/get-subscription-by-user-id';
export type CreateCheckoutOptions = {

View File

@@ -4,9 +4,9 @@ import { match } from 'ts-pattern';
import { getPricesByInterval } from '@documenso/ee/server-only/stripe/get-prices-by-interval';
import { getProductByPriceId } from '@documenso/ee/server-only/stripe/get-product-by-price-id';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { getServerComponentFlag } from '@documenso/lib/server-only/feature-flags/get-server-component-feature-flag';
import { Stripe } from '@documenso/lib/server-only/stripe';
import type { Stripe } from '@documenso/lib/server-only/stripe';
import { getSubscriptionByUserId } from '@documenso/lib/server-only/subscription/get-subscription-by-user-id';
import { LocaleDate } from '~/components/formatter/locale-date';

View File

@@ -1,4 +1,4 @@
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { PasswordForm } from '~/components/forms/password';

View File

@@ -1,4 +1,4 @@
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { ProfileForm } from '~/components/forms/profile';

View File

@@ -3,17 +3,16 @@ import { NextResponse } from 'next/server';
import { P, match } from 'ts-pattern';
import { getRecipientOrSenderByShareLinkSlug } from '@documenso/lib/server-only/share/get-recipient-or-sender-by-share-link-slug';
import type { ShareHandlerAPIResponse } from '~/pages/api/share';
import { Logo } from '~/components/branding/logo';
import { getAssetBuffer } from '~/helpers/get-asset-buffer';
export const runtime = 'edge';
const CARD_OFFSET_TOP = 152;
const CARD_OFFSET_LEFT = 350;
const CARD_WIDTH = 500;
const CARD_HEIGHT = 250;
const CARD_OFFSET_TOP = 173;
const CARD_OFFSET_LEFT = 307;
const CARD_WIDTH = 590;
const CARD_HEIGHT = 337;
const size = {
const IMAGE_SIZE = {
width: 1200,
height: 630,
};
@@ -24,15 +23,27 @@ type SharePageOpenGraphImageProps = {
export async function GET(_request: Request, { params: { slug } }: SharePageOpenGraphImageProps) {
const [interSemiBold, interRegular, caveatRegular, shareFrameImage] = await Promise.all([
getAssetBuffer('/fonts/inter-semibold.ttf'),
getAssetBuffer('/fonts/inter-regular.ttf'),
getAssetBuffer('/fonts/caveat-regular.ttf'),
getAssetBuffer('/static/og-share-frame.png'),
fetch(new URL('@documenso/assets/fonts/inter-semibold.ttf', import.meta.url)).then(
async (res) => res.arrayBuffer(),
),
fetch(new URL('@documenso/assets/fonts/inter-regular.ttf', import.meta.url)).then(async (res) =>
res.arrayBuffer(),
),
fetch(new URL('@documenso/assets/fonts/caveat-regular.ttf', import.meta.url)).then(
async (res) => res.arrayBuffer(),
),
fetch(new URL('@documenso/assets/static/og-share-frame2.png', import.meta.url)).then(
async (res) => res.arrayBuffer(),
),
]);
const recipientOrSender = await getRecipientOrSenderByShareLinkSlug({ slug }).catch(() => null);
const baseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL || 'http://localhost:3000';
if (!recipientOrSender) {
const recipientOrSender: ShareHandlerAPIResponse = await fetch(
new URL(`/api/share?slug=${slug}`, baseUrl),
).then(async (res) => res.json());
if ('error' in recipientOrSender) {
return NextResponse.json({ error: 'Not found' }, { status: 404 });
}
@@ -60,11 +71,6 @@ export async function GET(_request: Request, { params: { slug } }: SharePageOpen
{/* @ts-expect-error Lack of typing from ImageResponse */}
<img src={shareFrameImage} alt="og-share-frame" tw="absolute inset-0 w-full h-full" />
<div tw="absolute top-20 flex w-full items-center justify-center">
{/* @ts-expect-error Lack of typing from ImageResponse */}
<Logo tw="h-8 w-60" />
</div>
{signatureImage ? (
<div
tw="absolute py-6 px-12 flex items-center justify-center text-center"
@@ -96,40 +102,28 @@ export async function GET(_request: Request, { params: { slug } }: SharePageOpen
</p>
)}
{/* <div
tw="absolute flex items-center justify-center text-slate-500"
style={{
top: `${CARD_OFFSET_TOP + CARD_HEIGHT - 45}px`,
left: `${CARD_OFFSET_LEFT}`,
width: `${CARD_WIDTH}px`,
fontSize: '30px',
}}
>
{signatureName}
</div> */}
<div
tw="absolute flex flex-col items-center justify-center pt-12 w-full"
tw="absolute flex w-full"
style={{
top: `${CARD_OFFSET_TOP + CARD_HEIGHT}px`,
top: `${CARD_OFFSET_TOP - 78}px`,
left: `${CARD_OFFSET_LEFT}px`,
}}
>
<h2
tw="text-3xl text-slate-500"
tw="text-xl"
style={{
color: '#828282',
fontFamily: 'Inter',
fontWeight: 600,
fontWeight: 700,
}}
>
{isRecipient
? 'I just signed with Documenso and you can too!'
: 'I just sent a document with Documenso and you can too!'}
{isRecipient ? 'Document Signed!' : 'Document Sent!'}
</h2>
</div>
</div>
),
{
...size,
...IMAGE_SIZE,
fonts: [
{
name: 'Caveat',

View File

@@ -11,7 +11,7 @@ type SharePageProps = {
export function generateMetadata({ params: { slug } }: SharePageProps) {
return {
title: 'Documenso - Share',
description: 'I just signed a document with Documenso!',
description: 'I just signed a document in style with Documenso!',
openGraph: {
title: 'Documenso - Join the open source signing revolution',
description: 'I just signed with Documenso!',

View File

@@ -4,6 +4,7 @@ import { notFound } from 'next/navigation';
import { CheckCircle2, Clock8 } from 'lucide-react';
import { match } from 'ts-pattern';
import signingCelebration from '@documenso/assets/images/signing-celebration.png';
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-for-token';
import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token';
@@ -13,8 +14,6 @@ import { DocumentDownloadButton } from '@documenso/ui/components/document/docume
import { DocumentShareButton } from '@documenso/ui/components/document/document-share-button';
import { SigningCard3D } from '@documenso/ui/components/signing-card';
import signingCelebration from '~/assets/signing-celebration.png';
export type CompletedSigningPageProps = {
params: {
token?: string;

View File

@@ -19,6 +19,7 @@ import { Label } from '@documenso/ui/primitives/label';
import { SignaturePad } from '@documenso/ui/primitives/signature-pad';
import { useRequiredSigningContext } from './provider';
import { SignDialog } from './sign-dialog';
export type SigningFormProps = {
document: Document;
@@ -31,7 +32,6 @@ export const SigningForm = ({ document, recipient, fields }: SigningFormProps) =
const { data: session } = useSession();
const { fullName, signature, setFullName, setSignature } = useRequiredSigningContext();
const [validateUninsertedFields, setValidateUninsertedFields] = useState(false);
const {
@@ -45,6 +45,7 @@ export const SigningForm = ({ document, recipient, fields }: SigningFormProps) =
const onFormSubmit = async () => {
setValidateUninsertedFields(true);
const isFieldsValid = validateFieldsInserted(fields);
if (!isFieldsValid) {
@@ -132,9 +133,12 @@ export const SigningForm = ({ document, recipient, fields }: SigningFormProps) =
Cancel
</Button>
<Button className="w-full" type="submit" size="lg" loading={isSubmitting}>
Complete
</Button>
<SignDialog
isSubmitting={isSubmitting}
onSignatureComplete={handleSubmit(onFormSubmit)}
document={document}
fields={fields}
/>
</div>
</div>
</div>

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { Header as AuthenticatedHeader } from '~/components/(dashboard)/layout/header';
import { NextAuthProvider } from '~/providers/next-auth';

View File

@@ -3,7 +3,7 @@ import { notFound, redirect } from 'next/navigation';
import { match } from 'ts-pattern';
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
import { viewedDocument } from '@documenso/lib/server-only/document/viewed-document';
import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-for-token';

View File

@@ -0,0 +1,77 @@
import { useState } from 'react';
import { Document, Field } from '@documenso/prisma/client';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
DialogContent,
DialogFooter,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
export type SignDialogProps = {
isSubmitting: boolean;
document: Document;
fields: Field[];
onSignatureComplete: () => void | Promise<void>;
};
export const SignDialog = ({
isSubmitting,
document,
fields,
onSignatureComplete,
}: SignDialogProps) => {
const [showDialog, setShowDialog] = useState(false);
const isComplete = fields.every((field) => field.inserted);
return (
<Dialog open={showDialog} onOpenChange={setShowDialog}>
<DialogTrigger asChild>
<Button
className="w-full"
type="button"
size="lg"
disabled={!isComplete}
loading={isSubmitting}
>
Complete
</Button>
</DialogTrigger>
<DialogContent>
<div className="text-center">
<div className="text-xl font-semibold text-neutral-800">Sign Document</div>
<div className="text-muted-foreground mx-auto w-4/5 py-2 text-center">
You are about to finish signing "{document.title}". Are you sure?
</div>
</div>
<DialogFooter>
<div className="flex w-full flex-1 flex-nowrap gap-4">
<Button
type="button"
className="dark:bg-muted dark:hover:bg-muted/80 flex-1 bg-black/5 hover:bg-black/10"
variant="secondary"
onClick={() => {
setShowDialog(false);
}}
>
Cancel
</Button>
<Button
type="button"
className="flex-1"
disabled={!isComplete}
loading={isSubmitting}
onClick={onSignatureComplete}
>
Sign
</Button>
</div>
</DialogFooter>
</DialogContent>
</Dialog>
);
};

View File

@@ -2,7 +2,7 @@ import React from 'react';
import Image from 'next/image';
import backgroundPattern from '~/assets/background-pattern.png';
import backgroundPattern from '@documenso/assets/images/background-pattern.png';
type UnauthenticatedLayoutProps = {
children: React.ReactNode;

View File

@@ -1,6 +1,6 @@
import Link from 'next/link';
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { Button } from '@documenso/ui/primitives/button';
import NotFoundPartial from '~/components/partials/not-found';

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 743 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 MiB

View File

@@ -1,8 +1,8 @@
'use server';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { setFieldsForDocument } from '@documenso/lib/server-only/field/set-fields-for-document';
import { TAddFieldsFormSchema } from '@documenso/ui/primitives/document-flow/add-fields.types';
import type { TAddFieldsFormSchema } from '@documenso/ui/primitives/document-flow/add-fields.types';
export type AddFieldsActionInput = TAddFieldsFormSchema & {
documentId: number;

View File

@@ -1,8 +1,8 @@
'use server';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { setRecipientsForDocument } from '@documenso/lib/server-only/recipient/set-recipients-for-document';
import { TAddSignersFormSchema } from '@documenso/ui/primitives/document-flow/add-signers.types';
import type { TAddSignersFormSchema } from '@documenso/ui/primitives/document-flow/add-signers.types';
export type AddSignersActionInput = TAddSignersFormSchema & {
documentId: number;

View File

@@ -1,9 +1,9 @@
'use server';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { upsertDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta';
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
import { TAddSubjectFormSchema } from '@documenso/ui/primitives/document-flow/add-subject.types';
import type { TAddSubjectFormSchema } from '@documenso/ui/primitives/document-flow/add-subject.types';
export type CompleteDocumentActionInput = TAddSubjectFormSchema & {
documentId: number;

View File

@@ -6,11 +6,10 @@ import { useRouter } from 'next/navigation';
import { motion } from 'framer-motion';
import { ChevronLeft } from 'lucide-react';
import backgroundPattern from '@documenso/assets/images/background-pattern.png';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import backgroundPattern from '~/assets/background-pattern.png';
export type NotFoundPartialProps = {
children?: React.ReactNode;
};

View File

@@ -0,0 +1,23 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { getRecipientOrSenderByShareLinkSlug } from '@documenso/lib/server-only/share/get-recipient-or-sender-by-share-link-slug';
export type ShareHandlerAPIResponse =
| Awaited<ReturnType<typeof getRecipientOrSenderByShareLinkSlug>>
| { error: string };
export default async function shareHandler(req: NextApiRequest, res: NextApiResponse) {
try {
if (typeof req.query.slug !== 'string') {
throw new Error('Invalid slug');
}
const data = await getRecipientOrSenderByShareLinkSlug({
slug: req.query.slug,
});
return res.json(data);
} catch (error) {
return res.status(404).json({ error: 'Not found' });
}
}

View File

@@ -1,53 +1,86 @@
###########################
# BASE CONTAINER #
###########################
FROM node:18-alpine AS base
# Install dependencies only when needed
FROM base AS production_deps
WORKDIR /app
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
# Copy our current monorepo
COPY . .
RUN npm ci --production
# Install dependencies only when needed
###########################
# BUILDER CONTAINER #
###########################
FROM base AS builder
WORKDIR /app
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
RUN apk add --no-cache jq
WORKDIR /app
# Copy our current monorepo
COPY . .
RUN TURBO_VERSION="$(npm list --package-lock-only --json turbo | jq -r '.dependencies.turbo.version')"
RUN npm install -g "turbo@$TURBO_VERSION"
# Outputs to the /out folder
# source: https://turbo.build/repo/docs/reference/command-line-reference/prune#--docker
RUN turbo prune --scope=@documenso/web --docker
###########################
# INSTALLER CONTAINER #
###########################
FROM base AS installer
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
RUN apk add --no-cache jq
# Required for node_modules/aws-crt
RUN apk add --no-cache make cmake g++
WORKDIR /app
# Disable husky from installing hooks
ENV HUSKY 0
ENV DOCKER_OUTPUT 1
ENV NEXT_TELEMETRY_DISABLED 1
# Uncomment and use build args to enable remote caching
# ARG TURBO_TEAM
# ENV TURBO_TEAM=$TURBO_TEAM
# ARG TURBO_TOKEN
# ENV TURBO_TOKEN=$TURBO_TOKEN
# First install the dependencies (as they change less often)
COPY .gitignore .gitignore
COPY --from=builder /app/out/json/ .
COPY --from=builder /app/out/package-lock.json ./package-lock.json
RUN npm ci
RUN npm run build --workspaces
# Then copy all the source code (as it changes more often)
COPY --from=builder /app/out/full/ .
# Finally copy the turbo.json file so that we can run turbo commands
COPY turbo.json turbo.json
# Production image, copy all the files and run next
RUN TURBO_VERSION="$(npm list --package-lock-only --json turbo | jq -r '.dependencies.turbo.version')"
RUN npm install -g "turbo@$TURBO_VERSION"
RUN turbo run build --filter=@documenso/web...
###########################
# RUNNER CONTAINER #
###########################
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
# Don't run production as root
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
USER nextjs
COPY --from=production_deps --chown=nextjs:nodejs /app/node_modules ./node_modules
COPY --from=production_deps --chown=nextjs:nodejs /app/package-lock.json ./package-lock.json
COPY --from=installer /app/apps/web/next.config.js .
COPY --from=installer /app/apps/web/package.json .
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/package.json ./package.json
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next ./.next
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/standalone ./
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public
EXPOSE 3000
ENV PORT 3000
CMD ["npm", "run", "start"]
CMD node apps/web/server.js

View File

@@ -7,6 +7,6 @@ cd "$MONOREPO_ROOT"
npm ci
npm run db-migrate:dev
npm run prisma:migrate-dev
npm run dev

32
docker/compose-test.yml Normal file
View File

@@ -0,0 +1,32 @@
name: documenso_test
services:
database:
image: postgres:15
environment:
- POSTGRES_USER=documenso
- POSTGRES_PASSWORD=password
- POSTGRES_DB=documenso
ports:
- 54322:5432
inbucket:
image: inbucket/inbucket
# ports:
# - 9000:9000
# - 2500:2500
# - 1100:1100
documenso:
build:
context: ../
dockerfile: docker/Dockerfile
depends_on:
- database
- inbucket
env_file:
- ../.env.example
environment:
- NEXT_PRIVATE_DATABASE_URL=postgres://documenso:password@database:5432/documenso
- NEXT_PRIVATE_DIRECT_DATABASE_URL=postgres://documenso:password@database:5432/documenso
ports:
- 3000:3000

287
package-lock.json generated
View File

@@ -1685,6 +1685,10 @@
"resolved": "packages/app-tests",
"link": true
},
"node_modules/@documenso/assets": {
"resolved": "packages/assets",
"link": true
},
"node_modules/@documenso/ee": {
"resolved": "packages/ee",
"link": true
@@ -6306,9 +6310,9 @@
}
},
"node_modules/@types/json-schema": {
"version": "7.0.13",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz",
"integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ=="
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="
},
"node_modules/@types/json5": {
"version": "0.0.29",
@@ -6413,9 +6417,9 @@
"integrity": "sha512-2L9ifAGl7wmXwP4v3pN4p2FLhD0O1qsJpvKmNin5VA8+UvNVb447UDaAEV6UdrkA+m/Xs58U1RFps44x6TFsVQ=="
},
"node_modules/@types/semver": {
"version": "7.5.3",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz",
"integrity": "sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw=="
"version": "7.5.5",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.5.tgz",
"integrity": "sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg=="
},
"node_modules/@types/unist": {
"version": "2.0.8",
@@ -6474,6 +6478,86 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "6.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.8.0.tgz",
"integrity": "sha512-RYOJdlkTJIXW7GSldUIHqc/Hkto8E+fZN96dMIFhuTJcQwdRoGN2rEWA8U6oXbLo0qufH7NPElUb+MceHtz54g==",
"dependencies": {
"@typescript-eslint/typescript-estree": "6.8.0",
"@typescript-eslint/utils": "6.8.0",
"debug": "^4.3.4",
"ts-api-utils": "^1.0.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^7.0.0 || ^8.0.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": {
"version": "6.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.8.0.tgz",
"integrity": "sha512-p5qOxSum7W3k+llc7owEStXlGmSl8FcGvhYt8Vjy7FqEnmkCVlM3P57XQEGj58oqaBWDQXbJDZxwUWMS/EAPNQ==",
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": {
"version": "6.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.8.0.tgz",
"integrity": "sha512-ISgV0lQ8XgW+mvv5My/+iTUdRmGspducmQcDw5JxznasXNnZn3SKNrTRuMsEXv+V/O+Lw9AGcQCfVaOPCAk/Zg==",
"dependencies": {
"@typescript-eslint/types": "6.8.0",
"@typescript-eslint/visitor-keys": "6.8.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"semver": "^7.5.4",
"ts-api-utils": "^1.0.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": {
"version": "6.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.8.0.tgz",
"integrity": "sha512-oqAnbA7c+pgOhW2OhGvxm0t1BULX5peQI/rLsNDpGM78EebV3C9IGbX5HNZabuZ6UQrYveCLjKo8Iy/lLlBkkg==",
"dependencies": {
"@typescript-eslint/types": "6.8.0",
"eslint-visitor-keys": "^3.4.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/types": {
"version": "5.62.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz",
@@ -6514,6 +6598,100 @@
}
}
},
"node_modules/@typescript-eslint/utils": {
"version": "6.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.8.0.tgz",
"integrity": "sha512-dKs1itdE2qFG4jr0dlYLQVppqTE+Itt7GmIf/vX6CSvsW+3ov8PbWauVKyyfNngokhIO9sKZeRGCUo1+N7U98Q==",
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0",
"@typescript-eslint/scope-manager": "6.8.0",
"@typescript-eslint/types": "6.8.0",
"@typescript-eslint/typescript-estree": "6.8.0",
"semver": "^7.5.4"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^7.0.0 || ^8.0.0"
}
},
"node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": {
"version": "6.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.8.0.tgz",
"integrity": "sha512-xe0HNBVwCph7rak+ZHcFD6A+q50SMsFwcmfdjs9Kz4qDh5hWhaPhFjRs/SODEhroBI5Ruyvyz9LfwUJ624O40g==",
"dependencies": {
"@typescript-eslint/types": "6.8.0",
"@typescript-eslint/visitor-keys": "6.8.0"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": {
"version": "6.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.8.0.tgz",
"integrity": "sha512-p5qOxSum7W3k+llc7owEStXlGmSl8FcGvhYt8Vjy7FqEnmkCVlM3P57XQEGj58oqaBWDQXbJDZxwUWMS/EAPNQ==",
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": {
"version": "6.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.8.0.tgz",
"integrity": "sha512-ISgV0lQ8XgW+mvv5My/+iTUdRmGspducmQcDw5JxznasXNnZn3SKNrTRuMsEXv+V/O+Lw9AGcQCfVaOPCAk/Zg==",
"dependencies": {
"@typescript-eslint/types": "6.8.0",
"@typescript-eslint/visitor-keys": "6.8.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"semver": "^7.5.4",
"ts-api-utils": "^1.0.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": {
"version": "6.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.8.0.tgz",
"integrity": "sha512-oqAnbA7c+pgOhW2OhGvxm0t1BULX5peQI/rLsNDpGM78EebV3C9IGbX5HNZabuZ6UQrYveCLjKo8Iy/lLlBkkg==",
"dependencies": {
"@typescript-eslint/types": "6.8.0",
"eslint-visitor-keys": "^3.4.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "5.62.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz",
@@ -9614,6 +9792,14 @@
"node": ">=12"
}
},
"node_modules/eslint-rule-composer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz",
"integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/eslint-scope": {
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
@@ -19351,6 +19537,10 @@
"undici-types": "~5.25.1"
}
},
"packages/assets": {
"name": "@documenso/assets",
"version": "0.1.0"
},
"packages/ee": {
"name": "@documenso/ee",
"version": "1.0.0",
@@ -19399,6 +19589,7 @@
"eslint-plugin-package-json": "^0.1.4",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-unused-imports": "^3.0.0",
"typescript": "5.2.2"
}
},
@@ -19501,32 +19692,6 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
"packages/eslint-config/node_modules/@typescript-eslint/type-utils": {
"version": "6.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.8.0.tgz",
"integrity": "sha512-RYOJdlkTJIXW7GSldUIHqc/Hkto8E+fZN96dMIFhuTJcQwdRoGN2rEWA8U6oXbLo0qufH7NPElUb+MceHtz54g==",
"dependencies": {
"@typescript-eslint/typescript-estree": "6.8.0",
"@typescript-eslint/utils": "6.8.0",
"debug": "^4.3.4",
"ts-api-utils": "^1.0.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^7.0.0 || ^8.0.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"packages/eslint-config/node_modules/@typescript-eslint/types": {
"version": "6.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.8.0.tgz",
@@ -19579,44 +19744,6 @@
"node": ">=10"
}
},
"packages/eslint-config/node_modules/@typescript-eslint/utils": {
"version": "6.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.8.0.tgz",
"integrity": "sha512-dKs1itdE2qFG4jr0dlYLQVppqTE+Itt7GmIf/vX6CSvsW+3ov8PbWauVKyyfNngokhIO9sKZeRGCUo1+N7U98Q==",
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0",
"@typescript-eslint/scope-manager": "6.8.0",
"@typescript-eslint/types": "6.8.0",
"@typescript-eslint/typescript-estree": "6.8.0",
"semver": "^7.5.4"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^7.0.0 || ^8.0.0"
}
},
"packages/eslint-config/node_modules/@typescript-eslint/utils/node_modules/semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"packages/eslint-config/node_modules/@typescript-eslint/visitor-keys": {
"version": "6.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.8.0.tgz",
@@ -19722,6 +19849,26 @@
"eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8"
}
},
"packages/eslint-config/node_modules/eslint-plugin-unused-imports": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-3.0.0.tgz",
"integrity": "sha512-sduiswLJfZHeeBJ+MQaG+xYzSWdRXoSw61DpU13mzWumCkR0ufD0HmO4kdNokjrkluMHpj/7PJeN35pgbhW3kw==",
"dependencies": {
"eslint-rule-composer": "^0.3.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"peerDependencies": {
"@typescript-eslint/eslint-plugin": "^6.0.0",
"eslint": "^8.0.0"
},
"peerDependenciesMeta": {
"@typescript-eslint/eslint-plugin": {
"optional": true
}
}
},
"packages/eslint-config/node_modules/resolve": {
"version": "2.0.0-next.5",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz",

View File

@@ -6,6 +6,7 @@
"dev": "turbo run dev --filter=@documenso/web --filter=@documenso/marketing",
"start": "cd apps && cd web && next start",
"lint": "turbo run lint",
"lint:fix": "turbo run lint:fix",
"format": "prettier --write \"**/*.{js,jsx,cjs,mjs,ts,tsx,cts,mts,mdx}\"",
"prepare": "husky install",
"commitlint": "commitlint --edit",

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
packages/assets/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 896 KiB

After

Width:  |  Height:  |  Size: 896 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 337 KiB

After

Width:  |  Height:  |  Size: 337 KiB

Some files were not shown because too many files have changed in this diff Show More