Compare commits

...

15 Commits

Author SHA1 Message Date
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
49 changed files with 1382 additions and 200 deletions

View File

@ -500,8 +500,35 @@ Now you can make a `POST` request to the `/api/v1/documents/{documentId}/fields`
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

@ -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,7 +10,6 @@
"signing-documents": "Signing Documents",
"templates": "Templates",
"direct-links": "Direct Signing Links",
"document-visibility": "Document Visibility",
"teams": "Teams",
"-- Legal Overview": {
"type": "separator",

View File

@ -1,5 +1,6 @@
{
"general-settings": "General Settings",
"preferences": "Preferences",
"document-visibility": "Document Visibility",
"sender-details": "Email Sender Details"
"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

@ -13,9 +13,9 @@ The default document visibility option allows you to control who can view and ac
- **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 general settings page](/teams/team-general-settings-document-visibility-select.webp)
![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 settings page](/users/teams/general-settings) and selecting a different visibility option.
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,

View File

@ -1,15 +0,0 @@
---
title: General Settings
description: Learn how to manage your team's General settings.
---
# General Settings
You can manage your team's general settings by clicking on the **General Settings** tab in the team's settings dashboard.
![A screenshot of team's General settings page](/teams/team-general-settings.webp)
The general settings 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).
- **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).

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).

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

View File

