Compare commits

...

175 Commits

Author SHA1 Message Date
c9fe134852 v1.9.0-rc.1 2024-12-12 10:31:44 +11:00
f2149719e3 fix: resolve issue with embed css injection 2024-12-12 10:14:23 +11:00
161d40cde7 fix: secure passkey cookies (#1533) 2024-12-12 01:16:29 +09:00
76028771b8 fix: add billing leeway (#1532) 2024-12-12 01:10:01 +09:00
5df1a6602e fix: refactor search routes (#1529)
Refactor find endpoints to be consistent in terms of input and output.
2024-12-11 19:39:50 +09:00
3d7b28a92b chore: update tailwind config 2024-12-11 13:52:34 +11:00
ed862413b1 v1.9.0-rc.0 2024-12-11 09:48:01 +11:00
9d02ab4a5e feat: open page api (#1419) 2024-12-10 21:19:05 +11:00
34c0868d77 chore: add openapi description for enterprise field (#1520) 2024-12-10 20:26:28 +11:00
fae9c0ca24 fix: refactor routers (#1523) 2024-12-10 16:11:20 +09:00
dd162205fa fix: prevent accidental signatures (#1515)
![CleanShot 2024-12-06 at 03 30
39](https://github.com/user-attachments/assets/d47dc820-f19d-43b7-a60d-914fc9ab24b8)

![CleanShot 2024-12-06 at 03 32
34](https://github.com/user-attachments/assets/0db98735-8c91-469b-873c-adb19d0fff7b)
2024-12-08 14:17:58 +11:00
a88ae1cc1e chore: extract translations 2024-12-06 16:11:54 +09:00
904948e2bc fix: refactor trpc errors (#1511) 2024-12-06 16:01:24 +09:00
3b6b96f551 chore: remove redundant translations on upload (#1510)
## Description

Clean redundant translations by default.

This should stop the AI from doing strange things to commented out
translations.
2024-12-06 09:04:15 +11:00
67e49c82a3 feat: return fields in GET /documents/:id endpoint (#1317)
To be able to use the PATCH `/api/v1/documents/{id}/fields/{fieldId}`
endpoint, we need to know the fields ID of a particular document. The
issue #1178 suggested to create a new endpoint for this. To be
consistent with the `/templates` endpoint, I propose in this PR to
directly add the `fields` field to the `/documents/:id` endpoint.
2024-12-06 09:03:32 +11:00
9f45fe62e4 fix: refactor teams router (#1500) 2024-12-05 22:14:47 +09:00
9e8094e34c Update README.md (#1509)
🚨 WE ARE LIVE ON PH WITH OUR LATEST LAUNCH 🚀
2024-12-05 13:51:49 +01:00
0e7e9e17c9 v1.8.1 2024-12-05 13:57:05 +11:00
b3ccb3d26f v1.8.1-rc.9 2024-12-05 13:53:35 +11:00
b17370c153 chore: reword some german translations to increase clarity (#1507) 2024-12-05 09:42:10 +11:00
0c53f5b061 v1.8.1-rc.8 2024-12-04 23:29:15 +11:00
ed6157de80 feat: upload signature as img (#1496)
Allow users to upload their signature as an image.

https://github.com/user-attachments/assets/375faad2-f0db-4f44-83d2-d969c5ab4442
2024-12-04 23:22:18 +11:00
5e08d0cffb v1.8.1-rc.7 2024-12-04 22:43:41 +11:00
5565aff7a3 fix: docs content (#1495) 2024-12-04 19:49:44 +09:00
428acf4ac3 fix: dateformat api bug (#1506) 2024-12-04 19:48:44 +09:00
f4b1e5104e feat: add platform plan pricing (#1505)
Add platform plan to the billing page.
2024-12-04 15:42:03 +09:00
a687064a42 v1.8.1-rc.6 2024-12-04 14:58:29 +11:00
8ec69388a5 fix: add document rejection webhook
Adds the document rejection webhook since it was missing.

Additionally, normalises and standardises the webhook body.
2024-12-04 14:35:20 +11:00
f3da11b3e7 fix: e2e tests failing due to same-site cookies 2024-12-04 14:33:21 +11:00
fc84ee8ec2 fix: use default nextauth logic for secure cookies 2024-12-03 21:35:09 +11:00
4282a96ee7 v1.8.1-rc.5 2024-12-03 15:44:10 +11:00
2aae7435f8 fix: auth cookies across iframes (#1501) 2024-12-03 15:28:30 +11:00
bdd33bd335 feat: signing volume (#1358)
adds a signing volume and leaderboard section to the admin panel
2024-12-03 11:27:22 +11:00
9e8d0ac906 v1.8.1-rc.4 2024-12-02 22:07:31 +11:00
f27d0f342c fix: putPdfFile to always include file extension 2024-12-02 22:06:53 +11:00
4326e27a2a v1.8.1-rc.3 2024-12-02 07:48:03 +11:00
62806298cf fix: wrong signing invitation message (#1497) 2024-12-02 07:47:11 +11:00
87186e08b1 v1.8.1-rc.2 2024-11-29 15:09:03 +11:00
b27fd800ed fix: add distribution settings to external api 2024-11-29 14:10:48 +11:00
98d85b086d feat: add initial api logging (#1494)
Improve API logging and error handling between client and server side.
2024-11-28 16:05:37 +07:00
04293968c6 chore: update embedding docs 2024-11-28 15:55:17 +11:00
e6d4005cd1 fix: document title truncation (#1467)
Truncates the document title across various instances where it previously hadn't been truncated.
2024-11-27 10:53:48 +11:00
337bdb3553 v1.8.0-rc.1 2024-11-26 21:26:12 +11:00
ab654a63d8 chore: enable typed signature by default (#1436)
Enable typed signature by default and also add the option to set a typed
signature in the profile page.
2024-11-26 21:03:44 +11:00
dcb7c2436f fix: update prettier and tailwind 2024-11-26 11:47:28 +11:00
fa33f83696 feat: download doc without signing certificate (#1477)
## Description

I added the option of downloading a document without the signing
certificate for teams. They can disable/enable the option in the
preferences tab.

The signing certificate can still be downloaded separately from the
`logs` page.
2024-11-25 15:47:26 +11:00
b15e1d6c47 feat: support whitelabelling in the embedding (#1491)
## Description

Adds support for customising the theme and CSS for the embedding
components which is restricted to platform customers and above.

Additionally adds proper support for the platform plan which will let us
update our stripe products.

<img width="1040" alt="image"
src="https://github.com/user-attachments/assets/f694cd1e-ac93-4dc0-9f78-92fa813f6404">
<img width="1015" alt="image"
src="https://github.com/user-attachments/assets/4209972a-b2bd-40c9-9049-0367382a4de5">
<img width="1065" alt="image"
src="https://github.com/user-attachments/assets/fdbaaaa5-a028-4b1d-a58a-ea6224e21abe">


## Related Issue

N/A

## Changes Made

- Added support for using CSS Vars and CSS within the embedding route
- Added a guard for platform and enterprise plans to activate the custom
css
- Added support for the platform plan

## Testing Performed
Yes
2024-11-25 15:47:00 +11:00
cd5adce7df fix: hardcode delete confirmation text to avoid translation mismatch (#1487) 2024-11-22 14:22:31 +07:00
11e483f1c4 chore: update changelog 2024-11-21 13:10:31 +11:00
2e2bc8382f v1.8.1-rc.0 2024-11-20 23:02:32 +11:00
1f3a9b578b chore: add translations (#1485)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Release Notes

- **New Features**
- Enhanced German, Spanish, French, and Polish translations for various
document management and marketing phrases.

- **Improvements**
- Updated translations for clarity and accuracy across multiple
languages, including user notifications and document actions.
- Added new translation entries to improve user experience and
localization in the Documenso application.

- **Chores**
- Updated revision dates in translation files to reflect recent changes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2024-11-20 23:00:59 +11:00
83e7a3c222 fix: improve field sizing (#1486)
## Description

Allows for smaller field sizing in addition to improving our styling
when displaying labels on smaller fields.

This is the minimum currently supported field size until we perform a
more extensive refactor of our current drag and drop system.

## Related Issue

Reported via support channels

## Changes Made

- Updated our minimum size constraints
- Attempted to add a general autosizing component for text and failed
- Updated styling in a bunch of places to use the css `clamp()` method
for dynamic sizing.
2024-11-20 22:49:30 +11:00
9ef8b1f0c3 feat: automatically sign fields in large documents (#1484)
## Description

Adds a dialog that will display when a certain field threshold is
reached asking the user if they would like to sign non-critical fields
such as name, date, initials, and email with information that is already
available.

This has not been added to direct templates since we would often not
have all the pre-requisite knowledge since users are mostly anonymous.
Additionally, this has not been added to the embedding view since it may
detract from the experience for some.

Will not prompt the user if there is action authentication on the
document.

See the below demo:


https://github.com/user-attachments/assets/71739b5c-1323-4da9-89fd-a1145c9714d5

## Related Issue

#1281 (Older PR relating to the feature)

## Changes Made

- Added a new auto-sign dialog that will automatically trigger once
certain criteria is met.

## Testing Performed

- Tested that the dialog displays when the threshold is met
- Tested that the dialog is hidden when the threshold is not met
- Tested that the messaging during errors is correct
- Tested that the dialog does not display when 2FA or Passkeys are
required
2024-11-20 10:59:09 +11:00
0eff336175 v1.8.0-rc.4 2024-11-19 16:44:25 +11:00
9bdd5c31cc fix: sort recipients for template with signing order (#1468) 2024-11-18 15:54:51 +07:00
57ad7c150b chore: add translations (#1474) 2024-11-18 08:40:25 +11:00
b0829e6cdf v1.8.0-rc.3 2024-11-16 09:23:05 +11:00
08a446fefd feat: support windows for 2fa tokens (#1478)
## Description

When using 2fa enabled authentication on direct templates we run into an
issue where a 2fa token has been attached to a field but it's submitted
at a later point.

To better facilitate this we have introduced the ability to have a
window of valid tokens.

This won't affect other signing methods since tokens are verified
immediately after they're entered.

## Related Issue

N/A

## Changes Made

- Updated our validate2FAToken method to use a window based approach
rather than the default verify method.

## Testing Performed

- Created a series of tokens and tested upon different intervals and
windows to confirm functionality works as expected.
2024-11-16 09:17:45 +11:00
f15f9ecdd1 chore: update docs 2024-11-15 21:47:22 +07:00
979e3f3e71 fix: always allow access to billing (#1476) 2024-11-15 21:34:39 +07:00
876803b5db fix: handle team invites being accepted but not added 2024-11-15 13:27:36 +11:00
1c87cb1e0d v1.8.0-rc.2 2024-11-15 01:56:22 +11:00
5398026b80 feat: signature rejection (#1472)
## Description

Adds support for rejecting a given document informing the document
owner.

Flows for resolving a rejection don't currently exist so it's up to the
document owner to reach out to the recipient and work out a way to move
forward via a new document or offline agreement.

## Related Issue


## Changes Made

- Added new rejection properties to the recipient schema
- Added API endpoints to support rejection
- Added email templates for notifying the document owner and recipient
- Added a dialog on the signing page to start the rejection flow.

## Testing Performed

- Manually tested the flow end to end
- Automated tests are planned
2024-11-14 21:37:42 +11:00
f2439abbc9 chore: update docs 2024-11-12 23:12:13 +07:00
5a6e031c90 chore: add translations (#1463) 2024-11-12 15:50:22 +07:00
bcc3b70335 fix: errors moving fields (#1429) 2024-11-12 15:49:31 +07:00
5a26610a01 fix: update publish workfow to only tag stable versions latest (#1405)
## Description

This pull request introduces modifications to the GitHub Actions
workflow to ensure that the `latest` Docker tag is only pushed for
stable releases. It prevents the `latest` tag from being assigned to
release candidates (`-rc`), beta versions, or other pre-release tags.
This enhancement improves version management by keeping the `latest` tag
reserved exclusively for fully stable versions (e.g., `1.0.0`).

## Related Issue

Fixes #1404

## Changes Made

- Added a conditional check to verify if the release version follows the
format `X.Y.Z` (stable release format).
- Updated the Docker manifest creation steps to only push the `latest`
tag if the version is a stable full release.
- Modified both DockerHub and GitHub Container Registry push steps to
reflect the new versioning conditions.
2024-11-12 17:37:34 +11:00
5d7a979baf chore: add translations (#1461) 2024-11-12 13:00:56 +07:00
552825b79e chore: extract translations 2024-11-12 12:37:34 +07:00
786566bae4 fix: certificate translations (#1460)
## Description

Currently certificate translations on production sometimes does not show
the required language.

This could not be replicated when creating certificates on staging
(Browserless.io) and local development (Chromium), which means this fix
ultimately cannot be tested unless on live.

This is an attempt to fix it by isolating the certificate generation
into it's own context, and applying a cookie to define the required
language.

This fix is based on the assumption that there is some sort of error
which pushes the certificate to be generated on the client side, which
ultimately will render in English due to constraints on nextjs.

## Changes Made

- Apply language into cookie instead purely dynamically on SSR
- Minor unrelated fixes

## Testing Performed

Tested to ensure certificates could still be generated
2024-11-12 15:26:14 +11:00
cb23357b42 fix: document url in the command menu search (#1453) 2024-11-12 00:12:15 +07:00
0078162159 chore: project babel (#1420)
blogpost babel
2024-11-08 16:42:25 +01:00
19e23d8ef3 v1.8.0-rc.0 2024-11-08 23:09:56 +11:00
e3b7ec82a3 chore: add translations (#1451) 2024-11-08 23:06:57 +11:00
23a0537648 feat: add global settings for teams (#1391)
## Description

This PR introduces global settings for teams. At the moment, it allows
team admins to configure the following:
* The default visibility of the documents uploaded to the team account
* Whether to include the document owner (sender) details when sending
emails to the recipients.

### Include Sender Details

If the Sender Details setting is enabled, the emails sent by the team
will include the sender's name:

> "Example User" on behalf of "Example Team" has invited you to sign
"document.pdf"

Otherwise, the email will say:

> "Example Team" has invited you to sign "document.pdf"

### Default Document Visibility

This new option allows users to set the default visibility for the
documents uploaded to the team account. It can have the following
values:
* Everyone
* Manager and above
* Admins only

If the default document visibility isn't set, the document will be set
to the role of the user who created the document:
* If a user with the "User" role creates a document, the document's
visibility is set to "Everyone".
* Manager role -> "Manager and above"
* Admin role -> "Admins only"

Otherwise, if there is a default document visibility value, it uses that
value.

#### Gotcha

To avoid issues, the `document owner` and the `recipient` can access the
document irrespective of their role. For example:
* If a team member with the role "Member" uploads a document and the
default document visibility is "Admins", only the document owner and
admins can access the document.
  * Similar to the other scenarios.

* If an admin uploads a document and the default document visibility is
"Admins", the recipient can access the document.

* The admins have access to all the documents.
* Managers have access to documents with the visibility set to
"Everyone" and "Manager and above"
* Members have access only to the documents with the visibility set to
"Everyone".

## Testing Performed

Tested it locally.
2024-11-08 22:50:49 +11:00
f6bcf921d5 feat: add document distribution setting (#1437)
Add a document distribution setting which will allow us to further
configure how recipients currently receive documents.
2024-11-08 13:32:13 +09:00
451723a8ab chore: extract translations 2024-11-08 00:34:25 +09:00
9b769e7e33 fix: email translations (#1454) 2024-11-08 00:33:48 +09:00
61ea4971ad fix: custom team email subject (#1450)
Fixed issue where custom email subjects for teams were being ignored.
2024-11-06 22:16:31 +09:00
ffc61af904 chore: extract translations 2024-11-06 22:03:51 +09:00
efbe94aea8 feat: add signing link copy (#1449) 2024-11-06 21:34:06 +09:00
1b10c55758 fix: update docker environment (#1438) 2024-11-06 19:21:52 +09:00
3da4603a47 fix: content pages breaking during ssr on vercel 2024-11-06 21:02:35 +11:00
dcc2ac8a71 feat(email): support configurable SMTP service (#1447) 2024-11-06 19:00:59 +09:00
5158584955 fix: checkout loading button (#1445) 2024-11-05 20:10:10 +09:00
54c0c6be14 fix: open page 2024-11-05 19:36:36 +09:00
927a24249c chore: add translations (#1444) 2024-11-05 18:53:33 +09:00
a50c758b07 chore: extract translations 2024-11-05 18:26:09 +09:00
cc249357b3 feat: add certificate translations (#1440)
Add translations for audit logs and certificates.
2024-11-05 18:25:23 +09:00
011dabcc04 chore: extract translations 2024-11-05 17:37:05 +09:00
4fa6dc1e21 feat: add template page (#1395)
Add a template page view to allow users to see more details about a
template at a glance.
2024-11-05 17:36:30 +09:00
32b65c4d49 fix: blog posts breaking during ssr on vercel 2024-11-05 18:34:27 +11:00
de880aa821 v1.7.2-rc.4 2024-11-05 13:50:01 +11:00
dc5723c386 chore: add i18n lang to document deleted email 2024-11-05 13:44:00 +11:00
c57d1dc55d chore: add translations (#1443)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2024-11-05 13:07:55 +11:00
4dd95016b1 feat: i18n for emails (#1442)
## Description

Support setting a document language that will control the language used
for sending emails to recipients. Additional work has been done to
convert all emails to using our i18n implementation so we can later add
controls for sending other kinds of emails in a users target language.

## Related Issue

N/A

## Changes Made

- Added `<Trans>` and `msg` macros to emails
- Introduced a new `renderEmailWithI18N` utility in the lib package
- Updated all emails to use the `<Tailwind>` component at the top level
due to rendering constraints
- Updated the `i18n.server.tsx` file to not use a top level await

## Testing Performed

- Configured document language and verified emails were sent in the
expected language
- Created a document from a template and verified that the templates
language was transferred to the document
2024-11-05 11:52:54 +11:00
04b1ce1aab fix: missing not found page for deleted documents (#1424) 2024-11-04 22:09:52 +09:00
885349ad94 fix: missing signing order when using templates (#1425) 2024-11-03 20:17:41 +09:00
28514ba2e7 fix: duplicate templates (#1434) 2024-11-01 21:29:38 +11:00
8aa6d8e602 chore: add translations (#1433) 2024-11-01 13:22:51 +09:00
378e515843 chore: extract translations 2024-11-01 12:56:07 +09:00
f42e600e3f chore: update workflow 2024-11-01 12:37:54 +09:00
88eaec91c9 chore: extract translations 2024-11-01 11:27:09 +09:00
f199183c78 feat: improve translation coverage (#1427)
Improves translation coverage across the app.
2024-11-01 10:57:32 +11:00
0cee07aed3 v1.7.2-rc.3 2024-10-31 15:33:03 +11:00
f76f87ff1c fix: use key for expansion on embeds 2024-10-31 15:31:40 +11:00
6020336792 v1.7.2-rc.2 2024-10-30 14:37:50 +11:00
634b30aa54 fix: signature flickering during embed 2024-10-30 14:36:35 +11:00
7fc497a642 fix: translation upload token (#1423) 2024-10-29 19:55:49 +09:00
e30ceeb038 style: update common.po (#1402)
Update translations
2024-10-28 11:26:12 +09:00
872762661a style: Update web.po (#1403)
Update translations
2024-10-28 11:23:08 +09:00
5fcd8610c9 fix: translate extract command (#1394)
Change how the translate extract command is run on build
2024-10-28 11:21:49 +09:00
b8310237e4 v1.7.2-rc.1 2024-10-23 13:28:54 +11:00
4a6238dc52 chore: show signing order when creating a document from template (#1410)
### Templates without signing order
![CleanShot 2024-10-18 at 01 27
24@2x](https://github.com/user-attachments/assets/222181e4-25a8-409b-aa8b-8452ddd32f6b)


### Template with signing order
![CleanShot 2024-10-18 at 01 26
12@2x](https://github.com/user-attachments/assets/bcee6213-20b5-44d8-90ed-881825f49756)
2024-10-23 10:20:27 +11:00
6fa5f63b69 fix: trigger webhook when a direct link signer signs a document (#1409) 2024-10-23 09:50:23 +11:00
c7564ba8f7 chore: enable spanish (#1417)
Enables the spanish translation of the application:


![image](https://github.com/user-attachments/assets/eacf4800-272e-4458-abcc-4d6c6f3071cf)
2024-10-23 09:01:14 +11:00
eafd7c551b feat: add team user management endpoints to api (#1416)
## Description

Adds user management capabilities to our current API. Allows for adding,
removing, listing and updating members of a given team using a valid API
token.

## Related Issue

N/A

## Changes Made

- Added an endpoint for inviting a team member
- Added an endpoint for removing a team member
- Added an endpoint for updating a team member
- Added an endpoint for listing team members

## Testing Performed

Tests were written for this feature request
2024-10-22 22:53:31 +11:00
514edf01d3 chore: add translations (#1406) 2024-10-22 14:02:51 +11:00
1a73c68d07 fix: close direct link dialog when you click on save (#1412) 2024-10-21 15:33:21 +11:00
1a9dcadba5 feat: add typed signature (#1357)
Add the ability to insert typed signatures.

Once the signature field is placed on the document, a checkbox appears
in the document editor where the document owner can allow signers to add
typed signatures. Typed signatures are disabled by default.

![CleanShot 2024-09-30 at 14 57
54](https://github.com/user-attachments/assets/c388abb5-bcb1-49d0-aad8-9148c3020420)
2024-10-18 14:25:19 +11:00
e0c948c2ac feat: add custom font sizes to fields (#1376)
Adds custom font sizes to fields

https://github.com/user-attachments/assets/1473a4d7-8dc6-4ead-acf5-dd78be7782a0
2024-10-16 16:05:41 +11:00
0bd2760792 feat: start the work on the API reference (#1392)
`later`

## Description

<!--- Describe the changes introduced by this pull request. -->
<!--- Explain what problem it solves or what feature/fix it adds. -->

## Related Issue

<!--- If this pull request is related to a specific issue, reference it
here using #issue_number. -->
<!--- For example, "Fixes #123" or "Addresses #456". -->

## Changes Made

<!--- Provide a summary of the changes made in this pull request. -->
<!--- Include any relevant technical details or architecture changes.
-->

- Change 1
- Change 2
- ...

## Testing Performed

<!--- Describe the testing that you have performed to validate these
changes. -->
<!--- Include information about test cases, testing environments, and
results. -->

- Tested feature X in scenario Y.
- Ran unit tests for component Z.
- Tested on browsers A, B, and C.
- ...

## Checklist

<!--- Please check the boxes that apply to this pull request. -->
<!--- You can add or remove items as needed. -->

- [ ] I have tested these changes locally and they work as expected.
- [ ] I have added/updated tests that prove the effectiveness of these
changes.
- [ ] I have updated the documentation to reflect these changes, if
applicable.
- [ ] I have followed the project's coding style guidelines.
- [ ] I have addressed the code review feedback from the previous
submission, if applicable.

## Additional Notes

<!--- Provide any additional context or notes for the reviewers. -->
<!--- This might include details about design decisions, potential
concerns, or anything else relevant. -->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
	- Introduced comprehensive documentation for the Documenso public API.
	- Added detailed guides for the following API endpoints:
		- Uploading documents
		- Generating documents from templates
		- Adding fields to documents
- Included example payloads and response structures for better
understanding.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-10-15 14:05:39 +03:00
abc559d923 chore: cal.com customer story article (#1396)
We got cal.com 🚀

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Introduced a new blog post detailing Cal.com's use of Documenso for
enhancing DPA and BAA processes.
- The blog outlines the challenges and solutions related to compliance
document management.
- Features images and captions to illustrate key points and improve
engagement.

- **Documentation**
- Added insights into Cal.com's journey as an open-source scheduling
solution and their commitment to transparency.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-10-11 16:01:57 +02:00
9ffdbe9c81 fix: improve lingui configuration (#1388)
## Description

Currently merge conflicts arise due to the compiled JS and PO
translation files.

This PR is a rework on how we handle extracting and compiling
translations to streamline PRs and merging branches.

## Changes Made

- Remove compiled translation files from being committed
- Extract and compile translations only on build
- Extract will still occur when commits land on main to sync and pull
new translations with Crowdin
2024-10-09 14:13:52 +11:00
2c1a18bafc fix: stacked avatar colors (#1361) 2024-10-09 12:25:56 +11:00
a2db5e9642 chore: update changelog 2024-10-09 12:23:38 +11:00
4ec9dc78c1 chore: add translations (#1359) 2024-10-09 10:55:21 +11:00
faf2bd5384 v1.7.2-rc.0 2024-10-08 21:56:44 +11:00
d40ed94b74 feat: highlight problematic fields (#1330) 2024-10-08 21:55:20 +11:00
cd3d9b701b fix: external id null for documents created from templates (#1362) 2024-10-08 21:45:16 +11:00
e40f47a73c feat: search documents by name or recipient name or recipient email (#1384) 2024-10-08 21:44:02 +11:00
64ea4a6f9f chore: add translation contribution docs (#1379) 2024-10-08 14:05:55 +11:00
18115e95d7 feat: add recipient email in activity log (#1386) 2024-10-08 14:05:12 +11:00
e736261056 fix: show the full count of documents (#1382)
![doc-count](https://github.com/user-attachments/assets/aad4fe92-e2d8-4b78-ac93-5f6ada73b03a)

A client requested it, and it makes sense showing the full count.

This is how it was before.

![CleanShot 2024-10-04 at 08 47
16@2x](https://github.com/user-attachments/assets/bd4c97a5-1805-4faa-bae7-feeb932ed614)


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Updated document status tab counts to display actual numbers without
capping at 99 or using '+' symbols.

- **Bug Fixes**
- Improved clarity and accuracy of document status counts in the user
interface.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-10-04 16:07:20 +10:00
2e57da7549 chore: open page data update (#1380) 2024-10-04 13:14:54 +10:00
574454db0a chore: Go Fork Yourself blog article (#1375)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced a new blog post titled "Go Fork Yourself," discussing the
philosophy of open-source software and the significance of forking
within the OSS community, along with real-world examples and an
invitation for reader engagement.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-10-03 14:25:32 +02:00
f05b670d93 fix: carousel slide change handling and video reset (#1364) 2024-10-03 10:52:43 +10:00
318149fbf3 chore: field fonts (#1350)
Before
![CleanShot 2024-09-16 at 12 25
44](https://github.com/user-attachments/assets/9ca7672d-b132-4c24-80b0-03fa13822e50)


After
![CleanShot 2024-09-16 at 12 24
07](https://github.com/user-attachments/assets/9e17b025-8064-4151-a9e2-817108b8da2a)


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Introduced a new font style for signature fields, enhancing visual
distinction.
	- Increased text size for signature fields to improve prominence.

- **Bug Fixes**
- Adjusted the text size for signature display on larger screens for
better visual hierarchy.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-10-02 17:45:16 +10:00
5f19dcf25c fix: dateformat bug (#1372)
## Description

It used the wrong property for finding the document's dateFormat in the
`DATE_FORMATS` array.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Improved date format selection in the settings form to ensure accurate
formatting based on value.
- Default timezone now automatically set to the user's local timezone
for better user experience.

- **Bug Fixes**
- Corrected initialization of the timezone field to enhance form
accuracy.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-10-02 17:42:15 +10:00
c99cf4b848 chore: prisma customer story on blog (#1366)
prisma customer story on blog

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Introduced a customer story blog post detailing why Prisma chose
Documenso for their signing needs, highlighting four key reasons.
- Added author information and metadata for enhanced content engagement.
- Included links to additional resources and social media for reader
interaction.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-09-27 18:35:50 +02:00
18ec40f6af fix: set lang cookie expiry (#1365)
## Description

Currently the language cookie is set to session, so restarting browser
will reset it.

This sets the expiry for two years.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Enhanced language preference functionality with extended cookie
lifespan for improved user experience.
  
- **Bug Fixes**
	- Resolved issues related to cookie expiration for language settings.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-09-25 13:10:24 +10:00
ddee8a8272 feat: allow editing pending documents (#1346)
## Description

Adds the ability for the document owner to edit recipients and their
fields after the document has been sent.

A recipient can only be updated or deleted if:
- The recipient has not inserted any fields
- Has not completed the document

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Release Notes

- **New Features**
- Added new localization messages to clarify user actions regarding
document signing.
  - Enhanced French translations for improved user interaction.

- **Improvements**
- Updated localization strings in German and English for clearer
feedback on signer and recipient statuses.
- Improved overall structure of localization files for better
maintainability.

- **Dependency Updates**
- Upgraded `next-axiom` and `remeda` libraries to their latest versions,
potentially enhancing performance and stability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Mythie <me@lucasjamessmith.me>
2024-09-20 13:58:21 +10:00
efb2bc94ab feat: add french (#1355)
## Description

Add initial French translations

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Added support for the French language, enhancing accessibility for
French-speaking users.
- Introduced localized French messages for various application
functionalities, improving user experience.
  
- **Bug Fixes**
- Minor formatting updates in French translation files to remove
extraneous newline characters.

- **Chores**
- Updated line references in French translation files to maintain
alignment with code changes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Lucas Smith <me@lucasjamessmith.me>
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: github-actions <github-actions@documenso.com>
2024-09-19 19:37:17 +10:00
97ee69e7a0 chore: add translations (#1354)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: github-actions <github-actions@documenso.com>
2024-09-19 16:17:16 +10:00
3da344fc5f v1.7.1-rc.3 2024-09-19 13:55:35 +10:00
404ca3202f chore: update action auth 2024-09-19 13:45:39 +10:00
c043fa9c06 fix: add check for invalid locales (#1353)
## Description

Currently invalid or missing `accept-language` headers will cause issues
rendering Plural components since we do not validate them.

This adds a check to try filter out invalid locales.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Enhanced locale filtering process to ensure only valid locales are
processed.
  
- **Bug Fixes**
- Improved data integrity by preventing invalid locales from affecting
application functionality.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-09-19 09:58:59 +10:00
9852e8971f v1.7.1-rc.2 2024-09-18 11:05:19 +10:00
5091112e4b fix: dont nullify externalId if not passed to update document settings 2024-09-18 11:00:48 +10:00
e76f732990 fix: completed signing page layout (#1349) 2024-09-18 10:54:00 +10:00
b7c3deb6cd chore: smaller text in signature pad (#1351) 2024-09-18 10:44:12 +10:00
08114f7b97 chore: add translations (#1327) 2024-09-18 10:43:43 +10:00
6e368cc333 chore: add document visibility section (#1352) 2024-09-18 02:41:57 +10:00
4ce4ca3f34 v1.7.1-rc.1 2024-09-17 15:26:38 +10:00
7644c0d855 feat: support smaller field bounds (#1344)
Currently this won't always display super well since
our insertion solution isn't amazing but our current
minimum bounds within the UI are a bit large and can be
smaller.

This change makes it smaller and uses container queries to
support dynamically displaying labels based on the container
size.
2024-09-17 00:29:42 +10:00
fa6453e811 feat: document visibility (#1262)
Adds the ability to set a visibility scope for documents within teams.
2024-09-17 00:14:16 +10:00
f7a20113e5 fix: fix passkeys page-breaking error (#1348) 2024-09-16 22:57:31 +10:00
3d644db286 feat: signing order (#1290)
Adds the ability to specify an optional signing order for documents.
When specified a document will be considered sequential with recipients
only being allowed to sign in the order that they were specified in.
2024-09-16 22:36:45 +10:00
357bdd374f feat: add language switcher (#1337)
## Description

Web changes:

- Enabled i18n for web
- Add option to change language in command menu
- Add option to change language in menu-switcher

Web and marketing changes:

- Stop setting 'en' preference into cookie if the user's language is not
supported
- Dropped middleware changes
- Rotated cookie from 'i18n' to 'language'

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Introduced a language switcher in the footer for improved language
selection.
	- Added dynamic language change functionality in the command menu.
- Implemented a dropdown menu item for quick access to the language
switcher.

- **Bug Fixes**
- Resolved issues related to language change notifications and state
management.

- **Translations**
- Added new translation entries for improved language support, including
"Search languages..." in English and German.
	- Updated existing translations to enhance clarity and accuracy.

- **Chores**
	- Simplified internationalization handling in middleware.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: github-actions <github-actions@documenso.com>
2024-09-11 13:22:43 +10:00
7b06b68572 v1.7.1-rc.0 2024-09-10 23:15:02 +10:00
9ee89346b1 fix: template with empty advanced fields backend (#1340)
extension of https://github.com/documenso/documenso/pull/1339
2024-09-10 21:35:35 +10:00
77da7847d9 fix: template with empty advanced fields (#1339)
Templates can be created and sent with advanced fields that have empty
values. That will cause an error when the user tries to sign the
document.

For example, you can create a template with a checkbox field and save
it. Then, you can click the "Use template" button and send the document
by clicking "Send document." However, this shouldn't be possible if the
advanced field doesn't have any values.
2024-09-10 16:23:16 +10:00
c36306d2c9 feat: add authOptions to the API (#1338)
Add the authOptions property to the document and
recipient related API endpoints.

These were previously missing so the only way API
users could set the authOptions was via templates
and using the generateTemplate endpoint.
2024-09-10 15:07:40 +10:00
f6f893fbf7 fix: prefill advanced field settings in templates (#1332)
## Description

Seems like I was overconfident in #1323 and I did not test properly.
Currently, the advanced settings for a field in a **template** is not
pre-filled with the current fieldMeta, although it is correctly
pre-filled in a **draft document** (That's probably where I messed up my
testing).

In this PR, I propose to directly use the fieldMeta provided by the
field prop in `FieldAdvancedSettings`, instead of multiplying tRPCs to
request the fieldMeta while we already have it.

I apologize for the wasted time in reviewing my previous PR which was
only correcting the display of the field label in the template view.

## Testing Performed

- This time, I correctly checked that the advanced settings for a field
is correctly pre-filled both in a document draft and in a template.
- `npm run build` builds correctly.
2024-09-10 13:22:03 +10:00
e1b2206d28 fix: select field ux (#1334)
When the Select field has a default value, it automatically signs with
it. If you change it, you need to refresh the page to re-sign again with
that value. This PR improves the UX by making the default value
"selectable" in the dropdown menu.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit


- **New Features**
- Updated the `DropdownField` component to simplify the handling of
default values, ensuring the dropdown starts without a pre-selected
option.
- Improved the clarity of the placeholder text in the dropdown,
enhancing user experience.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-09-10 13:15:47 +10:00
ad135b72d8 feat: marketing cta (#1335)
---
name: Pull Request
about: Submit changes to the project for review and inclusion
---

## Description

<!--- Describe the changes introduced by this pull request. -->
<!--- Explain what problem it solves or what feature/fix it adds. -->

## Related Issue

<!--- If this pull request is related to a specific issue, reference it
here using #issue_number. -->
<!--- For example, "Fixes #123" or "Addresses #456". -->

## Changes Made

<!--- Provide a summary of the changes made in this pull request. -->
<!--- Include any relevant technical details or architecture changes.
-->

- Change 1
- Change 2
- ...

## Testing Performed

<!--- Describe the testing that you have performed to validate these
changes. -->
<!--- Include information about test cases, testing environments, and
results. -->

- Tested feature X in scenario Y.
- Ran unit tests for component Z.
- Tested on browsers A, B, and C.
- ...

## Checklist

<!--- Please check the boxes that apply to this pull request. -->
<!--- You can add or remove items as needed. -->

- [ ] I have tested these changes locally and they work as expected.
- [ ] I have added/updated tests that prove the effectiveness of these
changes.
- [ ] I have updated the documentation to reflect these changes, if
applicable.
- [ ] I have followed the project's coding style guidelines.
- [ ] I have addressed the code review feedback from the previous
submission, if applicable.

## Additional Notes

<!--- Provide any additional context or notes for the reviewers. -->
<!--- This might include details about design decisions, potential
concerns, or anything else relevant. -->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Introduced a new `CallToAction` component to enhance user engagement
in the self-hosting documentation.
- Added interactive call-to-action elements in the self-hosting
documentation pages to guide users towards specific actions.

- **Documentation**
- Updated self-hosting documentation to include the new call-to-action
feature, improving usability and interactivity.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-09-10 12:42:12 +10:00
e81023f8d4 fix: refactor dates (#1321)
## Description

Refactor the current date formatting system to utilize Lingui.

## Changes Made

- Remove redundant `LocaleData` component with Lingui dates

## Important notes

For the internal pages for certificates, default to en-US to format any
dates.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit


- **New Features**
- Enhanced internationalization support across various components by
utilizing the `i18n` object for date formatting.
- Streamlined locale management by removing cookie-based language
handling and adopting a more centralized approach.

- **Bug Fixes**
- Improved date formatting consistency by replacing the `LocaleDate`
component with direct calls to `i18n.date()` in multiple components.

- **Documentation**
- Updated localization strings in the `web.po` files to reflect recent
changes in the source code structure.

- **Chores**
- Minor formatting adjustments and code organization improvements across
various files to enhance readability and maintainability.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: github-actions <github-actions@documenso.com>
2024-09-10 12:38:08 +10:00
bfb09e7928 v1.7.0 2024-09-09 11:20:33 +10:00
d7e5aa1d26 v1.7.0-rc.5 2024-09-09 08:47:26 +10:00
8cb3ad4f3c chore: embedding blog post (#1329) 2024-09-06 22:11:55 +10:00
6c3acb1c2d feat: add and use NEXT_PRIVATE_INTERNAL_WEBAPP_URL (#1298)
Introduces `NEXT_PRIVATE_INTERNAL_WEBAPP_URL` used for requesting the
app by itself (backend) [e.g. for background jobs]
2024-09-06 20:37:10 +10:00
3f82720383 feat: allow recipient to select signature color (#1325)
Allow users to select a colour for the signature. The allowed colours
are black, white, red, blue, and green.
2024-09-06 15:23:34 +10:00
a6f93698b4 fix: changed the default file path for local certs (#1277)
Changed the default value of NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH env
variable
2024-09-06 14:09:25 +10:00
bdc4ec1a31 chore: compile translations 2024-09-06 13:30:45 +10:00
bc471fcd9f chore: add translations (#1308)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit


- **Chores**
- Updated the metadata revision dates in multiple German translation
files for consistency.
- Added extraneous blank lines in multiple translation files for cleaner
formatting.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2024-09-06 12:54:18 +10:00
654 changed files with 50651 additions and 9807 deletions

48
.cursorrules Normal file
View File

@ -0,0 +1,48 @@
Code Style and Structure:
- Write concise, technical TypeScript code with accurate examples
- Use functional and declarative programming patterns; avoid classes
- Prefer iteration and modularization over code duplication
- Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError)
- Structure files: exported component, subcomponents, helpers, static content, types
Naming Conventions:
- Use lowercase with dashes for directories (e.g., components/auth-wizard)
- Favor named exports for components
TypeScript Usage:
- Use TypeScript for all code; prefer interfaces over types
- Avoid enums; use maps instead
- Use functional components with TypeScript interfaces
Syntax and Formatting:
- Use the "function" keyword for pure functions
- Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements
- Use declarative JSX
Error Handling and Validation:
- Prioritize error handling: handle errors and edge cases early
- Use early returns and guard clauses
- Implement proper error logging and user-friendly messages
- Use Zod for form validation
- Model expected errors as return values in Server Actions
- Use error boundaries for unexpected errors
UI and Styling:
- Use Shadcn UI, Radix, and Tailwind Aria for components and styling
- Implement responsive design with Tailwind CSS; use a mobile-first approach
Performance Optimization:
- Minimize 'use client', 'useEffect', and 'setState'; favor React Server Components (RSC)
- Wrap client components in Suspense with fallback
- Use dynamic loading for non-critical components
- Optimize images: use WebP format, include size data, implement lazy loading
Key Conventions:
- Use 'nuqs' for URL search parameter state management
- Optimize Web Vitals (LCP, CLS, FID)
- Limit 'use client':
- Favor server components and Next.js SSR
- Use only for Web API access in small components
- Avoid for data fetching or state management
Follow Next.js docs for Data Fetching, Rendering, and Routing

View File

@ -10,13 +10,7 @@
"ghcr.io/devcontainers/features/node:1": {}
},
"onCreateCommand": "./.devcontainer/on-create.sh",
"forwardPorts": [
3000,
54320,
9000,
2500,
1100
],
"forwardPorts": [3000, 54320, 9000, 2500, 1100],
"customizations": {
"vscode": {
"extensions": [
@ -35,4 +29,4 @@
]
}
}
}
}

View File

@ -27,6 +27,8 @@ NEXT_PRIVATE_OIDC_SKIP_VERIFY=""
# [[URLS]]
NEXT_PUBLIC_WEBAPP_URL="http://localhost:3000"
NEXT_PUBLIC_MARKETING_URL="http://localhost:3001"
# URL used by the web app to request itself (e.g. local background jobs)
NEXT_PRIVATE_INTERNAL_WEBAPP_URL="http://localhost:3000"
# [[DATABASE]]
NEXT_PRIVATE_DATABASE_URL="postgres://documenso:password@127.0.0.1:54320/documenso"
@ -91,6 +93,8 @@ NEXT_PRIVATE_SMTP_UNSAFE_IGNORE_TLS=
NEXT_PRIVATE_SMTP_FROM_NAME="Documenso"
# REQUIRED: Defines the email address to use as the from address.
NEXT_PRIVATE_SMTP_FROM_ADDRESS="noreply@documenso.com"
# OPTIONAL: Defines the service for nodemailer
NEXT_PRIVATE_SMTP_SERVICE=
# OPTIONAL: The API key to use for Resend.com
NEXT_PRIVATE_RESEND_API_KEY=
# OPTIONAL: The API key to use for MailChannels.
@ -135,3 +139,6 @@ E2E_TEST_AUTHENTICATE_USER_PASSWORD="test_Password123"
# [[REDIS]]
NEXT_PRIVATE_REDIS_URL=
NEXT_PRIVATE_REDIS_TOKEN=
# [[LOGGER]]
NEXT_PRIVATE_LOGGER_HONEY_BADGER_API_KEY=

View File

@ -32,6 +32,9 @@ jobs:
- name: Run Playwright tests
run: npm run ci
env:
# Needed since we use next start which will set the NODE_ENV to production
NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH: './example/cert.p12'
- uses: actions/upload-artifact@v4
if: always()

View File

@ -89,22 +89,35 @@ jobs:
APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
GIT_SHA="$(git rev-parse HEAD)"
docker manifest create \
documenso/documenso:latest \
--amend documenso/documenso-amd64:latest \
--amend documenso/documenso-arm64:latest \
# Check if the version is stable (no rc or beta in the version)
if [[ "$APP_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
docker manifest create \
documenso/documenso:latest \
--amend documenso/documenso-amd64:latest \
--amend documenso/documenso-arm64:latest
docker manifest push documenso/documenso:latest
fi
if [[ "$APP_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$ ]]; then
docker manifest create \
documenso/documenso:rc \
--amend documenso/documenso-amd64:rc \
--amend documenso/documenso-arm64:rc
docker manifest push documenso/documenso:rc
fi
docker manifest create \
documenso/documenso:$GIT_SHA \
--amend documenso/documenso-amd64:$GIT_SHA \
--amend documenso/documenso-arm64:$GIT_SHA \
--amend documenso/documenso-arm64:$GIT_SHA
docker manifest create \
documenso/documenso:$APP_VERSION \
--amend documenso/documenso-amd64:$APP_VERSION \
--amend documenso/documenso-arm64:$APP_VERSION \
--amend documenso/documenso-arm64:$APP_VERSION
docker manifest push documenso/documenso:latest
docker manifest push documenso/documenso:$GIT_SHA
docker manifest push documenso/documenso:$APP_VERSION
@ -113,21 +126,34 @@ jobs:
APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
GIT_SHA="$(git rev-parse HEAD)"
docker manifest create \
ghcr.io/documenso/documenso:latest \
--amend ghcr.io/documenso/documenso-amd64:latest \
--amend ghcr.io/documenso/documenso-arm64:latest \
# Check if the version is stable (no rc or beta in the version)
if [[ "$APP_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
docker manifest create \
ghcr.io/documenso/documenso:latest \
--amend ghcr.io/documenso/documenso-amd64:latest \
--amend ghcr.io/documenso/documenso-arm64:latest
docker manifest push ghcr.io/documenso/documenso:latest
fi
if [[ "$APP_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$ ]]; then
docker manifest create \
ghcr.io/documenso/documenso:rc \
--amend ghcr.io/documenso/documenso-amd64:rc \
--amend ghcr.io/documenso/documenso-arm64:rc
docker manifest push ghcr.io/documenso/documenso:rc
fi
docker manifest create \
ghcr.io/documenso/documenso:$GIT_SHA \
--amend ghcr.io/documenso/documenso-amd64:$GIT_SHA \
--amend ghcr.io/documenso/documenso-arm64:$GIT_SHA \
--amend ghcr.io/documenso/documenso-arm64:$GIT_SHA
docker manifest create \
ghcr.io/documenso/documenso:$APP_VERSION \
--amend ghcr.io/documenso/documenso-amd64:$APP_VERSION \
--amend ghcr.io/documenso/documenso-arm64:$APP_VERSION \
--amend ghcr.io/documenso/documenso-arm64:$APP_VERSION
docker manifest push ghcr.io/documenso/documenso:latest
docker manifest push ghcr.io/documenso/documenso:$GIT_SHA
docker manifest push ghcr.io/documenso/documenso:$APP_VERSION

View File

@ -1,38 +0,0 @@
# Extract and compile translations for all PRs.
name: 'Extract and compile translations'
on:
workflow_call:
pull_request:
branches: ['main']
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
extract_translations:
name: Extract and compile translations
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.ref }}
- uses: ./.github/actions/node-install
- name: Extract and compile translations
run: |
npm run translate:extract
npm run translate:compile
- name: Check and commit any files created
run: |
git config --global user.name 'github-actions'
git config --global user.email 'github-actions@documenso.com'
git add packages/lib/translations
git diff --staged --quiet --exit-code || (git commit -m "chore: extract translations" && git push)

View File

@ -21,14 +21,12 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.ref }}
token: ${{ secrets.GH_PAT }}
- uses: ./.github/actions/node-install
- name: Extract and compile translations
run: |
npm run translate:extract
npm run translate:compile
- name: Extract translations
run: npm run translate:extract
- name: Check and commit any files created
run: |

View File

@ -13,9 +13,4 @@ node "$MONOREPO_ROOT/scripts/copy-wellknown.cjs"
git add "$MONOREPO_ROOT/apps/web/public/"
git add "$MONOREPO_ROOT/apps/marketing/public/"
echo "Extract and compile translations"
npm run translate:extract
npm run translate:compile
git add "$MONOREPO_ROOT/packages/lib/translations/"
npx lint-staged

View File

@ -1,3 +1,7 @@
> 🚨 We are live on Product Hunt 🎉 Check out our latest launch: <a href="documen.so/sign-everywhere">The Platform Plan</a>!
<a href="https://www.producthunt.com/posts/documenso-platform-plan?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-documenso&#0045;platform&#0045;plan" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=670576&theme=light" alt="Documenso&#0032;Platform&#0032;Plan - Whitelabeled&#0032;signing&#0032;flows&#0032;in&#0032;your&#0032;product | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
<img src="https://github.com/documenso/documenso/assets/13398220/a643571f-0239-46a6-a73e-6bef38d1228b" alt="Documenso Logo">
<p align="center" style="margin-top: 20px">

View File

@ -1,36 +1 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Open [http://localhost:3002](http://localhost:3002) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
# @documenso/documentation

View File

@ -27,9 +27,6 @@
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10.0.1",
"postcss": "^8",
"tailwindcss": "^3.3.0",
"typescript": "^5"
}
}

View File

@ -0,0 +1,4 @@
{
"index": "Getting Started",
"contributing-translations": "Contributing Translations"
}

View File

@ -0,0 +1,69 @@
---
title: Contributing Translations
description: Learn how to contribute translations to Documenso and become part of our community.
---
import { Callout, Steps } from 'nextra/components';
# Contributing Translations
We are always open for help with translations! Currently we utilise AI to generate the initial translations for new languages, which are then improved over time by our awesome community.
If you are looking for development notes on translations, you can find them [here](/developers/local-development/translations).
<Callout type="info">
Contributions are made through GitHub Pull Requests, so you will need a GitHub account to
contribute.
</Callout>
## Overview
We store our translations in PO files, which are located in our GitHub repository [here](https://github.com/documenso/documenso/tree/main/packages/lib/translations).
The translation files are organized into folders represented by their respective language codes (`en` for English, `de` for German, etc). Each language folder contains three PO files:
1. `web.po`: Translations for the web application
2. `marketing.po`: Translations for the marketing application
3. `common.po`: Shared translations between web and marketing
Each PO file contains translations which look like this:
```po
#: apps/web/src/app/(signing)/sign/[token]/no-longer-available.tsx:61
msgid "Want to send slick signing links like this one? <0>Check out Documenso.</0>"
msgstr "Möchten Sie auffällige Signatur-Links wie diesen senden? <0>Überprüfen Sie Documenso.</0>"
```
- `msgid`: The original text in English (never edit this manually)
- `msgstr`: The translated text in the target language
<Callout type="warning">
Notice the `<0>` tags? These represent HTML elements and must remain in both the `msgid` and `msgstr`. Make sure to translate the content between these tags while keeping the tags intact.
</Callout>
## How to Contribute
### Updating Existing Translations
1. Fork the repository.
2. Navigate to the appropriate language folder.
3. Open the PO file you want to update (web.po, marketing.po, or common.po).
4. Make your changes, ensuring you follow the PO file format.
5. Commit your changes with a message such as `chore: update German translations`
6. Create a Pull Request.
### Adding a New Language
If you want to add translations for a language that doesn't exist yet:
1. Create an issue in our GitHub repository requesting the addition of the new language.
2. Wait for our team to review and approve the request.
3. Once approved, we will set up the necessary files and kickstart the translations with AI to provide initial coverage.
## Need Help?
<Callout type="info">
If you have any questions, hop into our [Discord](https://documen.so/discord) and ask us directly!
</Callout>
Thank you for helping make Documenso more accessible to users around the world!

View File

@ -1,5 +1,5 @@
---
title: Contributing Guide
title: Getting started
description: Learn how to contribute to Documenso and become part of our community.
---

View File

@ -0,0 +1,9 @@
{
"index": "Get Started",
"react": "React Integration",
"vue": "Vue Integration",
"svelte": "Svelte Integration",
"solid": "Solid Integration",
"preact": "Preact Integration",
"css-variables": "CSS Variables"
}

View File

@ -0,0 +1,120 @@
---
title: CSS Variables
description: Learn about all available CSS variables for customizing your embedded signing experience
---
# CSS Variables
Platform customers have access to a comprehensive set of CSS variables that can be used to customize the appearance of the embedded signing experience. These variables control everything from colors to spacing and can be used to match your application's design system.
## Available Variables
### Colors
| Variable | Description | Default |
| ----------------------- | ---------------------------------- | -------------- |
| `background` | Base background color | System default |
| `foreground` | Base text color | System default |
| `muted` | Muted/subtle background color | System default |
| `mutedForeground` | Muted/subtle text color | System default |
| `popover` | Popover/dropdown background color | System default |
| `popoverForeground` | Popover/dropdown text color | System default |
| `card` | Card background color | System default |
| `cardBorder` | Card border color | System default |
| `cardBorderTint` | Card border tint/highlight color | System default |
| `cardForeground` | Card text color | System default |
| `fieldCard` | Field card background color | System default |
| `fieldCardBorder` | Field card border color | System default |
| `fieldCardForeground` | Field card text color | System default |
| `widget` | Widget background color | System default |
| `widgetForeground` | Widget text color | System default |
| `border` | Default border color | System default |
| `input` | Input field border color | System default |
| `primary` | Primary action/button color | System default |
| `primaryForeground` | Primary action/button text color | System default |
| `secondary` | Secondary action/button color | System default |
| `secondaryForeground` | Secondary action/button text color | System default |
| `accent` | Accent/highlight color | System default |
| `accentForeground` | Accent/highlight text color | System default |
| `destructive` | Destructive/danger action color | System default |
| `destructiveForeground` | Destructive/danger text color | System default |
| `ring` | Focus ring color | System default |
| `warning` | Warning/alert color | System default |
### Spacing and Layout
| Variable | Description | Default |
| -------- | ------------------------------- | -------------- |
| `radius` | Border radius size in REM units | System default |
## Usage Example
Here's how to use these variables in your embedding implementation:
```jsx
const cssVars = {
// Colors
background: '#ffffff',
foreground: '#000000',
primary: '#0000ff',
primaryForeground: '#ffffff',
accent: '#4f46e5',
destructive: '#ef4444',
// Spacing
radius: '0.5rem'
};
// React/Preact
<EmbedDirectTemplate
token={token}
cssVars={cssVars}
/>
// Vue
<EmbedDirectTemplate
:token="token"
:cssVars="cssVars"
/>
// Svelte
<EmbedDirectTemplate
{token}
cssVars={cssVars}
/>
// Solid
<EmbedDirectTemplate
token={token}
cssVars={cssVars}
/>
```
## Color Format
Colors can be specified in any valid CSS color format:
- Hexadecimal: `#ff0000`
- RGB: `rgb(255, 0, 0)`
- HSL: `hsl(0, 100%, 50%)`
- Named colors: `red`
The colors will be automatically converted to the appropriate format internally.
## Best Practices
1. **Maintain Contrast**: When customizing colors, ensure there's sufficient contrast between background and foreground colors for accessibility.
2. **Test Dark Mode**: If you haven't disabled dark mode, test your color variables in both light and dark modes.
3. **Use Your Brand Colors**: Align the primary and accent colors with your brand's color scheme for a cohesive look.
4. **Consistent Radius**: Use a consistent border radius value that matches your application's design system.
## Related
- [React Integration](/developers/embedding/react)
- [Vue Integration](/developers/embedding/vue)
- [Svelte Integration](/developers/embedding/svelte)
- [Solid Integration](/developers/embedding/solid)
- [Preact Integration](/developers/embedding/preact)

View File

@ -11,7 +11,11 @@ Our embedding feature lets you integrate our document signing experience into yo
Embedding is currently available for all users on a **Teams Plan** and above, as well as **Early Adopter's** within a team (Early Adopters can create a team for free).
In the future, we will roll out a **Platform Plan** that will offer additional enhancements for embedding, including the option to remove Documenso branding for a more customized experience.
Our **Platform Plan** offers enhanced customization features including:
- Custom CSS and styling variables
- Dark mode controls
- The removal of Documenso branding from the embedding experience
## How Embedding Works
@ -22,6 +26,49 @@ Embedding with Documenso allows you to handle document signing in two main ways:
_For most use-cases we recommend using direct templates, however if you have a need for a more advanced integration, we are happy to help you get started._
## Customization Options
### Styling and Theming
Platform customers have access to advanced styling options to customize the embedding experience:
1. **Custom CSS**: You can provide custom CSS to style the embedded component:
```jsx
<EmbedDirectTemplate
token={token}
css={`
.documenso-embed {
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
`}
/>
```
2. **CSS Variables**: Fine-tune the appearance using CSS variables for colors, spacing, and more:
```jsx
<EmbedDirectTemplate
token={token}
cssVars={{
colorPrimary: '#0000FF',
colorBackground: '#F5F5F5',
borderRadius: '8px',
}}
/>
```
For a complete list of available CSS variables and their usage, see our [CSS Variables](/developers/embedding/css-variables) documentation.
3. **Dark Mode Control**: Disable dark mode if it doesn't match your application's theme:
```jsx
<EmbedDirectTemplate token={token} darkModeDisabled={true} />
```
These customization options are available for both Direct Templates and Signing Token embeds.
## Supported Frameworks
We support embedding across a range of popular JavaScript frameworks, including:
@ -120,12 +167,11 @@ Once you've obtained the appropriate tokens, you can integrate the signing exper
If you're using **web components**, the integration process is slightly different. Keep in mind that web components are currently less tested but can still provide flexibility for general use cases.
## Stay Tuned for the Platform Plan
## Related
While embedding is already a powerful tool, we're working on a **Platform Plan** that will introduce even more functionality. This plan will offer:
- Additional customization options
- The ability to remove Documenso branding
- Additional controls for the signing experience
More details will be shared as we approach the release.
- [React Integration](/developers/embedding/react)
- [Vue Integration](/developers/embedding/vue)
- [Svelte Integration](/developers/embedding/svelte)
- [Solid Integration](/developers/embedding/solid)
- [Preact Integration](/developers/embedding/preact)
- [CSS Variables](/developers/embedding/css-variables)

View File

@ -44,6 +44,9 @@ const MyEmbeddingComponent = () => {
| email | string (optional) | The email the signer that will be used by default for signing |
| lockEmail | boolean (optional) | Whether or not the email field should be locked disallowing modifications |
| externalId | string (optional) | The external ID to be used for the document that will be created upon completion |
| css | string (optional) | Custom CSS to style the embedded component (Platform Plan only) |
| cssVars | object (optional) | CSS variables for customizing colors, spacing, etc. (Platform Plan only) |
| darkModeDisabled | boolean (optional) | Disable dark mode functionality (Platform Plan only) |
| onDocumentReady | function (optional) | A callback function that will be called when the document is loaded and ready to be signed |
| onDocumentCompleted | function (optional) | A callback function that will be called when the document has been completed |
| onDocumentError | function (optional) | A callback function that will be called when an error occurs with the document |
@ -75,3 +78,30 @@ const MyEmbeddingComponent = () => {
| onDocumentReady | function (optional) | A callback function that will be called when the document is loaded and ready to be signed |
| onDocumentCompleted | function (optional) | A callback function that will be called when the document has been completed |
| onDocumentError | function (optional) | A callback function that will be called when an error occurs with the document |
### Styling and Theming (Platform Plan)
Platform customers have access to advanced styling options:
```jsx
import { EmbedDirectTemplate } from '@documenso/embed-preact';
const MyEmbeddingComponent = () => {
const token = 'your-token';
const customCss = `
.documenso-embed {
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
`;
const cssVars = {
colorPrimary: '#0000FF',
colorBackground: '#F5F5F5',
borderRadius: '8px',
};
return (
<EmbedDirectTemplate token={token} css={customCss} cssVars={cssVars} darkModeDisabled={true} />
);
};
```

View File

@ -44,6 +44,9 @@ const MyEmbeddingComponent = () => {
| email | string (optional) | The email the signer that will be used by default for signing |
| lockEmail | boolean (optional) | Whether or not the email field should be locked disallowing modifications |
| externalId | string (optional) | The external ID to be used for the document that will be created upon completion |
| css | string (optional) | Custom CSS to style the embedded component (Platform Plan only) |
| cssVars | object (optional) | CSS variables for customizing colors, spacing, etc. (Platform Plan only) |
| darkModeDisabled | boolean (optional) | Disable dark mode functionality (Platform Plan only) |
| onDocumentReady | function (optional) | A callback function that will be called when the document is loaded and ready to be signed |
| onDocumentCompleted | function (optional) | A callback function that will be called when the document has been completed |
| onDocumentError | function (optional) | A callback function that will be called when an error occurs with the document |
@ -75,3 +78,34 @@ const MyEmbeddingComponent = () => {
| onDocumentReady | function (optional) | A callback function that will be called when the document is loaded and ready to be signed |
| onDocumentCompleted | function (optional) | A callback function that will be called when the document has been completed |
| onDocumentError | function (optional) | A callback function that will be called when an error occurs with the document |
### Styling and Theming (Platform Plan)
Platform customers have access to advanced styling options:
```jsx
import { EmbedDirectTemplate } from '@documenso/embed-react';
const MyEmbeddingComponent = () => {
return (
<EmbedDirectTemplate
token="your-token"
// Custom CSS
css={`
.documenso-embed {
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
`}
// CSS Variables
cssVars={{
colorPrimary: '#0000FF',
colorBackground: '#F5F5F5',
borderRadius: '8px',
}}
// Dark Mode Control
darkModeDisabled={true}
/>
);
};
```

View File

@ -44,6 +44,9 @@ const MyEmbeddingComponent = () => {
| email | string (optional) | The email the signer that will be used by default for signing |
| lockEmail | boolean (optional) | Whether or not the email field should be locked disallowing modifications |
| externalId | string (optional) | The external ID to be used for the document that will be created upon completion |
| css | string (optional) | Custom CSS to style the embedded component (Platform Plan only) |
| cssVars | object (optional) | CSS variables for customizing colors, spacing, etc. (Platform Plan only) |
| darkModeDisabled | boolean (optional) | Disable dark mode functionality (Platform Plan only) |
| onDocumentReady | function (optional) | A callback function that will be called when the document is loaded and ready to be signed |
| onDocumentCompleted | function (optional) | A callback function that will be called when the document has been completed |
| onDocumentError | function (optional) | A callback function that will be called when an error occurs with the document |
@ -75,3 +78,30 @@ const MyEmbeddingComponent = () => {
| onDocumentReady | function (optional) | A callback function that will be called when the document is loaded and ready to be signed |
| onDocumentCompleted | function (optional) | A callback function that will be called when the document has been completed |
| onDocumentError | function (optional) | A callback function that will be called when an error occurs with the document |
### Styling and Theming (Platform Plan)
Platform customers have access to advanced styling options:
```jsx
import { EmbedDirectTemplate } from '@documenso/embed-solid';
const MyEmbeddingComponent = () => {
const token = 'your-token';
const customCss = `
.documenso-embed {
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
`;
const cssVars = {
colorPrimary: '#0000FF',
colorBackground: '#F5F5F5',
borderRadius: '8px',
};
return (
<EmbedDirectTemplate token={token} css={customCss} cssVars={cssVars} darkModeDisabled={true} />
);
};
```

View File

@ -46,6 +46,9 @@ If you have a direct link template, you can simply provide the token for the tem
| email | string (optional) | The email the signer that will be used by default for signing |
| lockEmail | boolean (optional) | Whether or not the email field should be locked disallowing modifications |
| externalId | string (optional) | The external ID to be used for the document that will be created upon completion |
| css | string (optional) | Custom CSS to style the embedded component (Platform Plan only) |
| cssVars | object (optional) | CSS variables for customizing colors, spacing, etc. (Platform Plan only) |
| darkModeDisabled | boolean (optional) | Disable dark mode functionality (Platform Plan only) |
| onDocumentReady | function (optional) | A callback function that will be called when the document is loaded and ready to be signed |
| onDocumentCompleted | function (optional) | A callback function that will be called when the document has been completed |
| onDocumentError | function (optional) | A callback function that will be called when an error occurs with the document |
@ -77,3 +80,28 @@ const MyEmbeddingComponent = () => {
| onDocumentReady | function (optional) | A callback function that will be called when the document is loaded and ready to be signed |
| onDocumentCompleted | function (optional) | A callback function that will be called when the document has been completed |
| onDocumentError | function (optional) | A callback function that will be called when an error occurs with the document |
### Styling and Theming (Platform Plan)
Platform customers have access to advanced styling options:
```html
<script lang="ts">
import { EmbedDirectTemplate } from '@documenso/embed-svelte';
const token = 'your-token';
const customCss = `
.documenso-embed {
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
`;
const cssVars = {
colorPrimary: '#0000FF',
colorBackground: '#F5F5F5',
borderRadius: '8px',
};
</script>
<EmbedDirectTemplate {token} css="{customCss}" cssVars="{cssVars}" darkModeDisabled="{true}" />
```

View File

@ -46,6 +46,9 @@ If you have a direct link template, you can simply provide the token for the tem
| email | string (optional) | The email the signer that will be used by default for signing |
| lockEmail | boolean (optional) | Whether or not the email field should be locked disallowing modifications |
| externalId | string (optional) | The external ID to be used for the document that will be created upon completion |
| css | string (optional) | Custom CSS to style the embedded component (Platform Plan only) |
| cssVars | object (optional) | CSS variables for customizing colors, spacing, etc. (Platform Plan only) |
| darkModeDisabled | boolean (optional) | Disable dark mode functionality (Platform Plan only) |
| onDocumentReady | function (optional) | A callback function that will be called when the document is loaded and ready to be signed |
| onDocumentCompleted | function (optional) | A callback function that will be called when the document has been completed |
| onDocumentError | function (optional) | A callback function that will be called when an error occurs with the document |
@ -77,3 +80,35 @@ const MyEmbeddingComponent = () => {
| onDocumentReady | function (optional) | A callback function that will be called when the document is loaded and ready to be signed |
| onDocumentCompleted | function (optional) | A callback function that will be called when the document has been completed |
| onDocumentError | function (optional) | A callback function that will be called when an error occurs with the document |
### Styling and Theming (Platform Plan)
Platform customers have access to advanced styling options:
```html
<script setup lang="ts">
import { EmbedDirectTemplate } from '@documenso/embed-vue';
const token = ref('your-token');
const customCss = `
.documenso-embed {
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
`;
const cssVars = {
colorPrimary: '#0000FF',
colorBackground: '#F5F5F5',
borderRadius: '8px',
};
</script>
<template>
<EmbedDirectTemplate
:token="token"
:css="customCss"
:cssVars="cssVars"
:darkModeDisabled="true"
/>
</template>
```

View File

@ -11,6 +11,10 @@ Digitally signing documents requires a signing certificate in `.p12` format. You
Follow the steps below to create a free, self-signed certificate for local development.
<Callout type="warning">
These steps should be run on a UNIX based system, otherwise you may run into an error.
</Callout>
<Steps>
### Generate Private Key
@ -38,11 +42,17 @@ You will be prompted to enter some information, such as the certificate's Common
Combine the private key and the self-signed certificate to create a `.p12` certificate. Use the following command:
```bash
openssl pkcs12 -export -out certificate.p12 -inkey private.key -in certificate.crt
openssl pkcs12 -export -out certificate.p12 -inkey private.key -in certificate.crt -legacy
```
<Callout type="warning">
If you get the error "Error: Failed to get private key bags", add the `-legacy` flag to the command `openssl pkcs12 -export -out certificate.p12 -inkey private.key -in certificate.crt -legacy`.
When running the application in Docker, you may encounter permission issues when attempting to sign documents using your certificate (.p12) file. This happens because the application runs as a non-root user inside the container and needs read access to the certificate.
To resolve this, you'll need to update the certificate file permissions to allow the container user 1001, which runs NextJS, to read it:
```bash
sudo chown 1001 certificate.p12
```
</Callout>
@ -54,8 +64,8 @@ Note that for local development, the password can be left empty.
### Add Certificate to the Project
Finally, add the certificate to the project. Place the `certificate.p12` file in the `/apps/web/resources` directory. If the directory doesn't exist, create it.
Use the `NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH` environment variable to point at the certificate you created.
The final file path should be `/apps/web/resources/certificate.p12`.
Details about environment variables associated with certificates can be found [here](/developers/self-hosting/signing-certificate#configure-documenso-to-use-the-certificate).
</Steps>

View File

@ -0,0 +1,534 @@
---
title: API Reference
description: Reference documentation for the Documenso public API.
---
import { Callout, Steps } from 'nextra/components';
# API Reference
The Swagger UI for the API is available at [/api/v1/openapi](https://app.documenso.com/api/v1/openapi). This page provides detailed information about the API endpoints, request and response formats, and authentication requirements.
## Upload a Document
Uploading a document to your Documenso account requires a two-step process.
<Steps>
### Create Document
First, you need to make a `POST` request to the `/api/v1/documents` endpoint, which takes a JSON payload with the following fields:
```json
{
"title": "string",
"externalId": "string",
"recipients": [
{
"name": "string",
"email": "user@example.com",
"role": "SIGNER",
"signingOrder": 0
}
],
"meta": {
"subject": "string",
"message": "string",
"timezone": "Etc/UTC",
"dateFormat": "yyyy-MM-dd hh:mm a",
"redirectUrl": "string",
"signingOrder": "PARALLEL"
},
"authOptions": {
"globalAccessAuth": "ACCOUNT",
"globalActionAuth": "ACCOUNT"
},
"formValues": {
"additionalProp1": "string",
"additionalProp2": "string",
"additionalProp3": "string"
}
}
```
- `title` _(required)_ - This represents the document's title.
- `externalId` - This is an optional field that you can use to store an external identifier for the document. This can be useful for tracking the document in your system.
- `recipients` _(required)_ - This is an array of recipient objects. Each recipient object has the following fields:
- `name` - The name of the recipient.
- `email` - The email address of the recipient.
- `role` - The role of the recipient. See the [available roles](/users/signing-documents#roles).
- `signingOrder` - The order in which the recipient should sign the document. This is an integer value starting from 0.
- `meta` - This object contains additional metadata for the document. It has the following fields:
- `subject` - The subject of the email that will be sent to the recipients.
- `message` - The message of the email that will be sent to the recipients.
- `timezone` - The timezone in which the document should be signed.
- `dateFormat` - The date format that should be used in the document.
- `redirectUrl` - The URL to which the user should be redirected after signing the document.
- `signingOrder` - The signing order for the document. This can be either `SEQUENTIAL` or `PARALLEL`.
- `authOptions` - This object contains authentication options for the document. It has the following fields:
- `globalAccessAuth` - The authentication level required to access the document. This can be either `ACCOUNT` or `null`.
- If the document is set to `ACCOUNT`, all recipients must authenticate with their Documenso account to access it.
- The document can be accessed without a Documenso account if it's set to `null`.
- `globalActionAuth` - The authentication level required to perform actions on the document. This can be `ACCOUNT`, `PASSKEY`, `TWO_FACTOR_AUTH`, or `null`.
- If the document is set to `ACCOUNT`, all recipients must authenticate with their Documenso account to perform actions on the document.
- If it's set to `PASSKEY`, all recipients must have the passkey active to perform actions on the document.
- If it's set to `TWO_FACTOR_AUTH`, all recipients must have the two-factor authentication active to perform actions on the document.
- If it's set to `null`, all the recipients can perform actions on the document without any authentication.
- `formValues` - This object contains additional form values for the document. This property only works with native PDF fields and accepts three types: number, text and boolean.
<Callout type="info">
The `globalActionAuth` property is only available for Enterprise accounts.
</Callout>
Here's an example of the JSON payload for uploading a document:
```json
{
"title": "my-document.pdf",
"externalId": "12345",
"recipients": [
{
"name": "Alex Blake",
"email": "alexblake@email.com",
"role": "SIGNER",
"signingOrder": 1
},
{
"name": "Ash Drew",
"email": "ashdrew@email.com",
"role": "SIGNER",
"signingOrder": 0
}
],
"meta": {
"subject": "Sign the document",
"message": "Hey there, please sign this document.",
"timezone": "Europe/London",
"dateFormat": "Day, Month Year",
"redirectUrl": "https://mysite.com/welcome",
"signingOrder": "SEQUENTIAL"
},
"authOptions": {
"globalAccessAuth": "ACCOUNT",
"globalActionAuth": "PASSKEY"
}
}
```
### Upload to S3
A successful API call to the `/api/v1/documents` endpoint returns a JSON response containing the upload URL, document ID, and recipient information.
The upload URL is a pre-signed S3 URL that you can use to upload the document to the Documenso (or your) S3 bucket. You need to make a `PUT` request to this URL to upload the document.
```json
{
"uploadUrl": "https://<url>/<bucket-name>/<id>/my-document.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=<credentials>&X-Amz-Date=<date>&X-Amz-Expires=3600&X-Amz-Signature=<signature>&X-Amz-SignedHeaders=host&x-id=PutObject",
"documentId": 51,
"recipients": [
{
"recipientId": 11,
"name": "Alex Blake",
"email": "alexblake@email.com",
"token": "<unique-signer-token>",
"role": "SIGNER",
"signingOrder": 1,
"signingUrl": "https://app.documenso.com/sign/<unique-signer-token>"
},
{
"recipientId": 12,
"name": "Ash Drew",
"email": "ashdrew@email.com",
"token": "<unique-signer-token>",
"role": "SIGNER",
"signingOrder": 0,
"signingUrl": "https://app.documenso.com/sign/<unique-signer-token>"
}
]
}
```
When you make the `PUT` request to the pre-signed URL, you need to include the document file you want to upload. The image below shows how to upload a document to the S3 bucket via Postman.
![Upload document to S3](/api-reference/upload-document-to-s3.webp)
Here's an example of how to upload a document using cURL:
```bash
curl --location --request PUT 'https://<url>/<bucket-name>/<id>/my-document.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=<credentials>&X-Amz-Date=<date>&X-Amz-Expires=3600&X-Amz-Signature=<signature>&X-Amz-SignedHeaders=host&x-id=PutObject' \
--form '=@"/Users/my-user/Documents/documenso.pdf"'
```
Once the document is successfully uploaded, you can access it in your Documenso account dashboard. The screenshot below shows the document that was uploaded via the API.
![Uploaded Document](/api-reference/document-uploaded-to-documenso-via-api.webp)
</Steps>
## Generate Document From Template
Documenso allows you to generate documents from templates. This is useful when you have a standard document format you want to reuse.
The API endpoint for generating a document from a template is `/api/v1/templates/{templateId}/generate-document`, and it takes a JSON payload with the following fields:
```json
{
"title": "string",
"externalId": "string",
"recipients": [
{
"id": 0,
"name": "string",
"email": "user@example.com",
"signingOrder": 0
}
],
"meta": {
"subject": "string",
"message": "string",
"timezone": "string",
"dateFormat": "string",
"redirectUrl": "string",
"signingOrder": "PARALLEL"
},
"authOptions": {
"globalAccessAuth": "ACCOUNT",
"globalActionAuth": "ACCOUNT"
},
"formValues": {
"additionalProp1": "string",
"additionalProp2": "string",
"additionalProp3": "string"
}
}
```
The JSON payload is identical to the payload for uploading a document, so you can read more about the fields in the [Create Document](/developers/public-api/reference#create-document) step. For this API endpoint, the `recipients` property is required.
<Steps>
### Grab the Template ID
The first step is to retrieve the template ID from the Documenso dashboard. You can find the template ID in the URL by navigating to the template details page.
![Template ID](/api-reference/documenso-template-id.webp)
In this case, the template ID is "99999".
### Retrieve the Recipient(s) ID(s)
Once you have the template ID, the next step involves retrieving the ID(s) of the recipient(s) from the template. You can do this by making a GET request to `/api/v1/templates/{template-id}`.
A successful response looks as follows:
```json
{
"id": 0,
"externalId": "string",
"type": "PUBLIC",
"title": "string",
"userId": 0,
"teamId": 0,
"templateDocumentDataId": "string",
"createdAt": "2024-10-11T08:46:58.247Z",
"updatedAt": "2024-10-11T08:46:58.247Z",
"templateMeta": {
"id": "string",
"subject": "string",
"message": "string",
"timezone": "string",
"dateFormat": "string",
"templateId": 0,
"redirectUrl": "string",
"signingOrder": "PARALLEL"
},
"directLink": {
"token": "string",
"enabled": true
},
"templateDocumentData": {
"id": "string",
"type": "S3_PATH",
"data": "string"
},
"Field": [
{
"id": 0,
"recipientId": 0,
"type": "SIGNATURE",
"page": 0,
"positionX": "string",
"positionY": "string",
"width": "string",
"height": "string"
}
],
"Recipient": [
{
"id": 0,
"email": "user@example.com",
"name": "string",
"signingOrder": 0,
"authOptions": "string",
"role": "CC"
}
]
}
```
You'll need the recipient(s) ID(s) for the next step.
### Generate the Document
To generate a document from the template, you need to make a POST request to the `/api/v1/templates/{template-id}/generate-document` endpoint.
At the minimum, you must provide the `recipients` array in the JSON payload. Here's an example of the JSON payload:
```json
{
"recipients": [
{
"id": 0,
"name": "Ash Drew",
"email": "ashdrew@email.com",
"signingOrder": 0
}
]
}
```
Filling the `recipients` array with the corresponding recipient for each template placeholder recipient is recommended. For example, if the template has two placeholders, you should provide at least two recipients in the `recipients` array. Otherwise, the document will be sent to inexistent recipients such as `<recipient.1@documenso.com>`. However, the recipients can always be edited via the API or the web app.
A successful response will contain the document ID and recipient(s) information.
```json
{
"documentId": 999,
"recipients": [
{
"recipientId": 0,
"name": "Ash Drew",
"email": "ashdrew@email.com",
"token": "<signing-token>",
"role": "SIGNER",
"signingOrder": null,
"signingUrl": "https://app.documenso.com/sign/<signing-token>"
}
]
}
```
You can now access the document in your Documenso account dashboard. The screenshot below shows the document that was generated from the template.
![Generated Document](/api-reference/document-generated-from-template.webp)
</Steps>
## Add Fields to Document
The API allows you to add fields to a document via the `/api/v1/documents/{documentId}/fields` endpoint. This is useful when you want to add fields to a document before sending it to recipients.
To add fields to a document, you need to make a `POST` request with a JSON payload containing the field(s) information.
```json
{
"recipientId": 0,
"type": "SIGNATURE",
"pageNumber": 0,
"pageX": 0,
"pageY": 0,
"pageWidth": 0,
"pageHeight": 0,
"fieldMeta": {
"label": "string",
"placeholder": "string",
"required": true,
"readOnly": true,
"type": "text",
"text": "string",
"characterLimit": 0
}
}
// or
[
{
"recipientId": 0,
"type": "SIGNATURE",
"pageNumber": 0,
"pageX": 0,
"pageY": 0,
"pageWidth": 0,
"pageHeight": 0
},
{
"recipientId": 0,
"type": "TEXT",
"pageNumber": 0,
"pageX": 0,
"pageY": 0,
"pageWidth": 0,
"pageHeight": 0,
"fieldMeta": {
"label": "string",
"placeholder": "string",
"required": true,
"readOnly": true,
"type": "text",
"text": "string",
"characterLimit": 0
}
}
]
```
<Callout type="info">This endpoint accepts either one field or an array of fields.</Callout>
Before adding fields to a document, you need each recipient's ID. If the document already has recipients, you can query the document to retrieve the recipient's details. If the document has no recipients, you need to add a recipient via the UI or API before adding a field.
<Steps>
### Retrieve the Recipient(s) ID(s)
Perform a `GET` request to the `/api/v1/documents/{id}` to retrieve the details of a specific document, including the recipient's information.
An example response would look like this:
```json
{
"id": 137,
"externalId": null,
"userId": 3,
"teamId": null,
"title": "documenso.pdf",
"status": "DRAFT",
"documentDataId": "<document-data-id>",
"createdAt": "2024-10-11T12:29:12.725Z",
"updatedAt": "2024-10-11T12:29:12.725Z",
"completedAt": null,
"recipients": [
{
"id": 55,
"documentId": 137,
"email": "ashdrew@email.com",
"name": "Ash Drew",
"role": "SIGNER",
"signingOrder": null,
"token": "<signing-token>",
"signedAt": null,
"readStatus": "NOT_OPENED",
"signingStatus": "NOT_SIGNED",
"sendStatus": "NOT_SENT",
"signingUrl": "https://app.documenso.com/sign/<signing-token>"
}
]
}
```
From this response, you'll only need the recipient ID, which is `55` in this case.
### (OR) Add a Recipient
If the document doesn't already have recipient(s), you can add recipient(s) via the API. Make a `POST` request to the `/api/v1/documents/{documentId}/recipients` endpoint with the recipient information. This endpoint takes the following JSON payload:
```json
{
"name": "string",
"email": "user@example.com",
"role": "SIGNER",
"signingOrder": 0,
"authOptions": {
"actionAuth": "ACCOUNT"
}
}
```
<Callout type="info">The `authOptions` property is only available for Enterprise accounts.</Callout>
Here's an example of the JSON payload for adding a recipient:
```json
{
"name": "Ash Drew",
"email": "ashdrew@email.com",
"role": "SIGNER",
"signingOrder": 0
}
```
A successful request will return a JSON response with the newly added recipient. You can now use the recipient ID to add fields to the document.
### Add Field(s)
Now you can make a `POST` request to the `/api/v1/documents/{documentId}/fields` endpoint with the field(s) information. Here's an example:
```json
[
{
"recipientId": 55,
"type": "SIGNATURE",
"pageNumber": 1,
"pageX": 50,
"pageY": 20,
"pageWidth": 25,
"pageHeight": 5
},
{
"recipientId": 55,
"type": "TEXT",
"pageNumber": 1,
"pageX": 20,
"pageY": 50,
"pageWidth": 30,
"pageHeight": 7.5,
"fieldMeta": {
"label": "Address",
"placeholder": "32 New York Street, 41241",
"required": true,
"readOnly": false,
"type": "text",
"text": "32 New York Street, 41241",
"characterLimit": 40
}
}
]
```
<Callout type="info">
The `text` field represents the default value of the field. If the user doesn't provide any other
value, this is the value that will be used to sign the field.
</Callout>
<Callout type="warning">
It's important to pass the `type` in the `fieldMeta` property for the advanced fields. [Read more
here](#a-note-on-advanced-fields)
</Callout>
A successful request will return a JSON response with the newly added fields. The image below illustrates the fields added to the document via the API.
![A screenshot of the document in the Documenso editor](/api-reference/fields-added-via-api.webp)
</Steps>
#### A Note on Advanced Fields
The advanced fields are: text, checkbox, radio, number, and select. Whenever you append any of these advanced fields to a document, you need to pass the `type` in the `fieldMeta` property:
```json
...
"fieldMeta": {
"type": "text",
}
...
```
Replace the `text` value with the corresponding field type:
- For the `TEXT` field it should be `text`.
- For the `CHECKBOX` field it should be `checkbox`.
- For the `RADIO` field it should be `radio`.
- For the `NUMBER` field it should be `number`.
- For the `SELECT` field it should be `select`. (check this before merge)
You must pass this property at all times, even if you don't need to set any other properties. If you don't, the endpoint will throw an error.

View File

@ -5,6 +5,8 @@ description: Learn how to self-host Documenso on your server or cloud infrastruc
import { Callout, Steps } from 'nextra/components';
import { CallToAction } from '@documenso/ui/components/call-to-action';
# Self Hosting
We support various deployment methods and are actively working on adding more. Please let us know if you have a specific deployment method in mind!
@ -131,7 +133,7 @@ volumes:
After updating the volume binding, save the `compose.yml` file and run the following command to start the containers:
```bash
docker-compose --env-file ./.env -d up
docker-compose --env-file ./.env up -d
```
The command will start the PostgreSQL database and the Documenso application containers.
@ -273,3 +275,5 @@ We offer several alternative deployment methods for Documenso if you need more o
## Koyeb
[![Deploy to Koyeb](https://www.koyeb.com/static/images/deploy/button.svg)](https://app.koyeb.com/deploy?type=git&repository=github.com/documenso/documenso&branch=main&name=documenso-app&builder=dockerfile&dockerfile=/docker/Dockerfile)
<CallToAction className="mt-12" utmSource="self-hosting" />

View File

@ -3,6 +3,10 @@ title: Getting Started with Self-Hosting
description: A step-by-step guide to setting up and hosting your own Documenso instance.
---
import { CallToAction } from '@documenso/ui/components/call-to-action';
# Getting Started with Self-Hosting
This is a step-by-step guide to setting up and hosting your own Documenso instance. Before getting started, [select the right license for you](/users/licenses).
<CallToAction className="mt-12" utmSource="self-hosting" />

View File

@ -20,6 +20,7 @@ Documenso supports Webhooks and allows you to subscribe to the following events:
- `document.opened`
- `document.signed`
- `document.completed`
- `document.rejected`
## Create a webhook subscription
@ -36,7 +37,7 @@ Clicking on the "**Create Webhook**" button opens a modal to create a new webhoo
To create a new webhook subscription, you need to provide the following information:
- Enter the webhook URL that will receive the event payload.
- Select the event(s) you want to subscribe to: `document.created`, `document.sent`, `document.opened`, `document.signed`, `document.completed`.
- Select the event(s) you want to subscribe to: `document.created`, `document.sent`, `document.opened`, `document.signed`, `document.completed`, `document.rejected`.
- Optionally, you can provide a secret key that will be used to sign the payload. This key will be included in the `X-Documenso-Secret` header of the request.
![A screenshot of the Create Webhook modal that shows the URL input field and the event checkboxes](/webhook-images/webhooks-page-create-webhook-modal.webp)
@ -53,45 +54,55 @@ You can edit or delete your webhook subscriptions by clicking the "**Edit**" or
The payload sent to the webhook URL contains the following fields:
| Field | Type | Description |
| -------------------------------------------- | --------- | ---------------------------------------------------- |
| `event` | string | The type of event that triggered the webhook. |
| `payload.id` | number | The id of the document. |
| `payload.userId` | number | The id of the user who owns the document. |
| `payload.authOptions` | json? | Authentication options for the document. |
| `payload.formValues` | json? | Form values for the document. |
| `payload.title` | string | The name of the document. |
| `payload.status` | string | The current status of the document. |
| `payload.documentDataId` | string | The identifier for the document data. |
| `payload.createdAt` | datetime | The creation date and time of the document. |
| `payload.updatedAt` | datetime | The last update date and time of the document. |
| `payload.completedAt` | datetime? | The completion date and time of the document. |
| `payload.deletedAt` | datetime? | The deletion date and time of the document. |
| `payload.teamId` | number? | The id of the team. |
| `payload.documentData.id` | string | The id of the document data. |
| `payload.documentData.type` | string | The type of the document data. |
| `payload.documentData.data` | string | The data of the document. |
| `payload.documentData.initialData` | string | The initial data of the document. |
| `payload.Recipient[].id` | number | The id of the recipient. |
| `payload.Recipient[].documentId` | number? | The id the document associated with the recipient. |
| `payload.Recipient[].templateId` | number? | The template identifier for the recipient. |
| `payload.Recipient[].email` | string | The email address of the recipient. |
| `payload.Recipient[].name` | string | The name of the recipient. |
| `payload.Recipient[].token` | string | The token associated with the recipient. |
| `payload.Recipient[].expired` | datetime? | The expiration status of the recipient. |
| `payload.Recipient[].signedAt` | datetime? | The date and time the recipient signed the document. |
| `payload.Recipient[].authOptions.accessAuth` | json? | Access authentication options. |
| `payload.Recipient[].authOptions.actionAuth` | json? | Action authentication options. |
| `payload.Recipient[].role` | string | The role of the recipient. |
| `payload.Recipient[].readStatus` | string | The read status of the document by the recipient. |
| `payload.Recipient[].signingStatus` | string | The signing status of the recipient. |
| `payload.Recipient[].sendStatus` | string | The send status of the document to the recipient. |
| `createdAt` | datetime | The creation date and time of the webhook event. |
| `webhookEndpoint` | string | The endpoint URL where the webhook is sent. |
## Webhook event payload example
When an event that you have subscribed to occurs, Documenso will send a POST request to the specified webhook URL with a payload containing information about the event.
| Field | Type | Description |
| -------------------------------------------- | --------- | ----------------------------------------------------- |
| `event` | string | The type of event that triggered the webhook. |
| `payload.id` | number | The id of the document. |
| `payload.externalId` | string? | External identifier for the document. |
| `payload.userId` | number | The id of the user who owns the document. |
| `payload.authOptions` | json? | Authentication options for the document. |
| `payload.formValues` | json? | Form values for the document. |
| `payload.visibility` | string | Document visibility (e.g., EVERYONE). |
| `payload.title` | string | The title of the document. |
| `payload.status` | string | The current status of the document. |
| `payload.documentDataId` | string | The identifier for the document data. |
| `payload.createdAt` | datetime | The creation date and time of the document. |
| `payload.updatedAt` | datetime | The last update date and time of the document. |
| `payload.completedAt` | datetime? | The completion date and time of the document. |
| `payload.deletedAt` | datetime? | The deletion date and time of the document. |
| `payload.teamId` | number? | The id of the team if document belongs to a team. |
| `payload.templateId` | number? | The id of the template if created from template. |
| `payload.source` | string | The source of the document (e.g., DOCUMENT, TEMPLATE) |
| `payload.documentMeta.id` | string | The id of the document metadata. |
| `payload.documentMeta.subject` | string? | The subject of the document. |
| `payload.documentMeta.message` | string? | The message associated with the document. |
| `payload.documentMeta.timezone` | string | The timezone setting for the document. |
| `payload.documentMeta.password` | string? | The password protection if set. |
| `payload.documentMeta.dateFormat` | string | The date format used in the document. |
| `payload.documentMeta.redirectUrl` | string? | The URL to redirect after signing. |
| `payload.documentMeta.signingOrder` | string | The signing order (e.g., PARALLEL, SEQUENTIAL). |
| `payload.documentMeta.typedSignatureEnabled` | boolean | Whether typed signatures are enabled. |
| `payload.documentMeta.language` | string | The language of the document. |
| `payload.documentMeta.distributionMethod` | string | The method of distributing the document. |
| `payload.documentMeta.emailSettings` | json? | Email notification settings. |
| `payload.Recipient[].id` | number | The id of the recipient. |
| `payload.Recipient[].documentId` | number? | The id of the document for this recipient. |
| `payload.Recipient[].templateId` | number? | The template id if from a template. |
| `payload.Recipient[].email` | string | The email address of the recipient. |
| `payload.Recipient[].name` | string | The name of the recipient. |
| `payload.Recipient[].token` | string | The unique token for this recipient. |
| `payload.Recipient[].documentDeletedAt` | datetime? | When the document was deleted for this recipient. |
| `payload.Recipient[].expired` | datetime? | When the recipient's access expired. |
| `payload.Recipient[].signedAt` | datetime? | When the recipient signed the document. |
| `payload.Recipient[].authOptions` | json? | Authentication options for this recipient. |
| `payload.Recipient[].signingOrder` | number? | The order in which this recipient should sign. |
| `payload.Recipient[].rejectionReason` | string? | The reason if the recipient rejected the document. |
| `payload.Recipient[].role` | string | The role of the recipient (e.g., SIGNER, VIEWER). |
| `payload.Recipient[].readStatus` | string | Whether the recipient has read the document. |
| `payload.Recipient[].signingStatus` | string | The signing status of this recipient. |
| `payload.Recipient[].sendStatus` | string | The sending status for this recipient. |
| `createdAt` | datetime | The creation date and time of the webhook event. |
| `webhookEndpoint` | string | The endpoint URL where the webhook is sent. |
## Example payloads
@ -104,9 +115,11 @@ Example payload for the `document.created` event:
"event": "DOCUMENT_CREATED",
"payload": {
"id": 10,
"externalId": null,
"userId": 1,
"authOptions": null,
"formValues": null,
"visibility": "EVERYONE",
"title": "documenso.pdf",
"status": "DRAFT",
"documentDataId": "hs8qz1ktr9204jn7mg6c5dxy0",
@ -114,7 +127,43 @@ Example payload for the `document.created` event:
"updatedAt": "2024-04-22T11:44:43.341Z",
"completedAt": null,
"deletedAt": null,
"teamId": null
"teamId": null,
"templateId": null,
"source": "DOCUMENT",
"documentMeta": {
"id": "doc_meta_123",
"subject": "Please sign this document",
"message": "Hello, please review and sign this document.",
"timezone": "UTC",
"password": null,
"dateFormat": "MM/DD/YYYY",
"redirectUrl": null,
"signingOrder": "PARALLEL",
"typedSignatureEnabled": true,
"language": "en",
"distributionMethod": "EMAIL",
"emailSettings": null
},
"Recipient": [
{
"id": 52,
"documentId": 10,
"templateId": null,
"email": "signer@documenso.com",
"name": "John Doe",
"token": "vbT8hi3jKQmrFP_LN1WcS",
"documentDeletedAt": null,
"expired": null,
"signedAt": null,
"authOptions": null,
"signingOrder": 1,
"rejectionReason": null,
"role": "SIGNER",
"readStatus": "NOT_OPENED",
"signingStatus": "NOT_SIGNED",
"sendStatus": "NOT_SENT"
}
]
},
"createdAt": "2024-04-22T11:44:44.779Z",
"webhookEndpoint": "https://mywebhooksite.com/mywebhook"
@ -128,9 +177,11 @@ Example payload for the `document.sent` event:
"event": "DOCUMENT_SENT",
"payload": {
"id": 10,
"externalId": null,
"userId": 1,
"authOptions": null,
"formValues": null,
"visibility": "EVERYONE",
"title": "documenso.pdf",
"status": "PENDING",
"documentDataId": "hs8qz1ktr9204jn7mg6c5dxy0",
@ -139,6 +190,22 @@ Example payload for the `document.sent` event:
"completedAt": null,
"deletedAt": null,
"teamId": null,
"templateId": null,
"source": "DOCUMENT",
"documentMeta": {
"id": "doc_meta_123",
"subject": "Please sign this document",
"message": "Hello, please review and sign this document.",
"timezone": "UTC",
"password": null,
"dateFormat": "MM/DD/YYYY",
"redirectUrl": null,
"signingOrder": "PARALLEL",
"typedSignatureEnabled": true,
"language": "en",
"distributionMethod": "EMAIL",
"emailSettings": null
},
"Recipient": [
{
"id": 52,
@ -147,12 +214,12 @@ Example payload for the `document.sent` event:
"email": "signer2@documenso.com",
"name": "Signer 2",
"token": "vbT8hi3jKQmrFP_LN1WcS",
"documentDeletedAt": null,
"expired": null,
"signedAt": null,
"authOptions": {
"accessAuth": null,
"actionAuth": null
},
"authOptions": null,
"signingOrder": 1,
"rejectionReason": null,
"role": "VIEWER",
"readStatus": "NOT_OPENED",
"signingStatus": "NOT_SIGNED",
@ -165,12 +232,12 @@ Example payload for the `document.sent` event:
"email": "signer1@documenso.com",
"name": "Signer 1",
"token": "HkrptwS42ZBXdRKj1TyUo",
"documentDeletedAt": null,
"expired": null,
"signedAt": null,
"authOptions": {
"accessAuth": null,
"actionAuth": null
},
"authOptions": null,
"signingOrder": 2,
"rejectionReason": null,
"role": "SIGNER",
"readStatus": "NOT_OPENED",
"signingStatus": "NOT_SIGNED",
@ -190,9 +257,11 @@ Example payload for the `document.opened` event:
"event": "DOCUMENT_OPENED",
"payload": {
"id": 10,
"externalId": null,
"userId": 1,
"authOptions": null,
"formValues": null,
"visibility": "EVERYONE",
"title": "documenso.pdf",
"status": "PENDING",
"documentDataId": "hs8qz1ktr9204jn7mg6c5dxy0",
@ -201,6 +270,22 @@ Example payload for the `document.opened` event:
"completedAt": null,
"deletedAt": null,
"teamId": null,
"templateId": null,
"source": "DOCUMENT",
"documentMeta": {
"id": "doc_meta_123",
"subject": "Please sign this document",
"message": "Hello, please review and sign this document.",
"timezone": "UTC",
"password": null,
"dateFormat": "MM/DD/YYYY",
"redirectUrl": null,
"signingOrder": "PARALLEL",
"typedSignatureEnabled": true,
"language": "en",
"distributionMethod": "EMAIL",
"emailSettings": null
},
"Recipient": [
{
"id": 52,
@ -209,24 +294,18 @@ Example payload for the `document.opened` event:
"email": "signer2@documenso.com",
"name": "Signer 2",
"token": "vbT8hi3jKQmrFP_LN1WcS",
"documentDeletedAt": null,
"expired": null,
"signedAt": null,
"authOptions": {
"accessAuth": null,
"actionAuth": null
},
"authOptions": null,
"signingOrder": 1,
"rejectionReason": null,
"role": "VIEWER",
"readStatus": "OPENED",
"signingStatus": "NOT_SIGNED",
"sendStatus": "SENT"
}
],
"documentData": {
"id": "hs8qz1ktr9204jn7mg6c5dxy0",
"type": "S3_PATH",
"data": "9753/xzqrshtlpokm/documenso.pdf",
"initialData": "9753/xzqrshtlpokm/documenso.pdf"
}
]
},
"createdAt": "2024-04-22T11:50:26.174Z",
"webhookEndpoint": "https://mywebhooksite.com/mywebhook"
@ -240,9 +319,11 @@ Example payload for the `document.signed` event:
"event": "DOCUMENT_SIGNED",
"payload": {
"id": 10,
"externalId": null,
"userId": 1,
"authOptions": null,
"formValues": null,
"visibility": "EVERYONE",
"title": "documenso.pdf",
"status": "COMPLETED",
"documentDataId": "hs8qz1ktr9204jn7mg6c5dxy0",
@ -251,6 +332,22 @@ Example payload for the `document.signed` event:
"completedAt": "2024-04-22T11:52:05.707Z",
"deletedAt": null,
"teamId": null,
"templateId": null,
"source": "DOCUMENT",
"documentMeta": {
"id": "doc_meta_123",
"subject": "Please sign this document",
"message": "Hello, please review and sign this document.",
"timezone": "UTC",
"password": null,
"dateFormat": "MM/DD/YYYY",
"redirectUrl": null,
"signingOrder": "PARALLEL",
"typedSignatureEnabled": true,
"language": "en",
"distributionMethod": "EMAIL",
"emailSettings": null
},
"Recipient": [
{
"id": 51,
@ -259,12 +356,15 @@ Example payload for the `document.signed` event:
"email": "signer1@documenso.com",
"name": "Signer 1",
"token": "HkrptwS42ZBXdRKj1TyUo",
"documentDeletedAt": null,
"expired": null,
"signedAt": "2024-04-22T11:52:05.688Z",
"authOptions": {
"accessAuth": null,
"actionAuth": null
},
"signingOrder": 1,
"rejectionReason": null,
"role": "SIGNER",
"readStatus": "OPENED",
"signingStatus": "SIGNED",
@ -284,9 +384,11 @@ Example payload for the `document.completed` event:
"event": "DOCUMENT_COMPLETED",
"payload": {
"id": 10,
"externalId": null,
"userId": 1,
"authOptions": null,
"formValues": null,
"visibility": "EVERYONE",
"title": "documenso.pdf",
"status": "COMPLETED",
"documentDataId": "hs8qz1ktr9204jn7mg6c5dxy0",
@ -295,11 +397,21 @@ Example payload for the `document.completed` event:
"completedAt": "2024-04-22T11:52:05.707Z",
"deletedAt": null,
"teamId": null,
"documentData": {
"id": "hs8qz1ktr9204jn7mg6c5dxy0",
"type": "S3_PATH",
"data": "bk9p1h7x0s3m/documenso-signed.pdf",
"initialData": "9753/xzqrshtlpokm/documenso.pdf"
"templateId": null,
"source": "DOCUMENT",
"documentMeta": {
"id": "doc_meta_123",
"subject": "Please sign this document",
"message": "Hello, please review and sign this document.",
"timezone": "UTC",
"password": null,
"dateFormat": "MM/DD/YYYY",
"redirectUrl": null,
"signingOrder": "PARALLEL",
"typedSignatureEnabled": true,
"language": "en",
"distributionMethod": "EMAIL",
"emailSettings": null
},
"Recipient": [
{
@ -309,12 +421,15 @@ Example payload for the `document.completed` event:
"email": "signer2@documenso.com",
"name": "Signer 2",
"token": "vbT8hi3jKQmrFP_LN1WcS",
"documentDeletedAt": null,
"expired": null,
"signedAt": "2024-04-22T11:51:10.055Z",
"authOptions": {
"accessAuth": null,
"actionAuth": null
},
"signingOrder": 1,
"rejectionReason": null,
"role": "VIEWER",
"readStatus": "OPENED",
"signingStatus": "SIGNED",
@ -327,12 +442,15 @@ Example payload for the `document.completed` event:
"email": "signer1@documenso.com",
"name": "Signer 1",
"token": "HkrptwS42ZBXdRKj1TyUo",
"documentDeletedAt": null,
"expired": null,
"signedAt": "2024-04-22T11:52:05.688Z",
"authOptions": {
"accessAuth": null,
"actionAuth": null
},
"signingOrder": 2,
"rejectionReason": null,
"role": "SIGNER",
"readStatus": "OPENED",
"signingStatus": "SIGNED",
@ -345,6 +463,71 @@ Example payload for the `document.completed` event:
}
```
Example payload for the `document.rejected` event:
```json
{
"event": "DOCUMENT_REJECTED",
"payload": {
"id": 10,
"externalId": null,
"userId": 1,
"authOptions": null,
"formValues": null,
"visibility": "EVERYONE",
"title": "documenso.pdf",
"status": "PENDING",
"documentDataId": "hs8qz1ktr9204jn7mg6c5dxy0",
"createdAt": "2024-04-22T11:44:43.341Z",
"updatedAt": "2024-04-22T11:48:07.569Z",
"completedAt": null,
"deletedAt": null,
"teamId": null,
"templateId": null,
"source": "DOCUMENT",
"documentMeta": {
"id": "doc_meta_123",
"subject": "Please sign this document",
"message": "Hello, please review and sign this document.",
"timezone": "UTC",
"password": null,
"dateFormat": "MM/DD/YYYY",
"redirectUrl": null,
"signingOrder": "PARALLEL",
"typedSignatureEnabled": true,
"language": "en",
"distributionMethod": "EMAIL",
"emailSettings": null
},
"Recipient": [
{
"id": 52,
"documentId": 10,
"templateId": null,
"email": "signer@documenso.com",
"name": "Signer",
"token": "vbT8hi3jKQmrFP_LN1WcS",
"documentDeletedAt": null,
"expired": null,
"signedAt": "2024-04-22T11:48:07.569Z",
"authOptions": {
"accessAuth": null,
"actionAuth": null
},
"signingOrder": 1,
"rejectionReason": "I do not agree with the terms",
"role": "SIGNER",
"readStatus": "OPENED",
"signingStatus": "REJECTED",
"sendStatus": "SENT"
}
]
},
"createdAt": "2024-04-22T11:48:07.945Z",
"webhookEndpoint": "https://mywebhooksite.com/mywebhook"
}
```
## Availability
Webhooks are available to individual users and teams.

View File

@ -10,6 +10,7 @@
"signing-documents": "Signing Documents",
"templates": "Templates",
"direct-links": "Direct Signing Links",
"teams": "Teams",
"-- Legal Overview": {
"type": "separator",
"title": "Legal Overview"

View File

@ -0,0 +1,6 @@
{
"preferences": "Preferences",
"document-visibility": "Document Visibility",
"sender-details": "Email Sender Details",
"branding-preferences": "Branding Preferences"
}

View File

@ -0,0 +1,16 @@
---
title: Branding Preferences
description: Learn how to set the branding preferences for your team account.
---
# Branding Preferences
You can set the branding preferences for your team account by going to the **Branding Preferences** tab in the team's settings dashboard.
![A screenshot of the team's branding preferences page](/teams/team-branding-preferences.webp)
On this page, you can:
- **Upload a Logo** - Upload your team's logo to be displayed instead of the default Documenso logo.
- **Set the Brand Website** - Enter the URL of your team's website to be displayed in the email communications sent by the team.
- **Add Additional Brand Details** - You can add additional information to display at the bottom of the emails sent by the team. This can include contact information, social media links, and other relevant details.

View File

@ -0,0 +1,45 @@
---
title: Document Visibility
description: Learn how to control the visibility of your team documents.
---
import { Callout } from 'nextra/components';
# Team's Document Visibility
The default document visibility option allows you to control who can view and access the documents uploaded to your team account. The document visibility can be set to one of the following options:
- **Everyone** - The document is visible to all team members.
- **Managers and above** - The document is visible to team members with the role of _Manager or above_ and _Admin_.
- **Admin only** - The document is only visible to the team's admins.
![A screenshot of the document visibility selector from the team's global preferences page](/teams/team-preferences-document-visibility.webp)
The default document visibility is set to "_EVERYONE_" by default. You can change this setting by going to the [team's general preferences page](/users/teams/preferences) and selecting a different visibility option.
<Callout type="warning">
If the team member uploading the document has a role lower than the default document visibility,
the document visibility will be set to a lower visibility level matching the team member's role.
</Callout>
Here's how it works:
- If a user with the "_Member_" role creates a document and the default document visibility is set to "_Admin_" or "_Managers and above_", the document's visibility is set to "_Everyone_".
- If a user with the "_Manager_" role creates a document and the default document visibility is set to "_Admin_", the document's visibility is set to "_Managers and above_".
- Otherwise, the document's visibility is set to the default document visibility.
You can change the visibility of a document at any time by editing the document and selecting a different visibility option.
![A screenshot of the Documenso's document editor page where you can update the document visibility](/teams/document-visibility-settings.webp)
<Callout type="warning">
Updating the default document visibility in the team's general settings will not affect the
visibility of existing documents. You will need to update the visibility of each document
individually.
</Callout>
## A Note on Document Access
The `document owner` (the user who created the document) always has access to the document, regardless of the document's visibility settings. This means that even if a document is set to "Admins only", the document owner can still view and edit the document.
The `recipient` (the user who receives the document for signature, approval, etc.) also has access to the document, regardless of the document's visibility settings. This means that even if a document is set to "Admins only", the recipient can still view and sign the document.

View File

@ -0,0 +1,19 @@
---
title: Preferences
description: Learn how to manage your team's global preferences.
---
# Preferences
You can manage your team's global preferences by clicking on the **Preferences** tab in the team's settings dashboard.
![A screenshot of the team's global preferences page](/teams/team-preferences.webp)
The preferences page allows you to update the following settings:
- **Document Visibility** - Set the default visibility of the documents created by team members. Learn more about [document visibility](/users/teams/document-visibility).
- **Default Document Language** - This setting allows you to set the default language for the documents uploaded in the team account. The default language is used as the default language in the email communications with the document recipients. You can change the language for individual documents when uploading them.
- **Sender Details** - Set whether the sender's name should be included in the emails sent by the team. Learn more about [sender details](/users/teams/sender-details).
- **Typed Signature** - It controls whether the document recipients can sign the documents with a typed signature or not. If enabled, the recipients can sign the document using either a drawn or a typed signature. If disabled, the recipients can only sign the documents usign a drawn signature. This setting can also be changed for individual documents when uploading them.
- **Include the Signing Certificate** - This setting controls whether the signing certificate should be included in the signed documents. If enabled, the signing certificate is included in the signed documents. If disabled, the signing certificate is not included in the signed documents. Regardless of this setting, the signing certificate is always available in the document's audit log page.
- **Branding Preferences** - Set the branding preferences and defaults for the team account. Learn more about [branding preferences](/users/teams/branding-preferences).

View File

@ -0,0 +1,14 @@
---
title: Email Sender Details
description: Learn how to update the sender details for your team's email notifications.
---
## Sender Details
If the **Sender Details** setting is enabled, the emails sent by the team will include the sender's name. The email will say:
> "Example User" on behalf of "Example Team" has invited you to sign "document.pdf"
If the **Sender Details** setting is disabled, the emails sent by the team will not include the sender's name. The email will say:
> "Example Team" has invited you to sign "document.pdf"

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

View File

@ -0,0 +1,82 @@
---
title: Cal.com Chooses Documenso for DPA and BAA Scalability and Compliance
description: Learn how Cal.com scales their Data Processing Agreement (DPA) and Business Associate Agreement (BAA) processes with Documensos open source platform as they grow.
authorName: 'Timur Ercan'
authorImage: '/blog/blog-author-timur.jpeg'
authorRole: 'Co-Founder'
date: 2024-10-11
tags:
- Customer Story
- Open Startup
- Open Source
---
<figure>
<MdxNextImage
src="/blog/cal2.png"
width="1260"
height="630"
alt="Scheduling Infrastructure for Everyone"
/>
<figcaption className="text-center">
Scheduling Infrastructure for Everyone.
</figcaption>
</figure>
TL;DR: Cal.com uses Documensos template direct links to facilitate low-friction compliance paperwork, enhancing scalability and user experience.
## Cal.com The Most Public Private Company
[Cal.com](Cal.com) is an open source company that needs no introduction. Founded in 2021 by Bailey Pumfleet and Peer Richelsen, it quickly evolved from an open source alternative to the widespread but limited scheduling platform Calendly into the internets most beloved scheduling solution. Starting with just two founders, Cal.com has grown into a team of 22, facilitating millions of bookings per year for its ever-growing user and customer base.
Their commitment to transparency is evident as they follow the [open startup movement](https://cal.com/open), opening up not only their source code but also providing insights into their business operations. Their Commercial Open Source Software (COSS) model, combining a company and an open source project, has inspired a whole cohort of startups joining the space—not least of which is Documenso.
## The Need
At this point, Cal.com serves customers of all sizes, from single users to large enterprises. To provide the best product for their customers, they are certified for SOC 2, HIPAA, GDPR, and many other compliance regulations. One challenge that comes with this is the increasing number of waivers that need to be signed when onboarding customers. Business Associate Agreements (BAAs) and Data Processing Agreements (DPAs) are two of the more commonly known examples. To get these signed with minimal effort for both sides, they were looking for a solution to handle these at scale.
> We love open source.
— Peer Richelsen, Co-Founder, Cal.com
Being an open source company, they also prefer open source in their vendors—for both the shared philosophy and the higher level of trust. The goal was to integrate signing into the checkout process as seamlessly as possible.
## The Solution
<figure>
<MdxNextImage
src="/blog/cal.png"
width="1260"
height="630"
alt="Cal.com direct link template to sign a DPA"
/>
<figcaption className="text-center">
Sign a DPA with Cal by clicking a link anytime.
</figcaption>
</figure>
Documenso offers exactly this solution through direct link templates, enabling Cal.com to:
- Provide Immediate Access: Customers can access and sign necessary compliance documents through direct links at any time.
- Enhance User Experience: Users are immediately forwarded to onboarding after signing.
- Ensure Easy Access: The documents are stored within the companys team account, allowing easy access for anyone who needs them.
Direct Link templates can also easily be embedded, using the [Documenso widget](https://documen.so/embedded). Embedding anywhere, pre-Filling the templates and notfiying the compliance team at certain point of the flow are a few of the many option the team now has in continously enhanceing their onboard and compliance UX.
Read more about our direct link templates here: [Direct Link Signing](https://docs.documenso.com/users/direct-links).
## The Journey
Initially, Cal.coms team approached the new solution with skepticism. As Bailey reflected:
> We were intrigued but skeptical at first, as we put a lot of thought into compliance and doing things right. Documensos documentation and support showcased how their direct link templates could meet our needs while being highly compliant.
This experience highlights Documensos trust philosophy. We strive to be transparent in everything we do and let people judge for themselves. It also shows that we neither want nor get trust by default. Doing things right is a conversation worth having, especially in a space as opaque as digital signatures. It goes without saying that the whole team is hyped to have Cal.com on board and yet another open source company joining the open signing movement 🚀
If you have any questions or comments, please reach out on [Twitter / X](https://twitter.com/eltimuro) (DM open) or [Discord](https://documen.so/discord).
Thinking about switching to a modern signing platform? Reach out anytime: [https://documen.so/sales](https://documen.so/sales)
Best from Hamburg\
Timur

View File

@ -0,0 +1,85 @@
---
title: 'Customer Story Prisma: 4 Reasons why Prisma chose Documenso for Signatures'
description: We are happy to welcome Prisma, another OSS company, as a customer. Read here why they choose us.
authorName: 'Timur Ercan'
authorImage: '/blog/blog-author-timur.jpeg'
authorRole: 'Co-Founder'
date: 2024-09-26
tags:
- Prisma
- Customer Story
- Open Source
---
<figure>
<MdxNextImage
src="/blog/prisma.png"
width="1200"
height="675"
alt="Primsa Landing Page We simplify database migration, connection pooling, database queries, and readable data models."
/>
<figcaption className="text-center">
Prisma uses Documenso for collaborative team signing.
</figcaption>
</figure>
> TLDR; Prisma is now using Documenso, and [we added visibility scopes](https://docs.documenso.com/users/document-visibility)
# Prisma
Prisma is an open-source company known for its modern OSS ORM (Object-Relational Mapping) tools that simplify database interactions for developers. Their flagship product, Prisma ORM, provides a type-safe way to query databases like PostgreSQL, MySQL, and many more. With the addition of Prisma Studio, an intuitive database management interface, Prisma makes it easier and more efficient for developers to work with databases. With their new additions, Prisma Pulse and Accelerate, you can react to real-time database changes and optimize your queries. And they are completely [open source](https://github.com/prisma/prisma)!
# We choose Prisma too!
I discovered Prisma when planning the tech stack for the [first version of Documenso](https://github.com/documenso/documenso/releases/tag/0.9-developer-preview). Prisma has felt natural to use since day one and has been the base of our database architecture ever since. It's great to see them develop and grow with us.
# Why they choose us
## 1. Signature Flows
Documenso signing flows are highly configurable, designed to adapt to the needs of any document signing process. Whether you're working with different roles, varying settings, or specific delivery methods, Documenso offers the flexibility to suit your requirements. You can choose to send documents via email, share a manual link, generate a link through the API, or even use a static direct link for quick access—all while ensuring a smooth signing experience.
Additionally, you can create templates to streamline and reuse common workflows, saving valuable time. Direct link templates enable users to drive the flow themselves, providing a straightforward path for signing. For a seamless experience, Documenso also allows you to embed the signing process directly into your website, ensuring an uninterrupted, integrated workflow tailored to your needs.
## 2. Modern UX
<figure>
<MdxNextImage
src="/blog/dsux.png"
width="1200"
height="675"
alt="A completed document in Documenso, ready to download."
/>
<figcaption className="text-center">
We call Documenso's design "Happy Minimalism"
</figcaption>
</figure>
Weve crafted Documenso with a sleek, modern interface that makes it incredibly easy to use. Whether youre signing documents, managing workflows, or fine-tuning settings, its intuitive design allows you to accomplish tasks quickly and effortlessly. More than just powerful, Documenso is a pleasure to navigate—designed to be accessible to everyone, no matter their level of tech experience.
## 3. Teams
### Teamwork Makes the Dream Work
Documenso makes teamwork a breeze with its team management features. You can easily set up and organize teams, making it simple to share and manage documents and workflows together. This is a lifesaver for larger organizations or teams spread across different departments, ensuring everyone stays in sync and on track. Different visibility scopes ensure private documents stay private and others are shared for easy collaboration.
### Document Visibility
Collaboration within a team often demands different levels of access to documents. For instance, the Documenso team at Prisma needed a way to set custom visibility on some documents while keeping others accessible to everyone. To address this need, we introduced role-based visibility scopes. This feature allows teams to manage documents more effectively. They can make certain documents visible only to managers or, in special cases, restricted to admins. This ensures sensitive information stays protected while general documents remain accessible to those who need them.
Learn more about visibility scopes and [how they can benefit your team here](https://docs.documenso.com/users/document-visibility).
## 4. OSS!
As you might know, we are open-source! This means you can peek under the hood, tweak things to your liking, and even contribute to making the platform better. We love the community-driven aspect of open-source, and it aligns perfectly with our goal to keep improving and innovating with input from our users.
So, whether you're looking to streamline your document workflows or just need a solid, reliable platform, Documenso has got your back. And we're thrilled to serve another OSS company and help make the space more open.
If you have any questions or comments, please reach out on [Twitter / X](https://twitter.com/eltimuro) (DM open) or [Discord](https://documen.so/discord).
Thinking about switching to a modern signing platform? Reach out anytime: [https://documen.so/sales](https://documen.so/sales)
Best from Hamburg\
Timur

View File

@ -0,0 +1,86 @@
---
title: Go Fork Yourself
description: Curious about our take on open-source and code forking? Discover why we see forking not as a threat but as a vital part of the Open Source ecosystem.
authorName: 'Timur Ercan'
authorImage: '/blog/blog-author-timur.jpeg'
authorRole: 'Co-Founder'
date: 2024-10-03
tags:
- Culture
- Open Startup
- Open Source
---
> TLDR; At Documenso, we see OSS as co-owned by all. Forking—collaborative or not—is part of the open-source spirit.
## Freedom vs. Ownership
Recently, there has been a lot of debate on the subject of forks and the usage of OSS IP (Open Source Software Intellectual Property). While I mostly aim to stay out of these controversies (as there is no “winning”), I wanted to take this opportunity to share my views on IP and forking culture here at Documenso. I dont presume this is the ideal path, but for me, its the only path that makes sense.
What these issues show foremost, in my opinion, is that the concept of Open Source is still evolving. I have heard many say, “Open Source is clearly defined” and that there is no ambiguity anymore. That may be true on the legal side, but there are vast differences in how these rules are interpreted and lived out. Here are a few questions to illustrate the point:
1. Is it okay to use an open-source project without ever giving back?
2. Is it okay to fork (some might say copy) an OSS product and build something on top of it?
3. Are we morally obliged to fight those who provide different answers to these questions than we do?
## Embracing Forks and Collaboration
Since starting Documenso, Ive thought a lot about what it actually means to be Open Source for us. So far, it has been about openness in working with everyone, from contributors to customers and sharing our work transparently. For this, we have been richly rewarded with attention and reach. This collaborative give-and-take is what people commonly associate with being Open Source, and it seems ideal.
Yet, there are the questions mentioned above. And while these may be contentious, my take is straightforward:
1. Yes.
2. Yes.
3. No.
I say this because, to me, the principles of Open Source are rooted in freedom and collaboration. That means allowing others to use, improve, or even compete with what youve built without feeling possessive over the code. The beauty of Open Source lies in its openness—its ability to be forked, reused, and adapted by anyone.
You may answer these questions differently for your own reasons. One thing Ive found lacking in the discourse is the fact that Open Source is still being treated as socially proprietary. If its under an open-source license, you can fork it and try to improve upon the original, and theres nothing wrong with that. The same is true for closed-source startups. Yet in Open Source, theres a notion that its somehow “dirty,” even though the license explicitly allows it.
## Forking in Action: Real-World Examples
When the team behind **Node.js** disagreed with its governance and pace of development, they forked the project to create **io.js**. This wasnt seen as dirty but as a necessary push for change. In fact, the fork resulted in positive changes—better community governance and faster development—which eventually led to the merge of the two projects under the Node.js Foundation. It shows that forking can be a catalyst for improvement, not just competition.
## The Misconception of “Exploitative” Usage
However, sometimes forks dont merge back but still bring positive change. A good example is **Jenkins**, which was forked from **Hudson** over disagreements in governance after Oracle acquired Sun Microsystems. Jenkins quickly overtook Hudson in terms of community support, development, and innovation. Rather than being seen as a hostile move, the fork enabled Jenkins to become a thriving project, better aligned with the open-source ethos of collaboration and transparency. It emphasizes that forking isnt inherently exploitative; it can simply be a way to realize a projects full potential.
And then theres **MariaDB**, a fork of **MySQL**. After Oracle acquired MySQL, many in the community feared the projects open-source nature could be compromised. The fork preserved its spirit, and MariaDB has since grown to become a popular and thriving database. Its a reminder that sometimes, forking is not just acceptable—its necessary to uphold the values and freedoms of open-source software.
<figure>
<MdxNextImage
src="/blog/owncode.jpeg"
width="1200"
height="675"
alt="Meme: If everyone owns the code, no one does."
/>
<figcaption className="text-center">
Funny Meme to drive the point home.
</figcaption>
</figure>
My view is that the code is not “your” code, just as Documensos code is not “our” code. Its been co-owned by the world ever since we published the repo under AGPL V3. That is the whole point. Its finally not owned by anyone (cue the “everyone/no one” meme). Open Source is for everyone, even competitors. Yet, we are still treating the licenses as extensions of the old, proprietary world and defending perceived injustices based on that model.
> Side Note: Full compliance with all license and other legal rules is a given here.
## Documensos Approach: Co-Ownership and Community
So, if you want to fork Documenso and build a business on it, you can. Whether thats a cool thing to do is another matter. Whether you do a better job than us is also another matter (you wont). But if you do, Ill be the first to join. But why not join us from the start since you already have the upside? We exist because we believe this to be the best way forward—not because we force it.
## The Bigger Picture: Open-Source as Progress
Ive also thought a lot about question #3. I understand the impulse to fight anyone who doesnt appreciate this collaborative approach, but there is no part of this model that backs that up. You are free to “exploit” as long as its in a way that adds value. The fallacy is in considering someone else using the OSS part for their business as treason, which its not. Its the whole point.
While some might say this is theoretical and that reality is different, this is the version of Open Source on which we are building Documenso. The point here is that OSS companies must be resilient to handle forking and competition; without this resilience, an open source driven economy cant thrive. The focus on freedom and collaboration means being prepared for forks and challenges as part of the growth, not as threats.
Of course, all of this applies to Documenso, the OSS project, not Documenso Inc., the company, which is very much a privately owned, for-profit entity. However, since the goal is to scale Documenso to the entire world, there is plenty of room to see everyone as co-owners of the Open Source project rather than as competitors. In the end, Open Source is about progress through freedom. If you dont like how we run things, go fork yourself and hold us accountable. We dont own this; we just happened to start it.
> Since this article is open source as well, you are free to fork it and change it here: [https://documen.so/repo](https://documen.so/repo)
If you have any questions or comments, please reach out on [Twitter / X](https://twitter.com/eltimuro) (DM open) or [Discord](https://documen.so/discord).
Thinking about switching to a modern signing platform? Reach out anytime: [https://documen.so/sales](https://documen.so/sales)
Best from Hamburg\
Timur

View File

@ -0,0 +1,72 @@
---
title: 'Introducing Embedding Support for Documenso'
description: 'Embedding is now here! Learn how we built it and how it can be used to bring e-signing to your own applications.'
authorName: 'Lucas Smith'
authorImage: '/blog/blog-author-lucas.png'
authorRole: 'Co-Founder'
date: 2024-09-06
tags:
- Development
---
When we first launched Documenso, one of the most requested features was embedding. We knew it was important and aligned with our desire to not just be a e-signing application but to instead provide the e-signature infrastructure for the web and beyond.
With that said, we decided to hold off initially so we could focus on building a solid, well-featured core application. Looking back, this was definitely the right call. Embedding is only as good as the features behind it, and we didn't want to release something that wasn't ready to meet user and developer expectations.
Over the past year, we've been busy adding tons of new features and reaching new levels of compliance, like 21 CFR Part 11. We've also introduced [new fields](/blog/introducing-advanced-signing-fields), [built out an API](/blog/public-api), [added webhooks, integrations with Zapier](/blog/launch-week-2-day-4), and a lot more.
Now that we've laid a solid foundation, it's finally time to focus on embedding, the top-requested feature from both our users and those self-hosting our platform.
## Why Embedding Took Time
In previous projects, Ive often seen embedding built by bundling components for use in a clients website or app. This method gives users maximum flexibility for styling and behavior, while avoiding certain cross-origin issues. However, it can also introduce problems like code conflicts or performance bottlenecks. For example, third-party tools such as Google Tag Manager (GTM) or other marketing scripts can interfere with your SDK. Additionally, the SDK must remain lightweight to avoid slowing down the clients page.
For Documenso, we decided to explore a different approach. After carefully researching our options, we opted for an iframe-based solution. While iframes are typically less flexible—especially when it comes to theming or passing pre-filled data containing personally identifiable information (PII)—we identified ways to mitigate these concerns.
One of the biggest challenges was ensuring that we could pass sensitive data, like emails for pre-filling forms, without exposing PII to our server. To solve this, we used [fragment identifiers](https://developer.mozilla.org/en-US/docs/Web/API/URL/hash) in the URL, which are processed client-side and never sent in network requests. This method ensures that PII is protected and not logged by our server or any intermediate web services.
### Using the PostMessage API for Communication
To maintain a high level of interactivity, our iframes communicate with the parent window using the [postMessage API](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage). This allows us to notify the parent app when specific events occur inside the iframe, creating a more dynamic user experience and bridging the gap between our iframe-based solution and typical fat SDKs.
Additionally, props are passed into the iframe via the [fragment identifier](https://developer.mozilla.org/en-US/docs/Web/API/URL/hash) of the URL. This avoids the need for complex two-way data synchronization between the parent and child frames, making the system stable and more reliable.
### Building the Embeds with Mitosis
Given that our iframe solution is quite lightweight, we saw this as a great opportunity to experiment with [Mitosis](https://mitosis.builder.io/) which would let us do something truly special. For those unfamiliar, Mitosis is a project by Builder.io that lets you write components once and then transpile them into a variety of frameworks like React, Vue, and Svelte.
We used Mitosis to build two key components: a direct template embed and a document signing embed. The direct template allows users to use a template as if it were an evergreen document—meaning that, when someone completes the template, a new document is automatically generated. This is the use case we expect most users to adopt for embedding. For more advanced workflows, we also offer a document signing embed, which can handle multi-recipient workflows and other complex scenarios intended for use in deeper, rich integrations.
Mitosis allowed us to quickly target several popular frameworks, including [React](https://www.npmjs.com/package/@documenso/embed-react), [Preact](https://www.npmjs.com/package/@documenso/embed-preact), [Vue](https://www.npmjs.com/package/@documenso/embed-vue), [Svelte](https://www.npmjs.com/package/@documenso/embed-svelte), and [SolidJS](https://www.npmjs.com/package/@documenso/embed-solid).
I had also hoped to include Angular, but while Mitosis makes it really easy to transpile component, we still have to take care of bundling and packaging the resulting component ourselves. While the above frameworks can all be bundled using Vite.js, Angular still has it's own set of tooling that we would need to learn and use. Given this constraint we opted to put Angular on hold for now while we wait for the newer Vite.js support to mature.
### Challenges and Lessons with Mitosis and more
While our experience with Mitosis was largely positive, there were some challenges along the way. For instance, certain state properties with the same names as props caused issues during the transpilation process, leading to type errors and unexpected transpilation results with some targets.
This was also a challenge since our initial implementation of the two components had some minor separation of concerns which also resulted in some transpilation issues with some targets. We addressed this by removing the separation of concerns for now since it was mostly for show rather than out of necessity.
On top of that, packaging and publishing the embeds posed its own set of challenges, particularly given the growing complexity of JavaScript package management. Tools like [Publint](https://www.npmjs.com/package/publint) helped streamline the process by ensuring we followed best practices for both CommonJS and ESM formats.
### To the Future, The Documenso Platform
With the embedding feature now in place, we're excited to continue expanding Documenso's capabilities. Embeds are just the beginning of what we're calling the Documenso platform. Through our user research, we've learned that while many businesses appreciate having a flexible e-signature solution, they're even more interested in using our tools to build signing functionality directly into their own apps—without worrying about the technical complexities of compliance and security that come with e-signing.
Over the coming months, we'll be working on enhancing our API, strengthening integrations with tools like Zapier, and improving our webhook system. Our goal is to give users the ability to embed e-signatures and document management wherever they need it, whether that's through self-hosting or by using Documenso as a platform. We can't wait to see how our users and self-hosters leverage these new capabilities!
### Ready to Get Started?
If you're ready to embed document signing into your own app or website, check out our [Embedding Documentation](https://docs.documenso.com/developers/embedding?utm_source=blog&utm_campaign=introducing-embedding) to see how easy it is to get started. You'll find everything you need to get started today!
<video
src="/blog/introducing-embedding/embedding-demo.mp4"
className="aspect-video w-full"
autoPlay
loop
controls
/>
We're always here to help! If you have questions or need support, join our [Discord](https://documen.so/discord) or [book a demo](https://documen.so/book-a-demo). We'd love to hear how you're using Documenso or wanting to use Documenso to enhance your workflow.
Stay tuned for more updates as we continue to evolve the Documenso platform and make it even easier to bring document signing into your workflows.

View File

@ -0,0 +1,64 @@
---
title: Project Babel
description: We are announcing Project Babel - an initiative to support all languages of the world on Documenso.
authorName: 'Timur Ercan'
authorImage: '/blog/blog-author-timur.jpeg'
authorRole: 'Co-Founder'
date: 2024-11-08
tags:
- Languages
- Community
- Open Source
---
<figure>
<MdxNextImage
src="/blog/babel.png"
width="800"
height="800"
alt=""
/>
<figcaption className="text-center">The tower of Babel to add some Gravitas to this project.</figcaption>
</figure>
> TLDR; We are opening up translations to the community. Read this to add a language: https://documen.so/babel-fish
## Announcing Project Babel: Powering Documenso with Global Language Support
At Documenso, we believe that open source is more than just a software philosophy—its a way to build solutions that are open to all. Now, were happy to take that mission further with Project Babel, a community-driven initiative designed to bring worldwide language support to the Documenso platform. This project aims to enable Documenso to support as many languages as possible.
## Why Language Support Matters
We already have customers from 36 different countries and are seeing traffic from even more. When it comes to critical tools like signature platforms, having a user interface in your native language can make all the difference. No matter who and where you are, your team deserves tools that are fully accessible and intuitive. Thats why were making it our goal to support every language, and we need your help to make it happen! Were building Documenso as a truly global public commodity.
## The Vision Behind Project Babel
The goal of Project Babel is simple but bold: We want to out-ship and out-customize every other document signing tool worldwide. How? By leveraging the collective power of our global community.
Unlike closed-source software, where localization means waiting for updates from the core team, Project Babel lets anyone contribute a new language, improve an existing translation, or customize the experience to meet local cultural nuances. This flexibility isnt just a bonus—its the baseline for truly global products.
Through Project Babel, you can help make Documenso the most inclusive e-signature tool. Whether by adding a language you speak or fine-tuning existing translations, youre shaping a platform that works for everyone, everywhere.
## How You Can Contribute
Weve created a simple GitHub-based contribution flow to get started. Well improve the flow and user experience as the project progresses. As always, your contributions are highly valued.
Check out the contribution guide here: [https://documen.so/babel-fish](https://documen.so/babel-fish)
## Open Source Makes It Possible
Closed-source solutions cant keep up with the speed or depth of customization that open source offers. While other companies might take months or years to localize their products, Documenso can adapt and grow in real-time, thanks to contributions from our community. Whether its a small regional dialect or a widely spoken language, Project Babel ensures that Documenso evolves to meet the needs of people everywhere.
> More importantly, this initiative empowers users. It allows you to control your software experience, ensuring it reflects your culture, language, and unique needs.
Project Babel is more than a localization effort—its the first step toward democratizing access to highly customized software for everyone, no matter where they are or what language they speak. Were incredibly excited about this initiative, but it can only succeed with your participation. We invite you to join us in making Documenso the most linguistically inclusive platform out there.
Ready to get started? Check out the full tutorial and become part of the Babel community today! Lets build open signing for the world: https://documen.so/babel-fish
If you have any questions or comments, reach out on [Twitter / X](https://twitter.com/eltimuro) (DMs open) or [Discord](https://documen.so/discord).
Thinking about switching to a modern signing platform? Reach out anytime: [https://documen.so/sales](https://documen.so/sales)
Best from Hamburg\
Timur

View File

@ -8,6 +8,188 @@ Check out what's new in the latest version and read our thoughts on it. For more
---
# Documenso v1.8.0: Team Preferences, Signature Rejection, and Document Distribution
We're excited to announce the release of Documenso v1.8.0! This update brings powerful new features to enhance your document signing process. Here's what's new:
## 🌟 Key New Features
### 1. Team Preferences
Introducing **Team Preferences**, allowing administrators to configure settings and preferences that apply to documents across the entire team. This feature ensures consistency and simplifies management by letting you set default options, permissions, and preferences that automatically apply to all team members.
![Team Preferences](/changelog/v1_8_0/team-global-settings.jpeg)
### 2. Signature Rejection
Recipients now have the option to **reject signatures**. This feature enhances communication by allowing recipients to decline signing, providing feedback or requesting changes before the document is finalized.
<video
src="/changelog/v1_8_0/reject-document.mp4"
className="aspect-video w-full"
autoPlay
loop
controls
/>
### 3. Document Distribution Settings
With the new **Document Distribution Settings**, you have greater control over how your documents are shared. Distribute communications via our automated emails and templates or take full control using our API and your own notifications infrastructure.
## 🔧 Other Improvements
- **Support for Gmail SMTP Service**: Adds support for using Gmail as your SMTP service provider.
- **Certificate and Email Translations**: Added support for multiple languages in document certificates and emails, enhancing the experience for international users.
- **Field Movement Fixes**: Resolved issues related to moving fields within documents, improving the document preparation experience.
- **Docker Environment Update**: Improved Docker setup for smoother deployments and better environment consistency.
- **Billing Access Improvements**: Users now have uninterrupted access to billing information, simplifying account management.
- **Support Time Windows for 2FA Tokens**: Enhanced two-factor authentication by supporting time windows in 2FA tokens, improving flexibility.
## 💡 Recent Features
Don't forget to take advantage of these powerful features from our recent releases:
- **Signing Order**: Define the sequence in which recipients sign your documents for a structured signing process.
- **Document Visibility Controls**: Manage who can view your documents and at what stages, offering greater privacy and control.
- **Embedded Signing Experience**: Integrate the signing process directly into your own applications for a seamless user experience.
**👏 Thank You**
As always, we're grateful for the community's contributions and feedback. Your support helps us improve Documenso and deliver a top-notch open-source document signing solution.
We hope you enjoy the new features in Documenso v1.8.0. Happy signing!
---
# Documenso v1.7.1: Signing order and document visibility
We're excited to introduce Documenso v1.7.1, bringing you improved control over your document signing process. Here are the key updates:
## 🌟 Key New Features
### 1. Signing Order
Specify the sequence in which recipients sign your documents. This ensures a structured signing process, particularly useful for complex agreements or hierarchical approvals.
<video
src="/changelog/signing-order-demo.mp4"
className="aspect-video w-full"
autoPlay
loop
controls
/>
### 2. Document Visibility
Manage who can view your documents and when. This feature offers greater privacy and flexibility in your document sharing workflows.
<video
src="/changelog/document-visibility-demo.mp4"
className="aspect-video w-full"
autoPlay
loop
controls
/>
## 🔧 Other Improvements
- Added language switcher for easier language selection
- Support for smaller field bounds in documents
- Improved select field UX
- Enhanced template functionality for advanced fields
- Added authOptions to the API
- Various UI refinements and bug fixes
## 💡 Recent Features
Don't forget about these powerful features from our recent v1.7.0 release:
- Embedded Signing Experience
- Copy and Paste Fields
- Customizable Signature Colors
## 👏 Thank You
As always, we're grateful for our community's contributions and feedback. Your input continues to shape Documenso into the leading open-source document signing solution.
We're eager to see how these new features enhance your document workflows. Enjoy exploring Documenso v1.7.1!
---
# Documenso v1.7.0: Embedded Signing, Copy and Paste, and More
We're thrilled to announce the release of Documenso v1.7.0, packed with exciting new features and improvements that enhance document signing flexibility, user experience, and global accessibility.
We're excited to see what you'll create with this release and we'd love to hear your feedback. Let's dive into the highlights:
## 🌟 Key Features
### Embedded Signing Experience
Take your document signing to the next level with our new embedded signing feature. Now you can seamlessly integrate Documenso's signing process directly into your own website or application, providing a smooth, branded experience for your users.
<video
src="/blog/introducing-embedding/embedding-demo.mp4"
className="aspect-video w-full"
controls
/>
Check out our [Embedding documentation](https://docs.documenso.com/developers/embedding) to learn more about how to get started.
### Copy and Paste Fields
Streamline your document preparation with our new copy and paste functionality for fields. This feature allows you to quickly duplicate fields across your document, saving time and ensuring consistency in your templates.
### Customizable Signature Colors
Recipients can now select a signature color from our list of available colors, supporting workflows where specific colors are required for each recipient, location, or document.
### Enhanced Internationalization (i18n)
Following on from our last release we've now expanded our i18n support to the main web application. We haven't yet added support for any additional languages but that will be coming quickly now that we have completed the hard work of wrapping all of our content in our new i18n system.
These enhancements make Documenso more accessible to users worldwide.
## 🔧 Other Improvements
- **API Enhancements**:
- New endpoint to prefill fields via API
- Updated createFields API endpoint for more flexibility
- Automatically set public profile URL for OIDC users
- **Security and Performance**:
- Document sealing moved to a background job for improved performance
- Disable 2FA with backup codes for enhanced account recovery options
- Extended lifespan for invites and confirmations
- **User Experience**:
- Updated email templates to reflect team-specific information
- Fixed issues with dialog closing on page refresh
- Improved field editing in document templates
- **Other Items**:
- Added Elestio as a one-click deploy option
- Updated README for manual self-hosting
- New environment variable for internal webapp URL configuration
## 📚 New Content
- [Advanced fields article to help you make the most of Documenso's capabilities](/blog/introducing-advanced-signing-fields)
- [Embedding blog post to guide you through how we implemented embedding](/blog/introducing-embedding)
## 👏 Community Contributions
A big thank you to our vibrant community! This release includes contributions from several new contributors, further enriching Documenso's capabilities.
We're excited to see how you'll use these new features to streamline your document workflows. As always, we appreciate your feedback and support in making Documenso the best open-source document signing solution available.
Enjoy exploring v1.7.0!
---
# Documenso v1.6.1: Internationalization, Enhanced OIDC, and More
We're excited to announce the release of Documenso v1.6.1, which brings several improvements to enhance your document signing experience. Here are the key updates:

View File

@ -1,11 +1,11 @@
{
"name": "@documenso/marketing",
"version": "1.7.0-rc.4",
"version": "1.9.0-rc.1",
"private": true,
"license": "AGPL-3.0",
"scripts": {
"dev": "next dev -p 3001",
"build": "next build",
"build": "npm run translate:extract --prefix ../../ && turbo run translate:compile && next build",
"start": "next start -p 3001",
"lint": "next lint",
"lint:fix": "next lint --fix",
@ -34,7 +34,7 @@
"micro": "^10.0.1",
"next": "14.2.6",
"next-auth": "4.24.5",
"next-axiom": "^1.1.1",
"next-axiom": "^1.5.1",
"next-contentlayer": "^0.3.4",
"next-plausible": "^3.10.1",
"perfect-freehand": "^1.2.0",
@ -47,7 +47,7 @@
"recharts": "^2.7.2",
"sharp": "0.32.6",
"typescript": "5.2.2",
"zod": "^3.22.4"
"zod": "^3.23.8"
},
"devDependencies": {
"@lingui/loader": "^4.11.3",

View File

@ -2,7 +2,8 @@ declare namespace NodeJS {
export interface ProcessEnv {
NEXT_PUBLIC_WEBAPP_URL?: string;
NEXT_PUBLIC_MARKETING_URL?: string;
NEXT_PRIVATE_INTERNAL_WEBAPP_URL?:string;
NEXT_PRIVATE_DATABASE_URL: string;
NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID: string;

Binary file not shown.

After

Width:  |  Height:  |  Size: 467 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 692 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

View File

@ -0,0 +1,23 @@
'use client';
import Image from 'next/image';
import type { DocumentTypes } from 'contentlayer/generated';
import type { MDXComponents } from 'mdx/types';
import { useMDXComponent } from 'next-contentlayer/hooks';
const mdxComponents: MDXComponents = {
MdxNextImage: (props: { width: number; height: number; alt?: string; src: string }) => (
<Image {...props} alt={props.alt ?? ''} />
),
};
export type ContentPageContentProps = {
document: DocumentTypes;
};
export const ContentPageContent = ({ document }: ContentPageContentProps) => {
const MDXContent = useMDXComponent(document.body.code);
return <MDXContent components={mdxComponents} />;
};

View File

@ -1,12 +1,11 @@
import Image from 'next/image';
import { notFound } from 'next/navigation';
import { allDocuments } from 'contentlayer/generated';
import type { MDXComponents } from 'mdx/types';
import { useMDXComponent } from 'next-contentlayer/hooks';
import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
import { ContentPageContent } from './content';
export const dynamic = 'force-dynamic';
export const generateMetadata = ({ params }: { params: { content: string } }) => {
@ -19,19 +18,13 @@ export const generateMetadata = ({ params }: { params: { content: string } }) =>
return { title: document.title };
};
const mdxComponents: MDXComponents = {
MdxNextImage: (props: { width: number; height: number; alt?: string; src: string }) => (
<Image {...props} alt={props.alt ?? ''} />
),
};
/**
* A generic catch all page for the root level that checks for content layer documents.
*
* Will render the document if it exists, otherwise will return a 404.
*/
export default function ContentPage({ params }: { params: { content: string } }) {
setupI18nSSR();
export default async function ContentPage({ params }: { params: { content: string } }) {
await setupI18nSSR();
const post = allDocuments.find((post) => post._raw.flattenedPath === params.content);
@ -39,11 +32,9 @@ export default function ContentPage({ params }: { params: { content: string } })
notFound();
}
const MDXContent = useMDXComponent(post.body.code);
return (
<article className="prose dark:prose-invert mx-auto">
<MDXContent components={mdxComponents} />
<ContentPageContent document={post} />
</article>
);
}

View File

@ -0,0 +1,23 @@
'use client';
import Image from 'next/image';
import type { BlogPost } from 'contentlayer/generated';
import type { MDXComponents } from 'mdx/types';
import { useMDXComponent } from 'next-contentlayer/hooks';
const mdxComponents: MDXComponents = {
MdxNextImage: (props: { width: number; height: number; alt?: string; src: string }) => (
<Image {...props} alt={props.alt ?? ''} />
),
};
export type BlogPostContentProps = {
post: BlogPost;
};
export const BlogPostContent = ({ post }: BlogPostContentProps) => {
const MdxContent = useMDXComponent(post.body.code);
return <MdxContent components={mdxComponents} />;
};

View File

@ -1,16 +1,15 @@
import Image from 'next/image';
import Link from 'next/link';
import { notFound } from 'next/navigation';
import { allBlogPosts } from 'contentlayer/generated';
import { ChevronLeft } from 'lucide-react';
import type { MDXComponents } from 'mdx/types';
import { useMDXComponent } from 'next-contentlayer/hooks';
import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
import { CallToAction } from '~/components/(marketing)/call-to-action';
import { BlogPostContent } from './content';
export const dynamic = 'force-dynamic';
export const generateMetadata = ({ params }: { params: { post: string } }) => {
@ -42,14 +41,8 @@ export const generateMetadata = ({ params }: { params: { post: string } }) => {
};
};
const mdxComponents: MDXComponents = {
MdxNextImage: (props: { width: number; height: number; alt?: string; src: string }) => (
<Image {...props} alt={props.alt ?? ''} />
),
};
export default function BlogPostPage({ params }: { params: { post: string } }) {
setupI18nSSR();
export default async function BlogPostPage({ params }: { params: { post: string } }) {
await setupI18nSSR();
const post = allBlogPosts.find((post) => post._raw.flattenedPath === `blog/${params.post}`);
@ -57,8 +50,6 @@ export default function BlogPostPage({ params }: { params: { post: string } }) {
notFound();
}
const MDXContent = useMDXComponent(post.body.code);
return (
<div>
<article className="prose dark:prose-invert mx-auto py-8">
@ -87,7 +78,7 @@ export default function BlogPostPage({ params }: { params: { post: string } }) {
</div>
</div>
<MDXContent components={mdxComponents} />
<BlogPostContent post={post} />
{post.tags.length > 0 && (
<ul className="not-prose flex list-none flex-row space-x-2 px-0">

View File

@ -9,8 +9,8 @@ export const metadata: Metadata = {
title: 'Blog',
};
export default function BlogPage() {
const { i18n } = setupI18nSSR();
export default async function BlogPage() {
const { i18n } = await setupI18nSSR();
const blogPosts = allBlogPosts.sort((a, b) => {
const dateA = new Date(a.date);

View File

@ -19,10 +19,10 @@ export const TEAM_MEMBERS = [
},
{
name: 'Ephraim Atta-Duncan',
role: 'Software Engineer - Intern',
salary: 15_000,
role: 'Software Engineer - I',
salary: 60_000,
location: 'Ghana',
engagement: msg`Part-Time`,
engagement: msg`Full-Time`,
joinDate: 'June 6th, 2023',
},
{

View File

@ -131,7 +131,7 @@ const fetchEarlyAdopters = async () => {
};
export default async function OpenPage() {
setupI18nSSR();
await setupI18nSSR();
const { _ } = useLingui();

View File

@ -26,7 +26,7 @@ const fontCaveat = Caveat({
});
export default async function IndexPage() {
setupI18nSSR();
await setupI18nSSR();
const starCount = await fetch('https://api.github.com/repos/documenso/documenso', {
headers: {

View File

@ -30,8 +30,8 @@ export type PricingPageProps = {
};
};
export default function PricingPage() {
setupI18nSSR();
export default async function PricingPage() {
await setupI18nSSR();
return (
<div className="mt-6 sm:mt-12">

View File

@ -163,11 +163,13 @@ export const SinglePlayerClient = () => {
expired: null,
signedAt: null,
readStatus: 'OPENED',
rejectionReason: null,
documentDeletedAt: null,
signingStatus: 'NOT_SIGNED',
sendStatus: 'NOT_SENT',
role: 'SIGNER',
authOptions: null,
signingOrder: null,
};
const onFileDrop = async (file: File) => {

View File

@ -14,8 +14,8 @@ export const dynamic = 'force-dynamic';
// !: This entire file is a hack to get around failed prerendering of
// !: the Single Player Mode page. This regression was introduced during
// !: the upgrade of Next.js to v13.5.x.
export default function SingleplayerPage() {
setupI18nSSR();
export default async function SingleplayerPage() {
await setupI18nSSR();
return <SinglePlayerClient />;
}

View File

@ -1,7 +1,6 @@
import { Suspense } from 'react';
import { Caveat, Inter } from 'next/font/google';
import { cookies, headers } from 'next/headers';
import { AxiomWebVitals } from 'next-axiom';
import { PublicEnvScript } from 'next-runtime-env';
@ -10,8 +9,6 @@ import { FeatureFlagProvider } from '@documenso/lib/client-only/providers/featur
import { I18nClientProvider } from '@documenso/lib/client-only/providers/i18n.client';
import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
import { NEXT_PUBLIC_MARKETING_URL } from '@documenso/lib/constants/app';
import type { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
import { ZSupportedLanguageCodeSchema } from '@documenso/lib/constants/i18n';
import { getAllAnonymousFlags } from '@documenso/lib/universal/get-feature-flag';
import { TrpcProvider } from '@documenso/trpc/react';
import { cn } from '@documenso/ui/lib/utils';
@ -59,25 +56,7 @@ export function generateMetadata() {
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const flags = await getAllAnonymousFlags();
let overrideLang: (typeof SUPPORTED_LANGUAGE_CODES)[number] | undefined;
// Should be safe to remove when we upgrade NextJS.
// https://github.com/vercel/next.js/pull/65008
// Currently if the middleware sets the cookie, it's not accessible in the cookies
// during the same render.
// So we go the roundabout way of checking the header for the set-cookie value.
if (!cookies().get('i18n')) {
const setCookieValue = headers().get('set-cookie');
const i18nCookie = setCookieValue?.split(';').find((cookie) => cookie.startsWith('i18n='));
if (i18nCookie) {
const i18n = i18nCookie.split('=')[1];
overrideLang = ZSupportedLanguageCodeSchema.parse(i18n);
}
}
const { lang, i18n } = setupI18nSSR(overrideLang);
const { lang, locales, i18n } = await setupI18nSSR();
return (
<html
@ -105,7 +84,10 @@ export default async function RootLayout({ children }: { children: React.ReactNo
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<PlausibleProvider>
<TrpcProvider>
<I18nClientProvider initialLocale={lang} initialMessages={i18n.messages}>
<I18nClientProvider
initialLocaleData={{ lang, locales }}
initialMessages={i18n.messages}
>
{children}
</I18nClientProvider>
</TrpcProvider>

View File

@ -108,14 +108,21 @@ export const Carousel = () => {
return;
}
setSelectedIndex(emblaApi.selectedScrollSnap());
emblaThumbsApi.scrollTo(emblaApi.selectedScrollSnap());
const newIndex = emblaApi.selectedScrollSnap();
setSelectedIndex(newIndex);
emblaThumbsApi.scrollTo(newIndex);
resetProgress();
const currentVideo = videoRefs.current[newIndex];
if (currentVideo) {
currentVideo.currentTime = 0;
}
// moduleResolution: bundler breaks this type
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const autoplay = emblaApi.plugins()?.autoplay as unknown as AutoplayType | undefined;
const autoplay = emblaApi?.plugins()?.autoplay as unknown as AutoplayType | undefined;
if (autoplay) {
autoplay.reset();

View File

@ -1,6 +1,6 @@
'use client';
import type { HTMLAttributes } from 'react';
import { type HTMLAttributes, useState } from 'react';
import Image from 'next/image';
import Link from 'next/link';
@ -9,15 +9,15 @@ import { msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { FaXTwitter } from 'react-icons/fa6';
import { LiaDiscord } from 'react-icons/lia';
import { LuGithub } from 'react-icons/lu';
import { LuGithub, LuLanguages } from 'react-icons/lu';
import LogoImage from '@documenso/assets/logo.png';
import { cn } from '@documenso/ui/lib/utils';
import { ThemeSwitcher } from '@documenso/ui/primitives/theme-switcher';
import { I18nSwitcher } from '~/components/(marketing)/i18n-switcher';
import { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n';
// import { StatusWidgetContainer } from './status-widget-container';
import { LanguageSwitcherDialog } from '@documenso/ui/components/common/language-switcher-dialog';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import { ThemeSwitcher } from '@documenso/ui/primitives/theme-switcher';
export type FooterProps = HTMLAttributes<HTMLDivElement>;
@ -44,7 +44,9 @@ const FOOTER_LINKS = [
];
export const Footer = ({ className, ...props }: FooterProps) => {
const { _ } = useLingui();
const { _, i18n } = useLingui();
const [languageSwitcherOpen, setLanguageSwitcherOpen] = useState(false);
return (
<div className={cn('border-t py-12', className)} {...props}>
@ -97,13 +99,22 @@ export const Footer = ({ className, ...props }: FooterProps) => {
</p>
<div className="flex flex-row-reverse items-center sm:flex-row">
<I18nSwitcher className="text-muted-foreground ml-2 rounded-full font-normal sm:mr-2" />
<Button
className="text-muted-foreground ml-2 rounded-full font-normal sm:mr-2"
variant="ghost"
onClick={() => setLanguageSwitcherOpen(true)}
>
<LuLanguages className="mr-1.5 h-4 w-4" />
{SUPPORTED_LANGUAGES[i18n.locale]?.full || i18n.locale}
</Button>
<div className="flex flex-wrap">
<ThemeSwitcher />
</div>
</div>
</div>
<LanguageSwitcherDialog open={languageSwitcherOpen} setOpen={setLanguageSwitcherOpen} />
</div>
);
};

View File

@ -1,71 +0,0 @@
import { useState } from 'react';
import { msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { CheckIcon } from 'lucide-react';
import { LuLanguages } from 'react-icons/lu';
import { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n';
import { switchI18NLanguage } from '@documenso/lib/server-only/i18n/switch-i18n-language';
import { dynamicActivate } from '@documenso/lib/utils/i18n';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import {
CommandDialog,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '@documenso/ui/primitives/command';
type I18nSwitcherProps = {
className?: string;
};
export const I18nSwitcher = ({ className }: I18nSwitcherProps) => {
const { i18n, _ } = useLingui();
const [open, setOpen] = useState(false);
const [value, setValue] = useState(i18n.locale);
const setLanguage = async (lang: string) => {
setValue(lang);
setOpen(false);
await dynamicActivate(i18n, lang);
await switchI18NLanguage(lang);
};
return (
<>
<Button className={className} variant="ghost" onClick={() => setOpen(true)}>
<LuLanguages className="mr-1.5 h-4 w-4" />
{SUPPORTED_LANGUAGES[value]?.full || i18n.locale}
</Button>
<CommandDialog open={open} onOpenChange={setOpen}>
<CommandInput placeholder={_(msg`Search languages...`)} />
<CommandList>
<CommandGroup>
{Object.values(SUPPORTED_LANGUAGES).map((language) => (
<CommandItem
key={language.short}
value={language.full}
onSelect={async () => setLanguage(language.short)}
>
<CheckIcon
className={cn(
'mr-2 h-4 w-4',
value === language.short ? 'opacity-100' : 'opacity-0',
)}
/>
{SUPPORTED_LANGUAGES[language.short].full}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</CommandDialog>
</>
);
};

View File

@ -1,39 +0,0 @@
import { cookies } from 'next/headers';
import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
import { extractSupportedLanguage } from '@documenso/lib/utils/i18n';
export default function middleware(req: NextRequest) {
const lang = extractSupportedLanguage({
headers: req.headers,
cookies: cookies(),
});
const response = NextResponse.next();
response.cookies.set('i18n', lang);
return response;
}
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
* - ingest (analytics)
* - site.webmanifest
*/
{
source: '/((?!api|_next/static|_next/image|ingest|favicon|site.webmanifest).*)',
missing: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
},
],
};

40
apps/openpage-api/.gitignore vendored Normal file
View File

@ -0,0 +1,40 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# env files (can opt-in for commiting if needed)
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

View File

@ -0,0 +1 @@
# @documenso/openpage-api

View File

@ -0,0 +1,36 @@
import type { NextRequest } from 'next/server';
import cors from '@/lib/cors';
const paths = [
{ path: '/total-prs', description: 'Total GitHub Merged PRs' },
{ path: '/total-stars', description: 'Total GitHub Stars' },
{ path: '/total-forks', description: 'Total GitHub Forks' },
{ path: '/total-issues', description: 'Total GitHub Issues' },
];
export function GET(request: NextRequest) {
const url = request.nextUrl.toString();
const apis = paths.map(({ path, description }) => {
return { path: url + path, description };
});
return cors(
request,
new Response(JSON.stringify(apis), {
status: 200,
headers: {
'content-type': 'application/json',
},
}),
);
}
export function OPTIONS(request: Request) {
return cors(
request,
new Response(null, {
status: 204,
}),
);
}

View File

@ -0,0 +1,27 @@
import cors from '@/lib/cors';
import { transformData } from '@/lib/transform-data';
export async function GET(request: Request) {
const res = await fetch('https://stargrazer-live.onrender.com/api/stats');
const data = await res.json();
const transformedData = transformData({ data, metric: 'forks' });
return cors(
request,
new Response(JSON.stringify(transformedData), {
status: 200,
headers: {
'content-type': 'application/json',
},
}),
);
}
export function OPTIONS(request: Request) {
return cors(
request,
new Response(null, {
status: 204,
}),
);
}

View File

@ -0,0 +1,27 @@
import cors from '@/lib/cors';
import { transformData } from '@/lib/transform-data';
export async function GET(request: Request) {
const res = await fetch('https://stargrazer-live.onrender.com/api/stats');
const data = await res.json();
const transformedData = transformData({ data, metric: 'openIssues' });
return cors(
request,
new Response(JSON.stringify(transformedData), {
status: 200,
headers: {
'content-type': 'application/json',
},
}),
);
}
export function OPTIONS(request: Request) {
return cors(
request,
new Response(null, {
status: 204,
}),
);
}

View File

@ -0,0 +1,27 @@
import cors from '@/lib/cors';
import { transformData } from '@/lib/transform-data';
export async function GET(request: Request) {
const res = await fetch('https://stargrazer-live.onrender.com/api/stats');
const data = await res.json();
const transformedData = transformData({ data, metric: 'mergedPRs' });
return cors(
request,
new Response(JSON.stringify(transformedData), {
status: 200,
headers: {
'content-type': 'application/json',
},
}),
);
}
export function OPTIONS(request: Request) {
return cors(
request,
new Response(null, {
status: 204,
}),
);
}

View File

@ -0,0 +1,27 @@
import cors from '@/lib/cors';
import { transformData } from '@/lib/transform-data';
export async function GET(request: Request) {
const res = await fetch('https://stargrazer-live.onrender.com/api/stats');
const data = await res.json();
const transformedData = transformData({ data, metric: 'stars' });
return cors(
request,
new Response(JSON.stringify(transformedData), {
status: 200,
headers: {
'content-type': 'application/json',
},
}),
);
}
export function OPTIONS(request: Request) {
return cors(
request,
new Response(null, {
status: 204,
}),
);
}

View File

@ -0,0 +1,25 @@
import cors from '@/lib/cors';
export async function GET(request: Request) {
const res = await fetch('https://api.github.com/repos/documenso/documenso');
const { forks_count } = await res.json();
return cors(
request,
new Response(JSON.stringify({ data: forks_count }), {
status: 200,
headers: {
'content-type': 'application/json',
},
}),
);
}
export function OPTIONS(request: Request) {
return cors(
request,
new Response(null, {
status: 204,
}),
);
}

View File

@ -0,0 +1,27 @@
import cors from '@/lib/cors';
export async function GET(request: Request) {
const res = await fetch(
'https://api.github.com/search/issues?q=repo:documenso/documenso+type:issue+state:open&page=0&per_page=1',
);
const { total_count } = await res.json();
return cors(
request,
new Response(JSON.stringify({ data: total_count }), {
status: 200,
headers: {
'content-type': 'application/json',
},
}),
);
}
export function OPTIONS(request: Request) {
return cors(
request,
new Response(null, {
status: 204,
}),
);
}

View File

@ -0,0 +1,27 @@
import cors from '@/lib/cors';
export async function GET(request: Request) {
const res = await fetch(
'https://api.github.com/search/issues?q=repo:documenso/documenso/+is:pr+merged:>=2010-01-01&page=0&per_page=1',
);
const { total_count } = await res.json();
return cors(
request,
new Response(JSON.stringify({ data: total_count }), {
status: 200,
headers: {
'content-type': 'application/json',
},
}),
);
}
export function OPTIONS(request: Request) {
return cors(
request,
new Response(null, {
status: 204,
}),
);
}

View File

@ -0,0 +1,36 @@
import type { NextRequest } from 'next/server';
import cors from '@/lib/cors';
const paths = [
{ path: '/forks', description: 'GitHub Forks' },
{ path: '/stars', description: 'GitHub Stars' },
{ path: '/issues', description: 'GitHub Merged Issues' },
{ path: '/prs', description: 'GitHub Pull Requests' },
];
export function GET(request: NextRequest) {
const url = request.nextUrl.toString();
const apis = paths.map(({ path, description }) => {
return { path: url + path, description };
});
return cors(
request,
new Response(JSON.stringify(apis), {
status: 200,
headers: {
'content-type': 'application/json',
},
}),
);
}
export function OPTIONS(request: Request) {
return cors(
request,
new Response(null, {
status: 204,
}),
);
}

View File

@ -0,0 +1,25 @@
import cors from '@/lib/cors';
export async function GET(request: Request) {
const res = await fetch('https://api.github.com/repos/documenso/documenso');
const { stargazers_count } = await res.json();
return cors(
request,
new Response(JSON.stringify({ data: stargazers_count }), {
status: 200,
headers: {
'content-type': 'application/json',
},
}),
);
}
export function OPTIONS(request: Request) {
return cors(
request,
new Response(null, {
status: 204,
}),
);
}

View File

@ -0,0 +1,25 @@
import cors from '@/lib/cors';
import { getCompletedDocumentsMonthly } from '@/lib/growth/get-monthly-completed-document';
export async function GET(request: Request) {
const completedDocuments = await getCompletedDocumentsMonthly();
return cors(
request,
new Response(JSON.stringify(completedDocuments), {
status: 200,
headers: {
'content-type': 'application/json',
},
}),
);
}
export function OPTIONS(request: Request) {
return cors(
request,
new Response(null, {
status: 204,
}),
);
}

View File

@ -0,0 +1,25 @@
import cors from '@/lib/cors';
import { getUserMonthlyGrowth } from '@/lib/growth/get-user-monthly-growth';
export async function GET(request: Request) {
const monthlyUsers = await getUserMonthlyGrowth();
return cors(
request,
new Response(JSON.stringify(monthlyUsers), {
status: 200,
headers: {
'content-type': 'application/json',
},
}),
);
}
export function OPTIONS(request: Request) {
return cors(
request,
new Response(null, {
status: 204,
}),
);
}

View File

@ -0,0 +1,39 @@
import type { NextRequest } from 'next/server';
import cors from '@/lib/cors';
const paths = [
{ path: '/total-customers', description: 'Total Customers' },
{ path: '/total-users', description: 'Total Users' },
{ path: '/new-users', description: 'New Users' },
{ path: '/completed-documents', description: 'Completed Documents per Month' },
{ path: '/total-completed-documents', description: 'Total Completed Documents' },
{ path: '/signer-conversion', description: 'Signers That Signed Up' },
{ path: '/total-signer-conversion', description: 'Total Signers That Signed Up' },
];
export function GET(request: NextRequest) {
const url = request.nextUrl.toString();
const apis = paths.map(({ path, description }) => {
return { path: url + path, description };
});
return cors(
request,
new Response(JSON.stringify(apis), {
status: 200,
headers: {
'content-type': 'application/json',
},
}),
);
}
export function OPTIONS(request: Request) {
return cors(
request,
new Response(null, {
status: 204,
}),
);
}

View File

@ -0,0 +1,25 @@
import cors from '@/lib/cors';
import { getSignerConversionMonthly } from '@/lib/growth/get-signer-conversion';
export async function GET(request: Request) {
const signers = await getSignerConversionMonthly();
return cors(
request,
new Response(JSON.stringify(signers), {
status: 200,
headers: {
'content-type': 'application/json',
},
}),
);
}
export function OPTIONS(request: Request) {
return cors(
request,
new Response(null, {
status: 204,
}),
);
}

View File

@ -0,0 +1,25 @@
import cors from '@/lib/cors';
import { getCompletedDocumentsMonthly } from '@/lib/growth/get-monthly-completed-document';
export async function GET(request: Request) {
const totalCompletedDocuments = await getCompletedDocumentsMonthly('cumulative');
return cors(
request,
new Response(JSON.stringify(totalCompletedDocuments), {
status: 200,
headers: {
'content-type': 'application/json',
},
}),
);
}
export function OPTIONS(request: Request) {
return cors(
request,
new Response(null, {
status: 204,
}),
);
}

View File

@ -0,0 +1,31 @@
import cors from '@/lib/cors';
import { transformData } from '@/lib/transform-data';
export async function GET(request: Request) {
const res = await fetch('https://stargrazer-live.onrender.com/api/stats/stripe');
const EARLY_ADOPTERS_DATA = await res.json();
const transformedData = transformData({
data: EARLY_ADOPTERS_DATA,
metric: 'earlyAdopters',
});
return cors(
request,
new Response(JSON.stringify(transformedData), {
status: 200,
headers: {
'content-type': 'application/json',
},
}),
);
}
export function OPTIONS(request: Request) {
return cors(
request,
new Response(null, {
status: 204,
}),
);
}

View File

@ -0,0 +1,25 @@
import cors from '@/lib/cors';
import { getSignerConversionMonthly } from '@/lib/growth/get-signer-conversion';
export async function GET(request: Request) {
const totalSigners = await getSignerConversionMonthly('cumulative');
return cors(
request,
new Response(JSON.stringify(totalSigners), {
status: 200,
headers: {
'content-type': 'application/json',
},
}),
);
}
export function OPTIONS(request: Request) {
return cors(
request,
new Response(null, {
status: 204,
}),
);
}

View File

@ -0,0 +1,25 @@
import cors from '@/lib/cors';
import { getUserMonthlyGrowth } from '@/lib/growth/get-user-monthly-growth';
export async function GET(request: Request) {
const totalUsers = await getUserMonthlyGrowth("cumulative");
return cors(
request,
new Response(JSON.stringify(totalUsers), {
status: 200,
headers: {
'content-type': 'application/json',
},
}),
);
}
export function OPTIONS(request: Request) {
return cors(
request,
new Response(null, {
status: 204,
}),
);
}

View File

@ -0,0 +1,35 @@
import type { NextRequest } from 'next/server';
import cors from '@/lib/cors';
const paths = [
{ path: 'github', description: 'GitHub Data' },
{ path: 'community', description: 'Community Data' },
{ path: 'growth', description: 'Growth Data' },
];
export function GET(request: NextRequest) {
const url = request.nextUrl.toString();
const apis = paths.map(({ path, description }) => {
return { path: url + path, description };
});
return cors(
request,
new Response(JSON.stringify(apis), {
status: 200,
headers: {
'content-type': 'application/json',
},
}),
);
}
export function OPTIONS(request: Request) {
return cors(
request,
new Response(null, {
status: 204,
}),
);
}

View File

@ -0,0 +1,135 @@
/**
* Multi purpose CORS lib.
* Note: Based on the `cors` package in npm but using only web APIs.
* Taken from: https://github.com/vercel/examples/blob/main/edge-functions/cors/lib/cors.ts
*/
type StaticOrigin = boolean | string | RegExp | (boolean | string | RegExp)[];
type OriginFn = (origin: string | undefined, req: Request) => StaticOrigin | Promise<StaticOrigin>;
interface CorsOptions {
origin?: StaticOrigin | OriginFn;
methods?: string | string[];
allowedHeaders?: string | string[];
exposedHeaders?: string | string[];
credentials?: boolean;
maxAge?: number;
preflightContinue?: boolean;
optionsSuccessStatus?: number;
}
const defaultOptions: CorsOptions = {
origin: '*',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
preflightContinue: false,
optionsSuccessStatus: 204,
};
function isOriginAllowed(origin: string, allowed: StaticOrigin): boolean {
return Array.isArray(allowed)
? allowed.some((o) => isOriginAllowed(origin, o))
: typeof allowed === 'string'
? origin === allowed
: allowed instanceof RegExp
? allowed.test(origin)
: !!allowed;
}
function getOriginHeaders(reqOrigin: string | undefined, origin: StaticOrigin) {
const headers = new Headers();
if (origin === '*') {
headers.set('Access-Control-Allow-Origin', '*');
} else if (typeof origin === 'string') {
headers.set('Access-Control-Allow-Origin', origin);
headers.append('Vary', 'Origin');
} else {
const allowed = isOriginAllowed(reqOrigin ?? '', origin);
if (allowed && reqOrigin) {
headers.set('Access-Control-Allow-Origin', reqOrigin);
}
headers.append('Vary', 'Origin');
}
return headers;
}
async function originHeadersFromReq(req: Request, origin: StaticOrigin | OriginFn) {
const reqOrigin = req.headers.get('Origin') || undefined;
const value = typeof origin === 'function' ? await origin(reqOrigin, req) : origin;
if (!value) return;
return getOriginHeaders(reqOrigin, value);
}
function getAllowedHeaders(req: Request, allowed?: string | string[]) {
const headers = new Headers();
if (!allowed) {
allowed = req.headers.get('Access-Control-Request-Headers')!;
headers.append('Vary', 'Access-Control-Request-Headers');
} else if (Array.isArray(allowed)) {
allowed = allowed.join(',');
}
if (allowed) {
headers.set('Access-Control-Allow-Headers', allowed);
}
return headers;
}
export default async function cors(req: Request, res: Response, options?: CorsOptions) {
const opts = { ...defaultOptions, ...options };
const { headers } = res;
const originHeaders = await originHeadersFromReq(req, opts.origin ?? false);
const mergeHeaders = (v: string, k: string) => {
if (k === 'Vary') headers.append(k, v);
else headers.set(k, v);
};
// If there's no origin we won't touch the response
if (!originHeaders) return res;
originHeaders.forEach(mergeHeaders);
if (opts.credentials) {
headers.set('Access-Control-Allow-Credentials', 'true');
}
const exposed = Array.isArray(opts.exposedHeaders)
? opts.exposedHeaders.join(',')
: opts.exposedHeaders;
if (exposed) {
headers.set('Access-Control-Expose-Headers', exposed);
}
// Handle the preflight request
if (req.method === 'OPTIONS') {
if (opts.methods) {
const methods = Array.isArray(opts.methods) ? opts.methods.join(',') : opts.methods;
headers.set('Access-Control-Allow-Methods', methods);
}
getAllowedHeaders(req, opts.allowedHeaders).forEach(mergeHeaders);
if (typeof opts.maxAge === 'number') {
headers.set('Access-Control-Max-Age', String(opts.maxAge));
}
if (opts.preflightContinue) return res;
headers.set('Content-Length', '0');
return new Response(null, { status: opts.optionsSuccessStatus, headers });
}
// If we got here, it's a normal request
return res;
}
export function initCors(options?: CorsOptions) {
return async (req: Request, res: Response) => cors(req, res, options);
}

View File

@ -0,0 +1,43 @@
import { DateTime } from 'luxon';
import { kyselyPrisma, sql } from '@documenso/prisma';
import { DocumentStatus } from '@documenso/prisma/client';
export const getCompletedDocumentsMonthly = async (type: 'count' | 'cumulative' = 'count') => {
const qb = kyselyPrisma.$kysely
.selectFrom('Document')
.select(({ fn }) => [
fn<Date>('DATE_TRUNC', [sql.lit('MONTH'), 'Document.updatedAt']).as('month'),
fn.count('id').as('count'),
fn
.sum(fn.count('id'))
// Feels like a bug in the Kysely extension but I just can not do this orderBy in a type-safe manner
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
.over((ob) => ob.orderBy(fn('DATE_TRUNC', [sql.lit('MONTH'), 'Document.updatedAt']) as any))
.as('cume_count'),
])
.where(() => sql`"Document"."status" = ${DocumentStatus.COMPLETED}::"DocumentStatus"`)
.groupBy('month')
.orderBy('month', 'desc')
.limit(12);
const result = await qb.execute();
const transformedData = {
labels: result.map((row) => DateTime.fromJSDate(row.month).toFormat('MMM yyyy')).reverse(),
datasets: [
{
label: type === 'count' ? 'Completed Documents per Month' : 'Total Completed Documents',
data: result
.map((row) => (type === 'count' ? Number(row.count) : Number(row.cume_count)))
.reverse(),
},
],
};
return transformedData;
};
export type GetCompletedDocumentsMonthlyResult = Awaited<
ReturnType<typeof getCompletedDocumentsMonthly>
>;

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