@ -1,6 +1,6 @@
{
"name": "@documenso/marketing",
"version": "1.8.1-rc.4",
"version": "1.8.1-rc.9",
"private": true,
"license": "AGPL-3.0",
"scripts": {

View File

@ -1,6 +1,6 @@
{
"name": "@documenso/web",
"version": "1.8.1-rc.4",
"version": "1.8.1-rc.9",
"private": true,
"license": "AGPL-3.0",
"scripts": {

View File

@ -0,0 +1,169 @@
'use client';
import { useEffect, useMemo, useState, useTransition } from 'react';
import { msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { ChevronDownIcon as CaretSortIcon, Loader } from 'lucide-react';
import { useDebouncedValue } from '@documenso/lib/client-only/hooks/use-debounced-value';
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
import { DataTable } from '@documenso/ui/primitives/data-table';
import { DataTablePagination } from '@documenso/ui/primitives/data-table-pagination';
import { Input } from '@documenso/ui/primitives/input';
export type SigningVolume = {
id: number;
name: string;
signingVolume: number;
createdAt: Date;
planId: string;
};
type LeaderboardTableProps = {
signingVolume: SigningVolume[];
totalPages: number;
perPage: number;
page: number;
sortBy: 'name' | 'createdAt' | 'signingVolume';
sortOrder: 'asc' | 'desc';
};
export const LeaderboardTable = ({
signingVolume,
totalPages,
perPage,
page,
sortBy,
sortOrder,
}: LeaderboardTableProps) => {
const { _, i18n } = useLingui();
const [isPending, startTransition] = useTransition();
const updateSearchParams = useUpdateSearchParams();
const [searchString, setSearchString] = useState('');
const debouncedSearchString = useDebouncedValue(searchString, 1000);
const columns = useMemo(() => {
return [
{
header: () => (
<div
className="flex cursor-pointer items-center"
onClick={() => handleColumnSort('name')}
>
{_(msg`Name`)}
<CaretSortIcon className="ml-2 h-4 w-4" />
</div>
),
accessorKey: 'name',
cell: ({ row }) => {
return (
<div>
<a
className="text-primary underline"
href={`https://dashboard.stripe.com/subscriptions/${row.original.planId}`}
target="_blank"
>
{row.getValue('name')}
</a>
</div>
);
},
size: 250,
},
{
header: () => (
<div
className="flex cursor-pointer items-center"
onClick={() => handleColumnSort('signingVolume')}
>
{_(msg`Signing Volume`)}
<CaretSortIcon className="ml-2 h-4 w-4" />
</div>
),
accessorKey: 'signingVolume',
cell: ({ row }) => <div>{Number(row.getValue('signingVolume'))}</div>,
},
{
header: () => {
return (
<div
className="flex cursor-pointer items-center"
onClick={() => handleColumnSort('createdAt')}
>
{_(msg`Created`)}
<CaretSortIcon className="ml-2 h-4 w-4" />
</div>
);
},
accessorKey: 'createdAt',
cell: ({ row }) => i18n.date(row.original.createdAt),
},
] satisfies DataTableColumnDef<SigningVolume>[];
}, [sortOrder]);
useEffect(() => {
startTransition(() => {
updateSearchParams({
search: debouncedSearchString,
page: 1,
perPage,
sortBy,
sortOrder,
});
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [debouncedSearchString]);
const onPaginationChange = (page: number, perPage: number) => {
startTransition(() => {
updateSearchParams({
page,
perPage,
});
});
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchString(e.target.value);
};
const handleColumnSort = (column: 'name' | 'createdAt' | 'signingVolume') => {
startTransition(() => {
updateSearchParams({
sortBy: column,
sortOrder: sortOrder === 'asc' ? 'desc' : 'asc',
});
});
};
return (
<div className="relative">
<Input
className="my-6 flex flex-row gap-4"
type="text"
placeholder={_(msg`Search by name or email`)}
value={searchString}
onChange={handleChange}
/>
<DataTable
columns={columns}
data={signingVolume}
perPage={perPage}
currentPage={page}
totalPages={totalPages}
onPaginationChange={onPaginationChange}
>
{(table) => <DataTablePagination additionalInformation="VisibleCount" table={table} />}
</DataTable>
{isPending && (
<div className="absolute inset-0 flex items-center justify-center bg-white/50">
<Loader className="h-8 w-8 animate-spin text-gray-500" />
</div>
)}
</div>
);
};

View File

@ -0,0 +1,25 @@
'use server';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { isAdmin } from '@documenso/lib/next-auth/guards/is-admin';
import { getSigningVolume } from '@documenso/lib/server-only/admin/get-signing-volume';
type SearchOptions = {
search: string;
page: number;
perPage: number;
sortBy: 'name' | 'createdAt' | 'signingVolume';
sortOrder: 'asc' | 'desc';
};
export async function search({ search, page, perPage, sortBy, sortOrder }: SearchOptions) {
const { user } = await getRequiredServerComponentSession();
if (!isAdmin(user)) {
throw new Error('Unauthorized');
}
const results = await getSigningVolume({ search, page, perPage, sortBy, sortOrder });
return results;
}

View File

@ -0,0 +1,60 @@
import { Trans } from '@lingui/macro';
import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { isAdmin } from '@documenso/lib/next-auth/guards/is-admin';
import { LeaderboardTable } from './data-table-leaderboard';
import { search } from './fetch-leaderboard.actions';
type AdminLeaderboardProps = {
searchParams?: {
search?: string;
page?: number;
perPage?: number;
sortBy?: 'name' | 'createdAt' | 'signingVolume';
sortOrder?: 'asc' | 'desc';
};
};
export default async function Leaderboard({ searchParams = {} }: AdminLeaderboardProps) {
await setupI18nSSR();
const { user } = await getRequiredServerComponentSession();
if (!isAdmin(user)) {
throw new Error('Unauthorized');
}
const page = Number(searchParams.page) || 1;
const perPage = Number(searchParams.perPage) || 10;
const searchString = searchParams.search || '';
const sortBy = searchParams.sortBy || 'signingVolume';
const sortOrder = searchParams.sortOrder || 'desc';
const { leaderboard: signingVolume, totalPages } = await search({
search: searchString,
page,
perPage,
sortBy,
sortOrder,
});
return (
<div>
<h2 className="text-4xl font-semibold">
<Trans>Signing Volume</Trans>
</h2>
<div className="mt-8">
<LeaderboardTable
signingVolume={signingVolume}
totalPages={totalPages}
page={page}
perPage={perPage}
sortBy={sortBy}
sortOrder={sortOrder}
/>
</div>
</div>
);
}

View File

@ -6,7 +6,7 @@ import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { Trans } from '@lingui/macro';
import { BarChart3, FileStack, Settings, Users, Wallet2 } from 'lucide-react';
import { BarChart3, FileStack, Settings, Trophy, Users, Wallet2 } from 'lucide-react';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
@ -80,6 +80,20 @@ export const AdminNav = ({ className, ...props }: AdminNavProps) => {
</Link>
</Button>
<Button
variant="ghost"
className={cn(
'justify-start md:w-full',
pathname?.startsWith('/admin/leaderboard') && 'bg-secondary',
)}
asChild
>
<Link href="/admin/leaderboard">
<Trophy className="mr-2 h-5 w-5" />
<Trans>Leaderboard</Trans>
</Link>
</Button>
<Button
variant="ghost"
className={cn(

View File

@ -41,7 +41,7 @@ export default async function BillingSettingsPage() {
const [subscriptions, prices, primaryAccountPlanPrices] = await Promise.all([
getSubscriptionsByUserId({ userId: user.id }),
getPricesByInterval({ plan: STRIPE_PLAN_TYPE.REGULAR }),
getPricesByInterval({ plans: [STRIPE_PLAN_TYPE.REGULAR, STRIPE_PLAN_TYPE.PLATFORM] }),
getPrimaryAccountPlanPrices(),
]);

View File

@ -80,7 +80,7 @@ export default async function EmbedSignDocumentPage({ params }: EmbedSignDocumen
return (
<EmbedAuthenticateView
email={user?.email || recipient.email}
returnTo={`/embed/direct/${token}`}
returnTo={`/embed/sign/${token}`}
/>
);
}

View File

@ -78,13 +78,14 @@ async function middleware(req: NextRequest): Promise<NextResponse> {
if (req.nextUrl.pathname.startsWith('/embed')) {
const res = NextResponse.next();
const origin = req.headers.get('Origin') ?? '*';
// Allow third parties to iframe the document.
res.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.headers.set('Access-Control-Allow-Origin', '*');
res.headers.set('Content-Security-Policy', 'frame-ancestors *');
res.headers.set('Access-Control-Allow-Origin', origin);
res.headers.set('Content-Security-Policy', `frame-ancestors ${origin}`);
res.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
res.headers.set('X-Content-Type-Options', 'nosniff');
res.headers.set('X-Frame-Options', 'ALLOW-ALL');
return res;
}

8
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "@documenso/root",
"version": "1.8.1-rc.4",
"version": "1.8.1-rc.9",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@documenso/root",
"version": "1.8.1-rc.4",
"version": "1.8.1-rc.9",
"workspaces": [
"apps/*",
"packages/*"
@ -77,7 +77,7 @@
},
"apps/marketing": {
"name": "@documenso/marketing",
"version": "1.8.1-rc.4",
"version": "1.8.1-rc.9",
"license": "AGPL-3.0",
"dependencies": {
"@documenso/assets": "*",
@ -438,7 +438,7 @@
},
"apps/web": {
"name": "@documenso/web",
"version": "1.8.1-rc.4",
"version": "1.8.1-rc.9",
"license": "AGPL-3.0",
"dependencies": {
"@documenso/api": "*",

View File

@ -1,6 +1,6 @@
{
"private": true,
"version": "1.8.1-rc.4",
"version": "1.8.1-rc.9",
"scripts": {
"build": "turbo run build",
"build:web": "turbo run build --filter=@documenso/web",

View File

@ -244,18 +244,10 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
}
const dateFormat = body.meta.dateFormat
? DATE_FORMATS.find((format) => format.label === body.meta.dateFormat)
? DATE_FORMATS.find((format) => format.value === body.meta.dateFormat)
: DATE_FORMATS.find((format) => format.value === DEFAULT_DOCUMENT_DATE_FORMAT);
const timezone = body.meta.timezone
? TIME_ZONES.find((tz) => tz === body.meta.timezone)
: DEFAULT_DOCUMENT_TIME_ZONE;
const isDateFormatValid = body.meta.dateFormat
? DATE_FORMATS.some((format) => format.label === dateFormat?.label)
: true;
const isTimeZoneValid = body.meta.timezone ? TIME_ZONES.includes(String(timezone)) : true;
if (!isDateFormatValid) {
if (body.meta.dateFormat && !dateFormat) {
return {
status: 400,
body: {
@ -264,6 +256,12 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
};
}
const timezone = body.meta.timezone
? TIME_ZONES.find((tz) => tz === body.meta.timezone)
: DEFAULT_DOCUMENT_TIME_ZONE;
const isTimeZoneValid = body.meta.timezone ? TIME_ZONES.includes(String(timezone)) : true;
if (!isTimeZoneValid) {
return {
status: 400,

View File

@ -12,10 +12,10 @@ export type GetPricesByIntervalOptions = {
/**
* Filter products by their meta 'plan' attribute.
*/
plan?: STRIPE_PLAN_TYPE.COMMUNITY | STRIPE_PLAN_TYPE.REGULAR;
plans?: STRIPE_PLAN_TYPE[];
};
export const getPricesByInterval = async ({ plan }: GetPricesByIntervalOptions = {}) => {
export const getPricesByInterval = async ({ plans }: GetPricesByIntervalOptions = {}) => {
let { data: prices } = await stripe.prices.search({
query: `active:'true' type:'recurring'`,
expand: ['data.product'],
@ -27,7 +27,8 @@ export const getPricesByInterval = async ({ plan }: GetPricesByIntervalOptions =
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const product = price.product as Stripe.Product;
const filter = !plan || product.metadata?.plan === plan;
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const filter = !plans || plans.includes(product.metadata?.plan as STRIPE_PLAN_TYPE);
// Filter out prices for products that are not active.
return product.active && filter;

View File

@ -21,6 +21,7 @@ import { insertFieldInPDF } from '../../../server-only/pdf/insert-field-in-pdf';
import { normalizeSignatureAppearances } from '../../../server-only/pdf/normalize-signature-appearances';
import { triggerWebhook } from '../../../server-only/webhooks/trigger/trigger-webhook';
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../../types/document-audit-logs';
import { ZWebhookDocumentSchema } from '../../../types/webhook-payload';
import { ZRequestMetadataSchema } from '../../../universal/extract-request-metadata';
import { getFile } from '../../../universal/upload/get-file';
import { putPdfFile } from '../../../universal/upload/put-file';
@ -249,13 +250,14 @@ export const SEAL_DOCUMENT_JOB_DEFINITION = {
},
include: {
documentData: true,
documentMeta: true,
Recipient: true,
},
});
await triggerWebhook({
event: WebhookTriggerEvents.DOCUMENT_COMPLETED,
data: updatedDocument,
data: ZWebhookDocumentSchema.parse(updatedDocument),
userId: updatedDocument.userId,
teamId: updatedDocument.teamId ?? undefined,
});

View File

@ -26,6 +26,10 @@ import { extractNextAuthRequestMetadata } from '../universal/extract-request-met
import { getAuthenticatorOptions } from '../utils/authenticator';
import { ErrorCode } from './error-codes';
const useSecureCookies =
process.env.NODE_ENV === 'production' && String(process.env.NEXTAUTH_URL).startsWith('https://');
const cookiePrefix = useSecureCookies ? '__Secure-' : '';
export const NEXT_AUTH_OPTIONS: AuthOptions = {
adapter: PrismaAdapter(prisma),
secret: process.env.NEXTAUTH_SECRET ?? 'secret',
@ -431,5 +435,53 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
return true;
},
},
cookies: {
sessionToken: {
name: `${cookiePrefix}next-auth.session-token`,
options: {
httpOnly: true,
sameSite: useSecureCookies ? 'none' : 'lax',
path: '/',
secure: useSecureCookies,
},
},
callbackUrl: {
name: `${cookiePrefix}next-auth.callback-url`,
options: {
sameSite: useSecureCookies ? 'none' : 'lax',
path: '/',
secure: useSecureCookies,
},
},
csrfToken: {
// Default to __Host- for CSRF token for additional protection if using useSecureCookies
// NB: The `__Host-` prefix is stricter than the `__Secure-` prefix.
name: `${cookiePrefix}next-auth.csrf-token`,
options: {
httpOnly: true,
sameSite: useSecureCookies ? 'none' : 'lax',
path: '/',
secure: useSecureCookies,
},
},
pkceCodeVerifier: {
name: `${cookiePrefix}next-auth.pkce.code_verifier`,
options: {
httpOnly: true,
sameSite: useSecureCookies ? 'none' : 'lax',
path: '/',
secure: useSecureCookies,
},
},
state: {
name: `${cookiePrefix}next-auth.state`,
options: {
httpOnly: true,
sameSite: useSecureCookies ? 'none' : 'lax',
path: '/',
secure: useSecureCookies,
},
},
},
// Note: `events` are handled in `apps/web/src/pages/api/auth/[...nextauth].ts` to allow access to the request.
};

View File

@ -0,0 +1,148 @@
import { prisma } from '@documenso/prisma';
import { Prisma } from '@documenso/prisma/client';
export type SigningVolume = {
id: number;
name: string;
signingVolume: number;
createdAt: Date;
planId: string;
};
export type GetSigningVolumeOptions = {
search?: string;
page?: number;
perPage?: number;
sortBy?: 'name' | 'createdAt' | 'signingVolume';
sortOrder?: 'asc' | 'desc';
};
export async function getSigningVolume({
search = '',
page = 1,
perPage = 10,
sortBy = 'signingVolume',
sortOrder = 'desc',
}: GetSigningVolumeOptions) {
const whereClause = Prisma.validator<Prisma.SubscriptionWhereInput>()({
status: 'ACTIVE',
OR: [
{
User: {
OR: [
{ name: { contains: search, mode: 'insensitive' } },
{ email: { contains: search, mode: 'insensitive' } },
],
},
},
{
team: {
name: { contains: search, mode: 'insensitive' },
},
},
],
});
const orderByClause = getOrderByClause({ sortBy, sortOrder });
const [subscriptions, totalCount] = await Promise.all([
prisma.subscription.findMany({
where: whereClause,
include: {
User: {
include: {
Document: {
where: {
status: 'COMPLETED',
deletedAt: null,
},
},
},
},
team: {
include: {
document: {
where: {
status: 'COMPLETED',
deletedAt: null,
},
},
},
},
},
orderBy: orderByClause,
skip: Math.max(page - 1, 0) * perPage,
take: perPage,
}),
prisma.subscription.count({
where: whereClause,
}),
]);
const leaderboardWithVolume: SigningVolume[] = subscriptions.map((subscription) => {
const name =
subscription.User?.name || subscription.team?.name || subscription.User?.email || 'Unknown';
const userSignedDocs = subscription.User?.Document?.length || 0;
const teamSignedDocs = subscription.team?.document?.length || 0;
return {
id: subscription.id,
name,
signingVolume: userSignedDocs + teamSignedDocs,
createdAt: subscription.createdAt,
planId: subscription.planId,
};
});
return {
leaderboard: leaderboardWithVolume,
totalPages: Math.ceil(totalCount / perPage),
};
}
function getOrderByClause(options: {
sortBy: string;
sortOrder: 'asc' | 'desc';
}): Prisma.SubscriptionOrderByWithRelationInput | Prisma.SubscriptionOrderByWithRelationInput[] {
const { sortBy, sortOrder } = options;
if (sortBy === 'name') {
return [
{
User: {
name: sortOrder,
},
},
{
team: {
name: sortOrder,
},
},
];
}
if (sortBy === 'createdAt') {
return {
createdAt: sortOrder,
};
}
// Default: sort by signing volume
return [
{
User: {
Document: {
_count: sortOrder,
},
},
},
{
team: {
document: {
_count: sortOrder,
},
},
},
];
}

View File

@ -13,6 +13,7 @@ import { WebhookTriggerEvents } from '@documenso/prisma/client';
import { jobs } from '../../jobs/client';
import type { TRecipientActionAuth } from '../../types/document-auth';
import { ZWebhookDocumentSchema } from '../../types/webhook-payload';
import { getIsRecipientsTurnToSign } from '../recipient/get-is-recipient-turn';
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
import { sendPendingEmail } from './send-pending-email';
@ -203,11 +204,19 @@ export const completeDocumentWithToken = async ({
});
}
const updatedDocument = await getDocument({ token, documentId });
const updatedDocument = await prisma.document.findFirstOrThrow({
where: {
id: document.id,
},
include: {
documentMeta: true,
Recipient: true,
},
});
await triggerWebhook({
event: WebhookTriggerEvents.DOCUMENT_SIGNED,
data: updatedDocument,
data: ZWebhookDocumentSchema.parse(updatedDocument),
userId: updatedDocument.userId,
teamId: updatedDocument.teamId ?? undefined,
});

View File

@ -9,6 +9,7 @@ import { DocumentSource, DocumentVisibility, WebhookTriggerEvents } from '@docum
import type { Team, TeamGlobalSettings } from '@documenso/prisma/client';
import { TeamMemberRole } from '@documenso/prisma/client';
import { ZWebhookDocumentSchema } from '../../types/webhook-payload';
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
export type CreateDocumentOptions = {
@ -135,13 +136,27 @@ export const createDocument = async ({
}),
});
const createdDocument = await tx.document.findFirst({
where: {
id: document.id,
},
include: {
documentMeta: true,
Recipient: true,
},
});
if (!createdDocument) {
throw new Error('Document not found');
}
await triggerWebhook({
event: WebhookTriggerEvents.DOCUMENT_CREATED,
data: document,
data: ZWebhookDocumentSchema.parse(createdDocument),
userId,
teamId,
});
return document;
return createdDocument;
});
};

View File

@ -3,10 +3,13 @@ import { TRPCError } from '@trpc/server';
import { jobs } from '@documenso/lib/jobs/client';
import { prisma } from '@documenso/prisma';
import { WebhookTriggerEvents } from '@documenso/prisma/client';
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
import { ZWebhookDocumentSchema } from '../../types/webhook-payload';
import type { RequestMetadata } from '../../universal/extract-request-metadata';
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
export type RejectDocumentWithTokenOptions = {
token: string;
@ -31,6 +34,8 @@ export async function rejectDocumentWithToken({
Document: {
include: {
User: true,
Recipient: true,
documentMeta: true,
},
},
},
@ -45,8 +50,6 @@ export async function rejectDocumentWithToken({
});
}
// Add the audit log entry before updating the recipient
// Update the recipient status to rejected
const [updatedRecipient] = await prisma.$transaction([
prisma.recipient.update({
@ -88,5 +91,28 @@ export async function rejectDocumentWithToken({
},
});
// Get the updated document with all recipients
const updatedDocument = await prisma.document.findFirst({
where: {
id: document.id,
},
include: {
Recipient: true,
documentMeta: true,
},
});
if (!updatedDocument) {
throw new Error('Document not found after update');
}
// Trigger webhook for document rejection
await triggerWebhook({
event: WebhookTriggerEvents.DOCUMENT_REJECTED,
data: ZWebhookDocumentSchema.parse(updatedDocument),
userId: document.userId,
teamId: document.teamId ?? undefined,
});
return updatedRecipient;
}

View File

@ -10,6 +10,7 @@ import { DocumentStatus, RecipientRole, SigningStatus } from '@documenso/prisma/
import { WebhookTriggerEvents } from '@documenso/prisma/client';
import { signPdf } from '@documenso/signing';
import { ZWebhookDocumentSchema } from '../../types/webhook-payload';
import type { RequestMetadata } from '../../universal/extract-request-metadata';
import { getFile } from '../../universal/upload/get-file';
import { putPdfFile } from '../../universal/upload/put-file';
@ -199,13 +200,14 @@ export const sealDocument = async ({
},
include: {
documentData: true,
documentMeta: true,
Recipient: true,
},
});
await triggerWebhook({
event: WebhookTriggerEvents.DOCUMENT_COMPLETED,
data: updatedDocument,
data: ZWebhookDocumentSchema.parse(updatedDocument),
userId: document.userId,
teamId: document.teamId ?? undefined,
});

View File

@ -14,6 +14,7 @@ import { WebhookTriggerEvents } from '@documenso/prisma/client';
import { jobs } from '../../jobs/client';
import { extractDerivedDocumentEmailSettings } from '../../types/document-email';
import { ZWebhookDocumentSchema } from '../../types/webhook-payload';
import { getFile } from '../../universal/upload/get-file';
import { insertFormValuesInPdf } from '../pdf/insert-form-values-in-pdf';
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
@ -236,6 +237,7 @@ export const sendDocument = async ({
status: DocumentStatus.PENDING,
},
include: {
documentMeta: true,
Recipient: true,
},
});
@ -243,7 +245,7 @@ export const sendDocument = async ({
await triggerWebhook({
event: WebhookTriggerEvents.DOCUMENT_SENT,
data: updatedDocument,
data: ZWebhookDocumentSchema.parse(updatedDocument),
userId,
teamId,
});

View File

@ -6,8 +6,8 @@ import { ReadStatus } from '@documenso/prisma/client';
import { WebhookTriggerEvents } from '@documenso/prisma/client';
import type { TDocumentAccessAuthTypes } from '../../types/document-auth';
import { ZWebhookDocumentSchema } from '../../types/webhook-payload';
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
import { getDocumentAndRecipientByToken } from './get-document-by-token';
export type ViewedDocumentOptions = {
token: string;
@ -63,11 +63,23 @@ export const viewedDocument = async ({
});
});
const document = await getDocumentAndRecipientByToken({ token, requireAccessAuth: false });
const document = await prisma.document.findFirst({
where: {
id: documentId,
},
include: {
documentMeta: true,
Recipient: true,
},
});
if (!document) {
throw new Error('Document not found');
}
await triggerWebhook({
event: WebhookTriggerEvents.DOCUMENT_OPENED,
data: document,
data: ZWebhookDocumentSchema.parse(document),
userId: document.userId,
teamId: document.teamId ?? undefined,
});

View File

@ -31,6 +31,7 @@ import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
import type { TRecipientActionAuthTypes } from '../../types/document-auth';
import { DocumentAccessAuth, ZRecipientAuthOptionsSchema } from '../../types/document-auth';
import { ZFieldMetaSchema } from '../../types/field-meta';
import { ZWebhookDocumentSchema } from '../../types/webhook-payload';
import type { RequestMetadata } from '../../universal/extract-request-metadata';
import type { CreateDocumentAuditLogDataResponse } from '../../utils/document-audit-logs';
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
@ -591,7 +592,7 @@ export const createDocumentFromDirectTemplate = async ({
requestMetadata,
});
const updatedDocument = await prisma.document.findFirstOrThrow({
const createdDocument = await prisma.document.findFirstOrThrow({
where: {
id: documentId,
},
@ -603,9 +604,9 @@ export const createDocumentFromDirectTemplate = async ({
await triggerWebhook({
event: WebhookTriggerEvents.DOCUMENT_SIGNED,
data: updatedDocument,
userId: updatedDocument.userId,
teamId: updatedDocument.teamId ?? undefined,
data: ZWebhookDocumentSchema.parse(createdDocument),
userId: template.userId,
teamId: template.teamId ?? undefined,
});
} catch (err) {
console.error('[CREATE_DOCUMENT_FROM_DIRECT_TEMPLATE]:', err);

View File

@ -18,6 +18,7 @@ import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
import { ZRecipientAuthOptionsSchema } from '../../types/document-auth';
import type { TDocumentEmailSettings } from '../../types/document-email';
import { ZFieldMetaSchema } from '../../types/field-meta';
import { ZWebhookDocumentSchema } from '../../types/webhook-payload';
import type { RequestMetadata } from '../../universal/extract-request-metadata';
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
import {
@ -291,9 +292,23 @@ export const createDocumentFromTemplate = async ({
}),
});
const createdDocument = await tx.document.findFirst({
where: {
id: document.id,
},
include: {
documentMeta: true,
Recipient: true,
},
});
if (!createdDocument) {
throw new Error('Document not found');
}
await triggerWebhook({
event: WebhookTriggerEvents.DOCUMENT_CREATED,
data: document,
data: ZWebhookDocumentSchema.parse(createdDocument),
userId,
teamId,
});

View File

@ -60,14 +60,19 @@ msgstr "{0} von {1} Zeile(n) ausgewählt."
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:136
#: packages/lib/server-only/document/resend-document.tsx:137
msgid "{0} on behalf of {1} has invited you to {recipientActionVerb} the document \"{2}\"."
msgstr "{0} hat dich im Namen von {1} eingeladen, das Dokument \"{2}\" {recipientActionVerb}."
msgid "{0} on behalf of \"{1}\" has invited you to {recipientActionVerb} the document \"{2}\"."
msgstr ""
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:136
#: packages/lib/server-only/document/resend-document.tsx:137
#~ msgid "{0} on behalf of {1} has invited you to {recipientActionVerb} the document \"{2}\"."
#~ msgstr "{0} hat dich im Namen von {1} eingeladen, das Dokument \"{2}\" {recipientActionVerb}."
#: packages/email/template-components/template-document-invite.tsx:51
#~ msgid "{0}<0/>\"{documentName}\""
#~ msgstr "{0}<0/>\"{documentName}\""
#: packages/email/templates/document-invite.tsx:94
#: packages/email/templates/document-invite.tsx:95
msgid "{inviterName} <0>({inviterEmail})</0>"
msgstr "{inviterName} <0>({inviterEmail})</0>"
@ -87,7 +92,7 @@ msgstr "{inviterName} hat dich eingeladen, {0}<0/>\"{documentName}\""
msgid "{inviterName} has invited you to {action} {documentName}"
msgstr "{inviterName} hat dich eingeladen, {action} {documentName}"
#: packages/email/templates/document-invite.tsx:106
#: packages/email/templates/document-invite.tsx:108
msgid "{inviterName} has invited you to {action} the document \"{documentName}\"."
msgstr "{inviterName} hat Sie eingeladen, das Dokument \"{documentName}\" {action}."
@ -100,16 +105,24 @@ msgid "{inviterName} has removed you from the document<0/>\"{documentName}\""
msgstr "{inviterName} hat dich aus dem Dokument<0/>\"{documentName}\" entfernt"
#: packages/email/template-components/template-document-invite.tsx:63
msgid "{inviterName} on behalf of {teamName} has invited you to {0}"
msgstr "{inviterName} im Namen von {teamName} hat Sie eingeladen, {0}"
msgid "{inviterName} on behalf of \"{teamName}\" has invited you to {0}"
msgstr ""
#: packages/email/templates/document-invite.tsx:45
msgid "{inviterName} on behalf of \"{teamName}\" has invited you to {action} {documentName}"
msgstr ""
#: packages/email/template-components/template-document-invite.tsx:63
#~ msgid "{inviterName} on behalf of {teamName} has invited you to {0}"
#~ msgstr "{inviterName} im Namen von {teamName} hat Sie eingeladen, {0}"
#: packages/email/template-components/template-document-invite.tsx:49
#~ msgid "{inviterName} on behalf of {teamName} has invited you to {0}<0/>\"{documentName}\""
#~ msgstr "{inviterName} on behalf of {teamName} has invited you to {0}<0/>\"{documentName}\""
#: packages/email/templates/document-invite.tsx:45
msgid "{inviterName} on behalf of {teamName} has invited you to {action} {documentName}"
msgstr "{inviterName} hat dich im Namen von {teamName} eingeladen, {action} {documentName}"
#~ msgid "{inviterName} on behalf of {teamName} has invited you to {action} {documentName}"
#~ msgstr "{inviterName} hat dich im Namen von {teamName} eingeladen, {action} {documentName}"
#: packages/email/templates/team-join.tsx:67
msgid "{memberEmail} joined the following team"
@ -568,7 +581,7 @@ msgstr "Unterschrift löschen"
#: packages/ui/primitives/document-flow/add-signature.tsx:394
msgid "Click to insert field"
msgstr "Klicken, um das Feld einzufügen"
msgstr "Klicken, um das Feld auszufüllen"
#: packages/ui/primitives/document-flow/missing-signature-field-dialog.tsx:44
msgid "Close"
@ -711,7 +724,7 @@ msgid "Document created"
msgstr "Dokument erstellt"
#: packages/email/templates/document-created-from-direct-template.tsx:32
#: packages/lib/server-only/template/create-document-from-direct-template.ts:573
#: packages/lib/server-only/template/create-document-from-direct-template.ts:574
msgid "Document created from direct template"
msgstr "Dokument erstellt aus direkter Vorlage"

View File

@ -51,16 +51,16 @@ msgstr "\"{placeholderEmail}\" im Namen von \"{0}\" hat Sie eingeladen, \"Beispi
#~ msgstr "\"{teamUrl}\" hat Sie eingeladen, \"Beispieldokument\" zu unterschreiben."
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:83
msgid "({0}) has invited you to approve this document"
msgstr "({0}) hat dich eingeladen, dieses Dokument zu genehmigen"
#~ msgid "({0}) has invited you to approve this document"
#~ msgstr "({0}) hat dich eingeladen, dieses Dokument zu genehmigen"
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:80
msgid "({0}) has invited you to sign this document"
msgstr "({0}) hat dich eingeladen, dieses Dokument zu unterzeichnen"
#~ msgid "({0}) has invited you to sign this document"
#~ msgstr "({0}) hat dich eingeladen, dieses Dokument zu unterzeichnen"
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:77
msgid "({0}) has invited you to view this document"
msgstr "({0}) hat dich eingeladen, dieses Dokument zu betrachten"
#~ msgid "({0}) has invited you to view this document"
#~ msgstr "({0}) hat dich eingeladen, dieses Dokument zu betrachten"
#: apps/web/src/app/(signing)/sign/[token]/text-field.tsx:313
msgid "{0, plural, one {(1 character over)} other {(# characters over)}}"
@ -944,7 +944,7 @@ msgstr "Klicken Sie, um den Signatur-Link zu kopieren, um ihn an den Empfänger
#: apps/web/src/app/embed/direct/[[...url]]/client.tsx:456
#: apps/web/src/app/embed/sign/[[...url]]/client.tsx:335
msgid "Click to insert field"
msgstr "Klicken Sie, um das Feld einzufügen"
msgstr "Klicken Sie, um das Feld auszufüllen"
#: apps/web/src/app/(dashboard)/templates/new-template-dialog.tsx:126
#: apps/web/src/app/(dashboard)/templates/use-template-dialog.tsx:389
@ -964,7 +964,7 @@ msgstr "Schließen"
#: apps/web/src/app/embed/sign/[[...url]]/client.tsx:325
#: apps/web/src/components/forms/v2/signup.tsx:534
msgid "Complete"
msgstr "Vollständig"
msgstr "Abschließen"
#: apps/web/src/app/(signing)/sign/[token]/sign-dialog.tsx:69
msgid "Complete Approval"
@ -1206,6 +1206,7 @@ msgid "Create your account and start using state-of-the-art document signing. Op
msgstr "Erstellen Sie Ihr Konto und beginnen Sie mit dem modernen Dokumentensignieren. Offenes und schönes Signieren liegt in Ihrer Reichweite."
#: apps/web/src/app/(dashboard)/admin/documents/document-results.tsx:62
#: apps/web/src/app/(dashboard)/admin/leaderboard/data-table-leaderboard.tsx:96
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view-information.tsx:35
#: apps/web/src/app/(dashboard)/documents/data-table.tsx:54
#: apps/web/src/app/(dashboard)/settings/security/passkeys/user-passkeys-data-table.tsx:65
@ -1245,6 +1246,11 @@ msgstr "Aktuelles Passwort"
msgid "Current plan: {0}"
msgstr "Aktueller Plan: {0}"
#: apps/web/src/app/(dashboard)/admin/leaderboard/data-table-leaderboard.tsx:94
#: apps/web/src/app/(dashboard)/admin/leaderboard/leaderboard-table.tsx:94
#~ msgid "Customer Type"
#~ msgstr ""
#: apps/web/src/app/(dashboard)/settings/billing/billing-plans.tsx:28
msgid "Daily"
msgstr "Täglich"
@ -1988,6 +1994,18 @@ msgstr "Zum Eigentümer gehen"
msgid "Go to your <0>public profile settings</0> to add documents."
msgstr "Gehen Sie zu Ihren <0>öffentlichen Profileinstellungen</0>, um Dokumente hinzuzufügen."
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:107
msgid "has invited you to approve this document"
msgstr ""
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:98
msgid "has invited you to sign this document"
msgstr ""
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:89
msgid "has invited you to view this document"
msgstr ""
#: apps/web/src/app/(dashboard)/settings/profile/page.tsx:29
msgid "Here you can edit your personal details."
msgstr "Hier können Sie Ihre persönlichen Daten bearbeiten."
@ -2204,6 +2222,10 @@ msgstr "Zuletzt aktualisiert am"
msgid "Last used"
msgstr "Zuletzt verwendet"
#: apps/web/src/app/(dashboard)/admin/nav.tsx:93
msgid "Leaderboard"
msgstr ""
#: apps/web/src/components/(teams)/dialogs/leave-team-dialog.tsx:111
#: apps/web/src/components/(teams)/tables/current-user-teams-data-table.tsx:117
msgid "Leave"
@ -2411,6 +2433,7 @@ msgid "My templates"
msgstr "Meine Vorlagen"
#: apps/web/src/app/(dashboard)/admin/documents/[id]/recipient-item.tsx:148
#: apps/web/src/app/(dashboard)/admin/leaderboard/data-table-leaderboard.tsx:56
#: apps/web/src/app/(dashboard)/admin/users/[id]/page.tsx:99
#: apps/web/src/app/(dashboard)/admin/users/data-table-users.tsx:66
#: apps/web/src/app/(dashboard)/settings/security/passkeys/user-passkeys-data-table-actions.tsx:144
@ -2524,6 +2547,18 @@ msgstr "Nichts zu tun"
msgid "Number"
msgstr "Nummer"
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:103
msgid "on behalf of \"{0}\" has invited you to approve this document"
msgstr ""
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:94
msgid "on behalf of \"{0}\" has invited you to sign this document"
msgstr ""
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:85
msgid "on behalf of \"{0}\" has invited you to view this document"
msgstr ""
#: apps/web/src/components/(dashboard)/settings/webhooks/create-webhook-dialog.tsx:128
msgid "On this page, you can create a new webhook."
msgstr "Auf dieser Seite können Sie einen neuen Webhook erstellen."
@ -3105,6 +3140,7 @@ msgstr "Suchen"
msgid "Search by document title"
msgstr "Nach Dokumenttitel suchen"
#: apps/web/src/app/(dashboard)/admin/leaderboard/data-table-leaderboard.tsx:147
#: apps/web/src/app/(dashboard)/admin/users/data-table-users.tsx:144
msgid "Search by name or email"
msgstr "Nach Name oder E-Mail suchen"
@ -3369,6 +3405,11 @@ msgstr "Unterzeichnungslinks wurden für dieses Dokument erstellt."
msgid "Signing up..."
msgstr "Registrierung..."
#: apps/web/src/app/(dashboard)/admin/leaderboard/data-table-leaderboard.tsx:82
#: apps/web/src/app/(dashboard)/admin/leaderboard/page.tsx:46
msgid "Signing Volume"
msgstr ""
#: apps/web/src/app/(profile)/p/[url]/page.tsx:109
msgid "Since {0}"
msgstr "Seit {0}"
@ -3377,7 +3418,7 @@ msgstr "Seit {0}"
msgid "Site Banner"
msgstr "Website Banner"
#: apps/web/src/app/(dashboard)/admin/nav.tsx:93
#: apps/web/src/app/(dashboard)/admin/nav.tsx:107
#: apps/web/src/app/(dashboard)/admin/site-settings/page.tsx:26
msgid "Site Settings"
msgstr "Website Einstellungen"

View File

@ -55,14 +55,19 @@ msgstr "{0} of {1} row(s) selected."
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:136
#: packages/lib/server-only/document/resend-document.tsx:137
msgid "{0} on behalf of {1} has invited you to {recipientActionVerb} the document \"{2}\"."
msgstr "{0} on behalf of {1} has invited you to {recipientActionVerb} the document \"{2}\"."
msgid "{0} on behalf of \"{1}\" has invited you to {recipientActionVerb} the document \"{2}\"."
msgstr "{0} on behalf of \"{1}\" has invited you to {recipientActionVerb} the document \"{2}\"."
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:136
#: packages/lib/server-only/document/resend-document.tsx:137
#~ msgid "{0} on behalf of {1} has invited you to {recipientActionVerb} the document \"{2}\"."
#~ msgstr "{0} on behalf of {1} has invited you to {recipientActionVerb} the document \"{2}\"."
#: packages/email/template-components/template-document-invite.tsx:51
#~ msgid "{0}<0/>\"{documentName}\""
#~ msgstr "{0}<0/>\"{documentName}\""
#: packages/email/templates/document-invite.tsx:94
#: packages/email/templates/document-invite.tsx:95
msgid "{inviterName} <0>({inviterEmail})</0>"
msgstr "{inviterName} <0>({inviterEmail})</0>"
@ -82,7 +87,7 @@ msgstr "{inviterName} has invited you to {0}<0/>\"{documentName}\""
msgid "{inviterName} has invited you to {action} {documentName}"
msgstr "{inviterName} has invited you to {action} {documentName}"
#: packages/email/templates/document-invite.tsx:106
#: packages/email/templates/document-invite.tsx:108
msgid "{inviterName} has invited you to {action} the document \"{documentName}\"."
msgstr "{inviterName} has invited you to {action} the document \"{documentName}\"."
@ -95,16 +100,24 @@ msgid "{inviterName} has removed you from the document<0/>\"{documentName}\""
msgstr "{inviterName} has removed you from the document<0/>\"{documentName}\""
#: packages/email/template-components/template-document-invite.tsx:63
msgid "{inviterName} on behalf of {teamName} has invited you to {0}"
msgstr "{inviterName} on behalf of {teamName} has invited you to {0}"
msgid "{inviterName} on behalf of \"{teamName}\" has invited you to {0}"
msgstr "{inviterName} on behalf of \"{teamName}\" has invited you to {0}"
#: packages/email/templates/document-invite.tsx:45
msgid "{inviterName} on behalf of \"{teamName}\" has invited you to {action} {documentName}"
msgstr "{inviterName} on behalf of \"{teamName}\" has invited you to {action} {documentName}"
#: packages/email/template-components/template-document-invite.tsx:63
#~ msgid "{inviterName} on behalf of {teamName} has invited you to {0}"
#~ msgstr "{inviterName} on behalf of {teamName} has invited you to {0}"
#: packages/email/template-components/template-document-invite.tsx:49
#~ msgid "{inviterName} on behalf of {teamName} has invited you to {0}<0/>\"{documentName}\""
#~ msgstr "{inviterName} on behalf of {teamName} has invited you to {0}<0/>\"{documentName}\""
#: packages/email/templates/document-invite.tsx:45
msgid "{inviterName} on behalf of {teamName} has invited you to {action} {documentName}"
msgstr "{inviterName} on behalf of {teamName} has invited you to {action} {documentName}"
#~ msgid "{inviterName} on behalf of {teamName} has invited you to {action} {documentName}"
#~ msgstr "{inviterName} on behalf of {teamName} has invited you to {action} {documentName}"
#: packages/email/templates/team-join.tsx:67
msgid "{memberEmail} joined the following team"
@ -706,7 +719,7 @@ msgid "Document created"
msgstr "Document created"
#: packages/email/templates/document-created-from-direct-template.tsx:32
#: packages/lib/server-only/template/create-document-from-direct-template.ts:573
#: packages/lib/server-only/template/create-document-from-direct-template.ts:574
msgid "Document created from direct template"
msgstr "Document created from direct template"

View File

@ -46,16 +46,16 @@ msgstr "\"{placeholderEmail}\" on behalf of \"{0}\" has invited you to sign \"ex
#~ msgstr "\"{teamUrl}\" has invited you to sign \"example document\"."
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:83
msgid "({0}) has invited you to approve this document"
msgstr "({0}) has invited you to approve this document"
#~ msgid "({0}) has invited you to approve this document"
#~ msgstr "({0}) has invited you to approve this document"
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:80
msgid "({0}) has invited you to sign this document"
msgstr "({0}) has invited you to sign this document"
#~ msgid "({0}) has invited you to sign this document"
#~ msgstr "({0}) has invited you to sign this document"
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:77
msgid "({0}) has invited you to view this document"
msgstr "({0}) has invited you to view this document"
#~ msgid "({0}) has invited you to view this document"
#~ msgstr "({0}) has invited you to view this document"
#: apps/web/src/app/(signing)/sign/[token]/text-field.tsx:313
msgid "{0, plural, one {(1 character over)} other {(# characters over)}}"
@ -1201,6 +1201,7 @@ msgid "Create your account and start using state-of-the-art document signing. Op
msgstr "Create your account and start using state-of-the-art document signing. Open and beautiful signing is within your grasp."
#: apps/web/src/app/(dashboard)/admin/documents/document-results.tsx:62
#: apps/web/src/app/(dashboard)/admin/leaderboard/data-table-leaderboard.tsx:96
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view-information.tsx:35
#: apps/web/src/app/(dashboard)/documents/data-table.tsx:54
#: apps/web/src/app/(dashboard)/settings/security/passkeys/user-passkeys-data-table.tsx:65
@ -1240,6 +1241,11 @@ msgstr "Current Password"
msgid "Current plan: {0}"
msgstr "Current plan: {0}"
#: apps/web/src/app/(dashboard)/admin/leaderboard/data-table-leaderboard.tsx:94
#: apps/web/src/app/(dashboard)/admin/leaderboard/leaderboard-table.tsx:94
#~ msgid "Customer Type"
#~ msgstr "Customer Type"
#: apps/web/src/app/(dashboard)/settings/billing/billing-plans.tsx:28
msgid "Daily"
msgstr "Daily"
@ -1983,6 +1989,18 @@ msgstr "Go to owner"
msgid "Go to your <0>public profile settings</0> to add documents."
msgstr "Go to your <0>public profile settings</0> to add documents."
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:107
msgid "has invited you to approve this document"
msgstr "has invited you to approve this document"
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:98
msgid "has invited you to sign this document"
msgstr "has invited you to sign this document"
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:89
msgid "has invited you to view this document"
msgstr "has invited you to view this document"
#: apps/web/src/app/(dashboard)/settings/profile/page.tsx:29
msgid "Here you can edit your personal details."
msgstr "Here you can edit your personal details."
@ -2199,6 +2217,10 @@ msgstr "Last updated at"
msgid "Last used"
msgstr "Last used"
#: apps/web/src/app/(dashboard)/admin/nav.tsx:93
msgid "Leaderboard"
msgstr "Leaderboard"
#: apps/web/src/components/(teams)/dialogs/leave-team-dialog.tsx:111
#: apps/web/src/components/(teams)/tables/current-user-teams-data-table.tsx:117
msgid "Leave"
@ -2406,6 +2428,7 @@ msgid "My templates"
msgstr "My templates"
#: apps/web/src/app/(dashboard)/admin/documents/[id]/recipient-item.tsx:148
#: apps/web/src/app/(dashboard)/admin/leaderboard/data-table-leaderboard.tsx:56
#: apps/web/src/app/(dashboard)/admin/users/[id]/page.tsx:99
#: apps/web/src/app/(dashboard)/admin/users/data-table-users.tsx:66
#: apps/web/src/app/(dashboard)/settings/security/passkeys/user-passkeys-data-table-actions.tsx:144
@ -2519,6 +2542,18 @@ msgstr "Nothing to do"
msgid "Number"
msgstr "Number"
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:103
msgid "on behalf of \"{0}\" has invited you to approve this document"
msgstr "on behalf of \"{0}\" has invited you to approve this document"
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:94
msgid "on behalf of \"{0}\" has invited you to sign this document"
msgstr "on behalf of \"{0}\" has invited you to sign this document"
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:85
msgid "on behalf of \"{0}\" has invited you to view this document"
msgstr "on behalf of \"{0}\" has invited you to view this document"
#: apps/web/src/components/(dashboard)/settings/webhooks/create-webhook-dialog.tsx:128
msgid "On this page, you can create a new webhook."
msgstr "On this page, you can create a new webhook."
@ -3100,6 +3135,7 @@ msgstr "Search"
msgid "Search by document title"
msgstr "Search by document title"
#: apps/web/src/app/(dashboard)/admin/leaderboard/data-table-leaderboard.tsx:147
#: apps/web/src/app/(dashboard)/admin/users/data-table-users.tsx:144
msgid "Search by name or email"
msgstr "Search by name or email"
@ -3364,6 +3400,11 @@ msgstr "Signing links have been generated for this document."
msgid "Signing up..."
msgstr "Signing up..."
#: apps/web/src/app/(dashboard)/admin/leaderboard/data-table-leaderboard.tsx:82
#: apps/web/src/app/(dashboard)/admin/leaderboard/page.tsx:46
msgid "Signing Volume"
msgstr "Signing Volume"
#: apps/web/src/app/(profile)/p/[url]/page.tsx:109
msgid "Since {0}"
msgstr "Since {0}"
@ -3372,7 +3413,7 @@ msgstr "Since {0}"
msgid "Site Banner"
msgstr "Site Banner"
#: apps/web/src/app/(dashboard)/admin/nav.tsx:93
#: apps/web/src/app/(dashboard)/admin/nav.tsx:107
#: apps/web/src/app/(dashboard)/admin/site-settings/page.tsx:26
msgid "Site Settings"
msgstr "Site Settings"

View File

@ -60,14 +60,19 @@ msgstr "{0} de {1} fila(s) seleccionada."
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:136
#: packages/lib/server-only/document/resend-document.tsx:137
msgid "{0} on behalf of {1} has invited you to {recipientActionVerb} the document \"{2}\"."
msgstr "{0} en nombre de {1} te ha invitado a {recipientActionVerb} el documento \"{2}\"."
msgid "{0} on behalf of \"{1}\" has invited you to {recipientActionVerb} the document \"{2}\"."
msgstr ""
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:136
#: packages/lib/server-only/document/resend-document.tsx:137
#~ msgid "{0} on behalf of {1} has invited you to {recipientActionVerb} the document \"{2}\"."
#~ msgstr "{0} en nombre de {1} te ha invitado a {recipientActionVerb} el documento \"{2}\"."
#: packages/email/template-components/template-document-invite.tsx:51
#~ msgid "{0}<0/>\"{documentName}\""
#~ msgstr "{0}<0/>\"{documentName}\""
#: packages/email/templates/document-invite.tsx:94
#: packages/email/templates/document-invite.tsx:95
msgid "{inviterName} <0>({inviterEmail})</0>"
msgstr "{inviterName} <0>({inviterEmail})</0>"
@ -87,7 +92,7 @@ msgstr "{inviterName} te ha invitado a {0}<0/>\"{documentName}\""
msgid "{inviterName} has invited you to {action} {documentName}"
msgstr "{inviterName} te ha invitado a {action} {documentName}"
#: packages/email/templates/document-invite.tsx:106
#: packages/email/templates/document-invite.tsx:108
msgid "{inviterName} has invited you to {action} the document \"{documentName}\"."
msgstr "{inviterName} te ha invitado a {action} el documento \"{documentName}\"."
@ -100,16 +105,24 @@ msgid "{inviterName} has removed you from the document<0/>\"{documentName}\""
msgstr "{inviterName} te ha eliminado del documento<0/>\"{documentName}\""
#: packages/email/template-components/template-document-invite.tsx:63
msgid "{inviterName} on behalf of {teamName} has invited you to {0}"
msgstr "{inviterName} en nombre de {teamName} te ha invitado a {0}"
msgid "{inviterName} on behalf of \"{teamName}\" has invited you to {0}"
msgstr ""
#: packages/email/templates/document-invite.tsx:45
msgid "{inviterName} on behalf of \"{teamName}\" has invited you to {action} {documentName}"
msgstr ""
#: packages/email/template-components/template-document-invite.tsx:63
#~ msgid "{inviterName} on behalf of {teamName} has invited you to {0}"
#~ msgstr "{inviterName} en nombre de {teamName} te ha invitado a {0}"
#: packages/email/template-components/template-document-invite.tsx:49
#~ msgid "{inviterName} on behalf of {teamName} has invited you to {0}<0/>\"{documentName}\""
#~ msgstr "{inviterName} on behalf of {teamName} has invited you to {0}<0/>\"{documentName}\""
#: packages/email/templates/document-invite.tsx:45
msgid "{inviterName} on behalf of {teamName} has invited you to {action} {documentName}"
msgstr "{inviterName} en nombre de {teamName} te ha invitado a {action} {documentName}"
#~ msgid "{inviterName} on behalf of {teamName} has invited you to {action} {documentName}"
#~ msgstr "{inviterName} en nombre de {teamName} te ha invitado a {action} {documentName}"
#: packages/email/templates/team-join.tsx:67
msgid "{memberEmail} joined the following team"
@ -711,7 +724,7 @@ msgid "Document created"
msgstr "Documento creado"
#: packages/email/templates/document-created-from-direct-template.tsx:32
#: packages/lib/server-only/template/create-document-from-direct-template.ts:573
#: packages/lib/server-only/template/create-document-from-direct-template.ts:574
msgid "Document created from direct template"
msgstr "Documento creado a partir de plantilla directa"

View File

@ -51,16 +51,16 @@ msgstr "\"{placeholderEmail}\" en nombre de \"{0}\" te ha invitado a firmar \"do
#~ msgstr "\"{teamUrl}\" te ha invitado a firmar \"ejemplo de documento\"."
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:83
msgid "({0}) has invited you to approve this document"
msgstr "({0}) te ha invitado a aprobar este documento"
#~ msgid "({0}) has invited you to approve this document"
#~ msgstr "({0}) te ha invitado a aprobar este documento"
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:80
msgid "({0}) has invited you to sign this document"
msgstr "({0}) te ha invitado a firmar este documento"
#~ msgid "({0}) has invited you to sign this document"
#~ msgstr "({0}) te ha invitado a firmar este documento"
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:77
msgid "({0}) has invited you to view this document"
msgstr "({0}) te ha invitado a ver este documento"
#~ msgid "({0}) has invited you to view this document"
#~ msgstr "({0}) te ha invitado a ver este documento"
#: apps/web/src/app/(signing)/sign/[token]/text-field.tsx:313
msgid "{0, plural, one {(1 character over)} other {(# characters over)}}"
@ -1206,6 +1206,7 @@ msgid "Create your account and start using state-of-the-art document signing. Op
msgstr "Crea tu cuenta y comienza a utilizar la firma de documentos de última generación. La firma abierta y hermosa está al alcance de tu mano."
#: apps/web/src/app/(dashboard)/admin/documents/document-results.tsx:62
#: apps/web/src/app/(dashboard)/admin/leaderboard/data-table-leaderboard.tsx:96
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view-information.tsx:35
#: apps/web/src/app/(dashboard)/documents/data-table.tsx:54
#: apps/web/src/app/(dashboard)/settings/security/passkeys/user-passkeys-data-table.tsx:65
@ -1988,6 +1989,18 @@ msgstr "Ir al propietario"
msgid "Go to your <0>public profile settings</0> to add documents."
msgstr "Ve a tu <0>configuración de perfil público</0> para agregar documentos."
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:107
msgid "has invited you to approve this document"
msgstr ""
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:98
msgid "has invited you to sign this document"
msgstr ""
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:89
msgid "has invited you to view this document"
msgstr ""
#: apps/web/src/app/(dashboard)/settings/profile/page.tsx:29
msgid "Here you can edit your personal details."
msgstr "Aquí puedes editar tus datos personales."
@ -2204,6 +2217,10 @@ msgstr "Última actualización el"
msgid "Last used"
msgstr "Último uso"
#: apps/web/src/app/(dashboard)/admin/nav.tsx:93
msgid "Leaderboard"
msgstr ""
#: apps/web/src/components/(teams)/dialogs/leave-team-dialog.tsx:111
#: apps/web/src/components/(teams)/tables/current-user-teams-data-table.tsx:117
msgid "Leave"
@ -2411,6 +2428,7 @@ msgid "My templates"
msgstr "Mis plantillas"
#: apps/web/src/app/(dashboard)/admin/documents/[id]/recipient-item.tsx:148
#: apps/web/src/app/(dashboard)/admin/leaderboard/data-table-leaderboard.tsx:56
#: apps/web/src/app/(dashboard)/admin/users/[id]/page.tsx:99
#: apps/web/src/app/(dashboard)/admin/users/data-table-users.tsx:66
#: apps/web/src/app/(dashboard)/settings/security/passkeys/user-passkeys-data-table-actions.tsx:144
@ -2524,6 +2542,18 @@ msgstr "Nada que hacer"
msgid "Number"
msgstr "Número"
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:103
msgid "on behalf of \"{0}\" has invited you to approve this document"
msgstr ""
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:94
msgid "on behalf of \"{0}\" has invited you to sign this document"
msgstr ""
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:85
msgid "on behalf of \"{0}\" has invited you to view this document"
msgstr ""
#: apps/web/src/components/(dashboard)/settings/webhooks/create-webhook-dialog.tsx:128
msgid "On this page, you can create a new webhook."
msgstr "En esta página, puedes crear un nuevo webhook."
@ -3105,6 +3135,7 @@ msgstr "Buscar"
msgid "Search by document title"
msgstr "Buscar por título del documento"
#: apps/web/src/app/(dashboard)/admin/leaderboard/data-table-leaderboard.tsx:147
#: apps/web/src/app/(dashboard)/admin/users/data-table-users.tsx:144
msgid "Search by name or email"
msgstr "Buscar por nombre o correo electrónico"
@ -3369,6 +3400,11 @@ msgstr "Se han generado enlaces de firma para este documento."
msgid "Signing up..."
msgstr "Registrándose..."
#: apps/web/src/app/(dashboard)/admin/leaderboard/data-table-leaderboard.tsx:82
#: apps/web/src/app/(dashboard)/admin/leaderboard/page.tsx:46
msgid "Signing Volume"
msgstr ""
#: apps/web/src/app/(profile)/p/[url]/page.tsx:109
msgid "Since {0}"
msgstr "Desde {0}"
@ -3377,7 +3413,7 @@ msgstr "Desde {0}"
msgid "Site Banner"
msgstr "Banner del sitio"
#: apps/web/src/app/(dashboard)/admin/nav.tsx:93
#: apps/web/src/app/(dashboard)/admin/nav.tsx:107
#: apps/web/src/app/(dashboard)/admin/site-settings/page.tsx:26
msgid "Site Settings"
msgstr "Configuraciones del sitio"

View File

@ -60,14 +60,19 @@ msgstr "{0} sur {1} ligne(s) sélectionnée(s)."
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:136
#: packages/lib/server-only/document/resend-document.tsx:137
msgid "{0} on behalf of {1} has invited you to {recipientActionVerb} the document \"{2}\"."
msgstr "{0} au nom de {1} vous a invité à {recipientActionVerb} le document \"{2}\"."
msgid "{0} on behalf of \"{1}\" has invited you to {recipientActionVerb} the document \"{2}\"."
msgstr ""
#: packages/lib/jobs/definitions/emails/send-signing-email.ts:136
#: packages/lib/server-only/document/resend-document.tsx:137
#~ msgid "{0} on behalf of {1} has invited you to {recipientActionVerb} the document \"{2}\"."
#~ msgstr "{0} au nom de {1} vous a invité à {recipientActionVerb} le document \"{2}\"."
#: packages/email/template-components/template-document-invite.tsx:51
#~ msgid "{0}<0/>\"{documentName}\""
#~ msgstr "{0}<0/>\"{documentName}\""
#: packages/email/templates/document-invite.tsx:94
#: packages/email/templates/document-invite.tsx:95
msgid "{inviterName} <0>({inviterEmail})</0>"
msgstr "{inviterName} <0>({inviterEmail})</0>"
@ -87,7 +92,7 @@ msgstr "{inviterName} vous a invité à {0}<0/>\"{documentName}\""
msgid "{inviterName} has invited you to {action} {documentName}"
msgstr "{inviterName} vous a invité à {action} {documentName}"
#: packages/email/templates/document-invite.tsx:106
#: packages/email/templates/document-invite.tsx:108
msgid "{inviterName} has invited you to {action} the document \"{documentName}\"."
msgstr "{inviterName} vous a invité à {action} le document \"{documentName}\"."
@ -100,16 +105,24 @@ msgid "{inviterName} has removed you from the document<0/>\"{documentName}\""
msgstr "{inviterName} vous a retiré du document<0/>\"{documentName}\""
#: packages/email/template-components/template-document-invite.tsx:63
msgid "{inviterName} on behalf of {teamName} has invited you to {0}"
msgstr "{inviterName} au nom de {teamName} vous a invité à {0}"
msgid "{inviterName} on behalf of \"{teamName}\" has invited you to {0}"
msgstr ""
#: packages/email/templates/document-invite.tsx:45
msgid "{inviterName} on behalf of \"{teamName}\" has invited you to {action} {documentName}"
msgstr ""
#: packages/email/template-components/template-document-invite.tsx:63
#~ msgid "{inviterName} on behalf of {teamName} has invited you to {0}"
#~ msgstr "{inviterName} au nom de {teamName} vous a invité à {0}"
#: packages/email/template-components/template-document-invite.tsx:49
#~ msgid "{inviterName} on behalf of {teamName} has invited you to {0}<0/>\"{documentName}\""
#~ msgstr "{inviterName} on behalf of {teamName} has invited you to {0}<0/>\"{documentName}\""
#: packages/email/templates/document-invite.tsx:45
msgid "{inviterName} on behalf of {teamName} has invited you to {action} {documentName}"
msgstr "{inviterName} au nom de {teamName} vous a invité à {action} {documentName}"
#~ msgid "{inviterName} on behalf of {teamName} has invited you to {action} {documentName}"
#~ msgstr "{inviterName} au nom de {teamName} vous a invité à {action} {documentName}"
#: packages/email/templates/team-join.tsx:67
msgid "{memberEmail} joined the following team"
@ -711,7 +724,7 @@ msgid "Document created"
msgstr "Document créé"
#: packages/email/templates/document-created-from-direct-template.tsx:32
#: packages/lib/server-only/template/create-document-from-direct-template.ts:573
#: packages/lib/server-only/template/create-document-from-direct-template.ts:574
msgid "Document created from direct template"
msgstr "Document créé à partir d'un modèle direct"

View File

@ -51,16 +51,16 @@ msgstr "\"{placeholderEmail}\" au nom de \"{0}\" vous a invité à signer \"exem
#~ msgstr "\"{teamUrl}\" vous a invité à signer \"example document\"."
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:83
msgid "({0}) has invited you to approve this document"
msgstr "({0}) vous a invité à approuver ce document"
#~ msgid "({0}) has invited you to approve this document"
#~ msgstr "({0}) vous a invité à approuver ce document"
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:80
msgid "({0}) has invited you to sign this document"
msgstr "({0}) vous a invité à signer ce document"
#~ msgid "({0}) has invited you to sign this document"
#~ msgstr "({0}) vous a invité à signer ce document"
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:77
msgid "({0}) has invited you to view this document"
msgstr "({0}) vous a invité à consulter ce document"
#~ msgid "({0}) has invited you to view this document"
#~ msgstr "({0}) vous a invité à consulter ce document"
#: apps/web/src/app/(signing)/sign/[token]/text-field.tsx:313
msgid "{0, plural, one {(1 character over)} other {(# characters over)}}"
@ -1206,6 +1206,7 @@ msgid "Create your account and start using state-of-the-art document signing. Op
msgstr "Créez votre compte et commencez à utiliser la signature de documents à la pointe de la technologie. Une signature ouverte et magnifique est à votre portée."
#: apps/web/src/app/(dashboard)/admin/documents/document-results.tsx:62
#: apps/web/src/app/(dashboard)/admin/leaderboard/data-table-leaderboard.tsx:96
#: apps/web/src/app/(dashboard)/documents/[id]/document-page-view-information.tsx:35
#: apps/web/src/app/(dashboard)/documents/data-table.tsx:54
#: apps/web/src/app/(dashboard)/settings/security/passkeys/user-passkeys-data-table.tsx:65
@ -1988,6 +1989,18 @@ msgstr "Aller au propriétaire"
msgid "Go to your <0>public profile settings</0> to add documents."
msgstr "Allez à vos <0>paramètres de profil public</0> pour ajouter des documents."
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:107
msgid "has invited you to approve this document"
msgstr ""
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:98
msgid "has invited you to sign this document"
msgstr ""
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:89
msgid "has invited you to view this document"
msgstr ""
#: apps/web/src/app/(dashboard)/settings/profile/page.tsx:29
msgid "Here you can edit your personal details."
msgstr "Ici, vous pouvez modifier vos coordonnées personnelles."
@ -2204,6 +2217,10 @@ msgstr "Dernière mise à jour à"
msgid "Last used"
msgstr "Dernière utilisation"
#: apps/web/src/app/(dashboard)/admin/nav.tsx:93
msgid "Leaderboard"
msgstr ""
#: apps/web/src/components/(teams)/dialogs/leave-team-dialog.tsx:111
#: apps/web/src/components/(teams)/tables/current-user-teams-data-table.tsx:117
msgid "Leave"
@ -2411,6 +2428,7 @@ msgid "My templates"
msgstr "Mes modèles"
#: apps/web/src/app/(dashboard)/admin/documents/[id]/recipient-item.tsx:148
#: apps/web/src/app/(dashboard)/admin/leaderboard/data-table-leaderboard.tsx:56
#: apps/web/src/app/(dashboard)/admin/users/[id]/page.tsx:99
#: apps/web/src/app/(dashboard)/admin/users/data-table-users.tsx:66
#: apps/web/src/app/(dashboard)/settings/security/passkeys/user-passkeys-data-table-actions.tsx:144
@ -2524,6 +2542,18 @@ msgstr "Rien à faire"
msgid "Number"
msgstr "Numéro"
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:103
msgid "on behalf of \"{0}\" has invited you to approve this document"
msgstr ""
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:94
msgid "on behalf of \"{0}\" has invited you to sign this document"
msgstr ""
#: apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx:85
msgid "on behalf of \"{0}\" has invited you to view this document"
msgstr ""
#: apps/web/src/components/(dashboard)/settings/webhooks/create-webhook-dialog.tsx:128
msgid "On this page, you can create a new webhook."
msgstr "Sur cette page, vous pouvez créer un nouveau webhook."
@ -3105,6 +3135,7 @@ msgstr "Recherche"
msgid "Search by document title"
msgstr "Recherche par titre de document"
#: apps/web/src/app/(dashboard)/admin/leaderboard/data-table-leaderboard.tsx:147
#: apps/web/src/app/(dashboard)/admin/users/data-table-users.tsx:144
msgid "Search by name or email"
msgstr "Recherche par nom ou e-mail"
@ -3369,6 +3400,11 @@ msgstr "Des liens de signature ont été générés pour ce document."
msgid "Signing up..."
msgstr "Inscription en cours..."
#: apps/web/src/app/(dashboard)/admin/leaderboard/data-table-leaderboard.tsx:82
#: apps/web/src/app/(dashboard)/admin/leaderboard/page.tsx:46
msgid "Signing Volume"
msgstr ""
#: apps/web/src/app/(profile)/p/[url]/page.tsx:109
msgid "Since {0}"
msgstr "Depuis {0}"
@ -3377,7 +3413,7 @@ msgstr "Depuis {0}"
msgid "Site Banner"
msgstr "Bannière du site"
#: apps/web/src/app/(dashboard)/admin/nav.tsx:93
#: apps/web/src/app/(dashboard)/admin/nav.tsx:107
#: apps/web/src/app/(dashboard)/admin/site-settings/page.tsx:26
msgid "Site Settings"
msgstr "Paramètres du site"

View File

@ -0,0 +1,80 @@
import { z } from 'zod';
import {
DocumentDistributionMethod,
DocumentSigningOrder,
DocumentSource,
DocumentStatus,
DocumentVisibility,
ReadStatus,
RecipientRole,
SendStatus,
SigningStatus,
} from '@documenso/prisma/client';
/**
* Schema for recipient data in webhook payloads.
*/
export const ZWebhookRecipientSchema = z.object({
id: z.number(),
documentId: z.number().nullable(),
templateId: z.number().nullable(),
email: z.string(),
name: z.string(),
token: z.string(),
documentDeletedAt: z.date().nullable(),
expired: z.date().nullable(),
signedAt: z.date().nullable(),
authOptions: z.any().nullable(),
signingOrder: z.number().nullable(),
rejectionReason: z.string().nullable(),
role: z.nativeEnum(RecipientRole),
readStatus: z.nativeEnum(ReadStatus),
signingStatus: z.nativeEnum(SigningStatus),
sendStatus: z.nativeEnum(SendStatus),
});
/**
* Schema for document meta in webhook payloads.
*/
export const ZWebhookDocumentMetaSchema = z.object({
id: z.string(),
subject: z.string().nullable(),
message: z.string().nullable(),
timezone: z.string(),
password: z.string().nullable(),
dateFormat: z.string(),
redirectUrl: z.string().nullable(),
signingOrder: z.nativeEnum(DocumentSigningOrder),
typedSignatureEnabled: z.boolean(),
language: z.string(),
distributionMethod: z.nativeEnum(DocumentDistributionMethod),
emailSettings: z.any().nullable(),
});
/**
* Schema for document data in webhook payloads.
*/
export const ZWebhookDocumentSchema = z.object({
id: z.number(),
externalId: z.string().nullable(),
userId: z.number(),
authOptions: z.any().nullable(),
formValues: z.any().nullable(),
visibility: z.nativeEnum(DocumentVisibility),
title: z.string(),
status: z.nativeEnum(DocumentStatus),
documentDataId: z.string(),
createdAt: z.date(),
updatedAt: z.date(),
completedAt: z.date().nullable(),
deletedAt: z.date().nullable(),
teamId: z.number().nullable(),
templateId: z.number().nullable(),
source: z.nativeEnum(DocumentSource),
documentMeta: ZWebhookDocumentMetaSchema.nullable(),
Recipient: z.array(ZWebhookRecipientSchema),
});
export type TWebhookRecipient = z.infer<typeof ZWebhookRecipientSchema>;
export type TWebhookDocument = z.infer<typeof ZWebhookDocumentSchema>;

View File

@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "WebhookTriggerEvents" ADD VALUE 'DOCUMENT_REJECTED';

View File

@ -162,6 +162,7 @@ enum WebhookTriggerEvents {
DOCUMENT_OPENED
DOCUMENT_SIGNED
DOCUMENT_COMPLETED
DOCUMENT_REJECTED
}
model Webhook {

View File

@ -6,7 +6,7 @@ import { useEffect, useMemo, useRef, useState } from 'react';
import { Caveat } from 'next/font/google';
import { Trans } from '@lingui/macro';
import { Undo2 } from 'lucide-react';
import { Undo2, Upload } from 'lucide-react';
import type { StrokeOptions } from 'perfect-freehand';
import { getStroke } from 'perfect-freehand';
@ -33,6 +33,64 @@ const fontCaveat = Caveat({
const DPI = 2;
const isBase64Image = (value: string) => value.startsWith('data:image/png;base64,');
const loadImage = async (file: File | undefined): Promise<HTMLImageElement> => {
if (!file) {
throw new Error('No file selected');
}
if (!file.type.startsWith('image/')) {
throw new Error('Invalid file type');
}
if (file.size > 5 * 1024 * 1024) {
throw new Error('Image size should be less than 5MB');
}
return new Promise((resolve, reject) => {
const img = new Image();
const objectUrl = URL.createObjectURL(file);
img.onload = () => {
URL.revokeObjectURL(objectUrl);
resolve(img);
};
img.onerror = () => {
URL.revokeObjectURL(objectUrl);
reject(new Error('Failed to load image'));
};
img.src = objectUrl;
});
};
const loadImageOntoCanvas = (
image: HTMLImageElement,
canvas: HTMLCanvasElement,
ctx: CanvasRenderingContext2D,
): ImageData => {
const scale = Math.min((canvas.width * 0.8) / image.width, (canvas.height * 0.8) / image.height);
const x = (canvas.width - image.width * scale) / 2;
const y = (canvas.height - image.height * scale) / 2;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'high';
ctx.drawImage(image, x, y, image.width * scale, image.height * scale);
ctx.restore();
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
return imageData;
};
export type SignaturePadProps = Omit<HTMLAttributes<HTMLCanvasElement>, 'onChange'> & {
onChange?: (_signatureDataUrl: string | null) => void;
containerClassName?: string;
@ -52,12 +110,15 @@ export const SignaturePad = ({
}: SignaturePadProps) => {
const $el = useRef<HTMLCanvasElement>(null);
const $imageData = useRef<ImageData | null>(null);
const $fileInput = useRef<HTMLInputElement>(null);
const [isPressed, setIsPressed] = useState(false);
const [lines, setLines] = useState<Point[][]>([]);
const [currentLine, setCurrentLine] = useState<Point[]>([]);
const [selectedColor, setSelectedColor] = useState('black');
const [typedSignature, setTypedSignature] = useState(defaultValue ?? '');
const [typedSignature, setTypedSignature] = useState(
defaultValue && !isBase64Image(defaultValue) ? defaultValue : '',
);
const perfectFreehandOptions = useMemo(() => {
const size = $el.current ? Math.min($el.current.height, $el.current.width) * 0.03 : 10;
@ -80,6 +141,14 @@ export const SignaturePad = ({
setIsPressed(true);
if (typedSignature) {
setTypedSignature('');
if ($el.current) {
const ctx = $el.current.getContext('2d');
ctx?.clearRect(0, 0, $el.current.width, $el.current.height);
}
}
const point = Point.fromEvent(event, DPI, $el.current);
setCurrentLine([point]);
@ -193,6 +262,10 @@ export const SignaturePad = ({
$imageData.current = null;
}
if ($fileInput.current) {
$fileInput.current.value = '';
}
onChange?.(null);
setTypedSignature('');
@ -255,12 +328,30 @@ export const SignaturePad = ({
}
};
const handleImageUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
try {
const img = await loadImage(event.target.files?.[0]);
if (!$el.current) return;
const ctx = $el.current.getContext('2d');
if (!ctx) return;
$imageData.current = loadImageOntoCanvas(img, $el.current, ctx);
onChange?.($el.current.toDataURL());
setLines([]);
setCurrentLine([]);
setTypedSignature('');
} catch (error) {
console.error(error);
}
};
useEffect(() => {
if (typedSignature.trim() !== '') {
if (typedSignature.trim() !== '' && !isBase64Image(typedSignature)) {
renderTypedSignature();
onChange?.(typedSignature);
} else {
onClearClick();
}
}, [typedSignature, selectedColor]);
@ -370,6 +461,26 @@ export const SignaturePad = ({
</div>
)}
<div className="text-foreground absolute left-3 top-3 filter">
<div
className="focus-visible:ring-ring ring-offset-background text-muted-foreground/60 hover:text-muted-foreground flex cursor-pointer flex-row gap-2 rounded-full p-0 text-[0.688rem] focus-visible:outline-none focus-visible:ring-2"
onClick={() => $fileInput.current?.click()}
>
<Input
ref={$fileInput}
type="file"
accept="image/*"
className="hidden"
onChange={handleImageUpload}
disabled={disabled}
/>
<Upload className="h-4 w-4" />
<span>
<Trans>Upload Signature</Trans>
</span>
</div>
</div>
<div className="text-foreground absolute right-2 top-2 filter">
<Select defaultValue={selectedColor} onValueChange={(value) => setSelectedColor(value)}>
<SelectTrigger className="h-auto w-auto border-none p-0.5">